diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index d60b0cc49..3303e9afb 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,7 +3,7 @@
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
-FROM node:12
+FROM node:22
# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
@@ -16,9 +16,11 @@ ARG USERNAME=node
ARG USER_UID=1000
ARG USER_GID=$USER_UID
+RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list
+
# Configure apt and install packages
RUN apt-get update \
- && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
+ && apt-get -y install --no-install-recommends dialog 2>&1 \
#
# Verify git and needed tools are installed
&& apt-get -y install git iproute2 procps \
@@ -33,6 +35,7 @@ RUN apt-get update \
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends yarn tmux locales postgresql \
+ && apt-get install libpq-dev python3 g++ make \
#
# Install eslint globally
&& npm install -g eslint \
@@ -47,7 +50,6 @@ RUN apt-get update \
&& apt-get install -y sudo \
&& echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME \
- #
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 14fb67344..c8e4b7108 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,31 +1,16 @@
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
- "name": "Node.js 12 & Postgres",
+ "name": "Node.js 20 & Postgres",
"dockerComposeFile": "docker-compose.yml",
"service": "web",
"workspaceFolder": "/workspace",
-
- // Use 'settings' to set *default* container specific settings.json values on container create.
- // You can edit these settings after create using File > Preferences > Settings > Remote.
- "settings": {
- "terminal.integrated.shell.linux": "/bin/bash"
- },
-
- // Uncomment the next line if you want start specific services in your Docker Compose config.
- // "runServices": [],
-
- // Uncomment the line below if you want to keep your containers running after VS Code shuts down.
- // "shutdownAction": "none",
-
- // Uncomment the next line to run commands after the container is created.
- // "postCreateCommand": "npm install",
-
- // Uncomment the next line to have VS Code connect as an existing non-root user in the container. See
- // https://aka.ms/vscode-remote/containers/non-root for details on adding a non-root user if none exist.
- // "remoteUser": "node",
-
// Add the IDs of extensions you want installed when the container is created in the array below.
- "extensions": [
- "dbaeumer.vscode-eslint"
- ]
-}
\ No newline at end of file
+ "customizations":{
+ "vscode": {
+ "extensions": ["dbaeumer.vscode-eslint"],
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash"
+ }
+ }
+ }
+}
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index 184aff0ed..83e302207 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -3,7 +3,6 @@
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
-version: '3'
services:
web:
# Uncomment the next line to use a non-root user for all processes. You can also
@@ -25,20 +24,28 @@ services:
PGUSER: user
PGDATABASE: data
PGHOST: db
+ # set this to true in the development environment until I can get SSL setup on the
+ # docker postgres instance
+ PGTESTNOSSL: 'true'
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
- links:
+ depends_on:
- db
+ links:
+ - db:db
+
db:
- image: postgres
+ image: postgres:14
restart: unless-stopped
ports:
- 5432:5432
+ command: postgres -c password_encryption=md5
environment:
+ POSTGRES_HOST_AUTH_METHOD: trust
+ POSTGRES_INITDB_ARGS: "--auth-local=md5"
POSTGRES_PASSWORD: pass
POSTGRES_USER: user
POSTGRES_DB: data
-
diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index e03680342..000000000
--- a/.eslintrc
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "plugins": ["prettier"],
- "parser": "@typescript-eslint/parser",
- "extends": ["plugin:prettier/recommended", "prettier/@typescript-eslint"],
- "ignorePatterns": ["node_modules", "coverage", "packages/pg-protocol/dist/**/*"],
- "parserOptions": {
- "ecmaVersion": 2017,
- "sourceType": "module"
- },
- "env": {
- "node": true,
- "es6": true,
- "mocha": true
- }
-}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..94f480de9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
\ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..7434a61c6
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+/packages/pg-connection-string @hjr3
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 000000000..41a081f92
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,7 @@
+
+version: 2
+updates:
+ - package-ecosystem: "npm"
+ directory: "/"
+ schedule:
+ interval: "monthly"
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..1a266291d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,84 @@
+name: CI
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ lint:
+ timeout-minutes: 5
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ - name: Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: yarn
+ - run: yarn install --frozen-lockfile
+ - run: yarn lint
+ build:
+ timeout-minutes: 15
+ needs: lint
+ services:
+ postgres:
+ image: ghcr.io/railwayapp-templates/postgres-ssl
+ env:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_HOST_AUTH_METHOD: 'md5'
+ POSTGRES_DB: ci_db_test
+ ports:
+ - 5432:5432
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
+ strategy:
+ fail-fast: false
+ matrix:
+ node:
+ - '16'
+ - '18'
+ - '20'
+ - '22'
+ - '24'
+ - '26'
+ os:
+ - ubuntu-latest
+ name: Node.js ${{ matrix.node }}
+ runs-on: ubuntu-latest
+ env:
+ PGUSER: postgres
+ PGPASSWORD: postgres
+ PGHOST: localhost
+ PGDATABASE: ci_db_test
+ PGTESTNOSSL: 'true'
+ SCRAM_TEST_PGUSER: scram_test
+ SCRAM_TEST_PGPASSWORD: test4scram
+ SCRAM_TEST_PGUSER_UNICODE: scram_unicode_test
+ # Raw form of a password whose NFKC normalization differs from itself.
+ # U+2168 (ROMAN NUMERAL IX) decomposes to ASCII "IX" under NFKC; the
+ # server stores the verifier from the SASLprep-normalized form, so the
+ # client must apply SASLprep too. This is the regression check for the
+ # RFC 4013 fix in packages/pg/lib/crypto/sasl.js.
+ SCRAM_TEST_PGPASSWORD_UNICODE: "IX-\u2168"
+ steps:
+ - name: Show OS
+ run: |
+ uname -a
+ - run: |
+ psql \
+ -c "SET password_encryption = 'scram-sha-256'" \
+ -c "CREATE ROLE scram_test LOGIN PASSWORD 'test4scram'" \
+ -c "CREATE ROLE scram_unicode_test LOGIN PASSWORD U&'IX-\2168'"
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ - name: Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node }}
+ cache: yarn
+ - run: yarn install --frozen-lockfile
+ - run: yarn test
diff --git a/.gitignore b/.gitignore
index b6e058f2e..8e242c10d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,5 +7,6 @@ package-lock.json
*.swp
dist
.DS_Store
+/.eslintcache
.vscode/
manually-test-on-heroku.js
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7987f761b..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-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
-
- # 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'
-
- # 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 000000000..0366cbd92
--- /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
index 7dabeb479..dd26374a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,13 +4,111 @@ For richer information consult the commit log on github with referenced pull req
We do not include break-fix version release in this file.
+## pg@8.21.0
+
+- Handle [SASL SCRAM](https://github.com/brianc/node-postgres/pull/3521) server error responses properly.
+- Add support for [node@26](https://github.com/brianc/node-postgres/pull/3667).
+- Add `scramMaxIterations` [config option](https://github.com/brianc/node-postgres/pull/3677).
+- Add `client.getTransactionStatus()` [method](https://github.com/brianc/node-postgres/pull/3645).
+
+## pg@8.20.0
+
+- Add [onConnect](https://github.com/brianc/node-postgres/pull/3620) callback to pg.Pool constructor options allowing for async initialization of newly created & connected pooled clients.
+
+## pg@8.19.0
+
+- [Deprecate interal query queue](https://github.com/brianc/node-postgres/pull/3603).
+- Pass connection parameters [to password callback](https://github.com/brianc/node-postgres/pull/3602).
+
+## pg@8.18.0
+
+- [Return the client instance](https://github.com/brianc/node-postgres/pull/3564) as the result of calling `connect` (previously it was `void`).
+
+## pg@8.17.0
+
+- Throw correct error if database URL parsing [fails](https://github.com/brianc/node-postgres/issues/3513).
+
+## pg@8.16.0
+
+- Add support for [min connection pool size](https://github.com/brianc/node-postgres/pull/3438).
+
+## pg@8.15.0
+
+- Add support for [esm](https://github.com/brianc/node-postgres/pull/3423) importing. CommonJS importing is still also supported.
+
+## pg@8.14.0
+
+- Add support for SCRAM-SHA-256-PLUS, i.e. [channel binding](https://github.com/brianc/node-postgres/pull/3356).
+
+## pg@8.13.0
+
+- Add ability to specify query timeout on [per-query basis](https://github.com/brianc/node-postgres/pull/3074).
+
+## pg@8.12.0
+
+- Add `queryMode` config option to [force use of the extended query protocol](https://github.com/brianc/node-postgres/pull/3214) on queries without any parameters.
+
+## pg-pool@8.10.0
+
+- Emit `release` event when client is returned to [the pool](https://github.com/brianc/node-postgres/pull/2845).
+
+## pg@8.9.0
+
+- Add support for [stream factory](https://github.com/brianc/node-postgres/pull/2898).
+- [Better errors](https://github.com/brianc/node-postgres/pull/2901) for SASL authentication.
+- [Use native crypto module](https://github.com/brianc/node-postgres/pull/2815) for SASL authentication.
+
+## pg@8.8.0
+
+- Bump minimum required version of [native bindings](https://github.com/brianc/node-postgres/pull/2787).
+- Catch previously uncatchable errors thrown in [`pool.query`](https://github.com/brianc/node-postgres/pull/2569).
+- Prevent the pool from blocking the event loop if all clients are [idle](https://github.com/brianc/node-postgres/pull/2721) (and `allowExitOnIdle` is enabled).
+- Support `lock_timeout` in [client config](https://github.com/brianc/node-postgres/pull/2779).
+- Fix errors thrown in callbacks from [interfering with cleanup](https://github.com/brianc/node-postgres/pull/2753).
+
+### pg-pool@3.5.0
+
+- Add connection [lifetime limit](https://github.com/brianc/node-postgres/pull/2698) config option.
+
+### pg@8.7.0
+
+- Add optional config to [pool](https://github.com/brianc/node-postgres/pull/2568) to allow process to exit if pool is idle.
+
+### pg-cursor@2.7.0
+
+- Convert to [es6 class](https://github.com/brianc/node-postgres/pull/2553)
+- Add support for promises [to cursor methods](https://github.com/brianc/node-postgres/pull/2554)
+
+### 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:
+- 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 improvement initiative I'm working on. Stay tuned as things are set to get much faster still! :rocket:
### pg-cursor@2.2.0
diff --git a/LICENSE b/LICENSE
index aa66489de..5c1405646 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2010 - 2020 Brian Carlson
+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
diff --git a/LOCAL_DEV.md b/LOCAL_DEV.md
new file mode 100644
index 000000000..3bbd9b456
--- /dev/null
+++ b/LOCAL_DEV.md
@@ -0,0 +1,43 @@
+# Local development
+
+Steps to install and configure Postgres on Mac for developing against locally
+
+1. Install homebrew
+2. Install postgres
+ ```sh
+ brew install postgresql
+ ```
+3. Create a database
+ ```sh
+ createdb test
+ ```
+4. Create SSL certificates
+ ```sh
+ cd /opt/homebrew/var/postgresql@14
+ openssl genrsa -aes128 2048 > server.key
+ openssl rsa -in server.key -out server.key
+ chmod 400 server.key
+ openssl req -new -key server.key -days 365 -out server.crt -x509
+ cp server.crt root.crt
+ ```
+5. Update config in `/opt/homebrew/var/postgresql@14/postgresql.conf`
+
+ ```conf
+ listen_addresses = '*'
+
+ password_encryption = md5
+
+ ssl = on
+ ssl_ca_file = 'root.crt'
+ ssl_cert_file = 'server.crt'
+ ssl_crl_file = ''
+ ssl_crl_dir = ''
+ ssl_key_file = 'server.key'
+ ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
+ ssl_prefer_server_ciphers = on
+ ```
+
+6. Start Postgres server
+ ```sh
+ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14
+ ```
diff --git a/README.md b/README.md
index 1fe69fa5f..4ca28ce5d 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,10 @@
# node-postgres
-[](http://travis-ci.org/brianc/node-postgres)
-[](https://david-dm.org/brianc/node-postgres?path=packages/pg)
+
-Non-blocking PostgreSQL client for Node.js. Pure JavaScript and optional native libpq bindings.
+Non-blocking PostgreSQL client for Node.js. Pure JavaScript and optional native libpq bindings.
## Monorepo
@@ -13,66 +12,73 @@ This repo is a monorepo which contains the core [pg](https://github.com/brianc/n
- [pg](https://github.com/brianc/node-postgres/tree/master/packages/pg)
- [pg-pool](https://github.com/brianc/node-postgres/tree/master/packages/pg-pool)
+- [pg-native](https://github.com/brianc/node-postgres/tree/master/packages/pg-native)
- [pg-cursor](https://github.com/brianc/node-postgres/tree/master/packages/pg-cursor)
- [pg-query-stream](https://github.com/brianc/node-postgres/tree/master/packages/pg-query-stream)
- [pg-connection-string](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string)
+- [pg-protocol](https://github.com/brianc/node-postgres/tree/master/packages/pg-protocol)
+## Install
-## Documenation
+```
+npm install pg
+```
-Each package in this repo should have it's own readme more focused on how to develop/contribute. For overall documentation on the project and the related modules managed by this repo please see:
+## Documentation
+
+Each package in this repo should have its own readme more focused on how to develop/contribute. For overall documentation on the project and the related modules managed by this repo please see:
### :star: [Documentation](https://node-postgres.com) :star:
+The source repo for the documentation is available for contribution [here](https://github.com/brianc/node-postgres/tree/master/docs).
+
### Features
-* Pure JavaScript client and native libpq bindings share _the same API_
-* Connection pooling
-* Extensible JS ↔ PostgreSQL data-type coercion
-* Supported PostgreSQL features
- * Parameterized queries
- * Named statements with query plan caching
- * Async notifications with `LISTEN/NOTIFY`
- * Bulk import & export with `COPY TO/COPY FROM`
+- Pure JavaScript client and native libpq bindings share _the same API_
+- Connection pooling
+- Extensible JS ↔ PostgreSQL data-type coercion
+- Supported PostgreSQL features
+ - Parameterized queries
+ - Named statements with query plan caching
+ - Async notifications with `LISTEN/NOTIFY`
+ - Bulk import & export with `COPY TO/COPY FROM`
### Extras
-node-postgres is by design pretty light on abstractions. These are some handy modules we've been using over the years to complete the picture.
+node-postgres is by design pretty light on abstractions. These are some handy modules we've been using over the years to complete the picture.
The entire list can be found on our [wiki](https://github.com/brianc/node-postgres/wiki/Extras).
## Support
-node-postgres is free software. If you encounter a bug with the library please open an issue on the [GitHub repo](https://github.com/brianc/node-postgres). If you have questions unanswered by the documentation please open an issue pointing out how the documentation was unclear & I will do my best to make it better!
+node-postgres is free software. If you encounter a bug with the library please open an issue on the [GitHub repo](https://github.com/brianc/node-postgres). If you have questions unanswered by the documentation please open an issue pointing out how the documentation was unclear & I will do my best to make it better!
When you open an issue please provide:
+
- version of Node
- version of Postgres
- smallest possible snippet of code to reproduce the problem
-You can also follow me [@briancarlson](https://twitter.com/briancarlson) if that's your thing. I try to always announce noteworthy changes & developments with node-postgres on Twitter.
+You can also follow me [@brianc](https://bsky.app/profile/brianc.bsky.social) on bluesky if that's your thing for updates on node-postgres with nearly zero non node-postgres content. My old twitter/x account is no longer used.
## Sponsorship :two_hearts:
-node-postgres's continued development has been made possible in part by generous finanical support from [the community](https://github.com/brianc/node-postgres/blob/master/SPONSORS.md) and these featured sponsors:
-
-
+node-postgres's continued development has been made possible in part by generous financial support from [the community](https://github.com/brianc/node-postgres/blob/master/SPONSORS.md).
If you or your company are benefiting from node-postgres and would like to help keep the project financially sustainable [please consider supporting](https://github.com/sponsors/brianc) its development.
+### Featured sponsor
+
+Special thanks to [medplum](https://medplum.com) for their generous and thoughtful support of node-postgres!
+
+
+
## Contributing
-__:heart: contributions!__
+**:heart: contributions!**
+
+I will **happily** accept your pull request if it:
-I will __happily__ accept your pull request if it:
-- __has tests__
+- **has tests**
- looks reasonable
- does not break backwards compatibility
@@ -81,10 +87,11 @@ If your change involves breaking backwards compatibility please please point tha
### Setting up for local development
1. Clone the repo
-2. From your workspace root run `yarn` and then `yarn lerna bootstrap`
-3. Ensure you have a PostgreSQL instance running with SSL enabled and an empty database for tests
-4. Ensure you have the proper environment variables configured for connecting to the instance
-5. Run `yarn test` to run all the tests
+2. Ensure you have installed libpq-dev in your system (the native bindings are built in the test process)
+3. From your workspace root run `yarn` and then `yarn lerna bootstrap`
+4. Ensure you have a PostgreSQL instance running with SSL enabled and an empty database for tests. _note: you can skip the tests requring SSL by setting the environment variable `PGTESTNOSSL=1` if you're not changing any SSL related code_.
+5. Ensure you have the proper environment variables configured for connecting to your postgres instance. Using the standard `PG*` environment variables like `PGUSER` and `PGPASSWORD` etc...
+6. Run `yarn test` to run all the tests.
## Troubleshooting and FAQ
@@ -94,20 +101,20 @@ The causes and solutions to common errors can be found among the [Frequently Ask
Copyright (c) 2010-2020 Brian Carlson (brian.m.carlson@gmail.com)
- 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.
+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/SPONSORS.md b/SPONSORS.md
index d01c1090d..b482f3678 100644
--- a/SPONSORS.md
+++ b/SPONSORS.md
@@ -8,6 +8,19 @@ node-postgres is made possible by the helpful contributors from the community as
- [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/)
+- [mpirik](https://github.com/mpirik)
+- [@BLUE-DEVIL1134](https://github.com/BLUE-DEVIL1134)
+- [bubble.io](https://bubble.io/)
+- [GitHub](https://github.com/github)
+- [n8n](https://n8n.io/)
+- [loveland](https://github.com/loveland)
+- [gajus](https://github.com/gajus)
+- [thirdiron](https://github.com/thirdiron)
+- [kiwicopple](https://github.com/kiwicopple)
# Supporters
@@ -31,3 +44,14 @@ node-postgres is made possible by the helpful contributors from the community as
- Raul Murray
- Simple Analytics
- Trevor Linton
+- Ian Walter
+- @Guido4000
+- [Martti Laine](https://github.com/codeclown)
+- [Tim Nolet](https://github.com/tnolet)
+- [Ideal Postcodes](https://github.com/ideal-postcodes)
+- [checkly](https://github.com/checkly)
+- [Scout APM](https://github.com/scoutapm-sponsorships)
+- [Sideline Sports](https://github.com/SidelineSports)
+- [Gadget](https://github.com/gadget-inc)
+- [Sentry](https://sentry.io/welcome/)
+- [devlikeapro](https://github.com/devlikepro)
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 000000000..2b3533c7e
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+.next
+out
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..d19c590b9
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,20 @@
+# node-postgres docs website
+
+This is the documentation for node-postgres which is currently hosted at [https://node-postgres.com](https://node-postgres.com).
+
+## Development
+
+To run the documentation locally, you need to have [Node.js](https://nodejs.org) installed. Then, you can clone the repository and install the dependencies:
+
+```bash
+cd docs
+yarn
+```
+
+Once you've installed the deps, you can run the development server:
+
+```bash
+yarn dev
+```
+
+This will start a local server at [http://localhost:3000](http://localhost:3000) where you can view the documentation and see your changes.
diff --git a/docs/components/alert.tsx b/docs/components/alert.tsx
new file mode 100644
index 000000000..9a3ed7359
--- /dev/null
+++ b/docs/components/alert.tsx
@@ -0,0 +1,9 @@
+import { Callout } from 'nextra/components'
+
+export const Alert = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/docs/components/info.tsx b/docs/components/info.tsx
new file mode 100644
index 000000000..79f11a7d6
--- /dev/null
+++ b/docs/components/info.tsx
@@ -0,0 +1,5 @@
+import { Callout } from 'nextra/components'
+
+export const Info = ({ children }) => {
+ return {children}
+}
diff --git a/docs/components/logo.tsx b/docs/components/logo.tsx
new file mode 100644
index 000000000..5252b4442
--- /dev/null
+++ b/docs/components/logo.tsx
@@ -0,0 +1,9 @@
+type Props = {
+ src: string
+ alt?: string
+}
+
+export function Logo(props: Props) {
+ const alt = props.alt || 'Logo'
+ return
+}
diff --git a/docs/next.config.js b/docs/next.config.js
new file mode 100644
index 000000000..57793f96c
--- /dev/null
+++ b/docs/next.config.js
@@ -0,0 +1,11 @@
+import nextra from 'nextra'
+
+const withNextra = nextra({
+ theme: 'nextra-theme-docs',
+ themeConfig: './theme.config.js',
+})
+
+export default withNextra({
+ output: 'export',
+ images: { unoptimized: true },
+})
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 000000000..a3bdb4f9f
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "docs",
+ "version": "1.0.0",
+ "description": "",
+ "main": "next.config.js",
+ "type": "module",
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "next": "^13.5.11",
+ "nextra": "^3.3.1",
+ "nextra-theme-docs": "^3.3.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "typescript": "^5.0.2"
+ }
+}
diff --git a/docs/pages/_app.js b/docs/pages/_app.js
new file mode 100644
index 000000000..3e7701bf0
--- /dev/null
+++ b/docs/pages/_app.js
@@ -0,0 +1,5 @@
+import 'nextra-theme-docs/style.css'
+
+export default function Nextra({ Component, pageProps }) {
+ return
+}
diff --git a/docs/pages/_meta.js b/docs/pages/_meta.js
new file mode 100644
index 000000000..adf56009b
--- /dev/null
+++ b/docs/pages/_meta.js
@@ -0,0 +1,5 @@
+export default {
+ index: 'Welcome',
+ announcements: 'Announcements',
+ apis: 'API',
+}
diff --git a/docs/pages/announcements.mdx b/docs/pages/announcements.mdx
new file mode 100644
index 000000000..d6a17c244
--- /dev/null
+++ b/docs/pages/announcements.mdx
@@ -0,0 +1,146 @@
+import { Alert } from '/components/alert.tsx'
+
+## 2020-02-25
+
+### pg@8.0 release
+
+`pg@8.0` is [being released](https://github.com/brianc/node-postgres/pull/2117) which contains a handful of breaking changes.
+
+I will outline each breaking change here and try to give some historical context on them. Most of them are small and subtle and likely wont impact you; **however**, there is one larger breaking change you will likely run into:
+
+---
+
+- Support all `tls.connect` [options](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback) being passed to the client/pool constructor under the `ssl` option.
+
+Previously we white listed the parameters passed here and did slight massaging of some of them. The main **breaking** change here is that now if you do this:
+
+```js
+const client = new Client({ ssl: true })
+```
+
+
+ Now we will use the default ssl options to tls.connect which includes rejectUnauthorized being enabled. This means
+ your connection attempt may fail if you are using a self-signed cert. To use the old behavior you should do this:
+
+
+```js
+const client = new Client({ ssl: { rejectUnauthorized: false } })
+```
+
+This makes pg a bit more secure "out of the box" while still enabling you to opt in to the old behavior.
+
+---
+
+The rest of the changes are relatively minor & you likely wont need to do anything, but good to be aware none the less!
+
+- change default database name
+
+If a database name is not specified, available in the environment at `PGDATABASE`, or available at `pg.defaults`, we used to use the username of the process user as the name of the database. Now we will use the `user` property supplied to the client as the database name, if it exists. What this means is this:
+
+```jsx
+new Client({
+ user: 'foo',
+})
+```
+
+`pg@7.x` will default the database name to the _process_ user. `pg@8.x` will use the `user` property supplied to the client. If you have not supplied `user` to the client, and it isn't available through any of its existing lookup mechanisms (environment variables, pg.defaults) then it will still use the process user for the database name.
+
+- drop support for versions of node older than 8.0
+
+Node@6.0 has been out of LTS for quite some time now, and I've removed it from our test matrix. `pg@8.0` _may_ still work on older versions of node, but it isn't a goal of the project anymore. Node@8.0 is actually no longer in the LTS support line, but pg will continue to test against and support 8.0 until there is a compelling reason to drop support for it. Any security vulnerability issues which come up I will back-port fixes to the `pg@7.x` line and do a release, but any other fixes or improvements will not be back ported.
+
+- prevent password from being logged accidentally
+
+`pg@8.0` makes the password field on the pool and client non-enumerable. This means when you do `console.log(client)` you wont have your database password printed out unintentionally. You can still do `console.log(client.password)` if you really want to see it!
+
+- make `pg.native` non-enumerable
+
+You can use `pg.native.Client` to access the native client. The first time you access the `pg.native` getter it imports the native bindings...which must be installed. In some cases (such as webpacking the pg code for lambda deployment) the `.native` property would be traversed and trigger an import of the native bindings as a side-effect. Making this property non-enumerable will fix this issue. An easy fix, but its technically a breaking change in cases where people _are_ relying on this side effect for any reason.
+
+- make `pg.Pool` an es6 class
+
+This makes extending `pg.Pool` possible. Previously it was not a "proper" es6 class and `class MyPool extends pg.Pool` wouldn't work.
+
+- make `Notice` messages _not_ an instance of a JavaScript error
+
+The code path for parsing `notice` and `error` messages from the postgres backend is the same. Previously created a JavaScript `Error` instance for _both_ of these message types. Now, only actual `errors` from the postgres backend will be an instance of an `Error`. The _shape_ and _properties_ of the two messages did not change outside of this.
+
+- monorepo
+
+While not technically a breaking change for the module itself, I have begun the process of [consolidating](https://github.com/brianc/node-pg-query-stream) [separate](https://github.com/brianc/node-pg-cursor/) [repos](https://github.com/brianc/node-pg-pool) into the main [repo](https://github.com/brianc/node-postgres) and converted it into a monorepo managed by lerna. This will help me stay on top of issues better (it was hard to bounce between 3-4 separate repos) and coordinate bug fixes and changes between dependant modules.
+
+Thanks for reading that! pg tries to be super pedantic about not breaking backwards-compatibility in non semver major releases....even for seemingly small things. If you ever notice a breaking change on a semver minor/patch release please stop by the [repo](https://github.com/brianc/node-postgres) and open an issue!
+
+_If you find `pg` valuable to you or your business please consider [supporting](http://github.com/sponsors/brianc) it's continued development! Big performance improvements, typescript, better docs, query pipelining and more are all in the works!_
+
+## 2019-07-18
+
+### New documentation
+
+After a _very_ long time on my todo list I've ported the docs from my old hand-rolled webapp running on route53 + elb + ec2 + dokku (I know, I went overboard!) to [gatsby](https://www.gatsbyjs.org/) hosted on [netlify](https://www.netlify.com/) which is _so_ much easier to manage. I've released the code at [https://github.com/brianc/node-postgres-docs](https://github.com/brianc/node-postgres-docs) and invite your contributions! Let's make this documentation better together. Any time changes are merged to master on the documentation repo it will automatically deploy.
+
+If you see an error in the docs, big or small, use the "edit on GitHub" button to edit the page & submit a pull request right there. I'll get a new version out ASAP with your changes! If you want to add new pages of documentation open an issue if you need guidance, and I'll help you get started.
+
+I want to extend a special **thank you** to all the [supporters](https://github.com/brianc/node-postgres/blob/master/SPONSORS.md) and [contributors](https://github.com/brianc/node-postgres/graphs/contributors) to the project that have helped keep me going through times of burnout or life "getting in the way." ❤️
+
+It's been quite a journey, and I look forward continuing it for as long as I can provide value to all y'all. 🤠
+
+## 2017-08-12
+
+### code execution vulnerability
+
+Today [@sehrope](https://github.com/sehrope) found and reported a code execution vulnerability in node-postgres. This affects all versions from `pg@2.x` through `pg@7.1.0`.
+
+I have published a fix on the tip of each major version branch of all affected versions as well as a fix on each minor version branch of `pg@6.x` and `pg@7.x`:
+
+### Fixes
+
+The following versions have been published to npm & contain a patch to fix the vulnerability:
+
+```
+pg@2.11.2
+pg@3.6.4
+pg@4.5.7
+pg@5.2.1
+pg@6.0.5
+pg@6.1.6
+pg@6.2.5
+pg@6.3.3
+pg@6.4.2
+pg@7.0.3
+pg@7.1.2
+```
+
+### Example
+
+To demonstrate the issue & see if you are vulnerable execute the following in node:
+
+```js
+import pg from 'pg'
+const { Client } = pg
+const client = new Client()
+client.connect()
+
+const sql = `SELECT 1 AS "\\'/*", 2 AS "\\'*/\n + console.log(process.env)] = null;\n//"`
+
+client.query(sql, (err, res) => {
+ client.end()
+})
+```
+
+You will see your environment variables printed to your console. An attacker can use this exploit to execute any arbitrary node code within your process.
+
+### Impact
+
+This vulnerability _likely_ does not impact you if you are connecting to a database you control and not executing user-supplied sql. Still, you should **absolutely** upgrade to the most recent patch version as soon as possible to be safe.
+
+Two attack vectors we quickly thought of:
+
+- 1 - executing unsafe, user-supplied sql which contains a malicious column name like the one above.
+- 2 - connecting to an untrusted database and executing a query which returns results where any of the column names are malicious.
+
+### Support
+
+I have created [an issue](https://github.com/brianc/node-postgres/issues/1408) you can use to discuss the vulnerability with me or ask questions, and I have reported this issue [on twitter](https://twitter.com/briancarlson) and directly to Heroku and [nodesecurity.io](https://nodesecurity.io/).
+
+I take security very seriously. If you or your company benefit from node-postgres **[please sponsor my work](https://www.patreon.com/node_postgres)**: this type of issue is one of the many things I am responsible for, and I want to be able to continue to tirelessly provide a world-class PostgreSQL experience in node for years to come.
diff --git a/docs/pages/apis/_meta.js b/docs/pages/apis/_meta.js
new file mode 100644
index 000000000..94b8802ed
--- /dev/null
+++ b/docs/pages/apis/_meta.js
@@ -0,0 +1,8 @@
+export default {
+ client: 'pg.Client',
+ pool: 'pg.Pool',
+ result: 'pg.Result',
+ types: 'pg.Types',
+ cursor: 'Cursor',
+ utilities: 'Utilities',
+}
diff --git a/docs/pages/apis/client.mdx b/docs/pages/apis/client.mdx
new file mode 100644
index 000000000..ecfd67fca
--- /dev/null
+++ b/docs/pages/apis/client.mdx
@@ -0,0 +1,300 @@
+---
+title: pg.Client
+---
+
+## new Client
+
+`new Client(config: Config)`
+
+Every field of the `config` object is entirely optional. A `Client` instance will use [environment variables](/features/connecting#environment-variables) for all missing values.
+
+```ts
+type Config = {
+ user?: string, // default process.env.PGUSER || process.env.USER
+ password?: string or function, //default process.env.PGPASSWORD
+ host?: string, // default process.env.PGHOST
+ port?: number, // default process.env.PGPORT
+ database?: string, // default process.env.PGDATABASE || user
+ connectionString?: string, // e.g. postgres://user:password@host:5432/database
+ ssl?: any, // passed directly to node.TLSSocket, supports all tls.connect options
+ types?: any, // custom type parsers
+ statement_timeout?: number, // number of milliseconds before a statement in query will time out, default is no timeout
+ query_timeout?: number, // number of milliseconds before a query call will timeout, default is no timeout
+ lock_timeout?: number, // number of milliseconds a query is allowed to be en lock state before it's cancelled due to lock timeout
+ application_name?: string, // The name of the application that created this Client instance
+ connectionTimeoutMillis?: number, // number of milliseconds to wait for connection, default is no timeout
+ keepAlive?: boolean, // if true, enable keepalive on the `net.Socket`
+ keepAliveInitialDelayMillis?: number, // number of milliseconds between last data packet received and first keepalive probe, default is 0 (keep existing value);
+ // no effect unless `keepAlive` is true
+ idle_in_transaction_session_timeout?: number, // number of milliseconds before terminating any session with an open idle transaction, default is no timeout
+ client_encoding?: string, // specifies the character set encoding that the database uses for sending data to the client
+ fallback_application_name?: string, // provide an application name to use if application_name is not set
+ options?: string // command-line options to be sent to the server
+}
+```
+
+example to create a client with specific connection information:
+
+```js
+import { Client } from 'pg'
+
+const client = new Client({
+ user: 'database-user',
+ password: 'secretpassword!!',
+ host: 'my.database-server.com',
+ port: 5334,
+ database: 'database-name',
+})
+```
+
+## client.connect
+
+```js
+import { Client } from 'pg'
+const client = new Client()
+
+await client.connect()
+```
+
+## client.query
+
+### QueryConfig
+
+You can pass an object to `client.query` with the signature of:
+
+```ts
+type QueryConfig {
+ // the raw query text
+ text: string;
+
+ // an array of query parameters
+ values?: Array;
+
+ // name of the query - used for prepared statements
+ name?: string;
+
+ // by default rows come out as a key/value pair for each row
+ // pass the string 'array' here to receive rows as an array of values
+ rowMode?: string;
+
+ // custom type parsers just for this query result
+ types?: Types;
+
+ // TODO: document
+ queryMode?: string;
+}
+```
+
+```ts
+client.query(text: string, values?: any[]) => Promise
+```
+
+**Plain text query**
+
+```js
+import { Client } from 'pg'
+const client = new Client()
+
+await client.connect()
+
+const result = await client.query('SELECT NOW()')
+console.log(result)
+
+await client.end()
+```
+
+**Parameterized query**
+
+```js
+import { Client } from 'pg'
+const client = new Client()
+
+await client.connect()
+
+const result = await client.query('SELECT $1::text as name', ['brianc'])
+console.log(result)
+
+await client.end()
+```
+
+```ts
+client.query(config: QueryConfig) => Promise
+```
+
+**client.query with a QueryConfig**
+
+If you pass a `name` parameter to the `client.query` method, the client will create a [prepared statement](/features/queries#prepared-statements).
+
+```js
+const query = {
+ name: 'get-name',
+ text: 'SELECT $1::text',
+ values: ['brianc'],
+ rowMode: 'array',
+}
+
+const result = await client.query(query)
+console.log(result.rows) // ['brianc']
+
+await client.end()
+```
+
+**client.query with a `Submittable`**
+
+If you pass an object to `client.query` and the object has a `.submit` function on it, the client will pass it's PostgreSQL server connection to the object and delegate query dispatching to the supplied object. This is an advanced feature mostly intended for library authors. It is incidentally also currently how the callback and promise based queries above are handled internally, but this is subject to change. It is also how [pg-cursor](https://github.com/brianc/node-pg-cursor) and [pg-query-stream](https://github.com/brianc/node-pg-query-stream) work.
+
+```js
+import { Query } from 'pg'
+const query = new Query('select $1::text as name', ['brianc'])
+
+const result = client.query(query)
+
+assert(query === result) // true
+
+query.on('row', (row) => {
+ console.log('row!', row) // { name: 'brianc' }
+})
+
+query.on('end', () => {
+ console.log('query done')
+})
+
+query.on('error', (err) => {
+ console.error(err.stack)
+})
+```
+
+---
+
+## client.end
+
+Disconnects the client from the PostgreSQL server.
+
+```js
+await client.end()
+console.log('client has disconnected')
+```
+
+## client.getTransactionStatus
+
+`client.getTransactionStatus() => string | null`
+
+Returns the current transaction status of the client connection. This can be useful for debugging transaction state issues or implementing custom transaction management logic.
+
+**Return values:**
+
+- `'I'` - Idle (not in a transaction)
+- `'T'` - Transaction active (BEGIN has been issued)
+- `'E'` - Error (transaction aborted, requires ROLLBACK)
+- `null` - Initial state (before first query)
+
+The transaction status is updated after each query completes based on the PostgreSQL backend's `ReadyForQuery` message.
+
+**Example: Checking transaction state**
+
+```js
+import { Client } from 'pg'
+const client = new Client()
+await client.connect()
+
+await client.query('BEGIN')
+console.log(client.getTransactionStatus()) // 'T' - in transaction
+
+await client.query('SELECT * FROM users')
+console.log(client.getTransactionStatus()) // 'T' - still in transaction
+
+await client.query('COMMIT')
+console.log(client.getTransactionStatus()) // 'I' - idle
+
+await client.end()
+```
+
+**Example: Handling transaction errors**
+
+```js
+import { Client } from 'pg'
+const client = new Client()
+await client.connect()
+
+await client.query('BEGIN')
+try {
+ await client.query('INVALID SQL')
+} catch (err) {
+ console.log(client.getTransactionStatus()) // 'E' - error state
+
+ // Must rollback to recover
+ await client.query('ROLLBACK')
+ console.log(client.getTransactionStatus()) // 'I' - idle again
+}
+
+await client.end()
+```
+
+## events
+
+### error
+
+```ts
+client.on('error', (err: Error) => void) => void
+```
+
+When the client is in the process of connecting, dispatching a query, or disconnecting it will catch and forward errors from the PostgreSQL server to the respective `client.connect` `client.query` or `client.end` promise; however, the client maintains a long-lived connection to the PostgreSQL back-end and due to network partitions, back-end crashes, fail-overs, etc the client can (and over a long enough time period _will_) eventually be disconnected while it is idle. To handle this you may want to attach an error listener to a client to catch errors. Here's a contrived example:
+
+```js
+const client = new pg.Client()
+client.connect()
+
+client.on('error', (err) => {
+ console.error('something bad has happened!', err.stack)
+})
+
+// walk over to server, unplug network cable
+
+// process output: 'something bad has happened!' followed by stacktrace :P
+```
+
+### end
+
+```ts
+client.on('end') => void
+```
+
+When the client disconnects from the PostgreSQL server it will emit an end event once.
+
+### notification
+
+Used for `listen/notify` events:
+
+```ts
+type Notification {
+ processId: number,
+ channel: string,
+ payload?: string
+}
+```
+
+```js
+const client = new pg.Client()
+await client.connect()
+
+client.query('LISTEN foo')
+
+client.on('notification', (msg) => {
+ console.log(msg.channel) // foo
+ console.log(msg.payload) // bar!
+})
+
+client.query(`NOTIFY foo, 'bar!'`)
+```
+
+### notice
+
+```ts
+client.on('notice', (notice: Error) => void) => void
+```
+
+Used to log out [notice messages](https://www.postgresql.org/docs/9.6/static/plpgsql-errors-and-messages.html) from the PostgreSQL server.
+
+```js
+client.on('notice', (msg) => console.warn('notice:', msg))
+```
diff --git a/docs/pages/apis/cursor.mdx b/docs/pages/apis/cursor.mdx
new file mode 100644
index 000000000..810bccdd3
--- /dev/null
+++ b/docs/pages/apis/cursor.mdx
@@ -0,0 +1,76 @@
+---
+title: pg.Cursor
+slug: /apis/cursor
+---
+
+A cursor can be used to efficiently read through large result sets without loading the entire result-set into memory ahead of time. It's useful to simulate a 'streaming' style read of data, or exit early from a large result set. The cursor is passed to `client.query` and is dispatched internally in a way very similar to how normal queries are sent, but the API it presents for consuming the result set is different.
+
+## install
+
+```
+$ npm install pg pg-cursor
+```
+
+## constructor
+
+### `new Cursor(text: String, values: Any[][, config: CursorQueryConfig])`
+
+Instantiates a new Cursor. A cursor is an instance of `Submittable` and should be passed directly to the `client.query` method.
+
+```js
+import { Pool } from 'pg'
+import Cursor from 'pg-cursor'
+
+const pool = new Pool()
+const client = await pool.connect()
+const text = 'SELECT * FROM my_large_table WHERE something > $1'
+const values = [10]
+
+const cursor = client.query(new Cursor(text, values))
+
+const { rows } = await cursor.read(100)
+console.log(rows.length) // 100 (unless the table has fewer than 100 rows)
+client.release()
+```
+
+```ts
+type CursorQueryConfig {
+ // by default rows come out as a key/value pair for each row
+ // pass the string 'array' here to receive rows as an array of values
+ rowMode?: string;
+
+ // custom type parsers just for this query result
+ types?: Types;
+}
+```
+
+## read
+
+### `cursor.read(rowCount: Number) => Promise`
+
+Read `rowCount` rows from the cursor instance. The callback will be called when the rows are available, loaded into memory, parsed, and converted to JavaScript types.
+
+If the cursor has read to the end of the result sets all subsequent calls to cursor#read will return a 0 length array of rows. Calling `read` on a cursor that has read to the end.
+
+Here is an example of reading to the end of a cursor:
+
+```js
+import { Pool } from 'pg'
+import Cursor from 'pg-cursor'
+
+const pool = new Pool()
+const client = await pool.connect()
+const cursor = client.query(new Cursor('select * from generate_series(0, 5)'))
+
+let rows = await cursor.read(100)
+assert(rows.length == 6)
+
+rows = await cursor.read(100)
+assert(rows.length == 0)
+```
+
+## close
+
+### `cursor.close() => Promise`
+
+Used to close the cursor early. If you want to stop reading from the cursor before you get all of the rows returned, call this.
diff --git a/docs/pages/apis/pool.mdx b/docs/pages/apis/pool.mdx
new file mode 100644
index 000000000..123bc8ba4
--- /dev/null
+++ b/docs/pages/apis/pool.mdx
@@ -0,0 +1,273 @@
+---
+title: pg.Pool
+---
+
+import { Alert } from '/components/alert.tsx'
+
+## new Pool
+
+```ts
+new Pool(config: Config)
+```
+
+Constructs a new pool instance.
+
+The pool is initially created empty and will create new clients lazily as they are needed. Every field of the `config` object is entirely optional. The config passed to the pool is also passed to every client instance within the pool when the pool creates that client.
+
+```ts
+type Config = {
+ // All valid client config options are also valid here.
+ // In addition here are the pool specific configuration parameters:
+
+ // Number of milliseconds to wait before timing out when connecting a new client.
+ // By default this is 0 which means no timeout.
+ connectionTimeoutMillis?: number
+
+ // Number of milliseconds a client must sit idle in the pool and not be checked out
+ // before it is disconnected from the backend and discarded.
+ // Default is 10000 (10 seconds) - set to 0 to disable auto-disconnection of idle clients.
+ idleTimeoutMillis?: number
+
+ // Maximum number of clients the pool should contain.
+ // By default this is set to 10. There is some nuance to setting the maximum size of your pool.
+ // See https://node-postgres.com/guides/pool-sizing for more information.
+ max?: number
+
+ // Minimum number of clients the pool should hold on to and _not_ destroy with the idleTimeoutMillis.
+ // This can be useful if you get very bursty traffic and want to keep a few clients around.
+ // Note: currently the pool will not automatically create and connect new clients up to the min, it will
+ // only not evict and close clients except those which exceed the min count.
+ // The default is 0 which disables this behavior.
+ min?: number
+
+ // Default behavior is the pool will keep clients open & connected to the backend
+ // until idleTimeoutMillis expire for each client and node will maintain a ref
+ // to the socket on the client, keeping the event loop alive until all clients are closed
+ // after being idle or the pool is manually shutdown with `pool.end()`.
+ //
+ // Setting `allowExitOnIdle: true` in the config will allow the node event loop to exit
+ // as soon as all clients in the pool are idle, even if their socket is still open
+ // to the postgres server. This can be handy in scripts & tests
+ // where you don't want to wait for your clients to go idle before your process exits.
+ allowExitOnIdle?: boolean
+
+ // Number of times a client can be checked out from the pool before it is
+ // disconnected and a new client is created in its place.
+ // The default is Infinity which means a client will never be automatically destroyed
+ // outside of other lifecycle events like manually removing it, it timing out due to idleness, etc.
+ maxUses?: number
+
+ // Sets a max overall life for the connection.
+ // A value of 60 would evict connections that have been around for over 60 seconds,
+ // regardless of whether they are idle. It's useful to force rotation of connection pools through
+ // middleware so that you can rotate the underlying servers. The default is disabled (value of zero).
+ maxLifetimeSeconds?: number
+
+ // Called once when a new client is created, before it is made available to the pool.
+ // The client is fully connected and queryable at this point.
+ // Can be a regular function or an async function.
+ // If the function throws or returns a promise that rejects, the client is destroyed
+ // and the error is returned to the caller requesting the connection.
+ onConnect?: (client: Client) => void | Promise
+}
+```
+
+example to create a new pool with configuration:
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool({
+ host: 'localhost',
+ user: 'database-user',
+ max: 20,
+ idleTimeoutMillis: 30000,
+ connectionTimeoutMillis: 2000,
+ maxLifetimeSeconds: 60,
+})
+```
+
+example using `onConnect` to run setup commands on each new client:
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool({
+ onConnect: async (client) => {
+ await client.query('SET search_path TO my_schema')
+ },
+})
+```
+
+## pool.query
+
+Often we only need to run a single query on the database, so as convenience the pool has a method to run a query on the first available idle client and return its result.
+
+```ts
+pool.query(text: string, values?: any[]) => Promise
+```
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+const result = await pool.query('SELECT $1::text as name', ['brianc'])
+console.log(result.rows[0].name) // brianc
+```
+
+Notice in the example above there is no need to check out or release a client. The pool is doing the acquiring and releasing internally. I find `pool.query` to be a handy shortcut in many situations and I use it exclusively unless I need a transaction.
+
+
+
+ Do not use pool.query if you are using a transaction.
+
+ The pool will dispatch every query passed to pool.query on the first available idle client. Transactions within PostgreSQL
+ are scoped to a single client and so dispatching individual queries within a single transaction across multiple, random
+ clients will cause big problems in your app and not work. For more info please read
+ transactions
+ .
+
+
+## pool.connect
+
+`pool.connect() => Promise`
+
+Acquires a client from the pool.
+
+- If there are idle clients in the pool one will be returned to the callback on `process.nextTick`.
+- If the pool is not full but all current clients are checked out a new client will be created & returned to this callback.
+- If the pool is 'full' and all clients are currently checked out, requests will wait in a FIFO queue until a client becomes available by being released back to the pool.
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+const client = await pool.connect()
+await client.query('SELECT NOW()')
+client.release()
+```
+
+### releasing clients
+
+`client.release(destroy?: boolean) => void`
+
+Client instances returned from `pool.connect` will have a `release` method which will release them from the pool.
+
+The `release` method on an acquired client returns it back to the pool. If you pass a truthy value in the `destroy` parameter, instead of releasing the client to the pool, the pool will be instructed to disconnect and destroy this client, leaving a space within itself for a new client.
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+// check out a single client
+const client = await pool.connect()
+
+// release the client
+client.release()
+```
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+assert(pool.totalCount === 0)
+assert(pool.idleCount === 0)
+
+const client = await pool.connect()
+await client.query('SELECT NOW()')
+assert(pool.totalCount === 1)
+assert(pool.idleCount === 0)
+
+// tell the pool to destroy this client
+await client.release(true)
+assert(pool.idleCount === 0)
+assert(pool.totalCount === 0)
+```
+
+
+
+ You must release a client when you are finished with it.
+
+ If you forget to release the client then your application will quickly exhaust available, idle clients in the pool and
+ all further calls to pool.connect will timeout with an error or hang indefinitely if you have
+ connectionTimeoutMillis
+ configured to 0.
+
+
+## pool.end
+
+Calling `pool.end` will drain the pool of all active clients, disconnect them, and shut down any internal timers in the pool. It is common to call this at the end of a script using the pool or when your process is attempting to shut down cleanly.
+
+```js
+// again both promises and callbacks are supported:
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+await pool.end()
+```
+
+## properties
+
+`pool.totalCount: number`
+
+The total number of clients existing within the pool.
+
+`pool.idleCount: number`
+
+The number of clients which are not checked out but are currently idle in the pool.
+
+`pool.waitingCount: number`
+
+The number of queued requests waiting on a client when all clients are checked out. It can be helpful to monitor this number to see if you need to adjust the size of the pool.
+
+## events
+
+`Pool` instances are also instances of [`EventEmitter`](https://nodejs.org/api/events.html).
+
+### connect
+
+`pool.on('connect', (client: Client) => void) => void`
+
+Whenever the pool establishes a new client connection to the PostgreSQL backend it will emit the `connect` event with the newly connected client.
+
+
+ The event listener does not wait for promises or async functions. If you want to run setup commands on each new client, use the `onConnect` option. (See documentation above.)
+
+
+### acquire
+
+`pool.on('acquire', (client: Client) => void) => void`
+
+Whenever a client is checked out from the pool the pool will emit the `acquire` event with the client that was acquired.
+
+### error
+
+`pool.on('error', (err: Error, client: Client) => void) => void`
+
+When a client is sitting idly in the pool it can still emit errors because it is connected to a live backend.
+
+If the backend goes down or a network partition is encountered all the idle, connected clients in your application will emit an error _through_ the pool's error event emitter.
+
+The error listener is passed the error as the first argument and the client upon which the error occurred as the 2nd argument. The client will be automatically terminated and removed from the pool, it is only passed to the error handler in case you want to inspect it.
+
+
+
You probably want to add an event listener to the pool to catch background errors!
+ Just like other event emitters, if a pool emits an error event and no listeners are added node will emit an
+ uncaught error and potentially crash your node process.
+
+
+### release
+
+`pool.on('release', (err: Error, client: Client) => void) => void`
+
+Whenever a client is released back into the pool, the pool will emit the `release` event.
+
+### remove
+
+`pool.on('remove', (client: Client) => void) => void`
+
+Whenever a client is closed & removed from the pool the pool will emit the `remove` event.
diff --git a/docs/pages/apis/result.mdx b/docs/pages/apis/result.mdx
new file mode 100644
index 000000000..da26adc70
--- /dev/null
+++ b/docs/pages/apis/result.mdx
@@ -0,0 +1,53 @@
+---
+title: pg.Result
+slug: /apis/result
+---
+
+The `pg.Result` shape is returned for every successful query.
+
+
note: you cannot instantiate this directly
+
+## properties
+
+### `result.rows: Array`
+
+Every result will have a rows array. If no rows are returned the array will be empty. Otherwise the array will contain one item for each row returned from the query. By default node-postgres creates a map from the name to value of each column, giving you a json-like object back for each row.
+
+### `result.fields: Array`
+
+Every result will have a fields array. This array contains the `name` and `dataTypeID` of each field in the result. These fields are ordered in the same order as the columns if you are using `arrayMode` for the query:
+
+```js
+import pg from 'pg'
+const { Pool } = pg
+
+const pool = new Pool()
+
+const client = await pool.connect()
+const result = await client.query({
+ rowMode: 'array',
+ text: 'SELECT 1 as one, 2 as two;',
+})
+console.log(result.fields[0].name) // one
+console.log(result.fields[1].name) // two
+console.log(result.rows) // [ [ 1, 2 ] ]
+await client.end()
+```
+
+### `result.command: string`
+
+The command type last executed: `INSERT` `UPDATE` `CREATE` `SELECT` etc.
+
+### `result.rowCount: int | null`
+
+The number of rows processed by the last command. Can be `null` for commands that never affect rows, such as the `LOCK`-command. More specifically, some commands, including `LOCK`, only return a command tag of the form `COMMAND`, without any `[ROWS]`-field to parse. For such commands `rowCount` will be `null`.
+
+_note: this does not reflect the number of rows __returned__ from a query. e.g. an update statement could update many rows (so high `result.rowCount` value) but `result.rows.length` would be zero. To check for an empty query response on a `SELECT` query use `result.rows.length === 0`_.
+
+[@sehrope](https://github.com/brianc/node-postgres/issues/2182#issuecomment-620553915) has a good explanation:
+
+The `rowCount` is populated from the command tag supplied by the PostgreSQL server. It's generally of the form: `COMMAND [OID] [ROWS]`
+
+For DML commands (INSERT, UPDATE, etc), it reflects how many rows the server modified to process the command. For SELECT or COPY commands it reflects how many rows were retrieved or copied. More info on the specifics here: https://www.postgresql.org/docs/current/protocol-message-formats.html (search for CommandComplete for the message type)
+
+The note in the docs about the difference is because that value is controlled by the server. It's possible for a non-standard server (ex: PostgreSQL fork) or a server version in the future to provide different information in some situations so it'd be best not to rely on it to assume that the rows array length matches the `rowCount`. It's fine to use it for DML counts though.
diff --git a/docs/pages/apis/types.mdx b/docs/pages/apis/types.mdx
new file mode 100644
index 000000000..cc8e4c1e3
--- /dev/null
+++ b/docs/pages/apis/types.mdx
@@ -0,0 +1,6 @@
+---
+title: Types
+slug: /apis/types
+---
+
+These docs are incomplete, for now please reference [pg-types docs](https://github.com/brianc/node-pg-types).
diff --git a/docs/pages/apis/utilities.mdx b/docs/pages/apis/utilities.mdx
new file mode 100644
index 000000000..10d9a0108
--- /dev/null
+++ b/docs/pages/apis/utilities.mdx
@@ -0,0 +1,33 @@
+---
+title: Utilities
+---
+import { Alert } from '/components/alert.tsx'
+
+## Utility Functions
+### pg.escapeIdentifier
+
+Escapes a string as a [SQL identifier](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS).
+
+```js
+import { escapeIdentifier } from 'pg';
+const escapedIdentifier = escapeIdentifier('FooIdentifier')
+console.log(escapedIdentifier) // '"FooIdentifier"'
+```
+
+
+ **Note**: When using an identifier that is the result of this function in an operation like `CREATE TABLE ${escapedIdentifier(identifier)}`, the table that is created will be CASE SENSITIVE. If you use any capital letters in the escaped identifier, you must always refer to the created table like `SELECT * from "MyCaseSensitiveTable"`; queries like `SELECT * FROM MyCaseSensitiveTable` will result in a "Non-existent table" error since case information is stripped from the query.
+
+
+### pg.escapeLiteral
+
+
+ **Note**: Instead of manually escaping SQL literals, it is recommended to use parameterized queries. Refer to [parameterized queries](/features/queries#parameterized-query) and the [client.query](/apis/client#clientquery) API for more information.
+
+
+Escapes a string as a [SQL literal](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS).
+
+```js
+import { escapeLiteral } from 'pg';
+const escapedLiteral = escapeLiteral("hello 'world'")
+console.log(escapedLiteral) // "'hello ''world'''"
+```
diff --git a/docs/pages/features/_meta.js b/docs/pages/features/_meta.js
new file mode 100644
index 000000000..62f1660ca
--- /dev/null
+++ b/docs/pages/features/_meta.js
@@ -0,0 +1,11 @@
+export default {
+ connecting: 'Connecting',
+ queries: 'Queries',
+ pooling: 'Pooling',
+ transactions: 'Transactions',
+ types: 'Data Types',
+ ssl: 'SSL',
+ native: 'Native',
+ esm: 'ESM',
+ callbacks: 'Callbacks',
+}
diff --git a/docs/pages/features/callbacks.mdx b/docs/pages/features/callbacks.mdx
new file mode 100644
index 000000000..8a6e2a525
--- /dev/null
+++ b/docs/pages/features/callbacks.mdx
@@ -0,0 +1,39 @@
+---
+title: Callbacks
+---
+
+## Callback Support
+
+`async` / `await` is the preferred way to write async code these days with node, but callbacks are supported in the `pg` module and the `pg-pool` module. To use them, pass a callback function as the last argument to the following methods & it will be called and a promise will not be returned:
+
+
+```js
+const { Pool, Client } = require('pg')
+
+// pool
+const pool = new Pool()
+// run a query on an available client
+pool.query('SELECT NOW()', (err, res) => {
+ console.log(err, res)
+})
+
+// check out a client to do something more complex like a transaction
+pool.connect((err, client, release) => {
+ client.query('SELECT NOW()', (err, res) => {
+ release()
+ console.log(err, res)
+ pool.end()
+ })
+
+})
+
+// single client
+const client = new Client()
+client.connect((err) => {
+ if (err) throw err
+ client.query('SELECT NOW()', (err, res) => {
+ console.log(err, res)
+ client.end()
+ })
+})
+```
diff --git a/docs/pages/features/connecting.mdx b/docs/pages/features/connecting.mdx
new file mode 100644
index 000000000..97b5c779f
--- /dev/null
+++ b/docs/pages/features/connecting.mdx
@@ -0,0 +1,157 @@
+---
+title: Connecting
+---
+
+## Environment variables
+
+node-postgres uses the same [environment variables](https://www.postgresql.org/docs/9.1/static/libpq-envars.html) as libpq and psql to connect to a PostgreSQL server. Both individual clients & pools will use these environment variables. Here's a tiny program connecting node.js to the PostgreSQL server:
+
+```js
+import pg from 'pg'
+const { Pool, Client } = pg
+
+// pools will use environment variables
+// for connection information
+const pool = new Pool()
+
+// you can also use async/await
+const res = await pool.query('SELECT NOW()')
+await pool.end()
+
+// clients will also use environment variables
+// for connection information
+const client = new Client()
+await client.connect()
+
+const res = await client.query('SELECT NOW()')
+await client.end()
+```
+
+To run the above program and specify which database to connect to we can invoke it like so:
+
+```sh
+$ PGUSER=dbuser \
+ PGPASSWORD=secretpassword \
+ PGHOST=database.server.com \
+ PGPORT=3211 \
+ PGDATABASE=mydb \
+ node script.js
+```
+
+This allows us to write our programs without having to specify connection information in the program and lets us reuse them to connect to different databases without having to modify the code.
+
+The default values for the environment variables used are:
+
+```
+PGUSER=process.env.USER
+PGPASSWORD=null
+PGHOST=localhost
+PGPORT=5432
+PGDATABASE=process.env.USER
+```
+
+## Programmatic
+
+node-postgres also supports configuring a pool or client programmatically with connection information. Here's our same script from above modified to use programmatic (hard-coded in this case) values. This can be useful if your application already has a way to manage config values or you don't want to use environment variables.
+
+```js
+import pg from 'pg'
+const { Pool, Client } = pg
+
+const pool = new Pool({
+ user: 'dbuser',
+ password: 'secretpassword',
+ host: 'database.server.com',
+ port: 3211,
+ database: 'mydb',
+})
+
+console.log(await pool.query('SELECT NOW()'))
+
+const client = new Client({
+ user: 'dbuser',
+ password: 'secretpassword',
+ host: 'database.server.com',
+ port: 3211,
+ database: 'mydb',
+})
+
+await client.connect()
+
+console.log(await client.query('SELECT NOW()'))
+
+await client.end()
+```
+
+Many cloud providers include alternative methods for connecting to database instances using short-lived authentication tokens. node-postgres supports dynamic passwords via a callback function, either synchronous or asynchronous. The callback function must resolve to a string.
+
+```js
+import pg from 'pg'
+const { Pool } = pg
+import { RDS } from 'aws-sdk'
+
+const signerOptions = {
+ credentials: {
+ accessKeyId: 'YOUR-ACCESS-KEY',
+ secretAccessKey: 'YOUR-SECRET-ACCESS-KEY',
+ },
+ region: 'us-east-1',
+ hostname: 'example.aslfdewrlk.us-east-1.rds.amazonaws.com',
+ port: 5432,
+ username: 'api-user',
+}
+
+const signer = new RDS.Signer(signerOptions)
+
+const getPassword = () => signer.getAuthToken()
+
+const pool = new Pool({
+ user: signerOptions.username,
+ password: getPassword,
+ host: signerOptions.hostname,
+ port: signerOptions.port,
+ database: 'my-db',
+})
+```
+
+### Unix Domain Sockets
+
+Connections to unix sockets can also be made. This can be useful on distros like Ubuntu, where authentication is managed via the socket connection instead of a password.
+
+```js
+import pg from 'pg'
+const { Client } = pg
+client = new Client({
+ user: 'username',
+ password: 'password',
+ host: '/cloudsql/myproject:zone:mydb',
+ database: 'database_name',
+})
+```
+
+## Connection URI
+
+You can initialize both a pool and a client with a connection string URI as well. This is common in environments like Heroku where the database connection string is supplied to your application dyno through an environment variable. Connection string parsing brought to you by [pg-connection-string](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string).
+
+```js
+import pg from 'pg'
+const { Pool, Client } = pg
+const connectionString = 'postgresql://dbuser:secretpassword@database.server.com:3211/mydb'
+
+const pool = new Pool({
+ connectionString,
+})
+
+await pool.query('SELECT NOW()')
+await pool.end()
+
+const client = new Client({
+ connectionString,
+})
+
+await client.connect()
+
+await client.query('SELECT NOW()')
+
+await client.end()
+```
diff --git a/docs/pages/features/esm.mdx b/docs/pages/features/esm.mdx
new file mode 100644
index 000000000..7aac546a7
--- /dev/null
+++ b/docs/pages/features/esm.mdx
@@ -0,0 +1,37 @@
+---
+title: ESM
+---
+
+## ESM Support
+
+As of v8.15.x node-postgres supporters the __ECMAScript Module__ (ESM) format. This means you can use `import` statements instead of `require` or `import pg from 'pg'`.
+
+CommonJS modules are still supported. The ESM format is an opt-in feature and will not affect existing codebases that use CommonJS.
+
+The docs have been changed to show ESM usage, but in a CommonJS context you can still use the same code, you just need to change the import format.
+
+If you're using CommonJS, you can use the following code to import the `pg` module:
+
+```js
+ const pg = require('pg')
+ const { Client } = pg
+ // etc...
+```
+
+### ESM Usage
+
+If you're using ESM, you can use the following code to import the `pg` module:
+
+```js
+ import { Client } from 'pg'
+ // etc...
+```
+
+
+Previously if you were using ESM you would have to use the following code:
+
+```js
+ import pg from 'pg'
+ const { Client } = pg
+ // etc...
+```
diff --git a/docs/pages/features/native.mdx b/docs/pages/features/native.mdx
new file mode 100644
index 000000000..a4febc7e6
--- /dev/null
+++ b/docs/pages/features/native.mdx
@@ -0,0 +1,42 @@
+---
+title: Native Bindings
+slug: /features/native
+metaTitle: bar
+---
+
+Native bindings between node.js & [libpq](https://www.postgresql.org/docs/9.5/static/libpq.html) are provided by the [node-pg-native](https://github.com/brianc/node-pg-native) package. node-postgres can consume this package & use the native bindings to access the PostgreSQL server while giving you the same interface that is used with the JavaScript version of the library.
+
+You need PostgreSQL client libraries & tools installed. An easy way to check is to type `pg_config`. If `pg_config` is in your path, you should be good to go. If it's not in your path you'll need to consult operating specific instructions on how to go about getting it there.
+
+Some ways I've done it in the past:
+
+- On macOS: `brew install libpq`
+- On Ubuntu/Debian and Debian-based Node images: `apt-get install libpq-dev python3 g++ make`
+- On RHEL/CentOS: `yum install postgresql-devel`
+- On Windows:
+ 1. Install Visual Studio C++ (successfully built with Express 2010). Express is free.
+ 2. Install PostgreSQL (`http://www.postgresql.org/download/windows/`)
+ 3. Add your Postgre Installation's `bin` folder to the system path (i.e. `C:\Program Files\PostgreSQL\9.3\bin`).
+ 4. Make sure that both `libpq.dll` and `pg_config.exe` are in that folder.
+
+Install `pg` and `pg-native` them:
+
+```sh
+$ npm install pg pg-native
+```
+
+Once `pg-native` is installed instead of requiring a `Client` or `Pool` constructor from `pg` you do the following:
+
+```js
+import pg from 'pg'
+const { native } = pg
+const { Client, Pool } = native
+```
+
+When you access the `.native` property on `'pg'` it will automatically require the `pg-native` package and wrap it in the same API.
+
+
+ Care has been taken to normalize between the two, but there might still be edge cases where things behave subtly differently due to the nature of using libpq over handling the binary protocol directly in JavaScript, so it's recommended you chose to either use the JavaScript driver or the native bindings both in development and production. For what its worth: I use the pure JavaScript driver because the JavaScript driver is more portable (doesn't need a compiler), and the pure JavaScript driver is plenty fast.
+
+
+Some of the modules using advanced features of PostgreSQL such as [pg-query-stream](https://github.com/brianc/node-pg-query-stream), [pg-cursor](https://github.com/brianc/node-pg-cursor),and [pg-copy-streams](https://github.com/brianc/node-pg-copy-streams) need to operate directly on the binary stream and therefore are incompatible with the native bindings.
diff --git a/docs/pages/features/pooling.mdx b/docs/pages/features/pooling.mdx
new file mode 100644
index 000000000..ebe2844bc
--- /dev/null
+++ b/docs/pages/features/pooling.mdx
@@ -0,0 +1,109 @@
+---
+title: Pooling
+---
+
+import { Alert } from '/components/alert.tsx'
+import { Info } from '/components/info.tsx'
+
+If you're working on a web application or other software which makes frequent queries you'll want to use a connection pool.
+
+The easiest and by far most common way to use node-postgres is through a connection pool.
+
+## Why?
+
+- Connecting a new client to the PostgreSQL server requires a handshake which can take 20-30 milliseconds. During this time passwords are negotiated, SSL may be established, and configuration information is shared with the client & server. Incurring this cost _every time_ we want to execute a query would substantially slow down our application.
+
+- The PostgreSQL server can only handle a [limited number of clients at a time](https://wiki.postgresql.org/wiki/Number_Of_Database_Connections). Depending on the available memory of your PostgreSQL server you may even crash the server if you connect an unbounded number of clients. _note: I have crashed a large production PostgreSQL server instance in RDS by opening new clients and never disconnecting them in a python application long ago. It was not fun._
+
+- PostgreSQL can only process one query at a time on a single connected client in a first-in first-out manner. If your multi-tenant web application is using only a single connected client all queries among all simultaneous requests will be pipelined and executed serially, one after the other. No good!
+
+### Good news
+
+node-postgres ships with built-in connection pooling via the [pg-pool](/apis/pool) module.
+
+## Examples
+
+The client pool allows you to have a reusable pool of clients you can check out, use, and return. You generally want a limited number of these in your application and usually just 1. Creating an unbounded number of pools defeats the purpose of pooling at all.
+
+### Checkout, use, and return
+
+```js
+import pg from 'pg'
+const { Pool } = pg
+
+const pool = new Pool()
+
+// the pool will emit an error on behalf of any idle clients
+// it contains if a backend error or network partition happens
+pool.on('error', (err, client) => {
+ console.error('Unexpected error on idle client', err)
+ process.exit(-1)
+})
+
+const client = await pool.connect()
+const res = await client.query('SELECT * FROM users WHERE id = $1', [1])
+console.log(res.rows[0])
+
+client.release()
+```
+
+
+
+ You must always return the client to the pool if you successfully check it out, regardless of whether or not
+ there was an error with the queries you ran on the client.
+
+ If you don't release the client your application will leak them and eventually your pool will be empty forever and all
+ future requests to check out a client from the pool will wait forever.
+
+
+### Single query
+
+If you don't need a transaction or you just need to run a single query, the pool has a convenience method to run a query on any available client in the pool. This is the preferred way to query with node-postgres if you can as it removes the risk of leaking a client.
+
+```js
+import pg from 'pg'
+const { Pool } = pg
+
+const pool = new Pool()
+
+const res = await pool.query('SELECT * FROM users WHERE id = $1', [1])
+console.log('user:', res.rows[0])
+```
+
+### Shutdown
+
+To shut down a pool call `pool.end()` on the pool. This will wait for all checked-out clients to be returned and then shut down all the clients and the pool timers.
+
+```js
+import pg from 'pg'
+const { Pool } = pg
+const pool = new Pool()
+
+console.log('starting async query')
+const result = await pool.query('SELECT NOW()')
+console.log('async query finished')
+
+console.log('starting callback query')
+pool.query('SELECT NOW()', (err, res) => {
+ console.log('callback query finished')
+})
+
+console.log('calling end')
+await pool.end()
+console.log('pool has drained')
+```
+
+The output of the above will be:
+
+```
+starting async query
+async query finished
+starting callback query
+calling end
+callback query finished
+pool has drained
+```
+
+
+ The pool will return errors when attempting to check out a client after you've called pool.end() on the pool.
+
diff --git a/docs/pages/features/queries.mdx b/docs/pages/features/queries.mdx
new file mode 100644
index 000000000..63ecdde1e
--- /dev/null
+++ b/docs/pages/features/queries.mdx
@@ -0,0 +1,135 @@
+---
+title: Queries
+slug: /features/queries
+---
+
+For the sake of brevity I am using the `client.query` method instead of the `pool.query` method - both methods support the same API. In fact, `pool.query` delegates directly to `client.query` internally.
+
+## Text only
+
+If your query has no parameters you do not need to include them to the query method:
+
+```js
+await client.query('SELECT NOW() as now')
+```
+
+## Parameterized query
+
+If you are passing parameters to your queries you will want to avoid string concatenating parameters into the query text directly. This can (and often does) lead to sql injection vulnerabilities. node-postgres supports parameterized queries, passing your query text _unaltered_ as well as your parameters to the PostgreSQL server where the parameters are safely substituted into the query with battle-tested parameter substitution code within the server itself.
+
+```js
+const text = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *'
+const values = ['brianc', 'brian.m.carlson@gmail.com']
+
+const res = await client.query(text, values)
+console.log(res.rows[0])
+// { name: 'brianc', email: 'brian.m.carlson@gmail.com' }
+```
+
+
+ PostgreSQL does not support parameters for identifiers. If you need to have dynamic database, schema, table, or column names (e.g. in DDL statements) use [pg-format](https://www.npmjs.com/package/pg-format) package for handling escaping these values to ensure you do not have SQL injection!
+
+
+Parameters passed as the second argument to `query()` will be converted to raw data types using the following rules:
+
+**null and undefined**
+
+If parameterizing `null` and `undefined` then both will be converted to `null`.
+
+**Date**
+
+Custom conversion to a UTC date string.
+
+**Buffer**
+
+Buffer instances are unchanged.
+
+**Array**
+
+Converted to a string that describes a Postgres array. Each array item is recursively converted using the rules described here.
+
+**Object**
+
+If a parameterized value has the method `toPostgres` then it will be called and its return value will be used in the query.
+The signature of `toPostgres` is the following:
+
+```
+toPostgres (prepareValue: (value) => any): any
+```
+
+The `prepareValue` function provided can be used to convert nested types to raw data types suitable for the database.
+
+Otherwise if no `toPostgres` method is defined then `JSON.stringify` is called on the parameterized value.
+
+**Everything else**
+
+All other parameterized values will be converted by calling `value.toString` on the value.
+
+## Query config object
+
+`pool.query` and `client.query` both support taking a config object as an argument instead of taking a string and optional array of parameters. The same example above could also be performed like so:
+
+```js
+const query = {
+ text: 'INSERT INTO users(name, email) VALUES($1, $2)',
+ values: ['brianc', 'brian.m.carlson@gmail.com'],
+}
+
+const res = await client.query(query)
+console.log(res.rows[0])
+```
+
+The query config object allows for a few more advanced scenarios:
+
+### Prepared statements
+
+PostgreSQL has the concept of a [prepared statement](https://www.postgresql.org/docs/9.3/static/sql-prepare.html). node-postgres supports this by supplying a `name` parameter to the query config object. If you supply a `name` parameter the query execution plan will be cached on the PostgreSQL server on a **per connection basis**. This means if you use two different connections each will have to parse & plan the query once. node-postgres handles this transparently for you: a client only requests a query to be parsed the first time that particular client has seen that query name:
+
+```js
+const query = {
+ // give the query a unique name
+ name: 'fetch-user',
+ text: 'SELECT * FROM user WHERE id = $1',
+ values: [1],
+}
+
+const res = await client.query(query)
+console.log(res.rows[0])
+```
+
+In the above example the first time the client sees a query with the name `'fetch-user'` it will send a 'parse' request to the PostgreSQL server & execute the query as normal. The second time, it will skip the 'parse' request and send the _name_ of the query to the PostgreSQL server.
+
+
+
+Be careful not to fall into the trap of premature optimization. Most of your queries will likely not benefit much, if at all, from using prepared statements. This is a somewhat "power user" feature of PostgreSQL that is best used when you know how to use it - namely with very complex queries with lots of joins and advanced operations like union and switch statements. I rarely use this feature in my own apps unless writing complex aggregate queries for reports and I know the reports are going to be executed very frequently.
+
+
+
+### Row mode
+
+By default node-postgres reads rows and collects them into JavaScript objects with the keys matching the column names and the values matching the corresponding row value for each column. If you do not need or do not want this behavior you can pass `rowMode: 'array'` to a query object. This will inform the result parser to bypass collecting rows into a JavaScript object, and instead will return each row as an array of values.
+
+```js
+const query = {
+ text: 'SELECT $1::text as first_name, $2::text as last_name',
+ values: ['Brian', 'Carlson'],
+ rowMode: 'array',
+}
+
+const res = await client.query(query)
+console.log(res.fields.map(field => field.name)) // ['first_name', 'last_name']
+console.log(res.rows[0]) // ['Brian', 'Carlson']
+```
+
+### Types
+
+You can pass in a custom set of type parsers to use when parsing the results of a particular query. The `types` property must conform to the [Types](/apis/types) API. Here is an example in which every value is returned as a string:
+
+```js
+const query = {
+ text: 'SELECT * from some_table',
+ types: {
+ getTypeParser: () => val => val,
+ },
+}
+```
diff --git a/docs/pages/features/ssl.mdx b/docs/pages/features/ssl.mdx
new file mode 100644
index 000000000..9983c0434
--- /dev/null
+++ b/docs/pages/features/ssl.mdx
@@ -0,0 +1,64 @@
+---
+title: SSL
+slug: /features/ssl
+---
+
+node-postgres supports TLS/SSL connections to your PostgreSQL server as long as the server is configured to support it. When instantiating a pool or a client you can provide an `ssl` property on the config object and it will be passed to the constructor for the [node TLSSocket](https://nodejs.org/api/tls.html#tls_class_tls_tlssocket).
+
+## Self-signed cert
+
+Here's an example of a configuration you can use to connect a client or a pool to a PostgreSQL server.
+
+```js
+const config = {
+ database: 'database-name',
+ host: 'host-or-ip',
+ // this object will be passed to the TLSSocket constructor
+ ssl: {
+ ca: fs.readFileSync('/path/to/server-certificates/root.crt'),
+ key: fs.readFileSync('/path/to/client-key/postgresql.key'),
+ cert: fs.readFileSync('/path/to/client-certificates/postgresql.crt'),
+ },
+}
+
+import { Client, Pool } from 'pg'
+
+const client = new Client(config)
+await client.connect()
+console.log('connected')
+await client.end()
+
+const pool = new Pool(config)
+const pooledClient = await pool.connect()
+console.log('connected')
+pooledClient.release()
+await pool.end()
+```
+
+## Usage with `connectionString`
+
+If you plan to use a combination of a database connection string from the environment and SSL settings in the config object directly, then you must avoid including any of `sslcert`, `sslkey`, `sslrootcert`, or `sslmode` in the connection string. If any of these options are used then the `ssl` object is replaced and any additional options provided there will be lost.
+
+```js
+const config = {
+ connectionString: 'postgres://user:password@host:port/db?sslmode=require',
+ // Beware! The ssl object is overwritten when parsing the connectionString
+ ssl: {
+ ca: fs.readFileSync('/path/to/server-certificates/root.crt'),
+ },
+}
+```
+
+## Channel binding
+
+If the PostgreSQL server offers SCRAM-SHA-256-PLUS (i.e. channel binding) for TLS/SSL connections, you can enable this as follows:
+
+```js
+const client = new Client({ ...config, enableChannelBinding: true})
+```
+
+or
+
+```js
+const pool = new Pool({ ...config, enableChannelBinding: true})
+```
diff --git a/docs/pages/features/transactions.mdx b/docs/pages/features/transactions.mdx
new file mode 100644
index 000000000..4433bd3e4
--- /dev/null
+++ b/docs/pages/features/transactions.mdx
@@ -0,0 +1,39 @@
+---
+title: Transactions
+---
+
+import { Alert } from '/components/alert.tsx'
+
+To execute a transaction with node-postgres you simply execute `BEGIN / COMMIT / ROLLBACK` queries yourself through a client. Because node-postgres strives to be low level and un-opinionated, it doesn't provide any higher level abstractions specifically around transactions.
+
+
+ You must use the same client instance for all statements within a transaction. PostgreSQL
+ isolates a transaction to individual clients. This means if you initialize or use transactions with the{' '}
+ pool.query method you will have problems. Do not use transactions with
+ the pool.query method.
+
+
+## Examples
+
+```js
+import { Pool } from 'pg'
+const pool = new Pool()
+
+const client = await pool.connect()
+
+try {
+ await client.query('BEGIN')
+ const queryText = 'INSERT INTO users(name) VALUES($1) RETURNING id'
+ const res = await client.query(queryText, ['brianc'])
+
+ const insertPhotoText = 'INSERT INTO photos(user_id, photo_url) VALUES ($1, $2)'
+ const insertPhotoValues = [res.rows[0].id, 's3.bucket.foo']
+ await client.query(insertPhotoText, insertPhotoValues)
+ await client.query('COMMIT')
+} catch (e) {
+ await client.query('ROLLBACK')
+ throw e
+} finally {
+ client.release()
+}
+```
diff --git a/docs/pages/features/types.mdx b/docs/pages/features/types.mdx
new file mode 100644
index 000000000..36e8b7035
--- /dev/null
+++ b/docs/pages/features/types.mdx
@@ -0,0 +1,106 @@
+---
+title: Data Types
+---
+
+import { Alert } from '/components/alert.tsx'
+
+PostgreSQL has a rich system of supported [data types](https://www.postgresql.org/docs/current/datatype.html). node-postgres does its best to support the most common data types out of the box and supplies an extensible type parser to allow for custom type serialization and parsing.
+
+## strings by default
+
+node-postgres will convert a database type to a JavaScript string if it doesn't have a registered type parser for the database type. Furthermore, you can send any type to the PostgreSQL server as a string and node-postgres will pass it through without modifying it in any way. To circumvent the type parsing completely do something like the following.
+
+```js
+const queryText = 'SELECT int_col::text, date_col::text, json_col::text FROM my_table'
+const result = await client.query(queryText)
+
+console.log(result.rows[0]) // will contain the unparsed string value of each column
+```
+
+## type parsing examples
+
+### uuid + json / jsonb
+
+There is no data type in JavaScript for a uuid/guid so node-postgres converts a uuid to a string. JavaScript has great support for JSON and node-postgres converts json/jsonb objects directly into their JavaScript object via [`JSON.parse`](https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js#L193). Likewise sending an object to the PostgreSQL server via a query from node-postgres, node-postgres will call [`JSON.stringify`](https://github.com/brianc/node-postgres/blob/e5f0e5d36a91a72dda93c74388ac890fa42b3be0/lib/utils.js#L47) on your outbound value, automatically converting it to json for the server.
+
+```js
+const createTableText = `
+CREATE EXTENSION IF NOT EXISTS "pgcrypto";
+
+CREATE TEMP TABLE IF NOT EXISTS users (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ data JSONB
+);
+`
+// create our temp table
+await client.query(createTableText)
+
+const newUser = { email: 'brian.m.carlson@gmail.com' }
+// create a new user
+await client.query('INSERT INTO users(data) VALUES($1)', [newUser])
+
+const { rows } = await client.query('SELECT * FROM users')
+
+console.log(rows)
+/*
+output:
+[{
+ id: 'd70195fd-608e-42dc-b0f5-eee975a621e9',
+ data: { email: 'brian.m.carlson@gmail.com' }
+}]
+*/
+```
+
+### date / timestamp / timestamptz
+
+node-postgres will convert instances of JavaScript date objects into the expected input value for your PostgreSQL server. Likewise, when reading a `date`, `timestamp`, or `timestamptz` column value back into JavaScript, node-postgres will parse the value into an instance of a JavaScript `Date` object.
+
+```js
+const createTableText = `
+CREATE TEMP TABLE dates(
+ date_col DATE,
+ timestamp_col TIMESTAMP,
+ timestamptz_col TIMESTAMPTZ
+);
+`
+// create our temp table
+await client.query(createTableText)
+
+// insert the current time into it
+const now = new Date()
+const insertText = 'INSERT INTO dates(date_col, timestamp_col, timestamptz_col) VALUES ($1, $2, $3)'
+await client.query(insertText, [now, now, now])
+
+// read the row back out
+const result = await client.query('SELECT * FROM dates')
+
+console.log(result.rows)
+// {
+// date_col: 2017-05-29T05:00:00.000Z,
+// timestamp_col: 2017-05-29T23:18:13.263Z,
+// timestamptz_col: 2017-05-29T23:18:13.263Z
+// }
+```
+
+psql output:
+
+```
+bmc=# select * from dates;
+ date_col | timestamp_col | timestamptz_col
+------------+-------------------------+----------------------------
+ 2017-05-29 | 2017-05-29 18:18:13.263 | 2017-05-29 18:18:13.263-05
+(1 row)
+```
+
+node-postgres converts `DATE` and `TIMESTAMP` columns into the **local** time of the node process set at `process.env.TZ`.
+
+_note: I generally use `TIMESTAMPTZ` when storing dates; otherwise, inserting a time from a process in one timezone and reading it out in a process in another timezone can cause unexpected differences in the time._
+
+
+
+ Although PostgreSQL supports microseconds in dates, JavaScript only supports dates to the millisecond precision.
+ Keep this in mind when you send dates to and from PostgreSQL from node: your microseconds will be truncated when
+ converting to a JavaScript date object even if they exist in the database. If you need to preserve them, I recommend
+ using a custom type parser.
+
+
diff --git a/docs/pages/guides/_meta.js b/docs/pages/guides/_meta.js
new file mode 100644
index 000000000..2f13073d5
--- /dev/null
+++ b/docs/pages/guides/_meta.js
@@ -0,0 +1,6 @@
+export default {
+ 'project-structure': 'Suggested Code Structure',
+ 'async-express': 'Express with Async/Await',
+ 'pool-sizing': 'Pool Sizing',
+ upgrading: 'Upgrading',
+}
diff --git a/docs/pages/guides/async-express.md b/docs/pages/guides/async-express.md
new file mode 100644
index 000000000..a44c15289
--- /dev/null
+++ b/docs/pages/guides/async-express.md
@@ -0,0 +1,82 @@
+---
+title: Express with async/await
+---
+
+My preferred way to use node-postgres (and all async code in node.js) is with `async/await`. I find it makes reasoning about control-flow easier and allows me to write more concise and maintainable code.
+
+This is how I typically structure express web-applications with node-postgres to use `async/await`:
+
+```
+- app.js
+- index.js
+- routes/
+ - index.js
+ - photos.js
+ - user.js
+- db/
+ - index.js <--- this is where I put data access code
+```
+
+That's the same structure I used in the [project structure](/guides/project-structure) example.
+
+My `db/index.js` file usually starts out like this:
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+export const query = (text, params) => pool.query(text, params)
+```
+
+Then I will install [express-promise-router](https://www.npmjs.com/package/express-promise-router) and use it to define my routes. Here is my `routes/user.js` file:
+
+```js
+import Router from 'express-promise-router'
+import db from '../db.js'
+
+// create a new express-promise-router
+// this has the same API as the normal express router except
+// it allows you to use async functions as route handlers
+const router = new Router()
+
+// export our router to be mounted by the parent application
+export default router
+
+router.get('/:id', async (req, res) => {
+ const { id } = req.params
+ const { rows } = await db.query('SELECT * FROM users WHERE id = $1', [id])
+ res.send(rows[0])
+})
+```
+
+Then in my `routes/index.js` file I'll have something like this which mounts each individual router into the main application:
+
+```js
+// ./routes/index.js
+import users from './user.js'
+import photos from './photos.js'
+
+const mountRoutes = (app) => {
+ app.use('/users', users)
+ app.use('/photos', photos)
+ // etc..
+}
+
+export default mountRoutes
+```
+
+And finally in my `app.js` file where I bootstrap express I will have my `routes/index.js` file mount all my routes. The routes know they're using async functions but because of express-promise-router the main express app doesn't know and doesn't care!
+
+```js
+// ./app.js
+import express from 'express'
+import mountRoutes from './routes.js'
+
+const app = express()
+mountRoutes(app)
+
+// ... more express setup stuff can follow
+```
+
+Now you've got `async/await`, node-postgres, and express all working together!
diff --git a/docs/pages/guides/pool-sizing.md b/docs/pages/guides/pool-sizing.md
new file mode 100644
index 000000000..430e69190
--- /dev/null
+++ b/docs/pages/guides/pool-sizing.md
@@ -0,0 +1,37 @@
+---
+title: Pool Sizing
+---
+
+If you're using a [pool](/apis/pool) in an application with multiple instances of your service running (common in most cloud/container environments currently), you'll need to think a bit about the `max` parameter of your pool across all services and all _instances_ of all services which are connecting to your Postgres server.
+
+This can get pretty complex depending on your cloud environment. Further nuance is introduced with things like pg-bouncer, RDS connection proxies, etc., which will do some forms of connection pooling and connection multiplexing. So, it's definitely worth thinking about. Let's run through a few setups. While certainly not exhaustive, these examples hopefully prompt you into thinking about what's right for your setup.
+
+## Simple apps, dev mode, fixed instance counts, etc.
+
+If your app isn't running in a k8s style env with containers scaling automatically or lambdas or cloud functions etc., you can do some "napkin math" for the `max` pool config you can use. Let's assume your Postgres instance is configured to have a maximum of 200 connections at any one time. You know your service is going to run on 4 instances. You can set the `max` pool size to 50, but if all your services are saturated waiting on database connections, you won't be able to connect to the database from any mgmt tools or scale up your services without changing config/code to adjust the max size.
+
+In this situation, I'd probably set the `max` to 20 or 25. This lets you have plenty of headroom for scaling more instances and realistically, if your app is starved for db connections, you probably want to take a look at your queries and make them execute faster, or cache, or something else to reduce the load on the database. I worked on a more reporting-heavy application with limited users, but each running 5-6 queries at a time which all took 100-200 milliseconds to run. In that situation, I upped the `max` to 50. Typically, though, I don't bother setting it to anything other than the default of `10` as that's usually _fine_.
+
+## Auto-scaling, cloud-functions, multi-tenancy, etc.
+
+If the number of instances of your services which connect to your database is more dynamic and based on things like load, auto-scaling containers, or running in cloud-functions, you need to be a bit more thoughtful about what your max might be. Often in these environments, there will be another database pooling proxy in front of the database like pg-bouncer or the RDS-proxy, etc. I'm not sure how all these function exactly, and they all have some trade-offs, but let's assume you're not using a proxy. Then I'd be pretty cautious about how large you set any individual pool. If you're running an application under pretty serious load where you need dynamic scaling or lots of lambdas spinning up and sending queries, your queries are likely fast and you should be fine setting the `max` to a low value like 10 -- or just leave it alone, since `10` is the default.
+
+### Vercel
+
+If you're running on Vercel with [fluid compute](https://vercel.com/kb/guide/efficiently-manage-database-connection-pools-with-fluid-compute), your serverless functions can handle multiple requests concurrently and stick around between invocations. In this case, you can treat it similarly to a traditional long-lived process and use a default-ish pool size of `10`. The pool will stay warm across requests and you'll get the benefits of connection reuse. You'll probably need to put pgBouncer (or some kind of pooler like what is offered with Supabase, RDS, GCP, etc.) in front of your database, as Vercel worker count can grow quite a bit larger than the number of reasonable max connections Postgres can handle.
+
+### Cloudflare workers
+
+In a fully stateless serverless environment like Cloudflare Workers where your worker is killed, suspended, moved to a new compute node, or shut down at the end of every request, you'll still probably be okay with a pool size `max` of `10`, though you can lower it if you start hitting connection exhaustion limits on your pooler. In Cloudflare the pooler is Hyperdrive, and in my experience it works fantastically with their workers setup. Make sure at the end of your serverless handler, after everything is done, you close and dispose of the pool by calling `pool.end()`. Setting the pool to a size larger than 1 is still recommended, as things like tRPC and other server-side routing & request batching code could result in multiple independent queries executing at the same time. With a pool size of `1` you are turning what is "a few things at once" into all things waiting in line one after another on the one available client in the pool.
+
+## pg-bouncer, RDS-proxy, etc.
+
+I'm not sure of all the pooling services for Postgres. I haven't used any myself. Throughout the years of working on `pg`, I've addressed issues caused by various proxies behaving differently than an actual Postgres backend. There are also gotchas with things like transactions. On the other hand, plenty of people run these with much success. In this situation, I would just recommend using some small but reasonable `max` value like the default value of `10` as it can still be helpful to keep a few TCP sockets from your services to the Postgres proxy open.
+
+## Conclusion, tl;dr
+
+It's a bit of a complicated topic and doesn't have much impact on things until you need to start scaling. At that point, your number of connections _still_ probably won't be your scaling bottleneck. It's worth thinking about a bit, but mostly I'd just leave the pool size to the default of `10` until you run into troubles: hopefully you never do!
+
+## Need help?
+
+In my career, this has been the most error-prone thing related to running Postgres & Node, particularly with the differences in various serverless providers (Cloudflare, Vercel, Lambda, etc.) versus more traditional hosting. If you have any questions or need help, please don't hesitate to email me at [brian.m.carlson@gmail.com](mailto:brian.m.carlson@gmail.com) or reach out on GitHub.
diff --git a/docs/pages/guides/project-structure.md b/docs/pages/guides/project-structure.md
new file mode 100644
index 000000000..5f53a4183
--- /dev/null
+++ b/docs/pages/guides/project-structure.md
@@ -0,0 +1,131 @@
+---
+title: Suggested Project Structure
+---
+
+Whenever I am writing a project & using node-postgres I like to create a file within it and make all interactions with the database go through this file. This serves a few purposes:
+
+- Allows my project to adjust to any changes to the node-postgres API without having to trace down all the places I directly use node-postgres in my application.
+- Allows me to have a single place to put logging and diagnostics around my database.
+- Allows me to make custom extensions to my database access code & share it throughout the project.
+- Allows a single place to bootstrap & configure the database.
+
+## example
+
+The location doesn't really matter - I've found it usually ends up being somewhat app specific and in line with whatever folder structure conventions you're using. For this example I'll use an express app structured like so:
+
+```
+- app.js
+- index.js
+- routes/
+ - index.js
+ - photos.js
+ - user.js
+- db/
+ - index.js <--- this is where I put data access code
+```
+
+Typically I'll start out my `db/index.js` file like so:
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+export const query = (text, params) => {
+ return pool.query(text, params)
+}
+```
+
+That's it. But now everywhere else in my application instead of requiring `pg` directly, I'll require this file. Here's an example of a route within `routes/user.js`:
+
+```js
+// notice here I'm requiring my database adapter file
+// and not requiring node-postgres directly
+import * as db from '../db/index.js'
+
+app.get('/:id', async (req, res, next) => {
+ const result = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id])
+ res.send(result.rows[0])
+})
+
+// ... many other routes in this file
+```
+
+Imagine we have lots of routes scattered throughout many files under our `routes/` directory. We now want to go back and log every single query that's executed, how long it took, and the number of rows it returned. If we had required node-postgres directly in every route file we'd have to go edit every single route - that would take forever & be really error prone! But thankfully we put our data access into `db/index.js`. Let's go add some logging:
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+export const query = async (text, params) => {
+ const start = Date.now()
+ const res = await pool.query(text, params)
+ const duration = Date.now() - start
+ console.log('executed query', { text, duration, rows: res.rowCount })
+ return res
+}
+```
+
+That was pretty quick! And now all of our queries everywhere in our application are being logged.
+
+_note: I didn't log the query parameters. Depending on your application you might be storing encrypted passwords or other sensitive information in your database. If you log your query parameters you might accidentally log sensitive information. Every app is different though so do what suits you best!_
+
+Now what if we need to check out a client from the pool to run several queries in a row in a transaction? We can add another method to our `db/index.js` file when we need to do this:
+
+```js
+import { Pool } from 'pg'
+
+const pool = new Pool()
+
+export const query = async (text, params) => {
+ const start = Date.now()
+ const res = await pool.query(text, params)
+ const duration = Date.now() - start
+ console.log('executed query', { text, duration, rows: res.rowCount })
+ return res
+}
+
+export const getClient = () => {
+ return pool.connect()
+}
+```
+
+Okay. Great - the simplest thing that could possibly work. It seems like one of our routes that checks out a client to run a transaction is forgetting to call `release` in some situation! Oh no! We are leaking a client & have hundreds of these routes to go audit. Good thing we have all our client access going through this single file. Lets add some deeper diagnostic information here to help us track down where the client leak is happening.
+
+```js
+export const query = async (text, params) => {
+ const start = Date.now()
+ const res = await pool.query(text, params)
+ const duration = Date.now() - start
+ console.log('executed query', { text, duration, rows: res.rowCount })
+ return res
+}
+
+export const getClient = async () => {
+ const client = await pool.connect()
+ const query = client.query
+ const release = client.release
+ // set a timeout of 5 seconds, after which we will log this client's last query
+ const timeout = setTimeout(() => {
+ console.error('A client has been checked out for more than 5 seconds!')
+ console.error(`The last executed query on this client was: ${client.lastQuery}`)
+ }, 5000)
+ // monkey patch the query method to keep track of the last query executed
+ client.query = (...args) => {
+ client.lastQuery = args
+ return query.apply(client, args)
+ }
+ client.release = () => {
+ // clear our timeout
+ clearTimeout(timeout)
+ // set the methods back to their old un-monkey-patched version
+ client.query = query
+ client.release = release
+ return release.apply(client)
+ }
+ return client
+}
+```
+
+That should hopefully give us enough diagnostic information to track down any leaks.
diff --git a/docs/pages/guides/upgrading.md b/docs/pages/guides/upgrading.md
new file mode 100644
index 000000000..14e7042b8
--- /dev/null
+++ b/docs/pages/guides/upgrading.md
@@ -0,0 +1,115 @@
+---
+title: Upgrading
+slug: /guides/upgrading
+---
+
+## node version support
+
+I have maintained legacy apps in production for many years. I get it...upgrading node and your entire dependency tree is rough, but so is missing out on critical fixes. I've taken pride over the years in not introducing breaking changes without a need because I've spent too much of my own time in my own apps upgrading a semver major version of a library with many breaking changes. That being said: node-postgres only _officially_ supports node versions which are still under the [LTS lifetime](https://nodejs.org/en/about/previous-releases). The [CI matrix](https://github.com/brianc/node-postgres/blob/master/.github/workflows/ci.yml#L39) is the most official and enforced compatiblity matrix; however, I may drop support for node versions outside of node's LTS lifetime at any time, with any semver minor release, if it is required to land new features or bug fixes on supported versions of node. I recommend in general to use a lockfile, and, if you're on an older version of node nearing EOL use absolutely pinned versions for as many of your modules as you can, including this one.
+
+# Upgrading to 8.0
+
+node-postgres at 8.0 introduces a breaking change to ssl-verified connections. If you connect with ssl and use
+
+```
+const client = new Client({ ssl: true })
+```
+
+and the server's SSL certificate is self-signed, connections will fail as of node-postgres 8.0. To keep the existing behavior, modify the invocation to
+
+```
+const client = new Client({ ssl: { rejectUnauthorized: false } })
+```
+
+The rest of the changes are relatively minor and unlikely to cause issues; see [the announcement](/announcements#2020-02-25) for full details.
+
+# Upgrading to 7.0
+
+node-postgres at 7.0 introduces somewhat significant breaking changes to the public API.
+
+## pg singleton
+
+In the past there was a singleton pool manager attached to the root `pg` object in the package. This singleton could be used to provision connection pools automatically by calling `pg.connect`. This API caused a lot of confusion for users. It also introduced a opaque module-managed singleton which was difficult to reason about, debug, error-prone, and inflexible. Starting in pg@6.0 the methods' documentation was removed, and starting in pg@6.3 the methods were deprecated with a warning message.
+
+If your application still relies on these they will be _gone_ in `pg@7.0`. In order to migrate you can do the following:
+
+```js
+// old way, deprecated in 6.3.0:
+
+// connection using global singleton
+pg.connect(function (err, client, done) {
+ client.query(/* etc, etc */)
+ done()
+})
+
+// singleton pool shutdown
+pg.end()
+
+// ------------------
+
+// new way, available since 6.0.0:
+
+// create a pool
+const pool = new pg.Pool()
+
+// connection using created pool
+pool.connect(function (err, client, done) {
+ client.query(/* etc, etc */)
+ done()
+})
+
+// pool shutdown
+pool.end()
+```
+
+node-postgres ships with a built-in pool object provided by [pg-pool](https://github.com/brianc/node-pg-pool) which is already used internally by the `pg.connect` and `pg.end` methods. Migrating to a user-managed pool (or set of pools) allows you to more directly control their set up their life-cycle.
+
+## client.query(...).on
+
+Before `pg@7.0` the `client.query` method would _always_ return an instance of a query. The query instance was an event emitter, accepted a callback, and was also a promise. A few problems...
+
+- too many flow control options on a single object was confusing
+- event emitter `.on('error')` does not mix well with promise `.catch`
+- the `row` event was a common source of errors: it looks like a stream but has no support for back-pressure, misleading users into trying to pipe results or handling them in the event emitter for a desired performance gain.
+- error handling with a `.done` and `.error` emitter pair for every query is cumbersome and returning the emitter from `client.query` indicated this sort of pattern may be encouraged: it is not.
+
+Starting with `pg@7.0` the return value `client.query` will be dependent on what you pass to the method: I think this aligns more with how most node libraries handle the callback/promise combo, and I hope it will make the "just works" :tm: feeling better while reducing surface area and surprises around event emitter / callback combos.
+
+### client.query with a callback
+
+```js
+const query = client.query('SELECT NOW()', (err, res) => {
+ /* etc, etc */
+})
+assert(query === undefined) // true
+```
+
+If you pass a callback to the method `client.query` will return `undefined`. This limits flow control to the callback which is in-line with almost all of node's core APIs.
+
+### client.query without a callback
+
+```js
+const query = client.query('SELECT NOW()')
+assert(query instanceof Promise) // true
+assert(query.on === undefined) // true
+query.then((res) => /* etc, etc */)
+```
+
+If you do **not** pass a callback `client.query` will return an instance of a `Promise`. This will **not** be a query instance and will not be an event emitter. This is in line with how most promise-based APIs work in node.
+
+### client.query(Submittable)
+
+`client.query` has always accepted any object that has a `.submit` method on it. In this scenario the client calls `.submit` on the object, delegating execution responsibility to it. In this situation the client also **returns the instance it was passed**. This is how [pg-cursor](https://github.com/brianc/node-pg-cursor) and [pg-query-stream](https://github.com/brianc/node-pg-query-stream) work. So, if you need the event emitter functionality on your queries for some reason, it is still possible because `Query` is an instance of `Submittable`:
+
+```js
+import pg from 'pg'
+const { Client, Query } = pg
+const query = client.query(new Query('SELECT NOW()'))
+query.on('row', (row) => {})
+query.on('end', (res) => {})
+query.on('error', (res) => {})
+```
+
+`Query` is considered a public, documented part of the API of node-postgres and this form will be supported indefinitely.
+
+_note: I have been building apps with node-postgres for almost 7 years. In that time I have never used the event emitter API as the primary way to execute queries. I used to use callbacks and now I use async/await. If you need to stream results I highly recommend you use [pg-cursor](https://github.com/brianc/node-pg-cursor) or [pg-query-stream](https://github.com/brianc/node-pg-query-stream) and **not** the query object as an event emitter._
diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx
new file mode 100644
index 000000000..ff27662e6
--- /dev/null
+++ b/docs/pages/index.mdx
@@ -0,0 +1,83 @@
+---
+title: Welcome
+slug: /
+---
+
+import { Logo } from '/components/logo.tsx'
+
+node-postgres is a collection of node.js modules for interfacing with your PostgreSQL database. It has support for callbacks, promises, async/await, connection pooling, prepared statements, cursors, streaming results, C/C++ bindings, rich type parsing, and more! Just like PostgreSQL itself there are a lot of features: this documentation aims to get you up and running quickly and in the right direction. It also tries to provide guides for more advanced & edge-case topics allowing you to tap into the full power of PostgreSQL from node.js.
+
+## Install
+
+```bash
+$ npm install pg
+```
+
+## Supporters
+
+node-postgres continued development and support is made possible by the many [supporters](https://github.com/brianc/node-postgres/blob/master/SPONSORS.md).
+
+Special thanks to [Medplum](https://www.medplum.com/) for sponsoring node-postgres for a whole year!
+
+
+
+
+
+If you or your company would like to sponsor node-postgres stop by [GitHub Sponsors](https://github.com/sponsors/brianc) and sign up or feel free to [email me](mailto:brian@pecanware.com) if you want to add your logo to the documentation or discuss higher tiers of sponsorship!
+
+# Version compatibility
+
+node-postgres strives to be compatible with all recent LTS versions of node & the most recent "stable" version. At the time of this writing node-postgres is compatible with node 18.x, 20.x, 22.x, and 24.x.
+
+## Getting started
+
+The simplest possible way to connect, query, and disconnect is with async/await:
+
+```js
+import { Client } from 'pg'
+const client = await new Client().connect()
+
+const res = await client.query('SELECT $1::text as message', ['Hello world!'])
+console.log(res.rows[0].message) // Hello world!
+await client.end()
+```
+
+### Error Handling
+
+For the sake of simplicity, these docs will assume that the methods are successful. In real life use, make sure to properly handle errors thrown in the methods. A `try/catch` block is a great way to do so:
+
+```js
+import { Client } from 'pg'
+const client = await new Client().connect()
+
+try {
+ const res = await client.query('SELECT $1::text as message', ['Hello world!'])
+ console.log(res.rows[0].message) // Hello world!
+} catch (err) {
+ console.error(err);
+} finally {
+ await client.end()
+}
+```
+
+### Pooling
+
+In most applications you'll want to use a [connection pool](/features/pooling) to manage your connections. This is a more advanced topic, but here's a simple example of how to use it:
+
+```js
+import { Pool } from 'pg'
+const pool = new Pool()
+const res = await pool.query('SELECT $1::text as message', ['Hello world!'])
+console.log(res.rows[0].message) // Hello world!
+```
+
+Our real-world apps are almost always more complicated than that, and I urge you to read on!
diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico
new file mode 100644
index 000000000..ab485092f
Binary files /dev/null and b/docs/public/favicon.ico differ
diff --git a/docs/theme.config.js b/docs/theme.config.js
new file mode 100644
index 000000000..4c10dc5c2
--- /dev/null
+++ b/docs/theme.config.js
@@ -0,0 +1,111 @@
+// theme.config.js
+export default {
+ project: {
+ link: 'https://github.com/brianc/node-postgres',
+ },
+ twitter: {
+ cardType: 'summary_large_image',
+ site: 'https://node-postgres.com',
+ },
+ docsRepositoryBase: 'https://github.com/brianc/node-postgres/blob/master/docs', // base URL for the docs repository
+ titleSuffix: ' – node-postgres',
+ darkMode: true,
+ navigation: {
+ prev: true,
+ next: true,
+ },
+ footer: {
+ content: (
+
+ Please consider{' '}
+
+ sponsoring this project on GitHub!
+
+ !
+
+ ),
+ },
+ editLink: {
+ text: 'Edit this page on GitHub',
+ },
+ logo: (
+ <>
+
+ node-postgres
+ >
+ ),
+ chat: {
+ link: 'https://discord.gg/2afXp5vUWm',
+ },
+ navbar: {
+ extraContent: (
+
+
+
+ ),
+ },
+ head: (
+ <>
+
+
+
+
+
+
+ >
+ ),
+}
diff --git a/docs/yarn.lock b/docs/yarn.lock
new file mode 100644
index 000000000..f65b39f82
--- /dev/null
+++ b/docs/yarn.lock
@@ -0,0 +1,3570 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@antfu/install-pkg@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz#78fa036be1a6081b5a77a5cf59f50c7752b6ba26"
+ integrity sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==
+ dependencies:
+ package-manager-detector "^1.3.0"
+ tinyexec "^1.0.1"
+
+"@antfu/utils@^9.2.0":
+ version "9.2.1"
+ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-9.2.1.tgz#0a73683cf0a8c4cd38397b002439488229651cdb"
+ integrity sha512-TMilPqXyii1AsiEii6l6ubRzbo76p6oshUSYPaKsmXDavyMLqjzVDkcp3pHp5ELMUNJHATcEOGxKTTsX9yYhGg==
+
+"@braintree/sanitize-url@^7.1.1":
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz#15e19737d946559289b915e5dad3b4c28407735e"
+ integrity sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==
+
+"@chevrotain/cst-dts-gen@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz#5e0863cc57dc45e204ccfee6303225d15d9d4783"
+ integrity sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==
+ dependencies:
+ "@chevrotain/gast" "11.0.3"
+ "@chevrotain/types" "11.0.3"
+ lodash-es "4.17.21"
+
+"@chevrotain/gast@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/gast/-/gast-11.0.3.tgz#e84d8880323fe8cbe792ef69ce3ffd43a936e818"
+ integrity sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==
+ dependencies:
+ "@chevrotain/types" "11.0.3"
+ lodash-es "4.17.21"
+
+"@chevrotain/regexp-to-ast@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz#11429a81c74a8e6a829271ce02fc66166d56dcdb"
+ integrity sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==
+
+"@chevrotain/types@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-11.0.3.tgz#f8a03914f7b937f594f56eb89312b3b8f1c91848"
+ integrity sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==
+
+"@chevrotain/utils@11.0.3":
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-11.0.3.tgz#e39999307b102cff3645ec4f5b3665f5297a2224"
+ integrity sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==
+
+"@floating-ui/core@^1.7.3":
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7"
+ integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==
+ dependencies:
+ "@floating-ui/utils" "^0.2.10"
+
+"@floating-ui/dom@^1.7.4":
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77"
+ integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==
+ dependencies:
+ "@floating-ui/core" "^1.7.3"
+ "@floating-ui/utils" "^0.2.10"
+
+"@floating-ui/react-dom@^2.1.2":
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz#189f681043c1400561f62972f461b93f01bf2231"
+ integrity sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==
+ dependencies:
+ "@floating-ui/dom" "^1.7.4"
+
+"@floating-ui/react@^0.26.16":
+ version "0.26.28"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7"
+ integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==
+ dependencies:
+ "@floating-ui/react-dom" "^2.1.2"
+ "@floating-ui/utils" "^0.2.8"
+ tabbable "^6.0.0"
+
+"@floating-ui/utils@^0.2.10", "@floating-ui/utils@^0.2.8":
+ version "0.2.10"
+ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c"
+ integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==
+
+"@formatjs/intl-localematcher@^0.5.4":
+ version "0.5.10"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz#1e0bd3fc1332c1fe4540cfa28f07e9227b659a58"
+ integrity sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==
+ dependencies:
+ tslib "2"
+
+"@headlessui/react@^2.1.2":
+ version "2.2.9"
+ resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.9.tgz#213f78534c86e03a7c986d2c2abe1270622b3e13"
+ integrity sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==
+ dependencies:
+ "@floating-ui/react" "^0.26.16"
+ "@react-aria/focus" "^3.20.2"
+ "@react-aria/interactions" "^3.25.0"
+ "@tanstack/react-virtual" "^3.13.9"
+ use-sync-external-store "^1.5.0"
+
+"@iconify/types@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57"
+ integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
+
+"@iconify/utils@^3.0.1":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@iconify/utils/-/utils-3.0.2.tgz#9599607f20690cd3e7a5d2d459af0eb81a89dc2b"
+ integrity sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==
+ dependencies:
+ "@antfu/install-pkg" "^1.1.0"
+ "@antfu/utils" "^9.2.0"
+ "@iconify/types" "^2.0.0"
+ debug "^4.4.1"
+ globals "^15.15.0"
+ kolorist "^1.8.0"
+ local-pkg "^1.1.1"
+ mlly "^1.7.4"
+
+"@mdx-js/mdx@^3.0.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.1.tgz#c5ffd991a7536b149e17175eee57a1a2a511c6d1"
+ integrity sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdx" "^2.0.0"
+ acorn "^8.0.0"
+ collapse-white-space "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-util-scope "^1.0.0"
+ estree-walker "^3.0.0"
+ hast-util-to-jsx-runtime "^2.0.0"
+ markdown-extensions "^2.0.0"
+ recma-build-jsx "^1.0.0"
+ recma-jsx "^1.0.0"
+ recma-stringify "^1.0.0"
+ rehype-recma "^1.0.0"
+ remark-mdx "^3.0.0"
+ remark-parse "^11.0.0"
+ remark-rehype "^11.0.0"
+ source-map "^0.7.0"
+ unified "^11.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+"@mdx-js/react@^3.0.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.1.tgz#24bda7fffceb2fe256f954482123cda1be5f5fef"
+ integrity sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==
+ dependencies:
+ "@types/mdx" "^2.0.0"
+
+"@mermaid-js/parser@^0.6.2":
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-0.6.2.tgz#6d505a33acb52ddeb592c596b14f9d92a30396a9"
+ integrity sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==
+ dependencies:
+ langium "3.3.1"
+
+"@napi-rs/simple-git-android-arm-eabi@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.22.tgz#f2d240dc87e924a8ca4428fa68b121e8697f1765"
+ integrity sha512-JQZdnDNm8o43A5GOzwN/0Tz3CDBQtBUNqzVwEopm32uayjdjxev1Csp1JeaqF3v9djLDIvsSE39ecsN2LhCKKQ==
+
+"@napi-rs/simple-git-android-arm64@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.22.tgz#80432fe8ccca85764ca773bd605f456de02e3159"
+ integrity sha512-46OZ0SkhnvM+fapWjzg/eqbJvClxynUpWYyYBn4jAj7GQs1/Yyc8431spzDmkA8mL0M7Xo8SmbkzTDE7WwYAfg==
+
+"@napi-rs/simple-git-darwin-arm64@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.22.tgz#cc9730b7895318519a941f8e42d22ec6e458a3f3"
+ integrity sha512-zH3h0C8Mkn9//MajPI6kHnttywjsBmZ37fhLX/Fiw5XKu84eHA6dRyVtMzoZxj6s+bjNTgaMgMUucxPn9ktxTQ==
+
+"@napi-rs/simple-git-darwin-x64@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.22.tgz#216d9472db2f5e261e4b2879eef571e24ff05258"
+ integrity sha512-GZN7lRAkGKB6PJxWsoyeYJhh85oOOjVNyl+/uipNX8bR+mFDCqRsCE3rRCFGV9WrZUHXkcuRL2laIRn7lLi3ag==
+
+"@napi-rs/simple-git-freebsd-x64@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-freebsd-x64/-/simple-git-freebsd-x64-0.1.22.tgz#a526ebf45fa955b6523b0194d4e702c830503536"
+ integrity sha512-xyqX1C5I0WBrUgZONxHjZH5a4LqQ9oki3SKFAVpercVYAcx3pq6BkZy1YUOP4qx78WxU1CCNfHBN7V+XO7D99A==
+
+"@napi-rs/simple-git-linux-arm-gnueabihf@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.22.tgz#54aad93e1391bb242b14ab194b30efca1f53d798"
+ integrity sha512-4LOtbp9ll93B9fxRvXiUJd1/RM3uafMJE7dGBZGKWBMGM76+BAcCEUv2BY85EfsU/IgopXI6n09TycRfPWOjxA==
+
+"@napi-rs/simple-git-linux-arm64-gnu@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.22.tgz#1f022edd88904b8e6d58b81b792a3b45f378ab74"
+ integrity sha512-GVOjP/JjCzbQ0kSqao7ctC/1sodVtv5VF57rW9BFpo2y6tEYPCqHnkQkTpieuwMNe+TVOhBUC1+wH0d9/knIHg==
+
+"@napi-rs/simple-git-linux-arm64-musl@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.22.tgz#68fc72bd7d6dec9154b9fb0a6ad48bd99eb0c5a2"
+ integrity sha512-MOs7fPyJiU/wqOpKzAOmOpxJ/TZfP4JwmvPad/cXTOWYwwyppMlXFRms3i98EU3HOazI/wMU2Ksfda3+TBluWA==
+
+"@napi-rs/simple-git-linux-ppc64-gnu@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-ppc64-gnu/-/simple-git-linux-ppc64-gnu-0.1.22.tgz#19e595c5a13c95ae6a48a096ba5debf6dbc896ed"
+ integrity sha512-L59dR30VBShRUIZ5/cQHU25upNgKS0AMQ7537J6LCIUEFwwXrKORZKJ8ceR+s3Sr/4jempWVvMdjEpFDE4HYww==
+
+"@napi-rs/simple-git-linux-s390x-gnu@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-s390x-gnu/-/simple-git-linux-s390x-gnu-0.1.22.tgz#399c7c6a1050ae382c61b276f898155636c83faf"
+ integrity sha512-4FHkPlCSIZUGC6HiADffbe6NVoTBMd65pIwcd40IDbtFKOgFMBA+pWRqKiQ21FERGH16Zed7XHJJoY3jpOqtmQ==
+
+"@napi-rs/simple-git-linux-x64-gnu@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.22.tgz#8f74a3a42d6b9e5d50e3f3ff68aeebc5d3768d9d"
+ integrity sha512-Ei1tM5Ho/dwknF3pOzqkNW9Iv8oFzRxE8uOhrITcdlpxRxVrBVptUF6/0WPdvd7R9747D/q61QG/AVyWsWLFKw==
+
+"@napi-rs/simple-git-linux-x64-musl@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.22.tgz#71e311b95d07c15c74075b487c6cfa56ac99733c"
+ integrity sha512-zRYxg7it0p3rLyEJYoCoL2PQJNgArVLyNavHW03TFUAYkYi5bxQ/UFNVpgxMaXohr5yu7qCBqeo9j4DWeysalg==
+
+"@napi-rs/simple-git-win32-arm64-msvc@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.22.tgz#01b948b52217ae79e3c80bccd9b05ae728c37ce4"
+ integrity sha512-XGFR1fj+Y9cWACcovV2Ey/R2xQOZKs8t+7KHPerYdJ4PtjVzGznI4c2EBHXtdOIYvkw7tL5rZ7FN1HJKdD5Quw==
+
+"@napi-rs/simple-git-win32-ia32-msvc@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-win32-ia32-msvc/-/simple-git-win32-ia32-msvc-0.1.22.tgz#3ec733ade79fc50050c9268c7ec163118e4bf1a2"
+ integrity sha512-Gqr9Y0gs6hcNBA1IXBpoqTFnnIoHuZGhrYqaZzEvGMLrTrpbXrXVEtX3DAAD2RLc1b87CPcJ49a7sre3PU3Rfw==
+
+"@napi-rs/simple-git-win32-x64-msvc@0.1.22":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.22.tgz#aee122d7aa030f45775bfd8abff49ffe12b89eb7"
+ integrity sha512-hQjcreHmUcpw4UrtkOron1/TQObfe484lxiXFLLUj7aWnnnOVs1mnXq5/Bo9+3NYZldFpFRJPdPBeHCisXkKJg==
+
+"@napi-rs/simple-git@^0.1.9":
+ version "0.1.22"
+ resolved "https://registry.yarnpkg.com/@napi-rs/simple-git/-/simple-git-0.1.22.tgz#00a2520aedcfc73b65bb8147fde352c78da27423"
+ integrity sha512-bMVoAKhpjTOPHkW/lprDPwv5aD4R4C3Irt8vn+SKA9wudLe9COLxOhurrKRsxmZccUbWXRF7vukNeGUAj5P8kA==
+ optionalDependencies:
+ "@napi-rs/simple-git-android-arm-eabi" "0.1.22"
+ "@napi-rs/simple-git-android-arm64" "0.1.22"
+ "@napi-rs/simple-git-darwin-arm64" "0.1.22"
+ "@napi-rs/simple-git-darwin-x64" "0.1.22"
+ "@napi-rs/simple-git-freebsd-x64" "0.1.22"
+ "@napi-rs/simple-git-linux-arm-gnueabihf" "0.1.22"
+ "@napi-rs/simple-git-linux-arm64-gnu" "0.1.22"
+ "@napi-rs/simple-git-linux-arm64-musl" "0.1.22"
+ "@napi-rs/simple-git-linux-ppc64-gnu" "0.1.22"
+ "@napi-rs/simple-git-linux-s390x-gnu" "0.1.22"
+ "@napi-rs/simple-git-linux-x64-gnu" "0.1.22"
+ "@napi-rs/simple-git-linux-x64-musl" "0.1.22"
+ "@napi-rs/simple-git-win32-arm64-msvc" "0.1.22"
+ "@napi-rs/simple-git-win32-ia32-msvc" "0.1.22"
+ "@napi-rs/simple-git-win32-x64-msvc" "0.1.22"
+
+"@next/env@13.5.11":
+ version "13.5.11"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.11.tgz#6712d907e2682199aa1e8229b5ce028ee5a8001b"
+ integrity sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==
+
+"@next/swc-darwin-arm64@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.9.tgz#46c3a525039171ff1a83c813d7db86fb7808a9b2"
+ integrity sha512-pVyd8/1y1l5atQRvOaLOvfbmRwefxLhqQOzYo/M7FQ5eaRwA1+wuCn7t39VwEgDd7Aw1+AIWwd+MURXUeXhwDw==
+
+"@next/swc-darwin-x64@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.9.tgz#b690452e9a6ce839f8738e27e9fd1a8567dd7554"
+ integrity sha512-DwdeJqP7v8wmoyTWPbPVodTwCybBZa02xjSJ6YQFIFZFZ7dFgrieKW4Eo0GoIcOJq5+JxkQyejmI+8zwDp3pwA==
+
+"@next/swc-linux-arm64-gnu@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.9.tgz#c3e335e2da3ba932c0b2f571f0672d1aa7af33df"
+ integrity sha512-wdQsKsIsGSNdFojvjW3Ozrh8Q00+GqL3wTaMjDkQxVtRbAqfFBtrLPO0IuWChVUP2UeuQcHpVeUvu0YgOP00+g==
+
+"@next/swc-linux-arm64-musl@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.9.tgz#54600d4917bace2508725cc963eeeb3b6432889e"
+ integrity sha512-6VpS+bodQqzOeCwGxoimlRoosiWlSc0C224I7SQWJZoyJuT1ChNCo+45QQH+/GtbR/s7nhaUqmiHdzZC9TXnXA==
+
+"@next/swc-linux-x64-gnu@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.9.tgz#f869c2066f13ff2818140e0a145dfea1ea7c0333"
+ integrity sha512-XxG3yj61WDd28NA8gFASIR+2viQaYZEFQagEodhI/R49gXWnYhiflTeeEmCn7Vgnxa/OfK81h1gvhUZ66lozpw==
+
+"@next/swc-linux-x64-musl@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.9.tgz#09295ea60a42a1b22d927802d6e543d8a8bbb186"
+ integrity sha512-/dnscWqfO3+U8asd+Fc6dwL2l9AZDl7eKtPNKW8mKLh4Y4wOpjJiamhe8Dx+D+Oq0GYVjuW0WwjIxYWVozt2bA==
+
+"@next/swc-win32-arm64-msvc@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.9.tgz#f39e3513058d7af6e9f6b1f296bf071301217159"
+ integrity sha512-T/iPnyurOK5a4HRUcxAlss8uzoEf5h9tkd+W2dSWAfzxv8WLKlUgbfk+DH43JY3Gc2xK5URLuXrxDZ2mGfk/jw==
+
+"@next/swc-win32-ia32-msvc@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.9.tgz#d567f471e182efa4ea29f47f3030613dd3fc68b5"
+ integrity sha512-BLiPKJomaPrTAb7ykjA0LPcuuNMLDVK177Z1xe0nAem33+9FIayU4k/OWrtSn9SAJW/U60+1hoey5z+KCHdRLQ==
+
+"@next/swc-win32-x64-msvc@13.5.9":
+ version "13.5.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.9.tgz#35c53bd6d33040ec0ce1dd613c59112aac06b235"
+ integrity sha512-/72/dZfjXXNY/u+n8gqZDjI6rxKMpYsgBBYNZKWOQw0BpBF7WCnPflRy3ZtvQ2+IYI3ZH2bPyj7K+6a6wNk90Q==
+
+"@react-aria/focus@^3.20.2":
+ version "3.21.1"
+ resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.21.1.tgz#fad9d0803e0e4423bb6e14ed3208fffd694e5e42"
+ integrity sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==
+ dependencies:
+ "@react-aria/interactions" "^3.25.5"
+ "@react-aria/utils" "^3.30.1"
+ "@react-types/shared" "^3.32.0"
+ "@swc/helpers" "^0.5.0"
+ clsx "^2.0.0"
+
+"@react-aria/interactions@^3.25.0", "@react-aria/interactions@^3.25.5":
+ version "3.25.5"
+ resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.25.5.tgz#f7f69467c899f9673460c3401fcaac08d2dcac7d"
+ integrity sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==
+ dependencies:
+ "@react-aria/ssr" "^3.9.10"
+ "@react-aria/utils" "^3.30.1"
+ "@react-stately/flags" "^3.1.2"
+ "@react-types/shared" "^3.32.0"
+ "@swc/helpers" "^0.5.0"
+
+"@react-aria/ssr@^3.9.10":
+ version "3.9.10"
+ resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.10.tgz#7fdc09e811944ce0df1d7e713de1449abd7435e6"
+ integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==
+ dependencies:
+ "@swc/helpers" "^0.5.0"
+
+"@react-aria/utils@^3.30.1":
+ version "3.30.1"
+ resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.30.1.tgz#9eb704d4193674816e1e0eab758b12c2d69d7b0b"
+ integrity sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==
+ dependencies:
+ "@react-aria/ssr" "^3.9.10"
+ "@react-stately/flags" "^3.1.2"
+ "@react-stately/utils" "^3.10.8"
+ "@react-types/shared" "^3.32.0"
+ "@swc/helpers" "^0.5.0"
+ clsx "^2.0.0"
+
+"@react-stately/flags@^3.1.2":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@react-stately/flags/-/flags-3.1.2.tgz#5c8e5ae416d37d37e2e583d2fcb3a046293504f2"
+ integrity sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==
+ dependencies:
+ "@swc/helpers" "^0.5.0"
+
+"@react-stately/utils@^3.10.8":
+ version "3.10.8"
+ resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.10.8.tgz#fdb9d172f7bbc2d083e69190f5ef0edfa4b4392f"
+ integrity sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==
+ dependencies:
+ "@swc/helpers" "^0.5.0"
+
+"@react-types/shared@^3.32.0":
+ version "3.32.0"
+ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.32.0.tgz#6c105ef05e1bd84ab04531e707074dc2a0b3ce07"
+ integrity sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==
+
+"@shikijs/core@1.29.2":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.29.2.tgz#9c051d3ac99dd06ae46bd96536380c916e552bf3"
+ integrity sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==
+ dependencies:
+ "@shikijs/engine-javascript" "1.29.2"
+ "@shikijs/engine-oniguruma" "1.29.2"
+ "@shikijs/types" "1.29.2"
+ "@shikijs/vscode-textmate" "^10.0.1"
+ "@types/hast" "^3.0.4"
+ hast-util-to-html "^9.0.4"
+
+"@shikijs/engine-javascript@1.29.2":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz#a821ad713a3e0b7798a1926fd9e80116e38a1d64"
+ integrity sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==
+ dependencies:
+ "@shikijs/types" "1.29.2"
+ "@shikijs/vscode-textmate" "^10.0.1"
+ oniguruma-to-es "^2.2.0"
+
+"@shikijs/engine-oniguruma@1.29.2":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz#d879717ced61d44e78feab16f701f6edd75434f1"
+ integrity sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==
+ dependencies:
+ "@shikijs/types" "1.29.2"
+ "@shikijs/vscode-textmate" "^10.0.1"
+
+"@shikijs/langs@1.29.2":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-1.29.2.tgz#4f1de46fde8991468c5a68fa4a67dd2875d643cd"
+ integrity sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==
+ dependencies:
+ "@shikijs/types" "1.29.2"
+
+"@shikijs/themes@1.29.2":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-1.29.2.tgz#293cc5c83dd7df3fdc8efa25cec8223f3a6acb0d"
+ integrity sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==
+ dependencies:
+ "@shikijs/types" "1.29.2"
+
+"@shikijs/twoslash@^1.0.0":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/twoslash/-/twoslash-1.29.2.tgz#c4b683e25151d66cc35b4d817fcbc1e665e8df67"
+ integrity sha512-2S04ppAEa477tiaLfGEn1QJWbZUmbk8UoPbAEw4PifsrxkBXtAtOflIZJNtuCwz8ptc/TPxy7CO7gW4Uoi6o/g==
+ dependencies:
+ "@shikijs/core" "1.29.2"
+ "@shikijs/types" "1.29.2"
+ twoslash "^0.2.12"
+
+"@shikijs/types@1.29.2":
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.29.2.tgz#a93fdb410d1af8360c67bf5fc1d1a68d58e21c4f"
+ integrity sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==
+ dependencies:
+ "@shikijs/vscode-textmate" "^10.0.1"
+ "@types/hast" "^3.0.4"
+
+"@shikijs/vscode-textmate@^10.0.1":
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
+ integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
+
+"@swc/helpers@0.5.2":
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
+ integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
+ dependencies:
+ tslib "^2.4.0"
+
+"@swc/helpers@^0.5.0":
+ version "0.5.17"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971"
+ integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==
+ dependencies:
+ tslib "^2.8.0"
+
+"@tanstack/react-virtual@^3.13.9":
+ version "3.13.12"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz#d372dc2783739cc04ec1a728ca8203937687a819"
+ integrity sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==
+ dependencies:
+ "@tanstack/virtual-core" "3.13.12"
+
+"@tanstack/virtual-core@3.13.12":
+ version "3.13.12"
+ resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz#1dff176df9cc8f93c78c5e46bcea11079b397578"
+ integrity sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==
+
+"@theguild/remark-mermaid@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@theguild/remark-mermaid/-/remark-mermaid-0.1.3.tgz#55bd0ff5cd890c49f8137e76b5158abd787c3642"
+ integrity sha512-2FjVlaaKXK7Zj7UJAgOVTyaahn/3/EAfqYhyXg0BfDBVUl+lXcoIWRaxzqfnDr2rv8ax6GsC5mNh6hAaT86PDw==
+ dependencies:
+ mermaid "^11.0.0"
+ unist-util-visit "^5.0.0"
+
+"@theguild/remark-npm2yarn@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@theguild/remark-npm2yarn/-/remark-npm2yarn-0.3.3.tgz#5c132375d4fc83c5c49cf0fabc4e5f4147dba87c"
+ integrity sha512-ma6DvR03gdbvwqfKx1omqhg9May/VYGdMHvTzB4VuxkyS7KzfZ/lzrj43hmcsggpMje0x7SADA/pcMph0ejRnA==
+ dependencies:
+ npm-to-yarn "^3.0.0"
+ unist-util-visit "^5.0.0"
+
+"@types/d3-array@*":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.2.tgz#e02151464d02d4a1b44646d0fcdb93faf88fde8c"
+ integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==
+
+"@types/d3-axis@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795"
+ integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-brush@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c"
+ integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-chord@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d"
+ integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==
+
+"@types/d3-color@*":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
+ integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
+
+"@types/d3-contour@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231"
+ integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==
+ dependencies:
+ "@types/d3-array" "*"
+ "@types/geojson" "*"
+
+"@types/d3-delaunay@*":
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1"
+ integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==
+
+"@types/d3-dispatch@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz#ef004d8a128046cfce434d17182f834e44ef95b2"
+ integrity sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==
+
+"@types/d3-drag@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02"
+ integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-dsv@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17"
+ integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==
+
+"@types/d3-ease@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b"
+ integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
+
+"@types/d3-fetch@*":
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980"
+ integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==
+ dependencies:
+ "@types/d3-dsv" "*"
+
+"@types/d3-force@*":
+ version "3.0.10"
+ resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a"
+ integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==
+
+"@types/d3-format@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90"
+ integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==
+
+"@types/d3-geo@*":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440"
+ integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==
+ dependencies:
+ "@types/geojson" "*"
+
+"@types/d3-hierarchy@*":
+ version "3.1.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b"
+ integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==
+
+"@types/d3-interpolate@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
+ integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
+ dependencies:
+ "@types/d3-color" "*"
+
+"@types/d3-path@*":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a"
+ integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==
+
+"@types/d3-polygon@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c"
+ integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==
+
+"@types/d3-quadtree@*":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f"
+ integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==
+
+"@types/d3-random@*":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb"
+ integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==
+
+"@types/d3-scale-chromatic@*":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39"
+ integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==
+
+"@types/d3-scale@*":
+ version "4.0.9"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb"
+ integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==
+ dependencies:
+ "@types/d3-time" "*"
+
+"@types/d3-selection@*":
+ version "3.0.11"
+ resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.11.tgz#bd7a45fc0a8c3167a631675e61bc2ca2b058d4a3"
+ integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==
+
+"@types/d3-shape@*":
+ version "3.1.7"
+ resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555"
+ integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==
+ dependencies:
+ "@types/d3-path" "*"
+
+"@types/d3-time-format@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2"
+ integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==
+
+"@types/d3-time@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f"
+ integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==
+
+"@types/d3-timer@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
+ integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
+
+"@types/d3-transition@*":
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.9.tgz#1136bc57e9ddb3c390dccc9b5ff3b7d2b8d94706"
+ integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-zoom@*":
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b"
+ integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==
+ dependencies:
+ "@types/d3-interpolate" "*"
+ "@types/d3-selection" "*"
+
+"@types/d3@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2"
+ integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==
+ dependencies:
+ "@types/d3-array" "*"
+ "@types/d3-axis" "*"
+ "@types/d3-brush" "*"
+ "@types/d3-chord" "*"
+ "@types/d3-color" "*"
+ "@types/d3-contour" "*"
+ "@types/d3-delaunay" "*"
+ "@types/d3-dispatch" "*"
+ "@types/d3-drag" "*"
+ "@types/d3-dsv" "*"
+ "@types/d3-ease" "*"
+ "@types/d3-fetch" "*"
+ "@types/d3-force" "*"
+ "@types/d3-format" "*"
+ "@types/d3-geo" "*"
+ "@types/d3-hierarchy" "*"
+ "@types/d3-interpolate" "*"
+ "@types/d3-path" "*"
+ "@types/d3-polygon" "*"
+ "@types/d3-quadtree" "*"
+ "@types/d3-random" "*"
+ "@types/d3-scale" "*"
+ "@types/d3-scale-chromatic" "*"
+ "@types/d3-selection" "*"
+ "@types/d3-shape" "*"
+ "@types/d3-time" "*"
+ "@types/d3-time-format" "*"
+ "@types/d3-timer" "*"
+ "@types/d3-transition" "*"
+ "@types/d3-zoom" "*"
+
+"@types/debug@^4.0.0":
+ version "4.1.12"
+ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
+ integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==
+ dependencies:
+ "@types/ms" "*"
+
+"@types/estree-jsx@^1.0.0":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
+ integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==
+ dependencies:
+ "@types/estree" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
+ integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+
+"@types/geojson@*":
+ version "7946.0.16"
+ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a"
+ integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==
+
+"@types/hast@^3.0.0", "@types/hast@^3.0.4":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
+ integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
+ dependencies:
+ "@types/unist" "*"
+
+"@types/katex@^0.16.0":
+ version "0.16.7"
+ resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.7.tgz#03ab680ab4fa4fbc6cb46ecf987ecad5d8019868"
+ integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
+
+"@types/mdast@^4.0.0":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
+ integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
+ dependencies:
+ "@types/unist" "*"
+
+"@types/mdx@^2.0.0":
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd"
+ integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==
+
+"@types/ms@*":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
+ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
+
+"@types/nlcst@^2.0.0":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/nlcst/-/nlcst-2.0.3.tgz#31cad346eaab48a9a8a58465d3d05e2530dda762"
+ integrity sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==
+ dependencies:
+ "@types/unist" "*"
+
+"@types/trusted-types@^2.0.7":
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
+ integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
+
+"@types/unist@*", "@types/unist@^3.0.0":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
+ integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
+
+"@types/unist@^2.0.0":
+ version "2.0.11"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
+ integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
+
+"@typescript/vfs@^1.6.0":
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript/vfs/-/vfs-1.6.1.tgz#fe7087d5a43715754f7ea9bf6e0b905176c9eebd"
+ integrity sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA==
+ dependencies:
+ debug "^4.1.1"
+
+"@ungap/structured-clone@^1.0.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
+ integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+
+"@xmldom/xmldom@0.9.8":
+ version "0.9.8"
+ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.9.8.tgz#1471e82bdff9e8f20ee8bbe60d4ffa8a516e78d8"
+ integrity sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==
+
+acorn-jsx@^5.0.0:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn@^8.0.0, acorn@^8.15.0:
+ version "8.15.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
+ integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
+
+arg@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
+ integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
+
+argparse@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
+array-iterate@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-2.0.1.tgz#6efd43f8295b3fee06251d3d62ead4bd9805dd24"
+ integrity sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==
+
+astring@^1.8.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef"
+ integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==
+
+bail@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d"
+ integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
+
+better-react-mathjax@^2.0.3:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/better-react-mathjax/-/better-react-mathjax-2.3.0.tgz#d9a29b2b9eae873e60c0ca8042d7ecb94e2aa297"
+ integrity sha512-K0ceQC+jQmB+NLDogO5HCpqmYf18AU2FxDbLdduYgkHYWZApFggkHE4dIaXCV1NqeoscESYXXo1GSkY6fA295w==
+ dependencies:
+ mathjax-full "^3.2.2"
+
+busboy@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+ integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+ dependencies:
+ streamsearch "^1.1.0"
+
+caniuse-lite@^1.0.30001406:
+ version "1.0.30001746"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz#199d20f04f5369825e00ff7067d45d5dfa03aee7"
+ integrity sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==
+
+ccount@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
+ integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
+
+chalk@^5.0.0:
+ version "5.6.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea"
+ integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
+
+character-entities-html4@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
+ integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
+
+character-entities-legacy@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
+ integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+
+character-entities@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
+ integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
+
+character-reference-invalid@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
+ integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
+
+chevrotain-allstar@~0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz#b7412755f5d83cc139ab65810cdb00d8db40e6ca"
+ integrity sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==
+ dependencies:
+ lodash-es "^4.17.21"
+
+chevrotain@~11.0.3:
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-11.0.3.tgz#88ffc1fb4b5739c715807eaeedbbf200e202fc1b"
+ integrity sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==
+ dependencies:
+ "@chevrotain/cst-dts-gen" "11.0.3"
+ "@chevrotain/gast" "11.0.3"
+ "@chevrotain/regexp-to-ast" "11.0.3"
+ "@chevrotain/types" "11.0.3"
+ "@chevrotain/utils" "11.0.3"
+ lodash-es "4.17.21"
+
+client-only@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
+ integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
+
+clipboardy@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-4.0.0.tgz#e73ced93a76d19dd379ebf1f297565426dffdca1"
+ integrity sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==
+ dependencies:
+ execa "^8.0.1"
+ is-wsl "^3.1.0"
+ is64bit "^2.0.0"
+
+clsx@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
+ integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+
+collapse-white-space@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca"
+ integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==
+
+comma-separated-tokens@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
+ integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
+
+commander@13.1.0:
+ version "13.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46"
+ integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==
+
+commander@7:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+ integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
+commander@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
+ integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+
+compute-scroll-into-view@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz#02c3386ec531fb6a9881967388e53e8564f3e9aa"
+ integrity sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==
+
+confbox@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
+ integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
+
+confbox@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110"
+ integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==
+
+cose-base@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a"
+ integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==
+ dependencies:
+ layout-base "^1.0.0"
+
+cose-base@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-2.2.0.tgz#1c395c35b6e10bb83f9769ca8b817d614add5c01"
+ integrity sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==
+ dependencies:
+ layout-base "^2.0.0"
+
+cross-spawn@^7.0.3:
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+cytoscape-cose-bilkent@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b"
+ integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==
+ dependencies:
+ cose-base "^1.0.0"
+
+cytoscape-fcose@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz#e4d6f6490df4fab58ae9cea9e5c3ab8d7472f471"
+ integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==
+ dependencies:
+ cose-base "^2.2.0"
+
+cytoscape@^3.29.3:
+ version "3.33.1"
+ resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.33.1.tgz#449e05d104b760af2912ab76482d24c01cdd4c97"
+ integrity sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==
+
+"d3-array@1 - 2":
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
+ integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
+ dependencies:
+ internmap "^1.0.0"
+
+"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
+ integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
+ dependencies:
+ internmap "1 - 2"
+
+d3-axis@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322"
+ integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==
+
+d3-brush@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c"
+ integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-drag "2 - 3"
+ d3-interpolate "1 - 3"
+ d3-selection "3"
+ d3-transition "3"
+
+d3-chord@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966"
+ integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==
+ dependencies:
+ d3-path "1 - 3"
+
+"d3-color@1 - 3", d3-color@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
+ integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
+d3-contour@4:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc"
+ integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==
+ dependencies:
+ d3-array "^3.2.0"
+
+d3-delaunay@6:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b"
+ integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==
+ dependencies:
+ delaunator "5"
+
+"d3-dispatch@1 - 3", d3-dispatch@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
+ integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
+
+"d3-drag@2 - 3", d3-drag@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
+ integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-selection "3"
+
+"d3-dsv@1 - 3", d3-dsv@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73"
+ integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
+ dependencies:
+ commander "7"
+ iconv-lite "0.6"
+ rw "1"
+
+"d3-ease@1 - 3", d3-ease@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
+ integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+
+d3-fetch@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22"
+ integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==
+ dependencies:
+ d3-dsv "1 - 3"
+
+d3-force@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4"
+ integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-quadtree "1 - 3"
+ d3-timer "1 - 3"
+
+"d3-format@1 - 3", d3-format@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
+ integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
+
+d3-geo@3:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d"
+ integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==
+ dependencies:
+ d3-array "2.5.0 - 3"
+
+d3-hierarchy@3:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
+ integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
+
+"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
+ integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+ dependencies:
+ d3-color "1 - 3"
+
+d3-path@1:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
+ integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
+
+"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
+ integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
+
+d3-polygon@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398"
+ integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==
+
+"d3-quadtree@1 - 3", d3-quadtree@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f"
+ integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
+
+d3-random@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
+ integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
+
+d3-sankey@^0.12.3:
+ version "0.12.3"
+ resolved "https://registry.yarnpkg.com/d3-sankey/-/d3-sankey-0.12.3.tgz#b3c268627bd72e5d80336e8de6acbfec9d15d01d"
+ integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==
+ dependencies:
+ d3-array "1 - 2"
+ d3-shape "^1.2.0"
+
+d3-scale-chromatic@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314"
+ integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==
+ dependencies:
+ d3-color "1 - 3"
+ d3-interpolate "1 - 3"
+
+d3-scale@4:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
+ integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
+ dependencies:
+ d3-array "2.10.0 - 3"
+ d3-format "1 - 3"
+ d3-interpolate "1.2.0 - 3"
+ d3-time "2.1.1 - 3"
+ d3-time-format "2 - 4"
+
+"d3-selection@2 - 3", d3-selection@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
+ integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
+
+d3-shape@3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
+ integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
+ dependencies:
+ d3-path "^3.1.0"
+
+d3-shape@^1.2.0:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
+ integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
+ dependencies:
+ d3-path "1"
+
+"d3-time-format@2 - 4", d3-time-format@4:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
+ integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+ dependencies:
+ d3-time "1 - 3"
+
+"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
+ integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
+ dependencies:
+ d3-array "2 - 3"
+
+"d3-timer@1 - 3", d3-timer@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
+ integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+
+"d3-transition@2 - 3", d3-transition@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
+ integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
+ dependencies:
+ d3-color "1 - 3"
+ d3-dispatch "1 - 3"
+ d3-ease "1 - 3"
+ d3-interpolate "1 - 3"
+ d3-timer "1 - 3"
+
+d3-zoom@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
+ integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-drag "2 - 3"
+ d3-interpolate "1 - 3"
+ d3-selection "2 - 3"
+ d3-transition "2 - 3"
+
+d3@^7.9.0:
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d"
+ integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==
+ dependencies:
+ d3-array "3"
+ d3-axis "3"
+ d3-brush "3"
+ d3-chord "3"
+ d3-color "3"
+ d3-contour "4"
+ d3-delaunay "6"
+ d3-dispatch "3"
+ d3-drag "3"
+ d3-dsv "3"
+ d3-ease "3"
+ d3-fetch "3"
+ d3-force "3"
+ d3-format "3"
+ d3-geo "3"
+ d3-hierarchy "3"
+ d3-interpolate "3"
+ d3-path "3"
+ d3-polygon "3"
+ d3-quadtree "3"
+ d3-random "3"
+ d3-scale "4"
+ d3-scale-chromatic "3"
+ d3-selection "3"
+ d3-shape "3"
+ d3-time "3"
+ d3-time-format "4"
+ d3-timer "3"
+ d3-transition "3"
+ d3-zoom "3"
+
+dagre-d3-es@7.0.11:
+ version "7.0.11"
+ resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz#2237e726c0577bfe67d1a7cfd2265b9ab2c15c40"
+ integrity sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==
+ dependencies:
+ d3 "^7.9.0"
+ lodash-es "^4.17.21"
+
+dayjs@^1.11.18:
+ version "1.11.18"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11"
+ integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==
+
+debug@^4.0.0, debug@^4.1.1, debug@^4.4.1:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
+decode-named-character-reference@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed"
+ integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==
+ dependencies:
+ character-entities "^2.0.0"
+
+delaunator@5:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278"
+ integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==
+ dependencies:
+ robust-predicates "^3.0.2"
+
+dequal@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
+ integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+
+devlop@^1.0.0, devlop@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018"
+ integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
+ dependencies:
+ dequal "^2.0.0"
+
+dompurify@^3.2.5:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.7.tgz#721d63913db5111dd6dfda8d3a748cfd7982d44a"
+ integrity sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==
+ optionalDependencies:
+ "@types/trusted-types" "^2.0.7"
+
+emoji-regex-xs@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
+ integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
+
+entities@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694"
+ integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
+
+esast-util-from-estree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz#8d1cfb51ad534d2f159dc250e604f3478a79f1ad"
+ integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-visit "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+
+esast-util-from-js@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz#5147bec34cc9da44accf52f87f239a40ac3e8225"
+ integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ acorn "^8.0.0"
+ esast-util-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+escape-string-regexp@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
+esm@^3.2.25:
+ version "3.2.25"
+ resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
+ integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
+
+esprima@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+estree-util-attach-comments@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz#344bde6a64c8a31d15231e5ee9e297566a691c2d"
+ integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+estree-util-build-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz#b6d0bced1dcc4f06f25cf0ceda2b2dcaf98168f1"
+ integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-walker "^3.0.0"
+
+estree-util-is-identifier-name@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.1.0.tgz#fb70a432dcb19045e77b05c8e732f1364b4b49b2"
+ integrity sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==
+
+estree-util-is-identifier-name@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd"
+ integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==
+
+estree-util-scope@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-scope/-/estree-util-scope-1.0.0.tgz#9cbdfc77f5cb51e3d9ed4ad9c4adbff22d43e585"
+ integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+
+estree-util-to-js@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17"
+ integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ astring "^1.8.0"
+ source-map "^0.7.0"
+
+estree-util-value-to-estree@^3.0.1, estree-util-value-to-estree@^3.3.3:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz#827122e40c3a756d3c4cf5d5d296fa06026a1a4f"
+ integrity sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+estree-util-visit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb"
+ integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/unist" "^3.0.0"
+
+estree-walker@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
+ integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+execa@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
+ integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^8.0.1"
+ human-signals "^5.0.0"
+ is-stream "^3.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^5.1.0"
+ onetime "^6.0.0"
+ signal-exit "^4.1.0"
+ strip-final-newline "^3.0.0"
+
+exsolve@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.7.tgz#3b74e4c7ca5c5f9a19c3626ca857309fa99f9e9e"
+ integrity sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+fault@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c"
+ integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==
+ dependencies:
+ format "^0.2.0"
+
+flexsearch@^0.7.43:
+ version "0.7.43"
+ resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.7.43.tgz#34f89b36278a466ce379c5bf6fb341965ed3f16c"
+ integrity sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==
+
+format@^0.2.0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+ integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
+
+get-stream@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
+ integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
+
+github-slugger@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-2.0.0.tgz#52cf2f9279a21eb6c59dd385b410f0c0adda8f1a"
+ integrity sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+globals@^15.15.0:
+ version "15.15.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8"
+ integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
+
+graceful-fs@^4.1.2, graceful-fs@^4.2.11:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+gray-matter@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798"
+ integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==
+ dependencies:
+ js-yaml "^3.13.1"
+ kind-of "^6.0.2"
+ section-matter "^1.0.0"
+ strip-bom-string "^1.0.0"
+
+hachure-fill@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/hachure-fill/-/hachure-fill-0.5.2.tgz#d19bc4cc8750a5962b47fb1300557a85fcf934cc"
+ integrity sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==
+
+hast-util-from-dom@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz#c3c92fbd8d4e1c1625edeb3a773952b9e4ad64a8"
+ integrity sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hastscript "^9.0.0"
+ web-namespaces "^2.0.0"
+
+hast-util-from-html-isomorphic@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz#b31baee386a899a2472326a3c5692f29f86d1d3c"
+ integrity sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-from-dom "^5.0.0"
+ hast-util-from-html "^2.0.0"
+ unist-util-remove-position "^5.0.0"
+
+hast-util-from-html@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82"
+ integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ devlop "^1.1.0"
+ hast-util-from-parse5 "^8.0.0"
+ parse5 "^7.0.0"
+ vfile "^6.0.0"
+ vfile-message "^4.0.0"
+
+hast-util-from-parse5@^8.0.0:
+ version "8.0.3"
+ resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e"
+ integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ devlop "^1.0.0"
+ hastscript "^9.0.0"
+ property-information "^7.0.0"
+ vfile "^6.0.0"
+ vfile-location "^5.0.0"
+ web-namespaces "^2.0.0"
+
+hast-util-is-element@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz#6e31a6532c217e5b533848c7e52c9d9369ca0932"
+ integrity sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hast-util-parse-selector@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27"
+ integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hast-util-raw@^9.0.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e"
+ integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ hast-util-from-parse5 "^8.0.0"
+ hast-util-to-parse5 "^8.0.0"
+ html-void-elements "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ parse5 "^7.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-estree@^3.0.0, hast-util-to-estree@^3.1.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz#e654c1c9374645135695cc0ab9f70b8fcaf733d7"
+ integrity sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-attach-comments "^3.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-js "^1.0.0"
+ unist-util-position "^5.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-html@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
+ integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-whitespace "^3.0.0"
+ html-void-elements "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ stringify-entities "^4.0.0"
+ zwitch "^2.0.4"
+
+hast-util-to-jsx-runtime@^2.0.0:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98"
+ integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-js "^1.0.0"
+ unist-util-position "^5.0.0"
+ vfile-message "^4.0.0"
+
+hast-util-to-parse5@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed"
+ integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-string@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c"
+ integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hast-util-to-text@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz#57b676931e71bf9cb852453678495b3080bfae3e"
+ integrity sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ hast-util-is-element "^3.0.0"
+ unist-util-find-after "^5.0.0"
+
+hast-util-whitespace@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621"
+ integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hastscript@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff"
+ integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-parse-selector "^4.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+
+html-void-elements@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
+ integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
+
+human-signals@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
+ integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
+
+iconv-lite@0.6:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+inline-style-parser@0.2.4:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22"
+ integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
+
+"internmap@1 - 2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
+ integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+
+internmap@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
+ integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
+
+is-alphabetical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b"
+ integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
+
+is-alphanumerical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875"
+ integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
+ dependencies:
+ is-alphabetical "^2.0.0"
+ is-decimal "^2.0.0"
+
+is-decimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7"
+ integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
+
+is-docker@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200"
+ integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==
+
+is-extendable@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+ integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
+
+is-hexadecimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027"
+ integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
+
+is-inside-container@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4"
+ integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==
+ dependencies:
+ is-docker "^3.0.0"
+
+is-plain-obj@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
+ integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+
+is-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac"
+ integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
+
+is-wsl@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2"
+ integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==
+ dependencies:
+ is-inside-container "^1.0.0"
+
+is64bit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is64bit/-/is64bit-2.0.0.tgz#198c627cbcb198bbec402251f88e5e1a51236c07"
+ integrity sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==
+ dependencies:
+ system-architecture "^0.1.0"
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+"js-tokens@^3.0.0 || ^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@^3.13.1:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+ integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+katex@^0.16.0, katex@^0.16.22, katex@^0.16.9:
+ version "0.16.22"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.22.tgz#d2b3d66464b1e6d69e6463b28a86ced5a02c5ccd"
+ integrity sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==
+ dependencies:
+ commander "^8.3.0"
+
+khroma@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.1.0.tgz#45f2ce94ce231a437cf5b63c2e886e6eb42bbbb1"
+ integrity sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==
+
+kind-of@^6.0.0, kind-of@^6.0.2:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+ integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+
+kolorist@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
+ integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
+
+langium@3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/langium/-/langium-3.3.1.tgz#da745a40d5ad8ee565090fed52eaee643be4e591"
+ integrity sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==
+ dependencies:
+ chevrotain "~11.0.3"
+ chevrotain-allstar "~0.3.0"
+ vscode-languageserver "~9.0.1"
+ vscode-languageserver-textdocument "~1.0.11"
+ vscode-uri "~3.0.8"
+
+layout-base@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2"
+ integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
+
+layout-base@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285"
+ integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==
+
+local-pkg@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.2.tgz#c03d208787126445303f8161619dc701afa4abb5"
+ integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==
+ dependencies:
+ mlly "^1.7.4"
+ pkg-types "^2.3.0"
+ quansync "^0.2.11"
+
+lodash-es@4.17.21, lodash-es@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+ integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
+longest-streak@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
+ integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
+
+loose-envify@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+markdown-extensions@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4"
+ integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==
+
+markdown-table@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a"
+ integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==
+
+marked@^16.2.1:
+ version "16.3.0"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-16.3.0.tgz#2f513891f867d6edc4772b4a026db9cc331eb94f"
+ integrity sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==
+
+mathjax-full@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.2.2.tgz#43f02e55219db393030985d2b6537ceae82f1fa7"
+ integrity sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==
+ dependencies:
+ esm "^3.2.25"
+ mhchemparser "^4.1.0"
+ mj-context-menu "^0.6.1"
+ speech-rule-engine "^4.0.6"
+
+mdast-util-find-and-replace@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df"
+ integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ escape-string-regexp "^5.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+mdast-util-from-markdown@^2.0.0, mdast-util-from-markdown@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a"
+ integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark "^4.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+mdast-util-frontmatter@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz#f5f929eb1eb36c8a7737475c7eb438261f964ee8"
+ integrity sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ escape-string-regexp "^5.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ micromark-extension-frontmatter "^2.0.0"
+
+mdast-util-gfm-autolink-literal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5"
+ integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ ccount "^2.0.0"
+ devlop "^1.0.0"
+ mdast-util-find-and-replace "^3.0.0"
+ micromark-util-character "^2.0.0"
+
+mdast-util-gfm-footnote@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403"
+ integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+
+mdast-util-gfm-strikethrough@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16"
+ integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm-table@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38"
+ integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ markdown-table "^3.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm-task-list-item@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936"
+ integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751"
+ integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==
+ dependencies:
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-gfm-autolink-literal "^2.0.0"
+ mdast-util-gfm-footnote "^2.0.0"
+ mdast-util-gfm-strikethrough "^2.0.0"
+ mdast-util-gfm-table "^2.0.0"
+ mdast-util-gfm-task-list-item "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-math@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-math/-/mdast-util-math-3.0.0.tgz#8d79dd3baf8ab8ac781f62b8853768190b9a00b0"
+ integrity sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.1.0"
+ unist-util-remove-position "^5.0.0"
+
+mdast-util-mdx-expression@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096"
+ integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdx-jsx@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d"
+ integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ parse-entities "^4.0.0"
+ stringify-entities "^4.0.0"
+ unist-util-stringify-position "^4.0.0"
+ vfile-message "^4.0.0"
+
+mdast-util-mdx@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz#792f9cf0361b46bee1fdf1ef36beac424a099c41"
+ integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==
+ dependencies:
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdxjs-esm@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97"
+ integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-phrasing@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3"
+ integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ unist-util-is "^6.0.0"
+
+mdast-util-to-hast@^13.0.0, mdast-util-to-hast@^13.2.0:
+ version "13.2.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4"
+ integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ trim-lines "^3.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+mdast-util-to-markdown@^2.0.0, mdast-util-to-markdown@^2.1.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b"
+ integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^4.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ unist-util-visit "^5.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814"
+ integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+mermaid@^11.0.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-11.12.0.tgz#8e394b6214e33cb52f6e8ad9eb1fd94c67ee5638"
+ integrity sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg==
+ dependencies:
+ "@braintree/sanitize-url" "^7.1.1"
+ "@iconify/utils" "^3.0.1"
+ "@mermaid-js/parser" "^0.6.2"
+ "@types/d3" "^7.4.3"
+ cytoscape "^3.29.3"
+ cytoscape-cose-bilkent "^4.1.0"
+ cytoscape-fcose "^2.2.0"
+ d3 "^7.9.0"
+ d3-sankey "^0.12.3"
+ dagre-d3-es "7.0.11"
+ dayjs "^1.11.18"
+ dompurify "^3.2.5"
+ katex "^0.16.22"
+ khroma "^2.1.0"
+ lodash-es "^4.17.21"
+ marked "^16.2.1"
+ roughjs "^4.6.6"
+ stylis "^4.3.6"
+ ts-dedent "^2.2.0"
+ uuid "^11.1.0"
+
+mhchemparser@^4.1.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/mhchemparser/-/mhchemparser-4.2.1.tgz#d73982e66bc06170a85b1985600ee9dabe157cb0"
+ integrity sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==
+
+micromark-core-commonmark@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4"
+ integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-destination "^2.0.0"
+ micromark-factory-label "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-factory-title "^2.0.0"
+ micromark-factory-whitespace "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-html-tag-name "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-frontmatter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz#651c52ffa5d7a8eeed687c513cd869885882d67a"
+ integrity sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==
+ dependencies:
+ fault "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-autolink-literal@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935"
+ integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-footnote@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750"
+ integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-strikethrough@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923"
+ integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-table@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b"
+ integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-tagfilter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57"
+ integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-task-list-item@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c"
+ integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b"
+ integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==
+ dependencies:
+ micromark-extension-gfm-autolink-literal "^2.0.0"
+ micromark-extension-gfm-footnote "^2.0.0"
+ micromark-extension-gfm-strikethrough "^2.0.0"
+ micromark-extension-gfm-table "^2.0.0"
+ micromark-extension-gfm-tagfilter "^2.0.0"
+ micromark-extension-gfm-task-list-item "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-math@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz#c42ee3b1dd5a9a03584e83dd8f08e3de510212c1"
+ integrity sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==
+ dependencies:
+ "@types/katex" "^0.16.0"
+ devlop "^1.0.0"
+ katex "^0.16.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdx-expression@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz#43d058d999532fb3041195a3c3c05c46fa84543b"
+ integrity sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-mdx-expression "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdx-jsx@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz#ffc98bdb649798902fa9fc5689f67f9c1c902044"
+ integrity sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ micromark-factory-mdx-expression "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-extension-mdx-md@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz#1d252881ea35d74698423ab44917e1f5b197b92d"
+ integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdxjs-esm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz#de21b2b045fd2059bd00d36746081de38390d54a"
+ integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-extension-mdxjs@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz#b5a2e0ed449288f3f6f6c544358159557549de18"
+ integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==
+ dependencies:
+ acorn "^8.0.0"
+ acorn-jsx "^5.0.0"
+ micromark-extension-mdx-expression "^3.0.0"
+ micromark-extension-mdx-jsx "^3.0.0"
+ micromark-extension-mdx-md "^2.0.0"
+ micromark-extension-mdxjs-esm "^3.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-destination@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639"
+ integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-label@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1"
+ integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-mdx-expression@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz#bb09988610589c07d1c1e4425285895041b3dfa9"
+ integrity sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-factory-space@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc"
+ integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-title@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94"
+ integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-whitespace@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1"
+ integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-character@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6"
+ integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-chunked@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051"
+ integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-classify-character@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629"
+ integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-combine-extensions@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9"
+ integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==
+ dependencies:
+ micromark-util-chunked "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-decode-numeric-character-reference@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5"
+ integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-decode-string@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2"
+ integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-encode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8"
+ integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
+
+micromark-util-events-to-acorn@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz#e7a8a6b55a47e5a06c720d5a1c4abae8c37c98f3"
+ integrity sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/unist" "^3.0.0"
+ devlop "^1.0.0"
+ estree-util-visit "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-util-html-tag-name@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825"
+ integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==
+
+micromark-util-normalize-identifier@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d"
+ integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-resolve-all@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b"
+ integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-util-sanitize-uri@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7"
+ integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-subtokenize@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee"
+ integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-symbol@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8"
+ integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
+
+micromark-util-types@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e"
+ integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
+
+micromark@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb"
+ integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==
+ dependencies:
+ "@types/debug" "^4.0.0"
+ debug "^4.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+mimic-fn@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
+ integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
+
+mj-context-menu@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz#a043c5282bf7e1cf3821de07b13525ca6f85aa69"
+ integrity sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==
+
+mlly@^1.7.4:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e"
+ integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==
+ dependencies:
+ acorn "^8.15.0"
+ pathe "^2.0.3"
+ pkg-types "^1.3.1"
+ ufo "^1.6.1"
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+nanoid@^3.3.6:
+ version "3.3.11"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
+ integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
+negotiator@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
+ integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
+
+next-themes@^0.4.0:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.6.tgz#8d7e92d03b8fea6582892a50a928c9b23502e8b6"
+ integrity sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==
+
+next@^13.5.11:
+ version "13.5.11"
+ resolved "https://registry.yarnpkg.com/next/-/next-13.5.11.tgz#a021e849c5748fb6e2a4447585614e0c0e6c778d"
+ integrity sha512-WUPJ6WbAX9tdC86kGTu92qkrRdgRqVrY++nwM+shmWQwmyxt4zhZfR59moXSI4N8GDYCBY3lIAqhzjDd4rTC8Q==
+ dependencies:
+ "@next/env" "13.5.11"
+ "@swc/helpers" "0.5.2"
+ busboy "1.6.0"
+ caniuse-lite "^1.0.30001406"
+ postcss "8.4.31"
+ styled-jsx "5.1.1"
+ watchpack "2.4.0"
+ optionalDependencies:
+ "@next/swc-darwin-arm64" "13.5.9"
+ "@next/swc-darwin-x64" "13.5.9"
+ "@next/swc-linux-arm64-gnu" "13.5.9"
+ "@next/swc-linux-arm64-musl" "13.5.9"
+ "@next/swc-linux-x64-gnu" "13.5.9"
+ "@next/swc-linux-x64-musl" "13.5.9"
+ "@next/swc-win32-arm64-msvc" "13.5.9"
+ "@next/swc-win32-ia32-msvc" "13.5.9"
+ "@next/swc-win32-x64-msvc" "13.5.9"
+
+nextra-theme-docs@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/nextra-theme-docs/-/nextra-theme-docs-3.3.1.tgz#af845b49d0773e9cf3b40b8749b09877d6f6f2cd"
+ integrity sha512-P305m2UcW2IDyQhjrcAu0qpdPArikofinABslUCAyixYShsmcdDRUhIMd4QBHYru4gQuVjGWX9PhWZZCbNvzDQ==
+ dependencies:
+ "@headlessui/react" "^2.1.2"
+ clsx "^2.0.0"
+ escape-string-regexp "^5.0.0"
+ flexsearch "^0.7.43"
+ next-themes "^0.4.0"
+ scroll-into-view-if-needed "^3.1.0"
+ zod "^3.22.3"
+
+nextra@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/nextra/-/nextra-3.3.1.tgz#d8c919954ba5e82c535efe5c18b32b0281dbeefd"
+ integrity sha512-jiwj+LfUPHHeAxJAEqFuglxnbjFgzAOnDWFsjv7iv3BWiX8OksDwd3I2Sv3j2zba00iIBDEPdNeylfzTtTLZVg==
+ dependencies:
+ "@formatjs/intl-localematcher" "^0.5.4"
+ "@headlessui/react" "^2.1.2"
+ "@mdx-js/mdx" "^3.0.0"
+ "@mdx-js/react" "^3.0.0"
+ "@napi-rs/simple-git" "^0.1.9"
+ "@shikijs/twoslash" "^1.0.0"
+ "@theguild/remark-mermaid" "^0.1.3"
+ "@theguild/remark-npm2yarn" "^0.3.2"
+ better-react-mathjax "^2.0.3"
+ clsx "^2.0.0"
+ estree-util-to-js "^2.0.0"
+ estree-util-value-to-estree "^3.0.1"
+ github-slugger "^2.0.0"
+ graceful-fs "^4.2.11"
+ gray-matter "^4.0.3"
+ hast-util-to-estree "^3.1.0"
+ katex "^0.16.9"
+ mdast-util-from-markdown "^2.0.1"
+ mdast-util-gfm "^3.0.0"
+ mdast-util-to-hast "^13.2.0"
+ negotiator "^1.0.0"
+ p-limit "^6.0.0"
+ react-medium-image-zoom "^5.2.12"
+ rehype-katex "^7.0.0"
+ rehype-pretty-code "0.14.0"
+ rehype-raw "^7.0.0"
+ remark-frontmatter "^5.0.0"
+ remark-gfm "^4.0.0"
+ remark-math "^6.0.0"
+ remark-reading-time "^2.0.1"
+ remark-smartypants "^3.0.0"
+ shiki "^1.0.0"
+ slash "^5.1.0"
+ title "^4.0.0"
+ unist-util-remove "^4.0.0"
+ unist-util-visit "^5.0.0"
+ yaml "^2.3.2"
+ zod "^3.22.3"
+ zod-validation-error "^3.0.0"
+
+nlcst-to-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz#05511e8461ebfb415952eb0b7e9a1a7d40471bd4"
+ integrity sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==
+ dependencies:
+ "@types/nlcst" "^2.0.0"
+
+npm-run-path@^5.1.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f"
+ integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==
+ dependencies:
+ path-key "^4.0.0"
+
+npm-to-yarn@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/npm-to-yarn/-/npm-to-yarn-3.0.1.tgz#d1ed47551321ad5cd51342729fe21c8146644529"
+ integrity sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==
+
+onetime@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4"
+ integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==
+ dependencies:
+ mimic-fn "^4.0.0"
+
+oniguruma-to-es@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz#35ea9104649b7c05f3963c6b3b474d964625028b"
+ integrity sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==
+ dependencies:
+ emoji-regex-xs "^1.0.0"
+ regex "^5.1.1"
+ regex-recursion "^5.1.1"
+
+p-limit@^6.0.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-6.2.0.tgz#c254d22ba6aeef441a3564c5e6c2f2da59268a0f"
+ integrity sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==
+ dependencies:
+ yocto-queue "^1.1.1"
+
+package-manager-detector@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-1.3.0.tgz#b42d641c448826e03c2b354272456a771ce453c0"
+ integrity sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==
+
+parse-entities@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159"
+ integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ character-entities-legacy "^3.0.0"
+ character-reference-invalid "^2.0.0"
+ decode-named-character-reference "^1.0.0"
+ is-alphanumerical "^2.0.0"
+ is-decimal "^2.0.0"
+ is-hexadecimal "^2.0.0"
+
+parse-latin@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-7.0.0.tgz#8dfacac26fa603f76417f36233fc45602a323e1d"
+ integrity sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==
+ dependencies:
+ "@types/nlcst" "^2.0.0"
+ "@types/unist" "^3.0.0"
+ nlcst-to-string "^4.0.0"
+ unist-util-modify-children "^4.0.0"
+ unist-util-visit-children "^3.0.0"
+ vfile "^6.0.0"
+
+parse-numeric-range@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3"
+ integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
+
+parse5@^7.0.0:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05"
+ integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==
+ dependencies:
+ entities "^6.0.0"
+
+path-data-parser@0.1.0, path-data-parser@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c"
+ integrity sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-key@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
+ integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
+
+pathe@^2.0.1, pathe@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
+ integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
+
+picocolors@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+pkg-types@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df"
+ integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==
+ dependencies:
+ confbox "^0.1.8"
+ mlly "^1.7.4"
+ pathe "^2.0.1"
+
+pkg-types@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.3.0.tgz#037f2c19bd5402966ff6810e32706558cb5b5726"
+ integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==
+ dependencies:
+ confbox "^0.2.2"
+ exsolve "^1.0.7"
+ pathe "^2.0.3"
+
+points-on-curve@0.2.0, points-on-curve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1"
+ integrity sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==
+
+points-on-path@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/points-on-path/-/points-on-path-0.2.1.tgz#553202b5424c53bed37135b318858eacff85dd52"
+ integrity sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==
+ dependencies:
+ path-data-parser "0.1.0"
+ points-on-curve "0.2.0"
+
+postcss@8.4.31:
+ version "8.4.31"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
+ integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+property-information@^6.0.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec"
+ integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==
+
+property-information@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d"
+ integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==
+
+quansync@^0.2.11:
+ version "0.2.11"
+ resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.11.tgz#f9c3adda2e1272e4f8cf3f1457b04cbdb4ee692a"
+ integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==
+
+react-dom@^18.3.1:
+ version "18.3.1"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
+ integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.2"
+
+react-medium-image-zoom@^5.2.12:
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/react-medium-image-zoom/-/react-medium-image-zoom-5.4.0.tgz#b89c74a4f631289e8a7a21af26614c58fff0ea81"
+ integrity sha512-BsE+EnFVQzFIlyuuQrZ9iTwyKpKkqdFZV1ImEQN573QPqGrIUuNni7aF+sZwDcxlsuOMayCr6oO/PZR/yJnbRg==
+
+react@^18.3.1:
+ version "18.3.1"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
+ integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+reading-time@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb"
+ integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==
+
+recma-build-jsx@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz#c02f29e047e103d2fab2054954e1761b8ea253c4"
+ integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-util-build-jsx "^3.0.0"
+ vfile "^6.0.0"
+
+recma-jsx@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/recma-jsx/-/recma-jsx-1.0.1.tgz#58e718f45e2102ed0bf2fa994f05b70d76801a1a"
+ integrity sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==
+ dependencies:
+ acorn-jsx "^5.0.0"
+ estree-util-to-js "^2.0.0"
+ recma-parse "^1.0.0"
+ recma-stringify "^1.0.0"
+ unified "^11.0.0"
+
+recma-parse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/recma-parse/-/recma-parse-1.0.0.tgz#c351e161bb0ab47d86b92a98a9d891f9b6814b52"
+ integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ esast-util-from-js "^2.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+recma-stringify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/recma-stringify/-/recma-stringify-1.0.0.tgz#54632030631e0c7546136ff9ef8fde8e7b44f130"
+ integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-util-to-js "^2.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+regex-recursion@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-5.1.1.tgz#5a73772d18adbf00f57ad097bf54171b39d78f8b"
+ integrity sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==
+ dependencies:
+ regex "^5.1.1"
+ regex-utilities "^2.3.0"
+
+regex-utilities@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280"
+ integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==
+
+regex@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/regex/-/regex-5.1.1.tgz#cf798903f24d6fe6e531050a36686e082b29bd03"
+ integrity sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==
+ dependencies:
+ regex-utilities "^2.3.0"
+
+rehype-katex@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-7.0.1.tgz#832e6d7af2744a228981d1b0fe89483a9e7c93a1"
+ integrity sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/katex" "^0.16.0"
+ hast-util-from-html-isomorphic "^2.0.0"
+ hast-util-to-text "^4.0.0"
+ katex "^0.16.0"
+ unist-util-visit-parents "^6.0.0"
+ vfile "^6.0.0"
+
+rehype-parse@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.1.tgz#9993bda129acc64c417a9d3654a7be38b2a94c20"
+ integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-from-html "^2.0.0"
+ unified "^11.0.0"
+
+rehype-pretty-code@0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/rehype-pretty-code/-/rehype-pretty-code-0.14.0.tgz#bdf828af4575737cc02204fb24d8097e6f010d37"
+ integrity sha512-hBeKF/Wkkf3zyUS8lal9RCUuhypDWLQc+h9UrP9Pav25FUm/AQAVh4m5gdvJxh4Oz+U+xKvdsV01p1LdvsZTiQ==
+ dependencies:
+ "@types/hast" "^3.0.4"
+ hast-util-to-string "^3.0.0"
+ parse-numeric-range "^1.3.0"
+ rehype-parse "^9.0.0"
+ unified "^11.0.5"
+ unist-util-visit "^5.0.0"
+
+rehype-raw@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4"
+ integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-raw "^9.0.0"
+ vfile "^6.0.0"
+
+rehype-recma@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/rehype-recma/-/rehype-recma-1.0.0.tgz#d68ef6344d05916bd96e25400c6261775411aa76"
+ integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ hast-util-to-estree "^3.0.0"
+
+remark-frontmatter@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz#b68d61552a421ec412c76f4f66c344627dc187a2"
+ integrity sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-frontmatter "^2.0.0"
+ micromark-extension-frontmatter "^2.0.0"
+ unified "^11.0.0"
+
+remark-gfm@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b"
+ integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-gfm "^3.0.0"
+ micromark-extension-gfm "^3.0.0"
+ remark-parse "^11.0.0"
+ remark-stringify "^11.0.0"
+ unified "^11.0.0"
+
+remark-math@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/remark-math/-/remark-math-6.0.0.tgz#0acdf74675f1c195fea6efffa78582f7ed7fc0d7"
+ integrity sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-math "^3.0.0"
+ micromark-extension-math "^3.0.0"
+ unified "^11.0.0"
+
+remark-mdx@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-3.1.1.tgz#047f97038bc7ec387aebb4b0a4fe23779999d845"
+ integrity sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==
+ dependencies:
+ mdast-util-mdx "^3.0.0"
+ micromark-extension-mdxjs "^3.0.0"
+
+remark-parse@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1"
+ integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unified "^11.0.0"
+
+remark-reading-time@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/remark-reading-time/-/remark-reading-time-2.0.2.tgz#394ec979ae3acf45655fa10fcf15078e806de694"
+ integrity sha512-ILjIuR0dQQ8pELPgaFvz7ralcSN62rD/L1pTUJgWb4gfua3ZwYEI8mnKGxEQCbrXSUF/OvycTkcUbifGOtOn5A==
+ dependencies:
+ estree-util-is-identifier-name "^2.0.0"
+ estree-util-value-to-estree "^3.3.3"
+ reading-time "^1.3.0"
+ unist-util-visit "^3.1.0"
+
+remark-rehype@^11.0.0:
+ version "11.1.2"
+ resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37"
+ integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-hast "^13.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+remark-smartypants@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/remark-smartypants/-/remark-smartypants-3.0.2.tgz#cbaf2b39624c78fcbd6efa224678c1d2e9bc1dfb"
+ integrity sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==
+ dependencies:
+ retext "^9.0.0"
+ retext-smartypants "^6.0.0"
+ unified "^11.0.4"
+ unist-util-visit "^5.0.0"
+
+remark-stringify@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3"
+ integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ unified "^11.0.0"
+
+retext-latin@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-4.0.0.tgz#d02498aa1fd39f1bf00e2ff59b1384c05d0c7ce3"
+ integrity sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==
+ dependencies:
+ "@types/nlcst" "^2.0.0"
+ parse-latin "^7.0.0"
+ unified "^11.0.0"
+
+retext-smartypants@^6.0.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-6.2.0.tgz#4e852c2974cf2cfa253eeec427c97efc43b5d158"
+ integrity sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==
+ dependencies:
+ "@types/nlcst" "^2.0.0"
+ nlcst-to-string "^4.0.0"
+ unist-util-visit "^5.0.0"
+
+retext-stringify@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-4.0.0.tgz#501d5440bd4d121e351c7c509f8507de9611e159"
+ integrity sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==
+ dependencies:
+ "@types/nlcst" "^2.0.0"
+ nlcst-to-string "^4.0.0"
+ unified "^11.0.0"
+
+retext@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/retext/-/retext-9.0.0.tgz#ab5cd72836894167b0ca6ae70fdcfaa166267f7a"
+ integrity sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==
+ dependencies:
+ "@types/nlcst" "^2.0.0"
+ retext-latin "^4.0.0"
+ retext-stringify "^4.0.0"
+ unified "^11.0.0"
+
+robust-predicates@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
+ integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
+
+roughjs@^4.6.6:
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/roughjs/-/roughjs-4.6.6.tgz#1059f49a5e0c80dee541a005b20cc322b222158b"
+ integrity sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==
+ dependencies:
+ hachure-fill "^0.5.2"
+ path-data-parser "^0.1.0"
+ points-on-curve "^0.2.0"
+ points-on-path "^0.2.1"
+
+rw@1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
+ integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
+
+"safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+scheduler@^0.23.2:
+ version "0.23.2"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
+ integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+scroll-into-view-if-needed@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz#fa9524518c799b45a2ef6bbffb92bcad0296d01f"
+ integrity sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==
+ dependencies:
+ compute-scroll-into-view "^3.0.2"
+
+section-matter@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"
+ integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
+ dependencies:
+ extend-shallow "^2.0.1"
+ kind-of "^6.0.0"
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+shiki@^1.0.0:
+ version "1.29.2"
+ resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.29.2.tgz#5c93771f2d5305ce9c05975c33689116a27dc657"
+ integrity sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==
+ dependencies:
+ "@shikijs/core" "1.29.2"
+ "@shikijs/engine-javascript" "1.29.2"
+ "@shikijs/engine-oniguruma" "1.29.2"
+ "@shikijs/langs" "1.29.2"
+ "@shikijs/themes" "1.29.2"
+ "@shikijs/types" "1.29.2"
+ "@shikijs/vscode-textmate" "^10.0.1"
+ "@types/hast" "^3.0.4"
+
+signal-exit@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
+slash@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
+ integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
+
+source-map-js@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+source-map@^0.7.0:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02"
+ integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==
+
+space-separated-tokens@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
+ integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
+
+speech-rule-engine@^4.0.6:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-4.1.2.tgz#3b31b5813a2fc2eaecdfda26ad29c32599e9a537"
+ integrity sha512-S6ji+flMEga+1QU79NDbwZ8Ivf0S/MpupQQiIC0rTpU/ZTKgcajijJJb1OcByBQDjrXCN1/DJtGz4ZJeBMPGJw==
+ dependencies:
+ "@xmldom/xmldom" "0.9.8"
+ commander "13.1.0"
+ wicked-good-xpath "1.3.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
+streamsearch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+ integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
+stringify-entities@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3"
+ integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
+ dependencies:
+ character-entities-html4 "^2.0.0"
+ character-entities-legacy "^3.0.0"
+
+strip-bom-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
+ integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==
+
+strip-final-newline@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
+ integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
+
+style-to-js@^1.0.0:
+ version "1.1.17"
+ resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.17.tgz#488b1558a8c1fd05352943f088cc3ce376813d83"
+ integrity sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==
+ dependencies:
+ style-to-object "1.0.9"
+
+style-to-object@1.0.9:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.9.tgz#35c65b713f4a6dba22d3d0c61435f965423653f0"
+ integrity sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==
+ dependencies:
+ inline-style-parser "0.2.4"
+
+styled-jsx@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
+ integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
+ dependencies:
+ client-only "0.0.1"
+
+stylis@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.6.tgz#7c7b97191cb4f195f03ecab7d52f7902ed378320"
+ integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
+
+system-architecture@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d"
+ integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==
+
+tabbable@^6.0.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
+ integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
+
+tinyexec@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.1.tgz#70c31ab7abbb4aea0a24f55d120e5990bfa1e0b1"
+ integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==
+
+title@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/title/-/title-4.0.1.tgz#f5226a0fbec7b3a1c42c2772d67a493d2f189c87"
+ integrity sha512-xRnPkJx9nvE5MF6LkB5e8QJjE2FW8269wTu/LQdf7zZqBgPly0QJPf/CWAo7srj5so4yXfoLEdCFgurlpi47zg==
+ dependencies:
+ arg "^5.0.0"
+ chalk "^5.0.0"
+ clipboardy "^4.0.0"
+
+trim-lines@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
+ integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+
+trough@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
+ integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
+
+ts-dedent@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
+ integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
+
+tslib@2, tslib@^2.4.0, tslib@^2.8.0:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+twoslash-protocol@0.2.12:
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/twoslash-protocol/-/twoslash-protocol-0.2.12.tgz#4c22fc287bc0fc32eec8e7faa6092b0dc5cc4ecb"
+ integrity sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg==
+
+twoslash@^0.2.12:
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/twoslash/-/twoslash-0.2.12.tgz#46b11fb23ff3d950264ca32877576e2c2b4e997e"
+ integrity sha512-tEHPASMqi7kqwfJbkk7hc/4EhlrKCSLcur+TcvYki3vhIfaRMXnXjaYFgXpoZRbT6GdprD4tGuVBEmTpUgLBsw==
+ dependencies:
+ "@typescript/vfs" "^1.6.0"
+ twoslash-protocol "0.2.12"
+
+typescript@^5.0.2:
+ version "5.9.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
+ integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
+
+ufo@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b"
+ integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==
+
+unified@^11.0.0, unified@^11.0.4, unified@^11.0.5:
+ version "11.0.5"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1"
+ integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ bail "^2.0.0"
+ devlop "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^4.0.0"
+ trough "^2.0.0"
+ vfile "^6.0.0"
+
+unist-util-find-after@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz#3fccc1b086b56f34c8b798e1ff90b5c54468e896"
+ integrity sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+
+unist-util-is@^5.0.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9"
+ integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-is@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424"
+ integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-modify-children@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz#981d6308e887b005d1f491811d3cbcc254b315e9"
+ integrity sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ array-iterate "^2.0.0"
+
+unist-util-position-from-estree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200"
+ integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-position@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4"
+ integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-remove-position@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz#fea68a25658409c9460408bc6b4991b965b52163"
+ integrity sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-visit "^5.0.0"
+
+unist-util-remove@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-4.0.0.tgz#94b7d6bbd24e42d2f841e947ed087be5c82b222e"
+ integrity sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+unist-util-stringify-position@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2"
+ integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-visit-children@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz#4bced199b71d7f3c397543ea6cc39e7a7f37dc7e"
+ integrity sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-visit-parents@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2"
+ integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+
+unist-util-visit-parents@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815"
+ integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+
+unist-util-visit@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-3.1.0.tgz#9420d285e1aee938c7d9acbafc8e160186dbaf7b"
+ integrity sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+ unist-util-visit-parents "^4.0.0"
+
+unist-util-visit@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6"
+ integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+use-sync-external-store@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"
+ integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
+
+uuid@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
+ integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
+
+vfile-location@^5.0.0:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3"
+ integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile "^6.0.0"
+
+vfile-message@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4"
+ integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+vfile@^6.0.0:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab"
+ integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile-message "^4.0.0"
+
+vscode-jsonrpc@8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
+ integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==
+
+vscode-languageserver-protocol@3.17.5:
+ version "3.17.5"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea"
+ integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==
+ dependencies:
+ vscode-jsonrpc "8.2.0"
+ vscode-languageserver-types "3.17.5"
+
+vscode-languageserver-textdocument@~1.0.11:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631"
+ integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==
+
+vscode-languageserver-types@3.17.5:
+ version "3.17.5"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
+ integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
+
+vscode-languageserver@~9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b"
+ integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==
+ dependencies:
+ vscode-languageserver-protocol "3.17.5"
+
+vscode-uri@~3.0.8:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
+ integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
+
+watchpack@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
+web-namespaces@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
+ integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+wicked-good-xpath@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c"
+ integrity sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==
+
+yaml@^2.3.2:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79"
+ integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==
+
+yocto-queue@^1.1.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.1.tgz#36d7c4739f775b3cbc28e6136e21aa057adec418"
+ integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==
+
+zod-validation-error@^3.0.0:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.5.3.tgz#85ba33290200d8db9f043621e284f40dddefb7e5"
+ integrity sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==
+
+zod@^3.22.3:
+ version "3.25.76"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34"
+ integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
+
+zwitch@^2.0.0, zwitch@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
+ integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 000000000..3f95083a0
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,76 @@
+import { defineConfig, globalIgnores } from 'eslint/config'
+import typescriptEslint from '@typescript-eslint/eslint-plugin'
+import prettier from 'eslint-plugin-prettier'
+import globals from 'globals'
+import tsParser from '@typescript-eslint/parser'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import js from '@eslint/js'
+import { FlatCompat } from '@eslint/eslintrc'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+})
+
+export default defineConfig([
+ globalIgnores([
+ '**/node_modules',
+ '**/coverage',
+ 'packages/*/dist',
+ 'packages/pg-protocol/dist/**/*',
+ 'packages/pg-query-stream/dist/**/*',
+ ]),
+ {
+ extends: compat.extends('eslint:recommended', 'plugin:prettier/recommended', 'prettier'),
+
+ plugins: {
+ '@typescript-eslint': typescriptEslint,
+ prettier,
+ },
+
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.mocha,
+ },
+
+ parser: tsParser,
+ ecmaVersion: 2017,
+ sourceType: 'module',
+ },
+
+ rules: {
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ args: 'none',
+ caughtErrors: 'none',
+ varsIgnorePattern: '^_$',
+ },
+ ],
+
+ // handled by @typescript-eslint/no-unused-vars
+ 'no-unused-vars': 'off',
+
+ 'no-var': 'error',
+ 'prefer-const': 'error',
+ 'no-constant-condition': [
+ 'error',
+ {
+ checkLoops: 'all',
+ },
+ ],
+ },
+ },
+ {
+ files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.tsx'],
+
+ rules: {
+ 'no-undef': 'off',
+ },
+ },
+])
diff --git a/lerna.json b/lerna.json
index eb366709a..9589a0aa6 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,12 +1,12 @@
{
- "packages": [
- "packages/*"
- ],
+ "packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "independent",
- "ignoreChanges": [
- "**/*.md",
- "**/test/**"
- ]
+ "command": {
+ "version": {
+ "allowBranch": "master"
+ }
+ },
+ "ignoreChanges": ["**/*.md", "**/test/**"]
}
diff --git a/package.json b/package.json
index 282ca9376..9285ad142 100644
--- a/package.json
+++ b/package.json
@@ -10,22 +10,28 @@
"packages/*"
],
"scripts": {
- "test": "yarn lint && yarn lerna exec yarn test",
- "build": "yarn lerna exec --scope pg-protocol yarn build",
+ "test": "yarn lerna exec --concurrency 1 yarn test",
+ "build": "tsc --build",
+ "build:watch": "tsc --build --watch",
+ "docs:build": "cd docs && yarn build",
+ "docs:start": "cd docs && yarn dev",
"pretest": "yarn build",
- "lint": "if [ -x ./node_modules/.bin/prettier ]; then eslint '*/**/*.{js,ts,tsx}'; fi;"
+ "prepublish": "yarn build",
+ "lint": "eslint --cache 'packages/**/*.{js,ts,tsx}'"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^2.27.0",
- "@typescript-eslint/parser": "^2.27.0",
- "eslint": "^6.8.0",
- "eslint-config-prettier": "^6.10.1",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "^10.0.1",
+ "@typescript-eslint/eslint-plugin": "^8.58.0",
+ "@typescript-eslint/parser": "^8.58.0",
+ "eslint": "^10.2.1",
+ "eslint-config-prettier": "^10.1.2",
"eslint-plugin-node": "^11.1.0",
- "eslint-plugin-prettier": "^3.1.2",
- "lerna": "^3.19.0"
- },
- "optionalDependencies": {
- "prettier": "2.0.4"
+ "eslint-plugin-prettier": "^5.1.2",
+ "lerna": "^3.19.0",
+ "prettier": "3.0.3",
+ "typescript": "^6.0.3",
+ "@types/node": "^16"
},
"prettier": {
"semi": false,
diff --git a/packages/pg-bundler-test/esbuild-cloudflare.config.mjs b/packages/pg-bundler-test/esbuild-cloudflare.config.mjs
new file mode 100644
index 000000000..b9947d626
--- /dev/null
+++ b/packages/pg-bundler-test/esbuild-cloudflare.config.mjs
@@ -0,0 +1,8 @@
+import * as esbuild from 'esbuild'
+
+await esbuild.build({
+ entryPoints: ['./src/index.mjs'],
+ bundle: true,
+ outfile: './dist/esbuild-cloudflare.js',
+ conditions: ['import', 'workerd'],
+})
diff --git a/packages/pg-bundler-test/esbuild-empty.config.mjs b/packages/pg-bundler-test/esbuild-empty.config.mjs
new file mode 100644
index 000000000..11019b999
--- /dev/null
+++ b/packages/pg-bundler-test/esbuild-empty.config.mjs
@@ -0,0 +1,7 @@
+import * as esbuild from 'esbuild'
+
+await esbuild.build({
+ entryPoints: ['./src/index.mjs'],
+ bundle: true,
+ outfile: './dist/esbuild-empty.js',
+})
diff --git a/packages/pg-bundler-test/package.json b/packages/pg-bundler-test/package.json
new file mode 100644
index 000000000..fc368c197
--- /dev/null
+++ b/packages/pg-bundler-test/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "pg-bundler-test",
+ "version": "0.3.0",
+ "description": "Test bundlers with pg-cloudflare, https://github.com/brianc/node-postgres/issues/3452",
+ "license": "MIT",
+ "private": true,
+ "type": "module",
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^28.0.3",
+ "@rollup/plugin-node-resolve": "^16.0.1",
+ "esbuild": "^0.25.5",
+ "pg-cloudflare": "^1.4.0",
+ "rollup": "^4.41.1",
+ "vite": "^7.1.7",
+ "webpack": "^5.99.9",
+ "webpack-cli": "^6.0.1"
+ },
+ "scripts": {
+ "test": "yarn webpack && yarn rollup && yarn vite && yarn esbuild",
+ "webpack": "webpack --config webpack-empty.config.mjs && webpack --config webpack-cloudflare.config.mjs",
+ "rollup": "rollup --config rollup-empty.config.mjs --failAfterWarnings && rollup --config rollup-cloudflare.config.mjs --failAfterWarnings",
+ "vite": "[ $(node --version | sed 's/v//' | cut -d'.' -f1) -ge 18 ] && vite build --config vite-empty.config.mjs && vite build --config vite-cloudflare.config.mjs || echo 'Skip Vite test'",
+ "esbuild": "node esbuild-empty.config.mjs && node esbuild-cloudflare.config.mjs"
+ }
+}
diff --git a/packages/pg-bundler-test/rollup-cloudflare.config.mjs b/packages/pg-bundler-test/rollup-cloudflare.config.mjs
new file mode 100644
index 000000000..592b477ea
--- /dev/null
+++ b/packages/pg-bundler-test/rollup-cloudflare.config.mjs
@@ -0,0 +1,13 @@
+import { defineConfig } from 'rollup'
+import { nodeResolve } from '@rollup/plugin-node-resolve'
+import commonjs from '@rollup/plugin-commonjs'
+
+export default defineConfig({
+ input: './src/index.mjs',
+ output: {
+ file: 'dist/rollup-cloudflare.js',
+ format: 'es',
+ },
+ plugins: [nodeResolve({ exportConditions: ['import', 'workerd'], preferBuiltins: true }), commonjs()],
+ external: ['cloudflare:sockets'],
+})
diff --git a/packages/pg-bundler-test/rollup-empty.config.mjs b/packages/pg-bundler-test/rollup-empty.config.mjs
new file mode 100644
index 000000000..acc2c9f7a
--- /dev/null
+++ b/packages/pg-bundler-test/rollup-empty.config.mjs
@@ -0,0 +1,12 @@
+import { defineConfig } from 'rollup'
+import { nodeResolve } from '@rollup/plugin-node-resolve'
+import commonjs from '@rollup/plugin-commonjs'
+
+export default defineConfig({
+ input: './src/index.mjs',
+ output: {
+ file: 'dist/rollup-empty.js',
+ format: 'es',
+ },
+ plugins: [nodeResolve(), commonjs()],
+})
diff --git a/packages/pg-bundler-test/src/index.mjs b/packages/pg-bundler-test/src/index.mjs
new file mode 100644
index 000000000..2c807d3d4
--- /dev/null
+++ b/packages/pg-bundler-test/src/index.mjs
@@ -0,0 +1 @@
+import 'pg-cloudflare'
diff --git a/packages/pg-bundler-test/vite-cloudflare.config.mjs b/packages/pg-bundler-test/vite-cloudflare.config.mjs
new file mode 100644
index 000000000..58ed0827d
--- /dev/null
+++ b/packages/pg-bundler-test/vite-cloudflare.config.mjs
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vite'
+import commonjs from '@rollup/plugin-commonjs'
+
+export default defineConfig({
+ build: {
+ emptyOutDir: false,
+ lib: {
+ entry: './src/index.mjs',
+ fileName: 'vite-cloudflare',
+ formats: ['es'],
+ },
+ rollupOptions: {
+ external: ['cloudflare:sockets'],
+ },
+ },
+ resolve: {
+ conditions: ['import', 'workerd'],
+ },
+ plugins: [commonjs()],
+})
diff --git a/packages/pg-bundler-test/vite-empty.config.mjs b/packages/pg-bundler-test/vite-empty.config.mjs
new file mode 100644
index 000000000..3f5dad6a4
--- /dev/null
+++ b/packages/pg-bundler-test/vite-empty.config.mjs
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ emptyOutDir: false,
+ lib: {
+ entry: './src/index.mjs',
+ fileName: 'vite-empty',
+ formats: ['es'],
+ },
+ },
+})
diff --git a/packages/pg-bundler-test/webpack-cloudflare.config.mjs b/packages/pg-bundler-test/webpack-cloudflare.config.mjs
new file mode 100644
index 000000000..50d4ff4a2
--- /dev/null
+++ b/packages/pg-bundler-test/webpack-cloudflare.config.mjs
@@ -0,0 +1,16 @@
+import webpack from 'webpack'
+
+export default {
+ mode: 'production',
+ entry: './src/index.mjs',
+ output: {
+ filename: 'webpack-cloudflare.js',
+ },
+ resolve: { conditionNames: ['import', 'workerd'] },
+ plugins: [
+ // ignore cloudflare:sockets imports
+ new webpack.IgnorePlugin({
+ resourceRegExp: /^cloudflare:sockets$/,
+ }),
+ ],
+}
diff --git a/packages/pg-bundler-test/webpack-empty.config.mjs b/packages/pg-bundler-test/webpack-empty.config.mjs
new file mode 100644
index 000000000..6037b5cd6
--- /dev/null
+++ b/packages/pg-bundler-test/webpack-empty.config.mjs
@@ -0,0 +1,7 @@
+export default {
+ mode: 'production',
+ entry: './src/index.mjs',
+ output: {
+ filename: 'webpack-empty.js',
+ },
+}
diff --git a/packages/pg-cloudflare/README.md b/packages/pg-cloudflare/README.md
new file mode 100644
index 000000000..68663c45c
--- /dev/null
+++ b/packages/pg-cloudflare/README.md
@@ -0,0 +1,112 @@
+# pg-cloudflare
+
+`pg-cloudflare` makes it easier to take an existing package that relies on `tls` and `net`, and make it work in environments where only `connect()` is supported, such as Cloudflare Workers.
+
+`pg-cloudflare` wraps `connect()`, the [TCP Socket API](https://github.com/wintercg/proposal-sockets-api) proposed within WinterCG, and implemented in [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/), and exposes an interface with methods similar to what the `net` and `tls` modules in Node.js expose. (ex: `net.connect(path[, options][, callback])`). This minimizes the number of changes needed in order to make an existing package work across JavaScript runtimes.
+
+## Installation
+
+```
+npm i --save-dev pg-cloudflare
+```
+
+The package uses conditional exports to support bundlers that don't know about
+`cloudflare:sockets`, so the consumer code by default imports an empty file. To
+enable the package, resolve to the `cloudflare` condition in your bundler's
+config. For example:
+
+- `webpack.config.js`
+ ```js
+ export default {
+ ...,
+ resolve: { conditionNames: [..., "workerd"] },
+ plugins: [
+ // ignore cloudflare:sockets imports
+ new webpack.IgnorePlugin({
+ resourceRegExp: /^cloudflare:sockets$/,
+ }),
+ ],
+ }
+ ```
+- `vite.config.js`
+
+ > [!NOTE]
+ > If you are using the [Cloudflare Vite plugin](https://www.npmjs.com/package/@cloudflare/vite-plugin) then the following configuration is not necessary.
+
+ ```js
+ export default defineConfig({
+ ...,
+ resolve: {
+ conditions: [..., "workerd"],
+ },
+ build: {
+ ...,
+ // don't try to bundle cloudflare:sockets
+ rollupOptions: {
+ external: [..., 'cloudflare:sockets'],
+ },
+ },
+ })
+ ```
+
+- `rollup.config.js`
+ ```js
+ export default defineConfig({
+ ...,
+ plugins: [..., nodeResolve({ exportConditions: [..., 'workerd'] })],
+ // don't try to bundle cloudflare:sockets
+ external: [..., 'cloudflare:sockets'],
+ })
+ ```
+- `esbuild.config.js`
+ ```js
+ await esbuild.build({
+ ...,
+ conditions: [..., 'workerd'],
+ })
+ ```
+
+The concrete examples can be found in `packages/pg-bundler-test`.
+
+## How to use conditionally, in non-Node.js environments
+
+As implemented in `pg` [here](https://github.com/brianc/node-postgres/commit/07553428e9c0eacf761a5d4541a3300ff7859578#diff-34588ad868ebcb232660aba7ee6a99d1e02f4bc93f73497d2688c3f074e60533R5-R13), a typical use case might look as follows, where in a Node.js environment the `net` module is used, while in a non-Node.js environment, where `net` is unavailable, `pg-cloudflare` is used instead, providing an equivalent interface:
+
+```js
+module.exports.getStream = function getStream(ssl = false) {
+ const net = require('net')
+ if (typeof net.Socket === 'function') {
+ return net.Socket()
+ }
+ const { CloudflareSocket } = require('pg-cloudflare')
+ return new CloudflareSocket(ssl)
+}
+```
+
+## Node.js implementation of the Socket API proposal
+
+If you're looking for a way to rely on `connect()` as the interface you use to interact with raw sockets, but need this interface to be available in a Node.js environment, [`@arrowood.dev/socket`](https://github.com/Ethan-Arrowood/socket) provides a Node.js implementation of the Socket API.
+
+### license
+
+The MIT License (MIT)
+
+Copyright (c) 2023 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-cloudflare/esm/index.mjs b/packages/pg-cloudflare/esm/index.mjs
new file mode 100644
index 000000000..6384216f5
--- /dev/null
+++ b/packages/pg-cloudflare/esm/index.mjs
@@ -0,0 +1,3 @@
+import cf from '../dist/index.js'
+
+export const CloudflareSocket = cf.CloudflareSocket
diff --git a/packages/pg-cloudflare/package.json b/packages/pg-cloudflare/package.json
new file mode 100644
index 000000000..4bc706c8a
--- /dev/null
+++ b/packages/pg-cloudflare/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "pg-cloudflare",
+ "version": "1.4.0",
+ "description": "A socket implementation that can run on Cloudflare Workers using native TCP connections.",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "license": "MIT",
+ "devDependencies": {
+ "ts-node": "^8.5.4",
+ "typescript": "^6.0.3"
+ },
+ "exports": {
+ ".": {
+ "workerd": {
+ "import": "./esm/index.mjs",
+ "require": "./dist/index.js"
+ },
+ "default": "./dist/empty.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "scripts": {
+ "build": "tsc",
+ "build:watch": "tsc --watch",
+ "prepublish": "yarn build",
+ "test": "echo e2e test in pg package"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/brianc/node-postgres.git",
+ "directory": "packages/pg-cloudflare"
+ },
+ "files": [
+ "/dist/*{js,ts,map}",
+ "/src",
+ "/esm"
+ ]
+}
diff --git a/packages/pg-cloudflare/src/empty.ts b/packages/pg-cloudflare/src/empty.ts
new file mode 100644
index 000000000..f1e6740db
--- /dev/null
+++ b/packages/pg-cloudflare/src/empty.ts
@@ -0,0 +1,3 @@
+// This is an empty module that is served up when outside of a workerd environment
+// See the `exports` field in package.json
+export default {}
diff --git a/packages/pg-cloudflare/src/index.ts b/packages/pg-cloudflare/src/index.ts
new file mode 100644
index 000000000..9b1e517ba
--- /dev/null
+++ b/packages/pg-cloudflare/src/index.ts
@@ -0,0 +1,169 @@
+import { SocketOptions, Socket, TlsOptions } from 'cloudflare:sockets'
+import { EventEmitter } from 'events'
+
+/**
+ * Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
+ */
+export class CloudflareSocket extends EventEmitter {
+ writable = false
+ destroyed = false
+
+ private _upgrading = false
+ private _upgraded = false
+ private _cfSocket: Socket | null = null
+ private _cfWriter: WritableStreamDefaultWriter | null = null
+ private _cfReader: ReadableStreamDefaultReader | null = null
+
+ constructor(readonly ssl: boolean) {
+ super()
+ }
+
+ setNoDelay() {
+ return this
+ }
+ setKeepAlive() {
+ return this
+ }
+ ref() {
+ return this
+ }
+ unref() {
+ return this
+ }
+
+ async connect(port: number, host: string, connectListener?: (...args: unknown[]) => void) {
+ try {
+ log('connecting')
+ if (connectListener) this.once('connect', connectListener)
+
+ const options: SocketOptions = this.ssl ? { secureTransport: 'starttls' } : {}
+ const mod = await import('cloudflare:sockets')
+ const connect = mod.connect
+ this._cfSocket = connect(`${host}:${port}`, options)
+ this._cfWriter = this._cfSocket.writable.getWriter()
+ this._addClosedHandler()
+
+ this._cfReader = this._cfSocket.readable.getReader()
+ if (this.ssl) {
+ this._listenOnce().catch((e) => this.emit('error', e))
+ } else {
+ this._listen().catch((e) => this.emit('error', e))
+ }
+
+ await this._cfWriter!.ready
+ log('socket ready')
+ this.writable = true
+ this.emit('connect')
+
+ return this
+ } catch (e) {
+ this.emit('error', e)
+ }
+ }
+
+ async _listen() {
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ log('awaiting receive from CF socket')
+ const { done, value } = await this._cfReader!.read()
+ log('CF socket received:', done, value)
+ if (done) {
+ log('done')
+ break
+ }
+ this.emit('data', Buffer.from(value))
+ }
+ }
+
+ async _listenOnce() {
+ log('awaiting first receive from CF socket')
+ const { done, value } = await this._cfReader!.read()
+ log('First CF socket received:', done, value)
+ this.emit('data', Buffer.from(value))
+ }
+
+ write(
+ data: Uint8Array | string,
+ encoding: BufferEncoding = 'utf8',
+ callback: (...args: unknown[]) => void = () => {}
+ ) {
+ if (data.length === 0) return callback()
+ if (typeof data === 'string') data = Buffer.from(data, encoding)
+
+ log('sending data direct:', data)
+ this._cfWriter!.write(data).then(
+ () => {
+ log('data sent')
+ callback()
+ },
+ (err) => {
+ log('send error', err)
+ callback(err)
+ }
+ )
+ return true
+ }
+
+ end(data = Buffer.alloc(0), encoding: BufferEncoding = 'utf8', callback: (...args: unknown[]) => void = () => {}) {
+ log('ending CF socket')
+ this.write(data, encoding, (err) => {
+ this._cfSocket!.close()
+ if (callback) callback(err)
+ })
+ return this
+ }
+
+ destroy(reason: string) {
+ log('destroying CF socket', reason)
+ this.destroyed = true
+ return this.end()
+ }
+
+ startTls(options: TlsOptions) {
+ if (this._upgraded) {
+ // Don't try to upgrade again.
+ this.emit('error', 'Cannot call `startTls()` more than once on a socket')
+ return
+ }
+ this._cfWriter!.releaseLock()
+ this._cfReader!.releaseLock()
+ this._upgrading = true
+ this._cfSocket = this._cfSocket!.startTls(options)
+ this._cfWriter = this._cfSocket.writable.getWriter()
+ this._cfReader = this._cfSocket.readable.getReader()
+ this._addClosedHandler()
+ this._listen().catch((e) => this.emit('error', e))
+ }
+
+ _addClosedHandler() {
+ this._cfSocket!.closed.then(() => {
+ if (!this._upgrading) {
+ log('CF socket closed')
+ this._cfSocket = null
+ this.emit('close')
+ } else {
+ this._upgrading = false
+ this._upgraded = true
+ }
+ }).catch((e) => this.emit('error', e))
+ }
+}
+
+const debug = false
+
+function dump(data: unknown) {
+ if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
+ // workaround https://github.com/microsoft/TypeScript/issues/63447
+ const buf = data instanceof Uint8Array ? Buffer.from(data) : Buffer.from(data)
+
+ const hex = buf.toString('hex')
+ const str = new TextDecoder().decode(data)
+ return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`
+ } else {
+ return data
+ }
+}
+
+function log(...args: unknown[]) {
+ debug && console.log(...args.map(dump))
+}
diff --git a/packages/pg-cloudflare/src/types.d.ts b/packages/pg-cloudflare/src/types.d.ts
new file mode 100644
index 000000000..f6f1c3f2f
--- /dev/null
+++ b/packages/pg-cloudflare/src/types.d.ts
@@ -0,0 +1,25 @@
+declare module 'cloudflare:sockets' {
+ export class Socket {
+ public readonly readable: any
+ public readonly writable: any
+ public readonly closed: Promise
+ public close(): Promise
+ public startTls(options: TlsOptions): Socket
+ }
+
+ export type TlsOptions = {
+ expectedServerHostname?: string
+ }
+
+ export type SocketAddress = {
+ hostname: string
+ port: number
+ }
+
+ export type SocketOptions = {
+ secureTransport?: 'off' | 'on' | 'starttls'
+ allowHalfOpen?: boolean
+ }
+
+ export function connect(address: string | SocketAddress, options?: SocketOptions): Socket
+}
diff --git a/packages/pg-cloudflare/tsconfig.json b/packages/pg-cloudflare/tsconfig.json
new file mode 100644
index 000000000..840b52aff
--- /dev/null
+++ b/packages/pg-cloudflare/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "module": "node16",
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "target": "es2020",
+ "noImplicitAny": true,
+ "moduleResolution": "node16",
+ "sourceMap": true,
+ "outDir": "dist",
+ "rootDir": "./src",
+ "incremental": true,
+ "declaration": true,
+ "paths": {
+ "*": [
+ "./node_modules/*",
+ "./src/types/*"
+ ]
+ },
+ "types": ["node"]
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/packages/pg-connection-string/.gitignore b/packages/pg-connection-string/.gitignore
index f28f01f78..18a0365ee 100644
--- a/packages/pg-connection-string/.gitignore
+++ b/packages/pg-connection-string/.gitignore
@@ -12,6 +12,7 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
+.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
@@ -23,4 +24,7 @@ build/Release
# 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
+package-lock.json
+
+# TypeScript output directory
+dist
diff --git a/packages/pg-connection-string/.mocharc.json b/packages/pg-connection-string/.mocharc.json
new file mode 100644
index 000000000..b00229fa7
--- /dev/null
+++ b/packages/pg-connection-string/.mocharc.json
@@ -0,0 +1,4 @@
+{
+ "extension": ["js", "ts"],
+ "require": "tsx"
+}
diff --git a/packages/pg-connection-string/.travis.yml b/packages/pg-connection-string/.travis.yml
deleted file mode 100644
index daf50ba6d..000000000
--- a/packages/pg-connection-string/.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-language: node_js
-node_js:
- - '0.10'
- - '6.9'
- - '8'
-after_success: 'npm run coveralls'
diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md
index d5b45ab9e..e47adc816 100644
--- a/packages/pg-connection-string/README.md
+++ b/packages/pg-connection-string/README.md
@@ -3,9 +3,6 @@ pg-connection-string
[](https://nodei.co/npm/pg-connection-string/)
-[](https://travis-ci.org/iceddev/pg-connection-string)
-[](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)
@@ -15,25 +12,47 @@ MIT License
## Usage
```js
-var parse = require('pg-connection-string').parse;
+const parse = require('pg-connection-string').parse;
-var config = parse('postgres://someuser:somepassword@somehost:381/somedatabase')
+const 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 doamain sockets, the socket filename
-* `port` - port on which to connect
* `user` - User with which to authenticate to the server
* `password` - Corresponding password
+* `host` - Postgres server hostname or, for UNIX domain sockets, the socket filename
+* `port` - port on which to connect
* `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.
+### ClientConfig Compatibility for TypeScript
+
+The pg-connection-string `ConnectionOptions` interface is not compatible with the `ClientConfig` interface that [pg.Client](https://node-postgres.com/apis/client) expects. To remedy this, use the `parseIntoClientConfig` function instead of `parse`:
+
+```ts
+import { ClientConfig } from 'pg';
+import { parseIntoClientConfig } from 'pg-connection-string';
+
+const config: ClientConfig = parseIntoClientConfig('postgres://someuser:somepassword@somehost:381/somedatabase')
+```
+
+You can also use `toClientConfig` to convert an existing `ConnectionOptions` interface into a `ClientConfig` interface:
+
+```ts
+import { ClientConfig } from 'pg';
+import { parse, toClientConfig } from 'pg-connection-string';
+
+const config = parse('postgres://someuser:somepassword@somehost:381/somedatabase')
+const clientConfig: ClientConfig = toClientConfig(config)
+```
+
## Connection Strings
The short summary of acceptable URLs is:
@@ -65,8 +84,22 @@ Query parameters follow a `?` character, including the following special query p
* `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
+ * `uselibpqcompat=true` - use libpq semantics
+ * `sslmode=` when `uselibpqcompat=true` is not set
+ * `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
+ * `sslmode=` when `uselibpqcompat=true`
+ * `sslmode=disable` - sets `ssl` to false
+ * `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }`
+ * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca`
+ * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA specified in sslrootcert.
+ * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity)
* `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.
+
+> [!CAUTION]
+> Choosing an sslmode other than verify-full has serious security implications. Please read https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS to understand the trade-offs.
diff --git a/packages/pg-connection-string/esm/index.mjs b/packages/pg-connection-string/esm/index.mjs
new file mode 100644
index 000000000..7b390c514
--- /dev/null
+++ b/packages/pg-connection-string/esm/index.mjs
@@ -0,0 +1,8 @@
+// ESM wrapper for pg-connection-string
+import connectionString from '../index.js'
+
+// Re-export the parse function
+export default connectionString.parse
+export const parse = connectionString.parse
+export const toClientConfig = connectionString.toClientConfig
+export const parseIntoClientConfig = connectionString.parseIntoClientConfig
diff --git a/packages/pg-connection-string/index.d.ts b/packages/pg-connection-string/index.d.ts
index 3081270e2..2ebe67534 100644
--- a/packages/pg-connection-string/index.d.ts
+++ b/packages/pg-connection-string/index.d.ts
@@ -1,4 +1,18 @@
-export function parse(connectionString: string): ConnectionOptions
+import { ClientConfig } from 'pg'
+
+export function parse(connectionString: string, options?: Options): ConnectionOptions
+
+export interface Options {
+ // Use libpq semantics when interpreting the connection string
+ useLibpqCompat?: boolean
+}
+
+interface SSLConfig {
+ ca?: string
+ cert?: string | null
+ key?: string
+ rejectUnauthorized?: boolean
+}
export interface ConnectionOptions {
host: string | null
@@ -7,9 +21,16 @@ export interface ConnectionOptions {
port?: string | null
database: string | null | undefined
client_encoding?: string
- ssl?: boolean | string
+ ssl?: boolean | string | SSLConfig
application_name?: string
fallback_application_name?: string
options?: string
+ keepalives?: number
+
+ // We allow any other options to be passed through
+ [key: string]: unknown
}
+
+export function toClientConfig(config: ConnectionOptions): ClientConfig
+export function parseIntoClientConfig(connectionString: string): ClientConfig
diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js
index 65951c374..4b8d7afb9 100644
--- a/packages/pg-connection-string/index.js
+++ b/packages/pg-connection-string/index.js
@@ -1,61 +1,70 @@
'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) {
+function parse(str, options = {}) {
//unix socket
if (str.charAt(0) === '/') {
- var config = str.split(' ')
+ const 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]
+ // Check for empty host in URL
+
+ const config = Object.create(null)
+ let result
+ let dummyHost = false
+ if (/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str)) {
+ // Ensure spaces are encoded as %20
+ str = encodeURI(str).replace(/%25(\d\d)/g, '%$1')
+ }
+
+ try {
+ try {
+ result = new URL(str, 'postgres://base')
+ } catch (e) {
+ // The URL is invalid so try again with a dummy host
+ result = new URL(str.replace('@/', '@___DUMMY___/'), 'postgres://base')
+ dummyHost = true
}
+ } catch (err) {
+ // Remove the input from the error message to avoid leaking sensitive information
+ err.input && (err.input = '*****REDACTED*****')
+ throw err
}
- var auth = (result.auth || ':').split(':')
- config.user = auth[0]
- config.password = auth.splice(1).join(':')
+ // We'd like to use Object.fromEntries() here but Node.js 10 does not support it
+ for (const entry of result.searchParams.entries()) {
+ config[entry[0]] = entry[1]
+ }
+
+ config.user = config.user || decodeURIComponent(result.username)
+ config.password = config.password || decodeURIComponent(result.password)
- config.port = result.port
if (result.protocol == 'socket:') {
config.host = decodeURI(result.pathname)
- config.database = result.query.db
- config.client_encoding = result.query.encoding
+ config.database = result.searchParams.get('db')
+ config.client_encoding = result.searchParams.get('encoding')
return config
}
+ const hostname = dummyHost ? '' : result.hostname
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('/')
+ config.host = decodeURIComponent(hostname)
+ } else if (hostname && /^%2f/i.test(hostname)) {
+ // Only prepend the hostname to the pathname if it is not a URL encoded Unix socket host.
+ result.pathname = hostname + result.pathname
}
- // 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
+ if (!config.port) {
+ // Only set the port if there is no equivalent query param.
+ config.port = result.port
}
- config.database = pathname && decodeURI(pathname)
+
+ const pathname = result.pathname.slice(1) || null
+ config.database = pathname ? decodeURI(pathname) : null
if (config.ssl === 'true' || config.ssl === '1') {
config.ssl = true
@@ -65,10 +74,13 @@ function parse(str) {
config.ssl = false
}
- if (config.sslcert || config.sslkey || config.sslrootcert) {
+ if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) {
config.ssl = {}
}
+ // Only try to load fs if we expect to read from the disk
+ const fs = config.sslcert || config.sslkey || config.sslrootcert ? require('fs') : null
+
if (config.sslcert) {
config.ssl.cert = fs.readFileSync(config.sslcert).toString()
}
@@ -81,9 +93,139 @@ function parse(str) {
config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
}
+ if (options.useLibpqCompat && config.uselibpqcompat) {
+ throw new Error('Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.')
+ }
+
+ if (config.uselibpqcompat === 'true' || options.useLibpqCompat) {
+ switch (config.sslmode) {
+ case 'disable': {
+ config.ssl = false
+ break
+ }
+ case 'prefer': {
+ config.ssl.rejectUnauthorized = false
+ break
+ }
+ case 'require': {
+ if (config.sslrootcert) {
+ // If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca`
+ config.ssl.checkServerIdentity = function () {}
+ } else {
+ config.ssl.rejectUnauthorized = false
+ }
+ break
+ }
+ case 'verify-ca': {
+ if (!config.ssl.ca) {
+ throw new Error(
+ 'SECURITY WARNING: Using sslmode=verify-ca requires specifying a CA with sslrootcert. If a public CA is used, verify-ca allows connections to a server that somebody else may have registered with the CA, making you vulnerable to Man-in-the-Middle attacks. Either specify a custom CA certificate with sslrootcert parameter or use sslmode=verify-full for proper security.'
+ )
+ }
+ config.ssl.checkServerIdentity = function () {}
+ break
+ }
+ case 'verify-full': {
+ break
+ }
+ }
+ } else {
+ switch (config.sslmode) {
+ case 'disable': {
+ config.ssl = false
+ break
+ }
+ case 'prefer':
+ case 'require':
+ case 'verify-ca':
+ case 'verify-full': {
+ if (config.sslmode !== 'verify-full') {
+ deprecatedSslModeWarning(config.sslmode)
+ }
+ break
+ }
+ case 'no-verify': {
+ config.ssl.rejectUnauthorized = false
+ break
+ }
+ }
+ }
+
return config
}
+// convert pg-connection-string ssl config to a ClientConfig.ConnectionOptions
+function toConnectionOptions(sslConfig) {
+ const connectionOptions = Object.entries(sslConfig).reduce((c, [key, value]) => {
+ // we explicitly check for undefined and null instead of `if (value)` because some
+ // options accept falsy values. Example: `ssl.rejectUnauthorized = false`
+ if (value !== undefined && value !== null) {
+ c[key] = value
+ }
+
+ return c
+ }, Object.create(null))
+
+ return connectionOptions
+}
+
+// convert pg-connection-string config to a ClientConfig
+function toClientConfig(config) {
+ const poolConfig = Object.entries(config).reduce((c, [key, value]) => {
+ if (key === 'ssl') {
+ const sslConfig = value
+
+ if (typeof sslConfig === 'boolean') {
+ c[key] = sslConfig
+ }
+
+ if (typeof sslConfig === 'object') {
+ c[key] = toConnectionOptions(sslConfig)
+ }
+ } else if (value !== undefined && value !== null) {
+ if (key === 'port') {
+ // when port is not specified, it is converted into an empty string
+ // we want to avoid NaN or empty string as a values in ClientConfig
+ if (value !== '') {
+ const v = parseInt(value, 10)
+ if (isNaN(v)) {
+ throw new Error(`Invalid ${key}: ${value}`)
+ }
+
+ c[key] = v
+ }
+ } else {
+ c[key] = value
+ }
+ }
+
+ return c
+ }, Object.create(null))
+
+ return poolConfig
+}
+
+// parses a connection string into ClientConfig
+function parseIntoClientConfig(str) {
+ return toClientConfig(parse(str))
+}
+
+function deprecatedSslModeWarning(sslmode) {
+ if (!deprecatedSslModeWarning.warned && typeof process !== 'undefined' && process.emitWarning) {
+ deprecatedSslModeWarning.warned = true
+ process.emitWarning(`SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'.
+In the next major version (pg-connection-string v3.0.0 and pg v9.0.0), these modes will adopt standard libpq semantics, which have weaker security guarantees.
+
+To prepare for this change:
+- If you want the current behavior, explicitly use 'sslmode=verify-full'
+- If you want libpq compatibility now, use 'uselibpqcompat=true&sslmode=${sslmode}'
+
+See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode definitions.`)
+ }
+}
+
module.exports = parse
parse.parse = parse
+parse.toClientConfig = toClientConfig
+parse.parseIntoClientConfig = parseIntoClientConfig
diff --git a/packages/pg-connection-string/package.json b/packages/pg-connection-string/package.json
index 9bf951d16..aa075e154 100644
--- a/packages/pg-connection-string/package.json
+++ b/packages/pg-connection-string/package.json
@@ -1,17 +1,25 @@
{
"name": "pg-connection-string",
- "version": "2.3.0",
+ "version": "2.13.0",
"description": "Functions for dealing with a PostgresSQL connection string",
"main": "./index.js",
"types": "./index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./index.d.ts",
+ "import": "./esm/index.mjs",
+ "require": "./index.js",
+ "default": "./index.js"
+ }
+ },
"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"
+ "test": "nyc --reporter=lcov mocha && npm run check-coverage",
+ "check-coverage": "nyc check-coverage --statements 100 --branches 100 --lines 100 --functions 100"
},
"repository": {
"type": "git",
- "url": "git://github.com/brianc/node-postgres.git"
+ "url": "git://github.com/brianc/node-postgres.git",
+ "directory": "packages/pg-connection-string"
},
"keywords": [
"pg",
@@ -22,17 +30,22 @@
"author": "Blaine Bublitz (http://iceddev.com/)",
"license": "MIT",
"bugs": {
- "url": "https://github.com/iceddev/pg-connection-string/issues"
+ "url": "https://github.com/brianc/node-postgres/issues"
},
- "homepage": "https://github.com/iceddev/pg-connection-string",
+ "homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string",
"devDependencies": {
+ "@types/pg": "^8.12.0",
"chai": "^4.1.1",
"coveralls": "^3.0.4",
"istanbul": "^0.4.5",
- "mocha": "^7.1.2"
+ "mocha": "^11.7.5",
+ "nyc": "^15",
+ "tsx": "^4.19.4",
+ "typescript": "^6.0.3"
},
"files": [
"index.js",
- "index.d.ts"
+ "index.d.ts",
+ "esm"
]
}
diff --git a/packages/pg-connection-string/test/clientConfig.ts b/packages/pg-connection-string/test/clientConfig.ts
new file mode 100644
index 000000000..c4aeec6a7
--- /dev/null
+++ b/packages/pg-connection-string/test/clientConfig.ts
@@ -0,0 +1,125 @@
+import chai from 'chai'
+const expect = chai.expect
+chai.should()
+
+import { parse, toClientConfig, parseIntoClientConfig } from '../'
+
+describe('toClientConfig', function () {
+ it('converts connection info', function () {
+ const config = parse('postgres://brian:pw@boom:381/lala')
+ const clientConfig = toClientConfig(config)
+
+ clientConfig.user?.should.equal('brian')
+ clientConfig.password?.should.equal('pw')
+ clientConfig.host?.should.equal('boom')
+ clientConfig.port?.should.equal(381)
+ clientConfig.database?.should.equal('lala')
+ })
+
+ it('converts query params', function () {
+ const config = parse(
+ 'postgres:///?application_name=TheApp&fallback_application_name=TheAppFallback&client_encoding=utf8&options=-c geqo=off'
+ )
+ const clientConfig = toClientConfig(config)
+
+ clientConfig.application_name?.should.equal('TheApp')
+ clientConfig.fallback_application_name?.should.equal('TheAppFallback')
+ clientConfig.client_encoding?.should.equal('utf8')
+ clientConfig.options?.should.equal('-c geqo=off')
+ })
+
+ it('converts SSL boolean', function () {
+ const config = parse('pg:///?ssl=true')
+ const clientConfig = toClientConfig(config)
+
+ clientConfig.ssl?.should.equal(true)
+ })
+
+ it('converts sslmode=disable', function () {
+ const config = parse('pg:///?sslmode=disable')
+ const clientConfig = toClientConfig(config)
+
+ clientConfig.ssl?.should.equal(false)
+ })
+
+ it('converts sslmode=noverify', function () {
+ const config = parse('pg:///?sslmode=no-verify')
+ const clientConfig = toClientConfig(config)
+
+ expect(clientConfig.ssl).to.deep.equal({
+ rejectUnauthorized: false,
+ })
+ })
+
+ it('converts other sslmode options', function () {
+ const config = parse('pg:///?sslmode=verify-ca')
+ const clientConfig = toClientConfig(config)
+
+ expect(clientConfig.ssl).to.deep.equal({})
+ })
+
+ it('converts other sslmode options', function () {
+ const config = parse('pg:///?sslmode=verify-ca')
+ const clientConfig = toClientConfig(config)
+
+ expect(clientConfig.ssl).to.deep.equal({})
+ })
+
+ it('converts ssl cert options', function () {
+ const connectionString =
+ 'pg:///?sslcert=' +
+ __dirname +
+ '/example.cert&sslkey=' +
+ __dirname +
+ '/example.key&sslrootcert=' +
+ __dirname +
+ '/example.ca'
+ const config = parse(connectionString)
+ const clientConfig = toClientConfig(config)
+
+ expect(clientConfig.ssl).to.deep.equal({
+ ca: 'example ca\n',
+ cert: 'example cert\n',
+ key: 'example key\n',
+ })
+ })
+
+ it('converts unix domain sockets', function () {
+ const config = parse('socket:/some path/?db=my[db]&encoding=utf8&client_encoding=bogus')
+ const clientConfig = toClientConfig(config)
+ clientConfig.host?.should.equal('/some path/')
+ clientConfig.database?.should.equal('my[db]', 'must to be escaped and unescaped through "my%5Bdb%5D"')
+ clientConfig.client_encoding?.should.equal('utf8')
+ })
+
+ it('handles invalid port', function () {
+ const config = parse('postgres://@boom:381/lala')
+ config.port = 'bogus'
+ expect(() => toClientConfig(config)).to.throw()
+ })
+
+ it('handles invalid sslconfig values', function () {
+ const config = parse('postgres://@boom/lala')
+ config.ssl = {}
+ config.ssl.cert = null
+ config.ssl.key = undefined
+
+ const clientConfig = toClientConfig(config)
+
+ expect(clientConfig.host).to.equal('boom')
+ expect(clientConfig.database).to.equal('lala')
+ expect(clientConfig.ssl).to.deep.equal({})
+ })
+})
+
+describe('parseIntoClientConfig', function () {
+ it('converts url', function () {
+ const clientConfig = parseIntoClientConfig('postgres://brian:pw@boom:381/lala')
+
+ clientConfig.user?.should.equal('brian')
+ clientConfig.password?.should.equal('pw')
+ clientConfig.host?.should.equal('boom')
+ clientConfig.port?.should.equal(381)
+ clientConfig.database?.should.equal('lala')
+ })
+})
diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js
deleted file mode 100644
index 035b025d1..000000000
--- a/packages/pg-connection-string/test/parse.js
+++ /dev/null
@@ -1,274 +0,0 @@
-'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('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-connection-string/test/parse.ts b/packages/pg-connection-string/test/parse.ts
new file mode 100644
index 000000000..814e49c58
--- /dev/null
+++ b/packages/pg-connection-string/test/parse.ts
@@ -0,0 +1,504 @@
+import chai from 'chai'
+const expect = chai.expect
+chai.should()
+
+import { parse } from '../'
+
+describe('parse', function () {
+ it('using connection string in client constructor', function () {
+ const 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 () {
+ const subject = parse('postgres://localhost/post gres')
+ subject.database?.should.equal('post gres')
+ })
+
+ it('do not double escape spaces', function () {
+ const subject = parse('postgres://localhost/post%20gres')
+ subject.database?.should.equal('post gres')
+ })
+
+ it('initializing with unix domain socket', function () {
+ const subject = parse('/const/run/')
+ subject.host?.should.equal('/const/run/')
+ })
+
+ it('initializing with unix domain socket and a specific database, the simple way', function () {
+ const subject = parse('/const/run/ mydb')
+ subject.host?.should.equal('/const/run/')
+ subject.database?.should.equal('mydb')
+ })
+
+ it('initializing with unix domain socket, the health way', function () {
+ const 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 () {
+ const 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 () {
+ const subject = parse('socket://brian:pw@/const/run/?db=mydb')
+ subject.user?.should.equal('brian')
+ subject.password?.should.equal('pw')
+ subject.host?.should.equal('/const/run/')
+ subject.database?.should.equal('mydb')
+ })
+
+ it('password contains < and/or > characters', function () {
+ const sourceConfig = {
+ user: 'brian',
+ password: 'helloe',
+ host: 'localhost',
+ port: 5432,
+ database: 'postgres',
+ }
+ const connectionString =
+ 'postgres://' +
+ sourceConfig.user +
+ ':' +
+ sourceConfig.password +
+ '@' +
+ sourceConfig.host +
+ ':' +
+ sourceConfig.port +
+ '/' +
+ sourceConfig.database
+ const subject = parse(connectionString)
+ subject.password?.should.equal(sourceConfig.password)
+ })
+
+ it('password contains colons', function () {
+ const sourceConfig = {
+ user: 'brian',
+ password: 'hello:pass:world',
+ host: 'localhost',
+ port: 5432,
+ database: 'postgres',
+ }
+ const connectionString =
+ 'postgres://' +
+ sourceConfig.user +
+ ':' +
+ sourceConfig.password +
+ '@' +
+ sourceConfig.host +
+ ':' +
+ sourceConfig.port +
+ '/' +
+ sourceConfig.database
+ const subject = parse(connectionString)
+ subject.password?.should.equal(sourceConfig.password)
+ })
+
+ it('username or password contains weird characters', function () {
+ const strang = 'pg://my f%irst name:is&%awesome!@localhost:9000'
+ const 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 () {
+ const encoded = 'pg://bi%25na%25%25ry%20:s%40f%23@localhost/%20u%2520rl'
+ const 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 () {
+ const relative = 'different_db_on_default_host'
+ const subject = parse(relative)
+ subject.database?.should.equal('different_db_on_default_host')
+ })
+
+ it('no pathname returns null database', function () {
+ const subject = parse('pg://myhost')
+ ;(subject.database === null).should.equal(true)
+ })
+
+ it('pathname of "/" returns null database', function () {
+ const subject = parse('pg://myhost/')
+ subject.host?.should.equal('myhost')
+ ;(subject.database === null).should.equal(true)
+ })
+
+ it('configuration parameter host', function () {
+ const 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 () {
+ const subject = parse('pg://user:pass@localhost/dbname?host=/unix/socket')
+ subject.database?.should.equal('dbname')
+ subject.host?.should.equal('/unix/socket')
+ })
+
+ it('url with encoded socket', function () {
+ const 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 () {
+ const 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 host as part of the db name', function () {
+ const 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 () {
+ const connectionString = 'pg:///?application_name=TheApp'
+ const subject = parse(connectionString)
+ subject.application_name?.should.equal('TheApp')
+ })
+
+ it('configuration parameter fallback_application_name', function () {
+ const connectionString = 'pg:///?fallback_application_name=TheAppFallback'
+ const subject = parse(connectionString)
+ subject.fallback_application_name?.should.equal('TheAppFallback')
+ })
+
+ it('configuration parameter options', function () {
+ const connectionString = 'pg:///?options=-c geqo=off'
+ const subject = parse(connectionString)
+ subject.options?.should.equal('-c geqo=off')
+ })
+
+ it('configuration parameter ssl=true', function () {
+ const connectionString = 'pg:///?ssl=true'
+ const subject = parse(connectionString)
+ subject.ssl?.should.equal(true)
+ })
+
+ it('configuration parameter ssl=1', function () {
+ const connectionString = 'pg:///?ssl=1'
+ const subject = parse(connectionString)
+ subject.ssl?.should.equal(true)
+ })
+
+ it('configuration parameter ssl=0', function () {
+ const connectionString = 'pg:///?ssl=0'
+ const subject = parse(connectionString)
+ subject.ssl?.should.equal(false)
+ })
+
+ it('set ssl', function () {
+ const subject = parse('pg://myhost/db?ssl=1')
+ subject.ssl?.should.equal(true)
+ })
+
+ it('configuration parameter sslcert=/path/to/cert', function () {
+ const connectionString = 'pg:///?sslcert=' + __dirname + '/example.cert'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ cert: 'example cert\n',
+ })
+ })
+
+ it('configuration parameter sslkey=/path/to/key', function () {
+ const connectionString = 'pg:///?sslkey=' + __dirname + '/example.key'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ key: 'example key\n',
+ })
+ })
+
+ it('configuration parameter sslrootcert=/path/to/ca', function () {
+ const connectionString = 'pg:///?sslrootcert=' + __dirname + '/example.ca'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ ca: 'example ca\n',
+ })
+ })
+
+ it('configuration parameter sslmode=no-verify', function () {
+ const connectionString = 'pg:///?sslmode=no-verify'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ rejectUnauthorized: false,
+ })
+ })
+
+ it('configuration parameter sslmode=disable', function () {
+ const connectionString = 'pg:///?sslmode=disable'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql(false)
+ })
+
+ it('configuration parameter sslmode=prefer', function () {
+ const connectionString = 'pg:///?sslmode=prefer'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({})
+ })
+
+ it('configuration parameter sslmode=require', function () {
+ const connectionString = 'pg:///?sslmode=require'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({})
+ })
+
+ it('configuration parameter sslmode=verify-ca', function () {
+ const connectionString = 'pg:///?sslmode=verify-ca'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({})
+ })
+
+ it('configuration parameter sslmode=verify-full', function () {
+ const connectionString = 'pg:///?sslmode=verify-full'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({})
+ })
+
+ it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca', function () {
+ const connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ ca: 'example ca\n',
+ })
+ })
+
+ it('configuration parameter sslmode=disable with uselibpqcompat query param', function () {
+ const connectionString = 'pg:///?sslmode=disable&uselibpqcompat=true'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql(false)
+ })
+
+ it('configuration parameter sslmode=prefer with uselibpqcompat query param', function () {
+ const connectionString = 'pg:///?sslmode=prefer&uselibpqcompat=true'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ rejectUnauthorized: false,
+ })
+ })
+
+ it('configuration parameter sslmode=require with uselibpqcompat query param', function () {
+ const connectionString = 'pg:///?sslmode=require&uselibpqcompat=true'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({
+ rejectUnauthorized: false,
+ })
+ })
+
+ it('configuration parameter sslmode=verify-ca with uselibpqcompat query param', function () {
+ const connectionString = 'pg:///?sslmode=verify-ca&uselibpqcompat=true'
+ expect(function () {
+ parse(connectionString)
+ }).to.throw()
+ })
+
+ it('when throwing on invalid url does not print out the password in the error message', function () {
+ const host = 'localhost'
+ const port = 5432
+ const user = 'user'
+ const password = 'g#4624$@F$#v`'
+ const database = 'db'
+
+ const connectionString = `postgres://${user}:${password}@${host}:${port}/${database}`
+ expect(function () {
+ parse(connectionString)
+ }).to.throw()
+ try {
+ parse(connectionString)
+ } catch (err: unknown) {
+ expect(JSON.stringify(err)).to.not.include(password, 'Password should not be in the error message')
+ expect(JSON.stringify(err)).to.include('REDACTED', 'The thrown error should contain the redacted URL')
+ return
+ }
+ throw new Error('Expected an error to be thrown')
+ })
+
+ it('configuration parameter sslmode=verify-ca and sslrootcert with uselibpqcompat query param', function () {
+ const connectionString = 'pg:///?sslmode=verify-ca&uselibpqcompat=true&sslrootcert=' + __dirname + '/example.ca'
+ const subject = parse(connectionString)
+ subject.ssl?.should.have.property('checkServerIdentity').that.is.a('function')
+ // We prove above that the checkServerIdentity function is defined
+ //
+ // FIXME: remove this if we upgrade to TypeScript 5
+ // @ts-ignore
+ expect(subject.ssl.checkServerIdentity()).be.undefined
+ })
+
+ it('configuration parameter sslmode=verify-full with uselibpqcompat query param', function () {
+ const connectionString = 'pg:///?sslmode=verify-full&uselibpqcompat=true'
+ const subject = parse(connectionString)
+ subject.ssl?.should.eql({})
+ })
+
+ it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with uselibpqcompat query param', function () {
+ const connectionString =
+ 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require&uselibpqcompat=true'
+ const subject = parse(connectionString)
+ subject.ssl?.should.have.property('ca', 'example ca\n')
+ subject.ssl?.should.have.property('checkServerIdentity').that.is.a('function')
+ // We prove above that the checkServerIdentity function is defined
+ //
+ // FIXME: remove this if we upgrade to TypeScript 5
+ // @ts-ignore
+ expect(subject.ssl?.checkServerIdentity()).be.undefined
+ })
+
+ it('configuration parameter sslmode=disable with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?sslmode=disable'
+ const subject = parse(connectionString, { useLibpqCompat: true })
+ subject.ssl?.should.eql(false)
+ })
+
+ it('configuration parameter sslmode=prefer with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?sslmode=prefer'
+ const subject = parse(connectionString, { useLibpqCompat: true })
+ subject.ssl?.should.eql({
+ rejectUnauthorized: false,
+ })
+ })
+
+ it('configuration parameter sslmode=require with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?sslmode=require'
+ const subject = parse(connectionString, { useLibpqCompat: true })
+ subject.ssl?.should.eql({
+ rejectUnauthorized: false,
+ })
+ })
+
+ it('configuration parameter sslmode=verify-ca with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?sslmode=verify-ca'
+ expect(function () {
+ parse(connectionString, { useLibpqCompat: true })
+ }).to.throw()
+ })
+
+ it('configuration parameter sslmode=verify-ca and sslrootcert with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?sslmode=verify-ca&sslrootcert=' + __dirname + '/example.ca'
+ const subject = parse(connectionString, { useLibpqCompat: true })
+ subject.ssl?.should.have.property('checkServerIdentity').that.is.a('function')
+ // We prove above that the checkServerIdentity function is defined
+ //
+ // FIXME: remove this if we upgrade to TypeScript 5
+ // @ts-ignore
+ expect(subject.ssl?.checkServerIdentity()).be.undefined
+ })
+
+ it('configuration parameter sslmode=verify-full with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?sslmode=verify-full'
+ const subject = parse(connectionString, { useLibpqCompat: true })
+ subject.ssl?.should.eql({})
+ })
+
+ it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with useLibpqCompat option', function () {
+ const connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require'
+ const subject = parse(connectionString, { useLibpqCompat: true })
+ subject.ssl?.should.have.property('ca', 'example ca\n')
+ subject.ssl?.should.have.property('checkServerIdentity').that.is.a('function')
+ // We prove above that the checkServerIdentity function is defined
+ //
+ // FIXME: remove this if we upgrade to TypeScript 5
+ // @ts-ignore
+ expect(subject.ssl?.checkServerIdentity()).be.undefined
+ })
+
+ it('does not allow uselibpqcompat query parameter and useLibpqCompat option at the same time', function () {
+ const connectionString = 'pg:///?uselibpqcompat=true'
+ expect(function () {
+ parse(connectionString, { useLibpqCompat: true })
+ }).to.throw()
+ })
+
+ it('allow other params like max, ...', function () {
+ const subject = parse('pg://myhost/db?max=18&min=4')
+ subject.max?.should.equal('18')
+ subject.min?.should.equal('4')
+ })
+
+ it('configuration parameter keepalives', function () {
+ const connectionString = 'pg:///?keepalives=1'
+ const subject = parse(connectionString)
+ subject.keepalives?.should.equal('1')
+ })
+
+ it('unknown configuration parameter is passed into client', function () {
+ const connectionString = 'pg:///?ThereIsNoSuchPostgresParameter=1234'
+ const subject = parse(connectionString)
+ subject.ThereIsNoSuchPostgresParameter?.should.equal('1234')
+ })
+
+ it('do not override a config field with value from query string', function () {
+ const 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 () {
+ const connectionString = 'pg:///?keepalives=1&keepalives=0'
+ const subject = parse(connectionString)
+ subject.keepalives?.should.equal('0')
+ })
+
+ it('use the port specified in the query parameters', function () {
+ const connectionString = 'postgres:///?host=localhost&port=1234'
+ const subject = parse(connectionString)
+ subject.port?.should.equal('1234')
+ })
+
+ describe('prototype pollution protection', function () {
+ it('returns object with null prototype', function () {
+ const subject = parse('postgres://localhost/db')
+ expect(Object.getPrototypeOf(subject)).to.equal(null)
+ })
+
+ it('__proto__ query parameter is stored as regular property', function () {
+ const subject = parse('postgres://localhost/db?__proto__=malicious')
+ expect(Object.getPrototypeOf(subject)).to.equal(null)
+ expect(subject['__proto__']).to.equal('malicious')
+ // global Object.prototype should not be affected
+ expect(({} as any).malicious).to.equal(undefined)
+ })
+
+ it('constructor query parameter is stored as regular property', function () {
+ const subject = parse('postgres://localhost/db?constructor=evil')
+ expect(subject.constructor).to.equal('evil')
+ })
+
+ it('prototype query parameter is stored as regular property', function () {
+ const subject = parse('postgres://localhost/db?prototype=evil')
+ expect(subject['prototype']).to.equal('evil')
+ })
+
+ it('multiple dangerous query parameters are handled safely', function () {
+ const subject = parse('postgres://localhost/db?__proto__=a&constructor=b&prototype=c&toString=d')
+ expect(Object.getPrototypeOf(subject)).to.equal(null)
+ expect(subject['__proto__']).to.equal('a')
+ expect(subject.constructor).to.equal('b')
+ expect(subject['prototype']).to.equal('c')
+ expect(subject['toString']).to.equal('d')
+ })
+ })
+})
diff --git a/packages/pg-connection-string/tsconfig.json b/packages/pg-connection-string/tsconfig.json
new file mode 100644
index 000000000..c6bf4f1a1
--- /dev/null
+++ b/packages/pg-connection-string/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "target": "es6",
+ "noImplicitAny": true,
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "outDir": "dist",
+ "incremental": true,
+ "baseUrl": ".",
+ "declaration": true
+ },
+ "include": [
+ "test/**/*"
+ ]
+}
diff --git a/packages/pg-cursor/README.md b/packages/pg-cursor/README.md
index 1b01b3d83..a3fdf4d00 100644
--- a/packages/pg-cursor/README.md
+++ b/packages/pg-cursor/README.md
@@ -10,7 +10,7 @@ $ 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:
+### :star: [Documentation](https://node-postgres.com/apis/cursor) :star:
### license
diff --git a/packages/pg-cursor/esm/index.mjs b/packages/pg-cursor/esm/index.mjs
new file mode 100644
index 000000000..65b0db041
--- /dev/null
+++ b/packages/pg-cursor/esm/index.mjs
@@ -0,0 +1,5 @@
+// ESM wrapper for pg-cursor
+import Cursor from '../index.js'
+
+// Export as default only to match CJS module
+export default Cursor
diff --git a/packages/pg-cursor/index.js b/packages/pg-cursor/index.js
index 9d672dbff..f1553cc9c 100644
--- a/packages/pg-cursor/index.js
+++ b/packages/pg-cursor/index.js
@@ -1,4 +1,5 @@
'use strict'
+// note: can remove these deep requires when we bump min version of pg to 9.x
const Result = require('pg/lib/result.js')
const prepare = require('pg/lib/utils.js').prepareValue
const EventEmitter = require('events').EventEmitter
@@ -6,213 +7,259 @@ const util = require('util')
let nextUniqueID = 1 // concept borrowed from org.postgresql.core.v3.QueryExecutorImpl
-function Cursor(text, values, config) {
- EventEmitter.call(this)
-
- 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)
-}
+class Cursor extends EventEmitter {
+ constructor(text, values, config) {
+ super()
-util.inherits(Cursor, EventEmitter)
+ 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._Promise = this._conf.Promise || global.Promise
+ this._cb = null
+ this._rows = null
+ this._portal = null
+ this._ifNoData = this._ifNoData.bind(this)
+ this._rowDescription = this._rowDescription.bind(this)
+ }
-Cursor.prototype._ifNoData = function () {
- this.state = 'idle'
- this._shiftQueue()
-}
+ _ifNoData() {
+ this.state = 'idle'
+ this._shiftQueue()
+ if (this.connection) {
+ this.connection.removeListener('rowDescription', this._rowDescription)
+ }
+ }
-Cursor.prototype._rowDescription = function () {
- if (this.connection) {
- this.connection.removeListener('noData', this._ifNoData)
+ _rowDescription() {
+ if (this.connection) {
+ this.connection.removeListener('noData', this._ifNoData)
+ }
}
-}
-Cursor.prototype.submit = function (connection) {
- this.connection = connection
- this._portal = 'C_' + nextUniqueID++
+ submit(connection) {
+ this.state = 'submitted'
+ this.connection = connection
+ this._portal = 'C_' + nextUniqueID++
- const con = connection
+ const con = connection
- con.parse(
- {
- text: this.text,
- },
- true
- )
+ con.parse(
+ {
+ text: this.text,
+ },
+ true
+ )
- con.bind(
- {
- portal: this._portal,
- values: this.values,
- },
- 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.describe(
+ {
+ type: 'P',
+ name: this._portal, // AWS Redshift requires a portal name
+ },
+ true
+ )
- con.flush()
+ con.flush()
- if (this._conf.types) {
- this._result._getTypeParser = this._conf.types.getTypeParser
- }
-
- con.once('noData', this._ifNoData)
- con.once('rowDescription', this._rowDescription)
-}
+ if (this._conf.types) {
+ this._result._getTypeParser = this._conf.types.getTypeParser
+ }
-Cursor.prototype._shiftQueue = function () {
- if (this._queue.length) {
- this._getRows.apply(this, this._queue.shift())
+ con.once('noData', this._ifNoData)
+ con.once('rowDescription', this._rowDescription)
}
-}
-Cursor.prototype._closePortal = function () {
- // 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 })
- this.connection.sync()
-}
+ _shiftQueue() {
+ if (this._queue.length) {
+ this._getRows.apply(this, this._queue.shift())
+ }
+ }
-Cursor.prototype.handleRowDescription = function (msg) {
- this._result.addFields(msg.fields)
- this.state = 'idle'
- this._shiftQueue()
-}
+ _closePortal() {
+ if (this.state === 'done') return
-Cursor.prototype.handleDataRow = function (msg) {
- const row = this._result.parseRow(msg.fields)
- this.emit('row', row, this._result)
- this._rows.push(row)
-}
+ // 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 })
-Cursor.prototype._sendRows = function () {
- 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)
+ // 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()
}
- this._rows = []
- })
-}
-Cursor.prototype.handleCommandComplete = function (msg) {
- this._result.addCommandComplete(msg)
- this._closePortal()
-}
-
-Cursor.prototype.handlePortalSuspended = function () {
- this._sendRows()
-}
+ this.state = 'done'
+ }
-Cursor.prototype.handleReadyForQuery = function () {
- this._sendRows()
- this.state = 'done'
- this.emit('end', this._result)
-}
+ handleRowDescription(msg) {
+ this._result.addFields(msg.fields)
+ this.state = 'idle'
+ this._shiftQueue()
+ }
-Cursor.prototype.handleEmptyQuery = function () {
- this.connection.sync()
-}
+ handleDataRow(msg) {
+ const row = this._result.parseRow(msg.fields)
+ this.emit('row', row, this._result)
+ this._rows.push(row)
+ }
-Cursor.prototype.handleError = function (msg) {
- this.connection.removeListener('noData', this._ifNoData)
- this.connection.removeListener('rowDescription', this._rowDescription)
- this.state = 'error'
- this._error = msg
- // satisfy any waiting callback
- if (this._cb) {
- this._cb(msg)
+ _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 = []
+ })
}
- // dispatch error to all waiting callbacks
- for (let i = 0; i < this._queue.length; i++) {
- this._queue.pop()[1](msg)
+
+ handleCommandComplete(msg) {
+ this._result.addCommandComplete(msg)
+ this._closePortal()
}
- if (this.listenerCount('error') > 0) {
- // only dispatch error events if we have a listener
- this.emit('error', msg)
+ handlePortalSuspended() {
+ this._sendRows()
}
- // call sync to keep this connection from hanging
- this.connection.sync()
-}
-Cursor.prototype._getRows = function (rows, cb) {
- this.state = 'busy'
- this._cb = cb
- this._rows = []
- const msg = {
- portal: this._portal,
- rows: rows,
+ handleReadyForQuery() {
+ this._sendRows()
+ this.state = 'done'
+ this.emit('end', this._result)
}
- 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
-Cursor.prototype.end = util.deprecate(function (cb) {
- if (this.state !== 'initialized') {
+ handleEmptyQuery() {
this.connection.sync()
}
- this.connection.once('end', cb)
- this.connection.end()
-}, 'Cursor.end is deprecated. Call end on the client itself to end a connection to the database.')
-Cursor.prototype.close = function (cb) {
- if (!this.connection || this.state === 'done') {
- if (cb) {
- return setImmediate(cb)
- } else {
- return
+ handleError(msg) {
+ // If this cursor has already closed, don't try to handle the error.
+ if (this.state === 'done') return
+
+ // 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++) {
+ const queuedCallback = this._queue[i][1]
+ queuedCallback.call(this, msg)
+ }
+ this._queue.length = 0
+
+ if (this.listenerCount('error') > 0) {
+ // only dispatch error events if we have a listener
+ this.emit('error', msg)
}
}
- this._closePortal()
- this.state = 'done'
- if (cb) {
+
+ _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) {
+ let promise
+
+ if (!cb) {
+ promise = new this._Promise((resolve, reject) => {
+ cb = (err) => (err ? reject(err) : resolve())
+ })
+ }
+
+ if (!this.connection || this.state === 'done') {
+ setImmediate(cb)
+ return promise
+ }
+
+ this._closePortal()
this.connection.once('readyForQuery', function () {
cb()
})
- }
-}
-Cursor.prototype.read = function (rows, cb) {
- if (this.state === 'idle') {
- 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))
+ // Return the promise (or undefined)
+ return promise
}
- if (this.state === 'done') {
- return setImmediate(() => cb(null, []))
- } else {
- throw new Error('Unknown state: ' + this.state)
+
+ read(rows, cb) {
+ let promise
+
+ if (!cb) {
+ promise = new this._Promise((resolve, reject) => {
+ cb = (err, rows) => (err ? reject(err) : resolve(rows))
+ })
+ }
+
+ if (this.state === 'idle' || this.state === 'submitted') {
+ this._getRows(rows, cb)
+ } else if (this.state === 'busy' || this.state === 'initialized') {
+ this._queue.push([rows, cb])
+ } else if (this.state === 'error') {
+ setImmediate(() => cb(this._error))
+ } else if (this.state === 'done') {
+ setImmediate(() => cb(null, []))
+ } else {
+ throw new Error('Unknown state: ' + this.state)
+ }
+
+ // Return the promise (or undefined)
+ return promise
}
}
+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
index 00fbcaaa2..3949d50c8 100644
--- a/packages/pg-cursor/package.json
+++ b/packages/pg-cursor/package.json
@@ -1,8 +1,15 @@
{
"name": "pg-cursor",
- "version": "2.3.0",
+ "version": "2.20.0",
"description": "Query cursor extension for node-postgres",
"main": "index.js",
+ "exports": {
+ ".": {
+ "import": "./esm/index.mjs",
+ "require": "./index.js",
+ "default": "./index.js"
+ }
+ },
"directories": {
"test": "test"
},
@@ -11,12 +18,20 @@
},
"repository": {
"type": "git",
- "url": "git://github.com/brianc/node-postgres.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.3.0"
- }
+ "mocha": "^11.7.5",
+ "pg": "^8.21.0"
+ },
+ "peerDependencies": {
+ "pg": "^8"
+ },
+ "files": [
+ "index.js",
+ "esm"
+ ]
}
diff --git a/packages/pg-cursor/test/close.js b/packages/pg-cursor/test/close.js
index e63512abd..4b4c913a3 100644
--- a/packages/pg-cursor/test/close.js
+++ b/packages/pg-cursor/test/close.js
@@ -16,21 +16,32 @@ describe('close', function () {
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()
})
+ this.client.once('drain', done)
+ })
+
+ it('can close a finished cursor a promise', function (done) {
+ const cursor = new Cursor(text)
+ this.client.query(cursor)
+ cursor.read(100, (err) => {
+ assert.ifError(err)
+ cursor.close().then(() => {
+ this.client.query('SELECT NOW()', done)
+ })
+ })
})
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()
})
+ this.client.once('drain', done)
})
it('works with callback style', function (done) {
diff --git a/packages/pg-cursor/test/error-handling.js b/packages/pg-cursor/test/error-handling.js
index f6edef6d5..9234870d5 100644
--- a/packages/pg-cursor/test/error-handling.js
+++ b/packages/pg-cursor/test/error-handling.js
@@ -19,6 +19,38 @@ describe('error handling', function () {
})
})
})
+
+ it('errors queued reads', async () => {
+ const client = new pg.Client()
+ await client.connect()
+
+ const cursor = client.query(new Cursor('asdfdffsdf'))
+
+ const immediateRead = cursor.read(1)
+ const queuedRead1 = cursor.read(1)
+ const queuedRead2 = cursor.read(1)
+
+ assert(
+ await immediateRead.then(
+ () => null,
+ (err) => err
+ )
+ )
+ assert(
+ await queuedRead1.then(
+ () => null,
+ (err) => err
+ )
+ )
+ assert(
+ await queuedRead2.then(
+ () => null,
+ (err) => err
+ )
+ )
+
+ client.end()
+ })
})
describe('read callback does not fire sync', () => {
diff --git a/packages/pg-cursor/test/promises.js b/packages/pg-cursor/test/promises.js
new file mode 100644
index 000000000..1635a1a8b
--- /dev/null
+++ b/packages/pg-cursor/test/promises.js
@@ -0,0 +1,51 @@
+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 using promises', 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('resolve with result', async function () {
+ const cursor = this.pgCursor(text)
+ const res = await cursor.read(6)
+ assert.strictEqual(res.length, 6)
+ })
+
+ it('reject with error', function (done) {
+ const cursor = this.pgCursor('select asdfasdf')
+ cursor.read(1).catch((err) => {
+ assert(err)
+ done()
+ })
+ })
+
+ it('read multiple times', async function () {
+ const cursor = this.pgCursor(text)
+ let res
+
+ res = await cursor.read(2)
+ assert.strictEqual(res.length, 2)
+
+ res = await cursor.read(3)
+ assert.strictEqual(res.length, 3)
+
+ res = await cursor.read(1)
+ assert.strictEqual(res.length, 1)
+
+ res = await cursor.read(1)
+ assert.strictEqual(res.length, 0)
+ })
+})
diff --git a/packages/pg-esm-test/README.md b/packages/pg-esm-test/README.md
new file mode 100644
index 000000000..9837abb71
--- /dev/null
+++ b/packages/pg-esm-test/README.md
@@ -0,0 +1,3 @@
+This is an internal package for node-postgres used to test esm & cjs module export compatibility.
+
+The only thing you really need to do is `yarn && yarn test` from the root of the project & these tests will run as well as all the other tests. So, basically, you can ignore this. 😄
diff --git a/packages/pg-esm-test/common-js-imports.test.cjs b/packages/pg-esm-test/common-js-imports.test.cjs
new file mode 100644
index 000000000..385cf9a4e
--- /dev/null
+++ b/packages/pg-esm-test/common-js-imports.test.cjs
@@ -0,0 +1,35 @@
+const assert = require('assert')
+const test = require('node:test')
+const { describe, it } = test
+
+const paths = [
+ 'pg',
+ 'pg/lib/index.js',
+ 'pg/lib/index',
+ 'pg/lib/connection-parameters',
+ 'pg/lib/connection-parameters.js',
+ 'pg/lib/type-overrides',
+ 'pg-protocol/dist/messages.js',
+ 'pg-protocol/dist/messages',
+ 'pg-native/lib/build-result.js',
+ 'pg-cloudflare/package.json',
+]
+for (const path of paths) {
+ describe(`importing ${path}`, () => {
+ it('works with require', () => {
+ const mod = require(path)
+ assert(mod)
+ })
+ })
+}
+
+describe('pg-native', () => {
+ it('should work with commonjs', async () => {
+ const pg = require('pg')
+
+ const pool = new pg.native.Pool()
+ const result = await pool.query('SELECT 1')
+ assert.strictEqual(result.rowCount, 1)
+ pool.end()
+ })
+})
diff --git a/packages/pg-esm-test/package.json b/packages/pg-esm-test/package.json
new file mode 100644
index 000000000..ec3c4a811
--- /dev/null
+++ b/packages/pg-esm-test/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "pg-esm-test",
+ "version": "1.7.0",
+ "description": "A test module for PostgreSQL with ESM support",
+ "main": "index.js",
+ "type": "module",
+ "scripts": {
+ "test": "node --test --conditions=workerd"
+ },
+ "keywords": [
+ "postgres",
+ "postgresql",
+ "esm",
+ "test"
+ ],
+ "devDependencies": {
+ "pg": "^8.21.0",
+ "pg-cloudflare": "^1.4.0",
+ "pg-cursor": "^2.20.0",
+ "pg-native": "^3.8.0",
+ "pg-pool": "^3.14.0",
+ "pg-protocol": "^1.14.0",
+ "pg-query-stream": "^4.15.0"
+ },
+ "author": "Brian M. Carlson ",
+ "license": "MIT"
+}
diff --git a/packages/pg-esm-test/pg-cloudflare.test.js b/packages/pg-esm-test/pg-cloudflare.test.js
new file mode 100644
index 000000000..a42620253
--- /dev/null
+++ b/packages/pg-esm-test/pg-cloudflare.test.js
@@ -0,0 +1,9 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import { CloudflareSocket } from 'pg-cloudflare'
+
+describe('pg-cloudflare', () => {
+ it('should export CloudflareSocket constructor', () => {
+ assert.ok(new CloudflareSocket())
+ })
+})
diff --git a/packages/pg-esm-test/pg-connection-string.test.js b/packages/pg-esm-test/pg-connection-string.test.js
new file mode 100644
index 000000000..252499a72
--- /dev/null
+++ b/packages/pg-esm-test/pg-connection-string.test.js
@@ -0,0 +1,17 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import { parse, toClientConfig, parseIntoClientConfig } from 'pg-connection-string'
+
+describe('pg-connection-string', () => {
+ it('should export parse function', () => {
+ assert.strictEqual(typeof parse, 'function')
+ })
+
+ it('should export toClientConfig function', () => {
+ assert.strictEqual(typeof toClientConfig, 'function')
+ })
+
+ it('should export parseIntoClientConfig function', () => {
+ assert.strictEqual(typeof parseIntoClientConfig, 'function')
+ })
+})
diff --git a/packages/pg-esm-test/pg-cursor.test.js b/packages/pg-esm-test/pg-cursor.test.js
new file mode 100644
index 000000000..6c5024485
--- /dev/null
+++ b/packages/pg-esm-test/pg-cursor.test.js
@@ -0,0 +1,9 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import Cursor from 'pg-cursor'
+
+describe('pg-cursor', () => {
+ it('should export Cursor constructor as default', () => {
+ assert.ok(new Cursor())
+ })
+})
diff --git a/packages/pg-esm-test/pg-native.test.js b/packages/pg-esm-test/pg-native.test.js
new file mode 100644
index 000000000..269460544
--- /dev/null
+++ b/packages/pg-esm-test/pg-native.test.js
@@ -0,0 +1,9 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import Client from 'pg-native'
+
+describe('pg-native', () => {
+ it('should export Client constructor', () => {
+ assert.ok(new Client())
+ })
+})
diff --git a/packages/pg-esm-test/pg-pool.test.js b/packages/pg-esm-test/pg-pool.test.js
new file mode 100644
index 000000000..7eb52afcb
--- /dev/null
+++ b/packages/pg-esm-test/pg-pool.test.js
@@ -0,0 +1,9 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import Pool from 'pg-pool'
+
+describe('pg-pool', () => {
+ it('should export Pool constructor', () => {
+ assert.ok(new Pool())
+ })
+})
diff --git a/packages/pg-esm-test/pg-protocol.test.js b/packages/pg-esm-test/pg-protocol.test.js
new file mode 100644
index 000000000..43df99de2
--- /dev/null
+++ b/packages/pg-esm-test/pg-protocol.test.js
@@ -0,0 +1,18 @@
+import protocol, { NoticeMessage, DatabaseError } from 'pg-protocol/dist/messages.js'
+import { describe, it } from 'node:test'
+import { strict as assert } from 'assert'
+
+describe('pg-protocol', () => {
+ it('should export database error', () => {
+ assert.ok(DatabaseError)
+ })
+ it('should export protocol', () => {
+ assert.ok(protocol)
+ assert.ok(protocol.noData)
+ assert.ok(protocol.parseComplete)
+ assert.ok(protocol.NoticeMessage)
+ })
+ it('should export NoticeMessage from file in dist folder', () => {
+ assert.ok(NoticeMessage)
+ })
+})
diff --git a/packages/pg-esm-test/pg-query-stream.test.js b/packages/pg-esm-test/pg-query-stream.test.js
new file mode 100644
index 000000000..07e1c37f0
--- /dev/null
+++ b/packages/pg-esm-test/pg-query-stream.test.js
@@ -0,0 +1,9 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import QueryStream from 'pg-query-stream'
+
+describe('pg-query-stream', () => {
+ it('should export QueryStream constructor as default', () => {
+ assert.ok(new QueryStream())
+ })
+})
diff --git a/packages/pg-esm-test/pg.test.js b/packages/pg-esm-test/pg.test.js
new file mode 100644
index 000000000..59e6182d8
--- /dev/null
+++ b/packages/pg-esm-test/pg.test.js
@@ -0,0 +1,60 @@
+import assert from 'assert'
+import { describe, it } from 'node:test'
+import pg, {
+ Client,
+ Pool,
+ Connection,
+ defaults,
+ types,
+ DatabaseError,
+ escapeIdentifier,
+ escapeLiteral,
+ Result,
+ TypeOverrides,
+} from 'pg'
+
+describe('pg', () => {
+ it('should export Client constructor', () => {
+ assert.ok(new Client())
+ })
+
+ it('should export Pool constructor', () => {
+ assert.ok(new Pool())
+ })
+
+ it('should still provide default export', () => {
+ assert.ok(new pg.Pool())
+ })
+
+ it('should export Connection constructor', () => {
+ assert.ok(new Connection())
+ })
+
+ it('should export defaults', () => {
+ assert.ok(defaults)
+ })
+
+ it('should export types', () => {
+ assert.ok(types)
+ })
+
+ it('should export DatabaseError', () => {
+ assert.ok(DatabaseError)
+ })
+
+ it('should export escapeIdentifier', () => {
+ assert.ok(escapeIdentifier)
+ })
+
+ it('should export escapeLiteral', () => {
+ assert.ok(escapeLiteral)
+ })
+
+ it('should export Result', () => {
+ assert.ok(Result)
+ })
+
+ it('should export TypeOverrides', () => {
+ assert.ok(TypeOverrides)
+ })
+})
diff --git a/packages/pg-native/README.md b/packages/pg-native/README.md
new file mode 100644
index 000000000..c19f300ed
--- /dev/null
+++ b/packages/pg-native/README.md
@@ -0,0 +1,306 @@
+# node-pg-native
+
+[](https://travis-ci.org/brianc/node-pg-native)
+
+High performance native bindings between node.js and PostgreSQL via [libpq](https://github.com/brianc/node-libpq) with a simple API.
+
+## install
+
+You need PostgreSQL client libraries & tools installed. An easy way to check is to type `pg_config`. If `pg_config` is in your path, you should be good to go. If it's not in your path you'll need to consult operating specific instructions on how to go about getting it there.
+
+Some ways I've done it in the past:
+
+- On macOS: `brew install libpq`
+- On Ubuntu/Debian and Debian-based Node images: `apt-get install libpq-dev python3 g++ make`
+- On RHEL/CentOS: `yum install postgresql-devel`
+- On Windows:
+ 1. Install Visual Studio C++ (successfully built with Express 2010). Express is free.
+ 2. Install PostgreSQL (`http://www.postgresql.org/download/windows/`)
+ 3. Add your Postgre Installation's `bin` folder to the system path (i.e. `C:\Program Files\PostgreSQL\9.3\bin`).
+ 4. Make sure that both `libpq.dll` and `pg_config.exe` are in that folder.
+
+Afterwards `pg_config` should be in your path. Then...
+
+```sh
+$ npm i pg-native
+```
+
+## use
+
+### async
+
+```js
+const Client = require('pg-native')
+
+const client = new Client();
+client.connect(function(err) {
+ if(err) throw err
+
+ // text queries
+ client.query('SELECT NOW() AS the_date', function(err, rows) {
+ if(err) throw err
+
+ console.log(rows[0].the_date) // Tue Sep 16 2014 23:42:39 GMT-0400 (EDT)
+
+ // parameterized statements
+ client.query('SELECT $1::text as twitter_handle', ['@briancarlson'], function(err, rows) {
+ if(err) throw err
+
+ console.log(rows[0].twitter_handle) //@briancarlson
+ })
+
+ // prepared statements
+ client.prepare('get_twitter', 'SELECT $1::text as twitter_handle', 1, function(err) {
+ if(err) throw err
+
+ // execute the prepared, named statement
+ client.execute('get_twitter', ['@briancarlson'], function(err, rows) {
+ if(err) throw err
+
+ console.log(rows[0].twitter_handle) //@briancarlson
+
+ // execute the prepared, named statement again
+ client.execute('get_twitter', ['@realcarrotfacts'], function(err, rows) {
+ if(err) throw err
+
+ console.log(rows[0].twitter_handle) // @realcarrotfacts
+
+ client.end(function() {
+ console.log('ended')
+ })
+ })
+ })
+ })
+ })
+})
+
+```
+
+### sync
+
+Because `pg-native` is bound to [libpq](https://github.com/brianc/node-libpq) it is able to provide _sync_ operations for both connecting and queries. This is a bad idea in _non-blocking systems_ like web servers, but is exteremly convienent in scripts and bootstrapping applications - much the same way `fs.readFileSync` comes in handy.
+
+```js
+const Client = require('pg-native')
+
+const client = new Client()
+client.connectSync()
+
+// text queries
+const rows = client.querySync('SELECT NOW() AS the_date')
+console.log(rows[0].the_date) // Tue Sep 16 2014 23:42:39 GMT-0400 (EDT)
+
+// parameterized queries
+const rows = client.querySync('SELECT $1::text as twitter_handle', ['@briancarlson'])
+console.log(rows[0].twitter_handle) // @briancarlson
+
+// prepared statements
+client.prepareSync('get_twitter', 'SELECT $1::text as twitter_handle', 1)
+
+const rows = client.executeSync('get_twitter', ['@briancarlson'])
+console.log(rows[0].twitter_handle) // @briancarlson
+
+const rows = client.executeSync('get_twitter', ['@realcarrotfacts'])
+console.log(rows[0].twitter_handle) // @realcarrotfacts
+```
+
+## api
+
+### constructor
+
+- __`constructor Client()`__
+
+Constructs and returns a new `Client` instance
+
+### async functions
+
+- __`client.connect(, callback:function(err:Error))`__
+
+Connect to a PostgreSQL backend server.
+
+__params__ is _optional_ and is in any format accepted by [libpq](http://www.postgresql.org/docs/9.3/static/libpq-connect.html#LIBPQ-CONNSTRING). The connection string is passed _as is_ to libpq, so any format supported by libpq will be supported here. Likewise, any format _unsupported_ by libpq will not work. If no parameters are supplied libpq will use [environment variables](http://www.postgresql.org/docs/9.3/static/libpq-envars.html) to connect.
+
+Returns an `Error` to the `callback` if the connection was unsuccessful. `callback` is _required_.
+
+##### example
+
+```js
+const client = new Client()
+client.connect(function(err) {
+ if(err) throw err
+
+ console.log('connected!')
+})
+
+const client2 = new Client()
+client2.connect('postgresql://user:password@host:5432/database?param=value', function(err) {
+ if(err) throw err
+
+ console.log('connected with connection string!')
+})
+```
+
+- __`client.query(queryText:string, , callback:Function(err:Error, rows:Object[]))`__
+
+Execute a query with the text of `queryText` and _optional_ parameters specified in the `values` array. All values are passed to the PostgreSQL backend server and executed as a parameterized statement. The callback is _required_ and is called with an `Error` object in the event of a query error, otherwise it is passed an array of result objects. Each element in this array is a dictionary of results with keys for column names and their values as the values for those columns.
+
+##### example
+
+```js
+const client = new Client()
+client.connect(function(err) {
+ if (err) throw err
+
+ client.query('SELECT NOW()', function(err, rows) {
+ if (err) throw err
+
+ console.log(rows) // [{ "now": "Tue Sep 16 2014 23:42:39 GMT-0400 (EDT)" }]
+
+ client.query('SELECT $1::text as name', ['Brian'], function(err, rows) {
+ if (err) throw err
+
+ console.log(rows) // [{ "name": "Brian" }]
+
+ client.end()
+ })
+ })
+})
+```
+
+
+- __`client.prepare(statementName:string, queryText:string, nParams:int, callback:Function(err:Error))`__
+
+Prepares a _named statement_ for later execution. You _must_ supply the name of the statement via `statementName`, the command to prepare via `queryText` and the number of parameters in `queryText` via `nParams`. Calls the callback with an `Error` if there was an error.
+
+##### example
+
+```js
+const client = new Client()
+client.connect(function(err) {
+ if(err) throw err
+
+ client.prepare('prepared_statement', 'SELECT $1::text as name', 1, function(err) {
+ if(err) throw err
+
+ console.log('statement prepared')
+ client.end()
+ })
+
+})
+```
+
+- __`client.execute(statementName:string, , callback:Function(err:err, rows:Object[]))`__
+
+Executes a previously prepared statement on this client with the name of `statementName`, passing it the optional array of query parameters as a `values` array. The `callback` is mandatory and is called with and `Error` if the execution failed, or with the same array of results as would be passed to the callback of a `client.query` result.
+
+##### example
+
+
+```js
+const client = new Client()
+client.connect(function(err) {
+ if(err) throw err
+
+ client.prepare('i_like_beans', 'SELECT $1::text as beans', 1, function(err) {
+ if(err) throw err
+
+ client.execute('i_like_beans', ['Brak'], function(err, rows) {
+ if(err) throw err
+
+ console.log(rows) // [{ "i_like_beans": "Brak" }]
+ client.end()
+ })
+ })
+})
+```
+
+- __`client.end(`__
+
+Ends the connection. Calls the _optional_ callback when the connection is terminated.
+
+##### example
+
+```js
+const client = new Client()
+client.connect(function(err) {
+ if(err) throw err
+ client.end(function() {
+ console.log('client ended') // client ended
+ })
+})
+```
+
+- __`client.cancel(callback:function(err))`__
+
+Cancels the active query on the client. Callback receives an error if there was an error _sending_ the cancel request.
+
+##### example
+```js
+const client = new Client()
+client.connectSync()
+// sleep for 100 seconds
+client.query('select pg_sleep(100)', function(err) {
+ console.log(err) // [Error: ERROR: canceling statement due to user request]
+})
+client.cancel(function(err) {
+ console.log('cancel dispatched')
+})
+
+```
+
+### sync functions
+
+- __`client.connectSync(params:string)`__
+
+Connect to a PostgreSQL backend server. Params is in any format accepted by [libpq](http://www.postgresql.org/docs/9.3/static/libpq-connect.html#LIBPQ-CONNSTRING). Throws an `Error` if the connection was unsuccessful.
+
+- __`client.querySync(queryText:string, ) -> results:Object[]`__
+
+Executes a query with a text of `queryText` and optional parameters as `values`. Uses a parameterized query if `values` are supplied. Throws an `Error` if the query fails, otherwise returns an array of results.
+
+- __`client.prepareSync(statementName:string, queryText:string, nParams:int)`__
+
+Prepares a name statement with name of `statementName` and a query text of `queryText`. You must specify the number of params in the query with the `nParams` argument. Throws an `Error` if the statement is un-preparable, otherwise returns an array of results.
+
+- __`client.executeSync(statementName:string, ) -> results:Object[]`__
+
+Executes a previously prepared statement on this client with the name of `statementName`, passing it the optional array of query parameters as a `values` array. Throws an `Error` if the execution fails, otherwise returns an array of results.
+
+## testing
+
+```sh
+$ npm test
+```
+
+To run the tests you need a PostgreSQL backend reachable by typing `psql` with no connection parameters in your terminal. The tests use [environment variables](http://www.postgresql.org/docs/9.3/static/libpq-envars.html) to connect to the backend.
+
+An example of supplying a specific host the tests:
+
+```sh
+$ PGHOST=blabla.mydatabasehost.com npm test
+```
+
+
+## license
+
+The MIT License (MIT)
+
+Copyright (c) 2014 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-native/bench/index.js b/packages/pg-native/bench/index.js
new file mode 100644
index 000000000..6f6641ccc
--- /dev/null
+++ b/packages/pg-native/bench/index.js
@@ -0,0 +1,52 @@
+const pg = require('pg').native
+const Native = require('../')
+
+const warmup = function (fn, cb) {
+ let count = 0
+ const max = 10
+ const run = function (err) {
+ if (err) return cb(err)
+
+ if (max >= count++) {
+ return fn(run)
+ }
+
+ cb()
+ }
+ run()
+}
+
+const native = Native()
+native.connectSync()
+
+const queryText = 'SELECT generate_series(0, 1000) as X, generate_series(0, 1000) as Y, generate_series(0, 1000) as Z'
+const client = new pg.Client()
+client.connect(function () {
+ const pure = function (cb) {
+ client.query(queryText, function (err) {
+ if (err) throw err
+ cb(err)
+ })
+ }
+ const nativeQuery = function (cb) {
+ native.query(queryText, function (err) {
+ if (err) throw err
+ cb(err)
+ })
+ }
+
+ const run = function () {
+ console.time('pure')
+ warmup(pure, function () {
+ console.timeEnd('pure')
+ console.time('native')
+ warmup(nativeQuery, function () {
+ console.timeEnd('native')
+ })
+ })
+ }
+
+ setInterval(function () {
+ run()
+ }, 500)
+})
diff --git a/packages/pg-native/bench/leaks.js b/packages/pg-native/bench/leaks.js
new file mode 100644
index 000000000..ec801af33
--- /dev/null
+++ b/packages/pg-native/bench/leaks.js
@@ -0,0 +1,50 @@
+const Client = require('../')
+const async = require('async')
+
+const loop = function () {
+ const client = new Client()
+
+ const connect = function (cb) {
+ client.connect(cb)
+ }
+
+ const simpleQuery = function (cb) {
+ client.query('SELECT NOW()', cb)
+ }
+
+ const paramsQuery = function (cb) {
+ client.query('SELECT $1::text as name', ['Brian'], cb)
+ }
+
+ const prepared = function (cb) {
+ client.prepare('test', 'SELECT $1::text as name', 1, function (err) {
+ if (err) return cb(err)
+ client.execute('test', ['Brian'], cb)
+ })
+ }
+
+ const sync = function (cb) {
+ client.querySync('SELECT NOW()')
+ client.querySync('SELECT $1::text as name', ['Brian'])
+ client.prepareSync('boom', 'SELECT $1::text as name', 1)
+ client.executeSync('boom', ['Brian'])
+ setImmediate(cb)
+ }
+
+ const end = function (cb) {
+ client.end(cb)
+ }
+
+ const ops = [connect, simpleQuery, paramsQuery, prepared, sync, end]
+
+ const start = performance.now()
+ async.series(ops, function (err) {
+ if (err) throw err
+ console.log(performance.now() - start)
+ setImmediate(loop)
+ })
+}
+
+// on my machine this will consume memory up to about 50 megs of ram
+// and then stabalize at that point
+loop()
diff --git a/packages/pg-native/esm/index.mjs b/packages/pg-native/esm/index.mjs
new file mode 100644
index 000000000..49363e338
--- /dev/null
+++ b/packages/pg-native/esm/index.mjs
@@ -0,0 +1,5 @@
+// ESM wrapper for pg-native
+import Client from '../index.js'
+
+// Export as default only to match CJS module
+export default Client
diff --git a/packages/pg-native/index.js b/packages/pg-native/index.js
new file mode 100644
index 000000000..1c18241db
--- /dev/null
+++ b/packages/pg-native/index.js
@@ -0,0 +1,339 @@
+const Libpq = require('libpq')
+const EventEmitter = require('events').EventEmitter
+const util = require('util')
+const assert = require('assert')
+const types = require('pg-types')
+const buildResult = require('./lib/build-result')
+const CopyStream = require('./lib/copy-stream')
+
+// https://www.postgresql.org/docs/current/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
+// 0=IDLE, 1=ACTIVE, 2=INTRANS, 3=INERROR
+const statusMap = { 0: 'I', 2: 'T', 3: 'E' }
+
+const Client = (module.exports = function (config) {
+ if (!(this instanceof Client)) {
+ return new Client(config)
+ }
+
+ config = config || {}
+
+ EventEmitter.call(this)
+ this.pq = new Libpq()
+ this._reading = false
+ this._read = this._read.bind(this)
+
+ // allow custom type conversion to be passed in
+ this._types = config.types || types
+
+ // allow config to specify returning results
+ // as an array of values instead of a hash
+ this.arrayMode = config.arrayMode || false
+ this._resultCount = 0
+ this._rows = undefined
+ this._results = undefined
+
+ // lazy start the reader if notifications are listened for
+ // this way if you only run sync queries you wont block
+ // the event loop artificially
+ this.on('newListener', (event) => {
+ if (event !== 'notification') return
+ this._startReading()
+ })
+
+ this.on('result', this._onResult.bind(this))
+ this.on('readyForQuery', this._onReadyForQuery.bind(this))
+})
+
+util.inherits(Client, EventEmitter)
+
+Client.prototype.connect = function (params, cb) {
+ this.pq.connect(params, cb)
+}
+
+Client.prototype.connectSync = function (params) {
+ this.pq.connectSync(params)
+}
+
+Client.prototype.query = function (text, values, cb) {
+ let queryFn
+
+ if (typeof values === 'function') {
+ cb = values
+ }
+
+ if (Array.isArray(values)) {
+ queryFn = () => {
+ return this.pq.sendQueryParams(text, values)
+ }
+ } else {
+ queryFn = () => {
+ return this.pq.sendQuery(text)
+ }
+ }
+
+ this._dispatchQuery(this.pq, queryFn, (err) => {
+ if (err) return cb(err)
+ this._awaitResult(cb)
+ })
+}
+
+Client.prototype.prepare = function (statementName, text, nParams, cb) {
+ const self = this
+ const fn = function () {
+ return self.pq.sendPrepare(statementName, text, nParams)
+ }
+
+ self._dispatchQuery(self.pq, fn, function (err) {
+ if (err) return cb(err)
+ self._awaitResult(cb)
+ })
+}
+
+Client.prototype.execute = function (statementName, parameters, cb) {
+ const self = this
+
+ const fn = function () {
+ return self.pq.sendQueryPrepared(statementName, parameters)
+ }
+
+ self._dispatchQuery(self.pq, fn, function (err, rows) {
+ if (err) return cb(err)
+ self._awaitResult(cb)
+ })
+}
+
+Client.prototype.getCopyStream = function () {
+ this.pq.setNonBlocking(true)
+ this._stopReading()
+ return new CopyStream(this.pq)
+}
+
+// cancel a currently executing query
+Client.prototype.cancel = function (cb) {
+ assert(cb, 'Callback is required')
+ // result is either true or a string containing an error
+ const result = this.pq.cancel()
+ return setImmediate(function () {
+ cb(result === true ? undefined : new Error(result))
+ })
+}
+
+Client.prototype.querySync = function (text, values) {
+ if (values) {
+ this.pq.execParams(text, values)
+ } else {
+ this.pq.exec(text)
+ }
+
+ throwIfError(this.pq)
+ const result = buildResult(this.pq, this._types, this.arrayMode)
+ return result.rows
+}
+
+Client.prototype.prepareSync = function (statementName, text, nParams) {
+ this.pq.prepare(statementName, text, nParams)
+ throwIfError(this.pq)
+}
+
+Client.prototype.executeSync = function (statementName, parameters) {
+ this.pq.execPrepared(statementName, parameters)
+ throwIfError(this.pq)
+ return buildResult(this.pq, this._types, this.arrayMode).rows
+}
+
+Client.prototype.escapeLiteral = function (value) {
+ return this.pq.escapeLiteral(value)
+}
+
+Client.prototype.escapeIdentifier = function (value) {
+ return this.pq.escapeIdentifier(value)
+}
+
+Client.prototype.getTransactionStatus = function () {
+ return statusMap[this.pq.transactionStatus()] ?? null
+}
+
+// export the version number so we can check it in node-postgres
+module.exports.version = require('./package.json').version
+
+Client.prototype.end = function (cb) {
+ this._stopReading()
+ this.pq.finish()
+ if (cb) setImmediate(cb)
+}
+
+Client.prototype._readError = function (message) {
+ const err = new Error(message || this.pq.errorMessage())
+ this.emit('error', err)
+}
+
+Client.prototype._stopReading = function () {
+ if (!this._reading) return
+ this._reading = false
+ this.pq.stopReader()
+ this.pq.removeListener('readable', this._read)
+}
+
+Client.prototype._consumeQueryResults = function (pq) {
+ return buildResult(pq, this._types, this.arrayMode)
+}
+
+Client.prototype._emitResult = function (pq) {
+ const status = pq.resultStatus()
+ switch (status) {
+ case 'PGRES_FATAL_ERROR':
+ this._queryError = new Error(this.pq.resultErrorMessage())
+ break
+
+ case 'PGRES_TUPLES_OK':
+ case 'PGRES_COMMAND_OK':
+ case 'PGRES_EMPTY_QUERY':
+ {
+ const result = this._consumeQueryResults(this.pq)
+ this.emit('result', result)
+ }
+ break
+
+ case 'PGRES_COPY_OUT':
+ case 'PGRES_COPY_BOTH': {
+ break
+ }
+
+ default:
+ this._readError('unrecognized command status: ' + status)
+ break
+ }
+ return status
+}
+
+// called when libpq is readable
+Client.prototype._read = function () {
+ const pq = this.pq
+ // read waiting data from the socket
+ // e.g. clear the pending 'select'
+ if (!pq.consumeInput()) {
+ // if consumeInput returns false
+ // than a read error has been encountered
+ return this._readError()
+ }
+
+ // check if there is still outstanding data
+ // if so, wait for it all to come in
+ if (pq.isBusy()) {
+ return
+ }
+
+ // load our result object
+
+ while (pq.getResult()) {
+ const resultStatus = this._emitResult(this.pq)
+
+ // if the command initiated copy mode we need to break out of the read loop
+ // so a substream can begin to read copy data
+ if (resultStatus === 'PGRES_COPY_BOTH' || resultStatus === 'PGRES_COPY_OUT') {
+ break
+ }
+
+ // if reading multiple results, sometimes the following results might cause
+ // a blocking read. in this scenario yield back off the reader until libpq is readable
+ if (pq.isBusy()) {
+ return
+ }
+ }
+
+ this.emit('readyForQuery')
+
+ let notice = this.pq.notifies()
+ while (notice) {
+ this.emit('notification', notice)
+ notice = this.pq.notifies()
+ }
+}
+
+// ensures the client is reading and
+// everything is set up for async io
+Client.prototype._startReading = function () {
+ if (this._reading) return
+ this._reading = true
+ this.pq.on('readable', this._read)
+ this.pq.startReader()
+}
+
+const throwIfError = function (pq) {
+ const err = pq.resultErrorMessage() || pq.errorMessage()
+ if (err) {
+ throw new Error(err)
+ }
+}
+
+Client.prototype._awaitResult = function (cb) {
+ this._queryCallback = cb
+ return this._startReading()
+}
+
+// wait for the writable socket to drain
+Client.prototype._waitForDrain = function (pq, cb) {
+ const res = pq.flush()
+ // res of 0 is success
+ if (res === 0) return cb()
+
+ // res of -1 is failure
+ if (res === -1) return cb(pq.errorMessage())
+
+ // otherwise outgoing message didn't flush to socket
+ // wait for it to flush and try again
+ const self = this
+ // you cannot read & write on a socket at the same time
+ return pq.writable(function () {
+ self._waitForDrain(pq, cb)
+ })
+}
+
+// send an async query to libpq and wait for it to
+// finish writing query text to the socket
+Client.prototype._dispatchQuery = function (pq, fn, cb) {
+ this._stopReading()
+ const success = pq.setNonBlocking(true)
+ if (!success) return cb(new Error('Unable to set non-blocking to true'))
+ const sent = fn()
+ if (!sent) return cb(new Error(pq.errorMessage() || 'Something went wrong dispatching the query'))
+ this._waitForDrain(pq, cb)
+}
+
+Client.prototype._onResult = function (result) {
+ if (this._resultCount === 0) {
+ this._results = result
+ this._rows = result.rows
+ } else if (this._resultCount === 1) {
+ this._results = [this._results, result]
+ this._rows = [this._rows, result.rows]
+ } else {
+ this._results.push(result)
+ this._rows.push(result.rows)
+ }
+ this._resultCount++
+}
+
+Client.prototype._onReadyForQuery = function () {
+ // remove instance callback
+ const cb = this._queryCallback
+ this._queryCallback = undefined
+
+ // remove instance query error
+ const err = this._queryError
+ this._queryError = undefined
+
+ // remove instance rows
+ const rows = this._rows
+ this._rows = undefined
+
+ // remove instance results
+ const results = this._results
+ this._results = undefined
+
+ this._resultCount = 0
+
+ if (cb) {
+ cb(err, rows || [], results)
+ }
+}
diff --git a/packages/pg-native/lib/build-result.js b/packages/pg-native/lib/build-result.js
new file mode 100644
index 000000000..9117a11ef
--- /dev/null
+++ b/packages/pg-native/lib/build-result.js
@@ -0,0 +1,81 @@
+'use strict'
+
+class Result {
+ constructor(types, arrayMode) {
+ this._types = types
+ this._arrayMode = arrayMode
+
+ this.command = undefined
+ this.rowCount = undefined
+ this.fields = []
+ this.rows = []
+ this._prebuiltEmptyResultObject = null
+ this._parsers = []
+ }
+
+ consumeCommand(pq) {
+ this.command = pq.cmdStatus().split(' ')[0]
+ this.rowCount = parseInt(pq.cmdTuples(), 10)
+ }
+
+ consumeFields(pq) {
+ const nfields = pq.nfields()
+ this.fields = new Array(nfields)
+ const row = {}
+ this._parsers = new Array(nfields)
+ for (let x = 0; x < nfields; x++) {
+ const name = pq.fname(x)
+ row[name] = null
+ const typeId = pq.ftype(x)
+ this.fields[x] = {
+ name: name,
+ dataTypeID: typeId,
+ }
+ this._parsers[x] = this._types.getTypeParser(typeId)
+ }
+ this._prebuiltEmptyResultObject = { ...row }
+ }
+
+ consumeRows(pq) {
+ const tupleCount = pq.ntuples()
+ this.rows = new Array(tupleCount)
+ for (let i = 0; i < tupleCount; i++) {
+ this.rows[i] = this._arrayMode ? this.consumeRowAsArray(pq, i) : this.consumeRowAsObject(pq, i)
+ }
+ }
+
+ consumeRowAsObject(pq, rowIndex) {
+ const row = { ...this._prebuiltEmptyResultObject }
+ for (let j = 0; j < this.fields.length; j++) {
+ row[this.fields[j].name] = this.readValue(pq, rowIndex, j)
+ }
+ return row
+ }
+
+ consumeRowAsArray(pq, rowIndex) {
+ const row = new Array(this.fields.length)
+ for (let j = 0; j < this.fields.length; j++) {
+ row[j] = this.readValue(pq, rowIndex, j)
+ }
+ return row
+ }
+
+ readValue(pq, rowIndex, colIndex) {
+ const rawValue = pq.getvalue(rowIndex, colIndex)
+ if (rawValue === '' && pq.getisnull(rowIndex, colIndex)) {
+ return null
+ }
+ return this._parsers[colIndex](rawValue)
+ }
+}
+
+function buildResult(pq, types, arrayMode) {
+ const result = new Result(types, arrayMode)
+ result.consumeCommand(pq)
+ result.consumeFields(pq)
+ result.consumeRows(pq)
+
+ return result
+}
+
+module.exports = buildResult
diff --git a/packages/pg-native/lib/copy-stream.js b/packages/pg-native/lib/copy-stream.js
new file mode 100644
index 000000000..94ae4f7e5
--- /dev/null
+++ b/packages/pg-native/lib/copy-stream.js
@@ -0,0 +1,155 @@
+const Duplex = require('stream').Duplex
+const Writable = require('stream').Writable
+const util = require('util')
+
+const CopyStream = (module.exports = function (pq, options) {
+ Duplex.call(this, options)
+ this.pq = pq
+ this._reading = false
+})
+
+util.inherits(CopyStream, Duplex)
+
+// writer methods
+CopyStream.prototype._write = function (chunk, encoding, cb) {
+ const result = this.pq.putCopyData(chunk)
+
+ // sent successfully
+ if (result === 1) return cb()
+
+ // error
+ if (result === -1) return cb(new Error(this.pq.errorMessage()))
+
+ // command would block. wait for writable and call again.
+ const self = this
+ this.pq.writable(function () {
+ self._write(chunk, encoding, cb)
+ })
+}
+
+CopyStream.prototype.end = function () {
+ const args = Array.prototype.slice.call(arguments, 0)
+ const self = this
+
+ const callback = args.pop()
+
+ if (args.length) {
+ this.write(args[0])
+ }
+ const result = this.pq.putCopyEnd()
+
+ // sent successfully
+ if (result === 1) {
+ // consume our results and then call 'end' on the
+ // "parent" writable class so we can emit 'finish' and
+ // all that jazz
+ return consumeResults(this.pq, function (err, res) {
+ Writable.prototype.end.call(self)
+
+ // handle possible passing of callback to end method
+ if (callback) {
+ callback(err)
+ }
+ })
+ }
+
+ // error
+ if (result === -1) {
+ const err = new Error(this.pq.errorMessage())
+ return this.emit('error', err)
+ }
+
+ // command would block. wait for writable and call end again
+ // don't pass any buffers to end on the second call because
+ // we already sent them to possible this.write the first time
+ // we called end
+ return this.pq.writable(function () {
+ return self.end.apply(self, callback)
+ })
+}
+
+// reader methods
+CopyStream.prototype._consumeBuffer = function (cb) {
+ const result = this.pq.getCopyData(true)
+ if (result instanceof Buffer) {
+ return setImmediate(function () {
+ cb(null, result)
+ })
+ }
+ if (result === -1) {
+ // end of stream
+ return cb(null, null)
+ }
+ if (result === 0) {
+ const self = this
+ this.pq.once('readable', function () {
+ self.pq.stopReader()
+ self.pq.consumeInput()
+ self._consumeBuffer(cb)
+ })
+ return this.pq.startReader()
+ }
+ cb(new Error('Unrecognized read status: ' + result))
+}
+
+CopyStream.prototype._read = function (size) {
+ if (this._reading) return
+ this._reading = true
+ // console.log('read begin');
+ const self = this
+ this._consumeBuffer(function (err, buffer) {
+ self._reading = false
+ if (err) {
+ return self.emit('error', err)
+ }
+ if (buffer === false) {
+ // nothing to read for now, return
+ return
+ }
+ self.push(buffer)
+ })
+}
+
+const consumeResults = function (pq, cb) {
+ const cleanup = function () {
+ pq.removeListener('readable', onReadable)
+ pq.stopReader()
+ }
+
+ const readError = function (message) {
+ cleanup()
+ return cb(new Error(message || pq.errorMessage()))
+ }
+
+ const onReadable = function () {
+ // read waiting data from the socket
+ // e.g. clear the pending 'select'
+ if (!pq.consumeInput()) {
+ return readError()
+ }
+
+ // check if there is still outstanding data
+ // if so, wait for it all to come in
+ if (pq.isBusy()) {
+ return
+ }
+
+ // load our result object
+ pq.getResult()
+
+ // "read until results return null"
+ // or in our case ensure we only have one result
+ if (pq.getResult() && pq.resultStatus() !== 'PGRES_COPY_OUT') {
+ return readError('Only one result at a time is accepted')
+ }
+
+ if (pq.resultStatus() === 'PGRES_FATAL_ERROR') {
+ return readError()
+ }
+
+ cleanup()
+ return cb(null)
+ }
+ pq.on('readable', onReadable)
+ pq.startReader()
+}
diff --git a/packages/pg-native/package.json b/packages/pg-native/package.json
new file mode 100644
index 000000000..513f3bb3f
--- /dev/null
+++ b/packages/pg-native/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "pg-native",
+ "version": "3.8.0",
+ "description": "A slightly nicer interface to Postgres over node-libpq",
+ "main": "index.js",
+ "exports": {
+ ".": {
+ "import": "./esm/index.mjs",
+ "require": "./index.js",
+ "default": "./index.js"
+ },
+ "./lib/*": {
+ "import": "./lib/*",
+ "require": "./lib/*",
+ "default": "./lib/*"
+ }
+ },
+ "scripts": {
+ "test": "mocha"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/brianc/node-postgres.git"
+ },
+ "keywords": [
+ "postgres",
+ "pg",
+ "libpq"
+ ],
+ "author": "Brian M. Carlson",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/brianc/node-postgres/issues"
+ },
+ "homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-native",
+ "dependencies": {
+ "libpq": "^1.8.15",
+ "pg-types": "2.2.0"
+ },
+ "devDependencies": {
+ "async": "^0.9.0",
+ "concat-stream": "^1.4.6",
+ "generic-pool": "^2.1.1",
+ "lodash": "^4.17.21",
+ "mocha": "11.7.5",
+ "node-gyp": ">=10.x",
+ "okay": "^0.3.0",
+ "semver": "^7.7.2"
+ },
+ "files": [
+ "index.js",
+ "lib",
+ "esm"
+ ]
+}
diff --git a/packages/pg-native/test/array-mode.js b/packages/pg-native/test/array-mode.js
new file mode 100644
index 000000000..0e6978a0a
--- /dev/null
+++ b/packages/pg-native/test/array-mode.js
@@ -0,0 +1,25 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('client with arrayMode', function () {
+ it('returns result as array', function (done) {
+ const client = new Client({ arrayMode: true })
+ client.connectSync()
+ client.querySync('CREATE TEMP TABLE blah(name TEXT)')
+ client.querySync('INSERT INTO blah (name) VALUES ($1)', ['brian'])
+ client.querySync('INSERT INTO blah (name) VALUES ($1)', ['aaron'])
+ const rows = client.querySync('SELECT * FROM blah')
+ assert.equal(rows.length, 2)
+ const row = rows[0]
+ assert.equal(row.length, 1)
+ assert.equal(row[0], 'brian')
+ assert.equal(rows[1][0], 'aaron')
+
+ client.query("SELECT 'brian', null", function (err, res) {
+ assert.ifError(err)
+ assert.strictEqual(res[0][0], 'brian')
+ assert.strictEqual(res[0][1], null)
+ client.end(done)
+ })
+ })
+})
diff --git a/packages/pg-native/test/async-workflow.js b/packages/pg-native/test/async-workflow.js
new file mode 100644
index 000000000..e86774b87
--- /dev/null
+++ b/packages/pg-native/test/async-workflow.js
@@ -0,0 +1,80 @@
+const Client = require('../')
+const ok = require('okay')
+const assert = require('assert')
+const concat = require('concat-stream')
+
+describe('async workflow', function () {
+ before(function (done) {
+ this.client = new Client()
+ this.client.connect(done)
+ })
+
+ const echoParams = function (params, cb) {
+ this.client.query(
+ 'SELECT $1::text as first, $2::text as second',
+ params,
+ ok(cb, function (rows) {
+ checkParams(params, rows)
+ cb(null, rows)
+ })
+ )
+ }
+
+ const checkParams = function (params, rows) {
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].first, params[0])
+ assert.equal(rows[0].second, params[1])
+ }
+
+ it('sends async query', function (done) {
+ const params = ['one', 'two']
+ echoParams.call(this, params, done)
+ })
+
+ it('sends multiple async queries', function (done) {
+ const self = this
+ const params = ['bang', 'boom']
+ echoParams.call(
+ this,
+ params,
+ ok(done, function (rows) {
+ echoParams.call(self, params, done)
+ })
+ )
+ })
+
+ it('sends an async query, copies in, copies out, and sends another query', function (done) {
+ const self = this
+ this.client.querySync('CREATE TEMP TABLE test(name text, age int)')
+ this.client.query(
+ "INSERT INTO test(name, age) VALUES('brian', 32)",
+ ok(done, function () {
+ self.client.querySync('COPY test FROM stdin')
+ const input = self.client.getCopyStream()
+ input.write(Buffer.from('Aaron\t30\n', 'utf8'))
+ input.end(function () {
+ self.client.query(
+ 'SELECT COUNT(*) FROM test',
+ ok(done, function (rows) {
+ assert.equal(rows.length, 1)
+ self.client.query(
+ 'COPY test TO stdout',
+ ok(done, function () {
+ const output = self.client.getCopyStream()
+
+ // pump the stream
+ output.read()
+ output.pipe(
+ concat(function (res) {
+ done()
+ })
+ )
+ })
+ )
+ })
+ )
+ })
+ })
+ )
+ })
+})
diff --git a/packages/pg-native/test/cancel.js b/packages/pg-native/test/cancel.js
new file mode 100644
index 000000000..8360c37f3
--- /dev/null
+++ b/packages/pg-native/test/cancel.js
@@ -0,0 +1,34 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('cancel query', function () {
+ it('works', function (done) {
+ const client = new Client()
+ client.connectSync()
+ client.query('SELECT pg_sleep(1000);', function (err) {
+ assert(err instanceof Error)
+ client.end(done)
+ })
+ setTimeout(() => {
+ client.cancel(function (err) {
+ assert.ifError(err)
+ })
+ }, 100)
+ })
+
+ it('does not raise error if no active query', function (done) {
+ const client = new Client()
+ client.connectSync()
+ client.cancel(function (err) {
+ assert.ifError(err)
+ done()
+ })
+ })
+
+ it('raises error if client is not connected', function (done) {
+ new Client().cancel(function (err) {
+ assert(err, 'should raise an error when not connected')
+ done()
+ })
+ })
+})
diff --git a/packages/pg-native/test/connection-errors.js b/packages/pg-native/test/connection-errors.js
new file mode 100644
index 000000000..ed836da2d
--- /dev/null
+++ b/packages/pg-native/test/connection-errors.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const Client = require('../')
+const assert = require('assert')
+
+describe('connection errors', function () {
+ it('raise error events', function (done) {
+ const client = new Client()
+ client.connectSync()
+ client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.fail)
+ client.on('error', function (err) {
+ assert(err)
+ assert.strictEqual(client.pq.resultErrorFields().sqlState, '57P01')
+ client.end()
+ done()
+ })
+ })
+})
diff --git a/packages/pg-native/test/connection.js b/packages/pg-native/test/connection.js
new file mode 100644
index 000000000..0c7059ea4
--- /dev/null
+++ b/packages/pg-native/test/connection.js
@@ -0,0 +1,23 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('connection error', function () {
+ it('doesnt segfault', function (done) {
+ const client = new Client()
+ client.connect('asldgsdgasgdasdg', function (err) {
+ assert(err)
+ // calling error on a closed client was segfaulting
+ client.end()
+ done()
+ })
+ })
+})
+
+describe('reading while not connected', function () {
+ it('does not seg fault but does throw execption', function () {
+ const client = new Client()
+ assert.throws(function () {
+ client.on('notification', function (msg) {})
+ })
+ })
+})
diff --git a/packages/pg-native/test/copy-from.js b/packages/pg-native/test/copy-from.js
new file mode 100644
index 000000000..37c64b7cd
--- /dev/null
+++ b/packages/pg-native/test/copy-from.js
@@ -0,0 +1,47 @@
+const assert = require('assert')
+const Client = require('../')
+
+describe('COPY FROM', function () {
+ before(function (done) {
+ this.client = Client()
+ this.client.connect(done)
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+
+ it('works', function (done) {
+ const client = this.client
+ this.client.querySync('CREATE TEMP TABLE blah(name text, age int)')
+ this.client.querySync('COPY blah FROM stdin')
+ const stream = this.client.getCopyStream()
+ stream.write(Buffer.from('Brian\t32\n', 'utf8'))
+ stream.write(Buffer.from('Aaron\t30\n', 'utf8'))
+ stream.write(Buffer.from('Shelley\t28\n', 'utf8'))
+ stream.end()
+
+ stream.once('finish', function () {
+ const rows = client.querySync('SELECT COUNT(*) FROM blah')
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].count, 3)
+ done()
+ })
+ })
+
+ it('works with a callback passed to end', function (done) {
+ const client = this.client
+ this.client.querySync('CREATE TEMP TABLE boom(name text, age int)')
+ this.client.querySync('COPY boom FROM stdin')
+ const stream = this.client.getCopyStream()
+ stream.write(Buffer.from('Brian\t32\n', 'utf8'))
+ stream.write(Buffer.from('Aaron\t30\n', 'utf8'), function () {
+ stream.end(Buffer.from('Shelley\t28\n', 'utf8'), function () {
+ const rows = client.querySync('SELECT COUNT(*) FROM boom')
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].count, 3)
+ done()
+ })
+ })
+ })
+})
diff --git a/packages/pg-native/test/copy-to.js b/packages/pg-native/test/copy-to.js
new file mode 100644
index 000000000..7322735db
--- /dev/null
+++ b/packages/pg-native/test/copy-to.js
@@ -0,0 +1,35 @@
+const assert = require('assert')
+const Client = require('../')
+const concat = require('concat-stream')
+const _ = require('lodash')
+
+describe('COPY TO', function () {
+ before(function (done) {
+ this.client = Client()
+ this.client.connect(done)
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+
+ it('works - basic check', function (done) {
+ const limit = 1000
+ const qText = 'COPY (SELECT * FROM generate_series(0, ' + (limit - 1) + ')) TO stdout'
+ const self = this
+ this.client.query(qText, function (err) {
+ if (err) return done(err)
+ const stream = self.client.getCopyStream()
+ // pump the stream for node v0.11.x
+ stream.read()
+ stream.pipe(
+ concat(function (buff) {
+ const res = buff.toString('utf8')
+ const expected = _.range(0, limit).join('\n') + '\n'
+ assert.equal(res, expected)
+ done()
+ })
+ )
+ })
+ })
+})
diff --git a/packages/pg-native/test/custom-types.js b/packages/pg-native/test/custom-types.js
new file mode 100644
index 000000000..329ff63af
--- /dev/null
+++ b/packages/pg-native/test/custom-types.js
@@ -0,0 +1,27 @@
+const Client = require('../')
+const ok = require('okay')
+const assert = require('assert')
+
+describe('Custom type parser', function () {
+ it('is used by client', function (done) {
+ const client = new Client({
+ types: {
+ getTypeParser: function () {
+ return function () {
+ return 'blah'
+ }
+ },
+ },
+ })
+ client.connectSync()
+ const rows = client.querySync('SELECT NOW() AS when')
+ assert.equal(rows[0].when, 'blah')
+ client.query(
+ 'SELECT NOW() as when',
+ ok(function (rows) {
+ assert.equal(rows[0].when, 'blah')
+ client.end(done)
+ })
+ )
+ })
+})
diff --git a/packages/pg-native/test/domains.js b/packages/pg-native/test/domains.js
new file mode 100644
index 000000000..f1fb98363
--- /dev/null
+++ b/packages/pg-native/test/domains.js
@@ -0,0 +1,32 @@
+const Client = require('../')
+const assert = require('assert')
+
+const checkDomain = function (domain, when) {
+ assert(process.domain, 'Domain was lost after ' + when)
+ assert.strictEqual(process.domain, domain, 'Domain switched after ' + when)
+}
+
+describe('domains', function () {
+ it('remains bound after a query', function (done) {
+ const domain = require('domain').create()
+ domain.run(function () {
+ const client = new Client()
+ client.connect(function () {
+ checkDomain(domain, 'connection')
+ client.query('SELECT NOW()', function () {
+ checkDomain(domain, 'query')
+ client.prepare('testing', 'SELECT NOW()', 0, function () {
+ checkDomain(domain, 'prepare')
+ client.execute('testing', [], function () {
+ checkDomain(domain, 'execute')
+ client.end(function () {
+ checkDomain(domain, 'end')
+ done()
+ })
+ })
+ })
+ })
+ })
+ })
+ })
+})
diff --git a/packages/pg-native/test/empty-query.js b/packages/pg-native/test/empty-query.js
new file mode 100644
index 000000000..aa3f05a0d
--- /dev/null
+++ b/packages/pg-native/test/empty-query.js
@@ -0,0 +1,16 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('empty query', () => {
+ it('has field metadata in result', (done) => {
+ const client = new Client()
+ client.connectSync()
+ client.query('SELECT NOW() as now LIMIT 0', (err, rows, res) => {
+ assert(!err)
+ assert.equal(rows.length, 0)
+ assert(Array.isArray(res.fields))
+ assert.equal(res.fields.length, 1)
+ client.end(done)
+ })
+ })
+})
diff --git a/packages/pg-native/test/huge-query.js b/packages/pg-native/test/huge-query.js
new file mode 100644
index 000000000..838a23823
--- /dev/null
+++ b/packages/pg-native/test/huge-query.js
@@ -0,0 +1,27 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('huge async query', function () {
+ before(function (done) {
+ this.client = Client()
+ this.client.connect(done)
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+
+ it('works', function (done) {
+ const params = ['']
+ const len = 100000
+ for (let i = 0; i < len; i++) {
+ params[0] += 'A'
+ }
+ const qText = "SELECT '" + params[0] + "'::text as my_text"
+ this.client.query(qText, function (err, rows) {
+ if (err) return done(err)
+ assert.equal(rows[0].my_text.length, len)
+ done()
+ })
+ })
+})
diff --git a/packages/pg-native/test/index.js b/packages/pg-native/test/index.js
new file mode 100644
index 000000000..905225b1b
--- /dev/null
+++ b/packages/pg-native/test/index.js
@@ -0,0 +1,36 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('connection', function () {
+ it('works', function (done) {
+ Client().connect(done)
+ })
+
+ it('connects with args', function (done) {
+ Client().connect(`host=${process.env.PGHOST || 'localhost'}`, done)
+ })
+
+ it('errors out with bad connection args', function (done) {
+ Client().connect('host=asldkfjasdf', function (err) {
+ assert(err, 'should raise an error for bad host')
+ done()
+ })
+ })
+})
+
+describe('connectSync', function () {
+ it('works without args', function () {
+ Client().connectSync()
+ })
+
+ it('works with args', function () {
+ const args = 'host=' + (process.env.PGHOST || 'localhost')
+ Client().connectSync(args)
+ })
+
+ it('throws if bad host', function () {
+ assert.throws(function () {
+ Client().connectSync('host=laksdjfdsf')
+ })
+ })
+})
diff --git a/packages/pg-native/test/load.js b/packages/pg-native/test/load.js
new file mode 100644
index 000000000..886be5d24
--- /dev/null
+++ b/packages/pg-native/test/load.js
@@ -0,0 +1,30 @@
+const Client = require('../')
+const async = require('async')
+const ok = require('okay')
+
+const execute = function (x, done) {
+ const client = new Client()
+ client.connectSync()
+ const query = function (n, cb) {
+ client.query('SELECT $1::int as num', [n], function (err) {
+ cb(err)
+ })
+ }
+ return async.timesSeries(
+ 5,
+ query,
+ ok(done, function () {
+ client.end()
+ done()
+ })
+ )
+}
+describe('Load tests', function () {
+ it('single client and many queries', function (done) {
+ async.times(1, execute, done)
+ })
+
+ it('multiple client and many queries', function (done) {
+ async.times(20, execute, done)
+ })
+})
diff --git a/packages/pg-native/test/many-connections.js b/packages/pg-native/test/many-connections.js
new file mode 100644
index 000000000..b1ed9fd47
--- /dev/null
+++ b/packages/pg-native/test/many-connections.js
@@ -0,0 +1,46 @@
+const Client = require('../')
+const async = require('async')
+const ok = require('okay')
+const bytes = require('crypto').pseudoRandomBytes
+
+describe('many connections', function () {
+ describe('async', function () {
+ const test = function (count, times) {
+ it(`connecting ${count} clients ${times} times`, function (done) {
+ this.timeout(200000)
+
+ const connectClient = function (n, cb) {
+ const client = new Client()
+ client.connect(
+ ok(cb, function () {
+ bytes(
+ 1000,
+ ok(cb, function (chunk) {
+ client.query(
+ 'SELECT $1::text as txt',
+ [chunk.toString('base64')],
+ ok(cb, function (rows) {
+ client.end(cb)
+ })
+ )
+ })
+ )
+ })
+ )
+ }
+
+ const run = function (n, cb) {
+ async.times(count, connectClient, cb)
+ }
+
+ async.timesSeries(times, run, done)
+ })
+ }
+
+ test(1, 1)
+ test(5, 5)
+ test(10, 10)
+ test(20, 20)
+ test(30, 10)
+ })
+})
diff --git a/packages/pg-native/test/many-errors.js b/packages/pg-native/test/many-errors.js
new file mode 100644
index 000000000..243e6d491
--- /dev/null
+++ b/packages/pg-native/test/many-errors.js
@@ -0,0 +1,26 @@
+const Client = require('../')
+const async = require('async')
+const assert = require('assert')
+
+describe('many errors', function () {
+ it('functions properly without segfault', function (done) {
+ const throwError = function (n, cb) {
+ const client = new Client()
+ client.connectSync()
+
+ const doIt = function (n, cb) {
+ client.query('select asdfiasdf', function (err) {
+ assert(err, 'bad query should emit an error')
+ cb(null)
+ })
+ }
+
+ async.timesSeries(10, doIt, function (err) {
+ if (err) return cb(err)
+ client.end(cb)
+ })
+ }
+
+ async.times(10, throwError, done)
+ })
+})
diff --git a/packages/pg-native/test/mocha.opts b/packages/pg-native/test/mocha.opts
new file mode 100644
index 000000000..25fe946ae
--- /dev/null
+++ b/packages/pg-native/test/mocha.opts
@@ -0,0 +1,2 @@
+--bail
+--no-exit
diff --git a/packages/pg-native/test/multiple-queries.js b/packages/pg-native/test/multiple-queries.js
new file mode 100644
index 000000000..438215ff3
--- /dev/null
+++ b/packages/pg-native/test/multiple-queries.js
@@ -0,0 +1,41 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('multiple commands in a single query', function () {
+ before(function (done) {
+ this.client = new Client()
+ this.client.connect(done)
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+
+ it('all execute to completion', function (done) {
+ this.client.query("SELECT '10'::int as num; SELECT 'brian'::text as name", function (err, rows) {
+ assert.ifError(err)
+ assert.equal(rows.length, 2, 'should return two sets rows')
+ assert.equal(rows[0][0].num, '10')
+ assert.equal(rows[1][0].name, 'brian')
+ done()
+ })
+ })
+
+ it('inserts and reads at once', function (done) {
+ let txt = 'CREATE TEMP TABLE boom(age int);'
+ txt += 'INSERT INTO boom(age) VALUES(10);'
+ txt += 'SELECT * FROM boom;'
+ this.client.query(txt, function (err, rows, results) {
+ assert.ifError(err)
+ assert.equal(rows.length, 3)
+ assert.equal(rows[0].length, 0)
+ assert.equal(rows[1].length, 0)
+ assert.equal(rows[2][0].age, 10)
+
+ assert.equal(results[0].command, 'CREATE')
+ assert.equal(results[1].command, 'INSERT')
+ assert.equal(results[2].command, 'SELECT')
+ done()
+ })
+ })
+})
diff --git a/packages/pg-native/test/multiple-statement-results.js b/packages/pg-native/test/multiple-statement-results.js
new file mode 100644
index 000000000..8c0a8574e
--- /dev/null
+++ b/packages/pg-native/test/multiple-statement-results.js
@@ -0,0 +1,28 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('multiple statements', () => {
+ before(() => {
+ this.client = new Client()
+ this.client.connectSync()
+ })
+
+ after(() => this.client.end())
+
+ it('works with multiple queries', (done) => {
+ const text = `
+ SELECT generate_series(1, 2) as foo;
+ SELECT generate_series(10, 11) as bar;
+ SELECT generate_series(20, 22) as baz;
+ `
+ this.client.query(text, (err, results) => {
+ if (err) return done(err)
+ assert(Array.isArray(results))
+ assert.equal(results.length, 3)
+ assert(Array.isArray(results[0]))
+ assert(Array.isArray(results[1]))
+ assert(Array.isArray(results[2]))
+ done()
+ })
+ })
+})
diff --git a/packages/pg-native/test/notify.js b/packages/pg-native/test/notify.js
new file mode 100644
index 000000000..6fe7a7072
--- /dev/null
+++ b/packages/pg-native/test/notify.js
@@ -0,0 +1,64 @@
+const Client = require('../')
+const ok = require('okay')
+
+const notify = function (channel, payload) {
+ const client = new Client()
+ client.connectSync()
+ client.querySync('NOTIFY ' + channel + ", '" + payload + "'")
+ client.end()
+}
+
+describe('simple LISTEN/NOTIFY', function () {
+ before(function (done) {
+ const client = (this.client = new Client())
+ client.connect(done)
+ })
+
+ it('works', function (done) {
+ const client = this.client
+ client.querySync('LISTEN boom')
+ client.on('notification', function (msg) {
+ done()
+ })
+ notify('boom', 'sup')
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+})
+
+if (!process.env.TRAVIS_CI) {
+ describe('async LISTEN/NOTIFY', function () {
+ before(function (done) {
+ const client = (this.client = new Client())
+ client.connect(done)
+ })
+
+ it('works', function (done) {
+ const client = this.client
+ let count = 0
+ const check = function () {
+ count++
+ if (count >= 2) return done()
+ }
+ client.on('notification', check)
+ client.query(
+ 'LISTEN test',
+ ok(done, function () {
+ notify('test', 'bot')
+ client.query(
+ 'SELECT pg_sleep(.05)',
+ ok(done, function () {
+ notify('test', 'bot')
+ })
+ )
+ })
+ )
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+ })
+}
diff --git a/packages/pg-native/test/prepare.js b/packages/pg-native/test/prepare.js
new file mode 100644
index 000000000..60ec32045
--- /dev/null
+++ b/packages/pg-native/test/prepare.js
@@ -0,0 +1,64 @@
+const Client = require('../')
+const ok = require('okay')
+const async = require('async')
+
+describe('async prepare', function () {
+ const run = function (n, cb) {
+ const client = new Client()
+ client.connectSync()
+
+ const exec = function (x, done) {
+ client.prepare('get_now' + x, 'SELECT NOW()', 0, done)
+ }
+
+ async.timesSeries(
+ 10,
+ exec,
+ ok(cb, function () {
+ client.end(cb)
+ })
+ )
+ }
+
+ const t = function (n) {
+ it('works for ' + n + ' clients', function (done) {
+ async.times(n, run, function (err) {
+ done(err)
+ })
+ })
+ }
+
+ for (let i = 0; i < 10; i++) {
+ t(i)
+ }
+})
+
+describe('async execute', function () {
+ const run = function (n, cb) {
+ const client = new Client()
+ client.connectSync()
+ client.prepareSync('get_now', 'SELECT NOW()', 0)
+ const exec = function (x, cb) {
+ client.execute('get_now', [], cb)
+ }
+ async.timesSeries(
+ 10,
+ exec,
+ ok(cb, function () {
+ client.end(cb)
+ })
+ )
+ }
+
+ const t = function (n) {
+ it('works for ' + n + ' clients', function (done) {
+ async.times(n, run, function (err) {
+ done(err)
+ })
+ })
+ }
+
+ for (let i = 0; i < 10; i++) {
+ t(i)
+ }
+})
diff --git a/packages/pg-native/test/query-async.js b/packages/pg-native/test/query-async.js
new file mode 100644
index 000000000..5e6cd17b2
--- /dev/null
+++ b/packages/pg-native/test/query-async.js
@@ -0,0 +1,115 @@
+const Client = require('../')
+const assert = require('assert')
+const async = require('async')
+const ok = require('okay')
+
+describe('async query', function () {
+ before(function (done) {
+ this.client = Client()
+ this.client.connect(done)
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+
+ it('can execute many prepared statements on a client', function (done) {
+ async.timesSeries(
+ 20,
+ (i, cb) => {
+ this.client.query('SELECT $1::text as name', ['brianc'], cb)
+ },
+ done
+ )
+ })
+
+ it('simple query works', function (done) {
+ const runQuery = function (n, done) {
+ this.client.query('SELECT NOW() AS the_time', function (err, rows) {
+ if (err) return done(err)
+ assert.equal(rows[0].the_time.getFullYear(), new Date().getFullYear())
+ return done()
+ })
+ }.bind(this)
+ async.timesSeries(3, runQuery, done)
+ })
+
+ it('parameters work', function (done) {
+ const runQuery = function (n, done) {
+ this.client.query('SELECT $1::text AS name', ['Brian'], done)
+ }.bind(this)
+ async.timesSeries(3, runQuery, done)
+ })
+
+ it('prepared, named statements work', function (done) {
+ const client = this.client
+ client.prepare('test', 'SELECT $1::text as name', 1, function (err) {
+ if (err) return done(err)
+ client.execute(
+ 'test',
+ ['Brian'],
+ ok(done, function (rows) {
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].name, 'Brian')
+ client.execute(
+ 'test',
+ ['Aaron'],
+ ok(done, function (rows) {
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].name, 'Aaron')
+ done()
+ })
+ )
+ })
+ )
+ })
+ })
+
+ it('returns error if prepare fails', function (done) {
+ this.client.prepare('test', 'SELECT AWWW YEAH', 0, function (err) {
+ assert(err, 'Should have returned an error')
+ done()
+ })
+ })
+
+ it('returns an error if execute fails', function (done) {
+ this.client.execute('test', [], function (err) {
+ assert(err, 'Should have returned an error')
+ done()
+ })
+ })
+
+ it('returns an error if there was a query error', function (done) {
+ const runErrorQuery = function (n, done) {
+ this.client.query('SELECT ALKJSFDSLFKJ', function (err) {
+ assert(err instanceof Error, 'Should return an error instance')
+ done()
+ })
+ }.bind(this)
+ async.timesSeries(3, runErrorQuery, done)
+ })
+
+ it('is still usable after an error', function (done) {
+ const runErrorQuery = (_, cb) => {
+ this.client.query('SELECT LKJSDJFLSDKFJ', (err) => {
+ assert(err instanceof Error, 'Should return an error instance')
+ cb(null, err)
+ })
+ }
+ async.timesSeries(3, runErrorQuery, (err, res) => {
+ assert(!err)
+ assert.equal(res.length, 3)
+ this.client.query('SELECT NOW()', done)
+ })
+ })
+
+ it('supports empty query', function (done) {
+ this.client.query('', function (err, rows) {
+ assert.ifError(err)
+ assert(Array.isArray(rows))
+ console.log('rows', rows)
+ assert(rows.length === 0)
+ done()
+ })
+ })
+})
diff --git a/packages/pg-native/test/query-sync.js b/packages/pg-native/test/query-sync.js
new file mode 100644
index 000000000..baf4e15ec
--- /dev/null
+++ b/packages/pg-native/test/query-sync.js
@@ -0,0 +1,83 @@
+const Client = require('../')
+const assert = require('assert')
+
+describe('query sync', function () {
+ before(function () {
+ this.client = Client()
+ this.client.connectSync()
+ })
+
+ after(function (done) {
+ this.client.end(done)
+ })
+
+ it('simple query works', function () {
+ const rows = this.client.querySync('SELECT NOW() AS the_time')
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].the_time.getFullYear(), new Date().getFullYear())
+ })
+
+ it('parameterized query works', function () {
+ const rows = this.client.querySync('SELECT $1::text AS name', ['Brian'])
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].name, 'Brian')
+ })
+
+ it('throws when second argument is not an array', function () {
+ assert.throws(() => {
+ this.client.querySync('SELECT $1::text AS name', 'Brian')
+ })
+ assert.throws(() => {
+ this.client.prepareSync('test-failure', 'SELECT $1::text as name', 1)
+
+ this.client.executeSync('test-failure', 'Brian')
+ })
+ })
+
+ it('prepared statement works', function () {
+ this.client.prepareSync('test', 'SELECT $1::text as name', 1)
+
+ const rows = this.client.executeSync('test', ['Brian'])
+ assert.equal(rows.length, 1)
+ assert.equal(rows[0].name, 'Brian')
+
+ const rows2 = this.client.executeSync('test', ['Aaron'])
+ assert.equal(rows2.length, 1)
+ assert.equal(rows2[0].name, 'Aaron')
+ })
+
+ it('prepare throws exception on error', function () {
+ assert.throws(
+ function () {
+ this.client.prepareSync('blah', 'I LIKE TO PARTY!!!', 0)
+ }.bind(this)
+ )
+ })
+
+ it('throws exception on executing improperly', function () {
+ assert.throws(function () {
+ // wrong number of parameters
+ this.client.executeSync('test', [])
+ })
+ })
+
+ it('throws exception on error', function () {
+ assert.throws(
+ function () {
+ this.client.querySync('SELECT ASLKJASLKJF')
+ }.bind(this)
+ )
+ })
+
+ it('is still usable after an error', function () {
+ const rows = this.client.querySync('SELECT NOW()')
+ assert(rows, 'should have returned rows')
+ assert.equal(rows.length, 1)
+ })
+
+ it('supports empty query', function () {
+ const rows = this.client.querySync('')
+ assert(rows, 'should return rows')
+ assert.equal(rows.length, 0, 'should return no rows')
+ })
+})
diff --git a/packages/pg-native/test/version.js b/packages/pg-native/test/version.js
new file mode 100644
index 000000000..f8e4d2b29
--- /dev/null
+++ b/packages/pg-native/test/version.js
@@ -0,0 +1,11 @@
+const Client = require('../')
+const assert = require('assert')
+const semver = require('semver')
+
+describe('version', function () {
+ it('is exported', function () {
+ assert(Client.version)
+ assert.equal(require('../package.json').version, Client.version)
+ assert(semver.gt(Client.version, '1.4.0'))
+ })
+})
diff --git a/packages/pg-pool/README.md b/packages/pg-pool/README.md
index c6d7e9287..80c644788 100644
--- a/packages/pg-pool/README.md
+++ b/packages/pg-pool/README.md
@@ -1,9 +1,11 @@
# pg-pool
+
[](https://travis-ci.org/brianc/node-pg-pool)
A connection pool for node-postgres
## install
+
```sh
npm i pg-pool pg
```
@@ -15,17 +17,16 @@ npm i pg-pool pg
to use pg-pool you must first create an instance of a pool
```js
-var Pool = require('pg-pool')
+const Pool = require('pg-pool')
// by default the pool uses the same
// configuration as whatever `pg` version you have installed
-var pool = new Pool()
+const 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({
+// and the pool constructor, allowing you to fully configure the behavior of both
+const pool2 = new Pool({
database: 'postgres',
user: 'brianc',
password: 'secret!',
@@ -37,25 +38,26 @@ var pool2 = new Pool({
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 supply a custom client constructor
+// if you want to use the native postgres client
+const NativeClient = require('pg').native.Client
+const nativePool = new Pool({ Client: NativeClient })
-//you can even pool pg-native clients directly
-var PgNativeClient = require('pg-native')
-var pgNativePool = new Pool({ Client: PgNativeClient })
+// you can even pool pg-native clients directly
+const PgNativeClient = require('pg-native')
+const 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 Pool = require('pg-pool')
const url = require('url')
-const params = url.parse(process.env.DATABASE_URL);
-const auth = params.auth.split(':');
+const params = url.parse(process.env.DATABASE_URL)
+const auth = params.auth.split(':')
const config = {
user: auth[0],
@@ -63,10 +65,10 @@ const config = {
host: params.hostname,
port: params.port,
database: params.pathname.split('/')[1],
- ssl: true
-};
+ ssl: true,
+}
-const pool = new Pool(config);
+const pool = new Pool(config)
/*
Transforms, 'postgres://DBuser:secret@DBHost:#####/myDB', into
@@ -79,23 +81,25 @@ const pool = new Pool(config);
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)
- })
+const 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)
+ })
})
```
@@ -105,27 +109,27 @@ this ends up looking much nicer if you're using [co](https://github.com/tj/co) o
```js
// with async/await
-(async () => {
- var pool = new Pool()
- var client = await pool.connect()
+;(async () => {
+ const pool = new Pool()
+ const client = await pool.connect()
try {
- var result = await client.query('select $1::text as name', ['brianc'])
+ const 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))
+})().catch((e) => console.error(e.message, e.stack))
// with co
-co(function * () {
- var client = yield pool.connect()
+co(function* () {
+ const client = yield pool.connect()
try {
- var result = yield client.query('select $1::text as name', ['brianc'])
+ const 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))
+}).catch((e) => console.error(e.message, e.stack))
```
### your new favorite helper method
@@ -133,39 +137,39 @@ co(function * () {
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)
+const pool = new Pool()
+const time = await pool.query('SELECT NOW()')
+const name = await pool.query('select $1::text as name', ['brianc'])
+console.log(name.rows[0].name, 'says hello at', time.rows[0].now)
```
you can also use a callback here if you'd like:
```js
-var pool = new Pool()
+const 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
+**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
+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:
+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()
+const 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)
+ return console.error('query error', err.message, err.stack)
}
console.log('hello from', res.rows[0].name)
})
@@ -175,11 +179,11 @@ pool.connect((err, client, done) => {
### 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:
+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()
+const pool = new Pool()
+const client = await pool.connect()
console.log(await client.query('select now()'))
client.release()
await pool.end()
@@ -187,14 +191,14 @@ 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:
+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()
+const pool = new pg.Pool()
// this is the right way to export the query method
module.exports.query = (text, values) => {
@@ -208,18 +212,18 @@ module.exports.connect = () => {
// 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()
+ const aPool = new pg.Pool()
return aPool.connect()
}
```
### events
-Every instance of a `Pool` is an event emitter. These instances emit the following 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.
+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:
@@ -229,7 +233,7 @@ 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) {
+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
})
@@ -237,7 +241,7 @@ pool.on('error', function(error, client) {
#### connect
-Fired whenever the pool creates a __new__ `pg.Client` instance and successfully connects it to the backend.
+Fired whenever the pool creates a **new** `pg.Client` instance and successfully connects it to the backend.
Example:
@@ -245,47 +249,46 @@ Example:
const Pool = require('pg-pool')
const pool = new Pool()
-var count = 0
+const count = 0
-pool.on('connect', client => {
+pool.on('connect', (client) => {
client.count = count++
})
pool
.connect()
- .then(client => {
+ .then((client) => {
return client
.query('SELECT $1::int AS "clientCount"', [client.count])
- .then(res => console.log(res.rows[0].clientCount)) // outputs 0
+ .then((res) => console.log(res.rows[0].clientCount)) // outputs 0
.then(() => client)
})
- .then(client => client.release())
-
+ .then((client) => client.release())
```
#### acquire
-Fired whenever the a client is acquired from the pool
+Fired whenever 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()
+const Pool = require('pg-pool')
+const pool = new Pool()
-var acquireCount = 0
+const acquireCount = 0
pool.on('acquire', function (client) {
acquireCount++
})
-var connectCount = 0
+const connectCount = 0
pool.on('connect', function () {
connectCount++
})
-for (var i = 0; i < 200; i++) {
+for (let i = 0; i < 200; i++) {
pool.query('SELECT NOW()')
}
@@ -293,12 +296,11 @@ 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:
+pg-pool & node-postgres support some of the same environment variables as `psql` supports. The most common are:
```
PGDATABASE=my_db
@@ -308,40 +310,19 @@ 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."
+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.
## 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.
+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.
+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.
+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.
+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:
@@ -362,7 +343,7 @@ 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.
+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
diff --git a/packages/pg-pool/esm/index.mjs b/packages/pg-pool/esm/index.mjs
new file mode 100644
index 000000000..a97fb624d
--- /dev/null
+++ b/packages/pg-pool/esm/index.mjs
@@ -0,0 +1,5 @@
+// ESM wrapper for pg-pool
+import Pool from '../index.js'
+
+// Export as default only to match CJS module
+export default Pool
diff --git a/packages/pg-pool/index.js b/packages/pg-pool/index.js
index eef490f91..ab514fa88 100644
--- a/packages/pg-pool/index.js
+++ b/packages/pg-pool/index.js
@@ -39,6 +39,11 @@ function promisify(Promise, callback) {
const result = new Promise(function (resolve, reject) {
res = resolve
rej = reject
+ }).catch((err) => {
+ // replace the stack trace that leads to `TCP.onStreamRead` with one that leads back to the
+ // application that created the query
+ Error.captureStackTrace(err)
+ throw err
})
return { callback: cb, result: result }
}
@@ -73,9 +78,19 @@ class Pool extends EventEmitter {
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.min = this.options.min || 0
this.options.maxUses = this.options.maxUses || Infinity
+ this.options.allowExitOnIdle = this.options.allowExitOnIdle || false
+ this.options.maxLifetimeSeconds = this.options.maxLifetimeSeconds || 0
this.log = this.options.log || function () {}
this.Client = this.options.Client || Client || require('pg').Client
this.Promise = this.options.Promise || global.Promise
@@ -86,16 +101,29 @@ class Pool extends EventEmitter {
this._clients = []
this._idle = []
+ this._expired = new WeakSet()
this._pendingQueue = []
this._endCallback = undefined
this.ending = false
this.ended = false
}
+ _promiseTry(f) {
+ const Promise = this.Promise
+ if (typeof Promise.try === 'function') {
+ return Promise.try(f)
+ }
+ return new Promise((resolve) => resolve(f()))
+ }
+
_isFull() {
return this._clients.length >= this.options.max
}
+ _isAboveMin() {
+ return this._clients.length > this.options.min
+ }
+
_pulseQueue() {
this.log('pulse queue')
if (this.ended) {
@@ -115,6 +143,7 @@ class Pool extends EventEmitter {
}
return
}
+
// if we don't have any waiting, do nothing
if (!this._pendingQueue.length) {
this.log('no queued requests')
@@ -129,6 +158,7 @@ class Pool extends EventEmitter {
const idleItem = this._idle.pop()
clearTimeout(idleItem.timeoutId)
const client = idleItem.client
+ client.ref && client.ref()
const idleListener = idleItem.idleListener
return this._acquireClient(client, pendingItem, idleListener, false)
@@ -139,7 +169,7 @@ class Pool extends EventEmitter {
throw new Error('unexpected condition')
}
- _remove(client) {
+ _remove(client, callback) {
const removed = removeWhere(this._idle, (item) => item.client === client)
if (removed !== undefined) {
@@ -147,8 +177,14 @@ class Pool extends EventEmitter {
}
this._clients = this._clients.filter((c) => c !== client)
- client.end()
- this.emit('remove', client)
+ const context = this
+ client.end(() => {
+ context.emit('remove', client)
+
+ if (typeof callback === 'function') {
+ callback()
+ }
+ })
}
connect(cb) {
@@ -161,7 +197,7 @@ class Pool extends EventEmitter {
const result = response.result
// if we don't have to connect a new client, don't do so
- if (this._clients.length >= this.options.max || this._idle.length) {
+ if (this._isFull() || this._idle.length) {
// if we have idle clients schedule a pulse immediately
if (this._idle.length) {
process.nextTick(() => this._pulseQueue())
@@ -188,6 +224,10 @@ class Pool extends EventEmitter {
response.callback(new Error('timeout exceeded when trying to connect'))
}, this.options.connectionTimeoutMillis)
+ if (tid.unref) {
+ tid.unref()
+ }
+
this._pendingQueue.push(pendingItem)
return result
}
@@ -209,10 +249,16 @@ class Pool extends EventEmitter {
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()
+ if (client.connection) {
+ this.log('ending client due to timeout')
+ timeoutHit = true
+ client.connection.stream.destroy()
+ } else if (!client.isConnected()) {
+ this.log('ending client due to timeout')
+ timeoutHit = true
+ // force kill the node driver, and let libpq do its teardown
+ client.end()
+ }
}, this.options.connectionTimeoutMillis)
}
@@ -227,7 +273,7 @@ class Pool extends EventEmitter {
// 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'
+ err = new Error('Connection terminated due to connection timeout', { cause: err })
}
// this client won’t be released, so move on immediately
@@ -239,11 +285,52 @@ class Pool extends EventEmitter {
} else {
this.log('new client connected')
- return this._acquireClient(client, pendingItem, idleListener, true)
+ if (this.options.onConnect) {
+ this._promiseTry(() => this.options.onConnect(client)).then(
+ () => {
+ this._afterConnect(client, pendingItem, idleListener)
+ },
+ (hookErr) => {
+ this._clients = this._clients.filter((c) => c !== client)
+ client.end(() => {
+ this._pulseQueue()
+ if (!pendingItem.timedOut) {
+ pendingItem.callback(hookErr, undefined, NOOP)
+ }
+ })
+ }
+ )
+ return
+ }
+
+ return this._afterConnect(client, pendingItem, idleListener)
}
})
}
+ _afterConnect(client, pendingItem, idleListener) {
+ if (this.options.maxLifetimeSeconds !== 0) {
+ const maxLifetimeTimeout = setTimeout(() => {
+ this.log('ending client due to expired lifetime')
+ this._expired.add(client)
+ const idleIndex = this._idle.findIndex((idleItem) => idleItem.client === client)
+ if (idleIndex !== -1) {
+ this._acquireClient(
+ client,
+ new PendingItem((err, client, clientRelease) => clientRelease()),
+ idleListener,
+ false
+ )
+ }
+ }, this.options.maxLifetimeSeconds * 1000)
+
+ maxLifetimeTimeout.unref()
+ client.once('end', () => clearTimeout(maxLifetimeTimeout))
+ }
+
+ return this._acquireClient(client, pendingItem, idleListener, true)
+ }
+
// acquire a client for a pending work item
_acquireClient(client, pendingItem, idleListener, isNew) {
if (isNew) {
@@ -299,23 +386,42 @@ class Pool extends EventEmitter {
client._poolUseCount = (client._poolUseCount || 0) + 1
+ this.emit('release', err, client)
+
// 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
+
+ return this._remove(client, this._pulseQueue.bind(this))
+ }
+
+ const isExpired = this._expired.has(client)
+ if (isExpired) {
+ this.log('remove expired client')
+ this._expired.delete(client)
+ return this._remove(client, this._pulseQueue.bind(this))
}
// idle timeout
let tid
- if (this.options.idleTimeoutMillis) {
+ if (this.options.idleTimeoutMillis && this._isAboveMin()) {
tid = setTimeout(() => {
- this.log('remove idle client')
- this._remove(client)
+ if (this._isAboveMin()) {
+ this.log('remove idle client')
+ this._remove(client, this._pulseQueue.bind(this))
+ }
}, this.options.idleTimeoutMillis)
+
+ if (this.options.allowExitOnIdle) {
+ // allow Node to exit if this is all that's left
+ tid.unref()
+ }
+ }
+
+ if (this.options.allowExitOnIdle) {
+ client.unref()
}
this._idle.push(new IdleItem(client, idleListener, tid))
@@ -332,7 +438,7 @@ class Pool extends EventEmitter {
return response.result
}
- // allow plain text query without values
+ // allow plain text query without values, but callback
if (typeof values === 'function') {
cb = values
values = undefined
@@ -357,20 +463,24 @@ class Pool extends EventEmitter {
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 {
+ try {
+ 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)
+ }
return cb(undefined, res)
- }
- })
+ })
+ } catch (err) {
+ client.release(err)
+ return cb(err)
+ }
})
return response.result
}
@@ -396,6 +506,10 @@ class Pool extends EventEmitter {
return this._idle.length
}
+ get expiredCount() {
+ return this._clients.reduce((acc, client) => acc + (this._expired.has(client) ? 1 : 0), 0)
+ }
+
get totalCount() {
return this._clients.length
}
diff --git a/packages/pg-pool/package.json b/packages/pg-pool/package.json
index 3acac307e..6b9f60155 100644
--- a/packages/pg-pool/package.json
+++ b/packages/pg-pool/package.json
@@ -1,8 +1,15 @@
{
"name": "pg-pool",
- "version": "3.2.1",
+ "version": "3.14.0",
"description": "Connection pool for node-postgres",
"main": "index.js",
+ "exports": {
+ ".": {
+ "import": "./esm/index.mjs",
+ "require": "./index.js",
+ "default": "./index.js"
+ }
+ },
"directories": {
"test": "test"
},
@@ -11,7 +18,8 @@
},
"repository": {
"type": "git",
- "url": "git://github.com/brianc/node-postgres.git"
+ "url": "git://github.com/brianc/node-postgres.git",
+ "directory": "packages/pg-pool"
},
"keywords": [
"pg",
@@ -22,18 +30,21 @@
"author": "Brian M. Carlson",
"license": "MIT",
"bugs": {
- "url": "https://github.com/brianc/node-pg-pool/issues"
+ "url": "https://github.com/brianc/node-postgres/issues"
},
- "homepage": "https://github.com/brianc/node-pg-pool#readme",
+ "homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-pool#readme",
"devDependencies": {
- "bluebird": "3.4.1",
+ "bluebird": "3.7.2",
"co": "4.6.0",
"expect.js": "0.3.1",
"lodash": "^4.17.11",
- "mocha": "^7.1.2",
- "pg-cursor": "^1.3.0"
+ "mocha": "^11.7.5"
},
"peerDependencies": {
"pg": ">=8.0"
- }
+ },
+ "files": [
+ "index.js",
+ "esm"
+ ]
}
diff --git a/packages/pg-pool/test/bring-your-own-promise.js b/packages/pg-pool/test/bring-your-own-promise.js
deleted file mode 100644
index e905ccc0b..000000000
--- a/packages/pg-pool/test/bring-your-own-promise.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'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-timeout.js b/packages/pg-pool/test/connection-timeout.js
index 05e8931df..c4fd1832b 100644
--- a/packages/pg-pool/test/connection-timeout.js
+++ b/packages/pg-pool/test/connection-timeout.js
@@ -57,7 +57,7 @@ describe('connection timeout', () => {
function* () {
const errors = []
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
- for (var i = 0; i < 15; i++) {
+ for (let i = 0; i < 15; i++) {
try {
yield pool.connect()
} catch (e) {
@@ -226,4 +226,25 @@ describe('connection timeout', () => {
})
})
})
+
+ it('should connect if timeout is passed, but native client in connected state', (done) => {
+ const Client = require('pg').native.Client
+
+ Client.prototype.connect = function (cb) {
+ this._connected = true
+
+ return setTimeout(() => {
+ cb()
+ }, 200)
+ }
+
+ const pool = new Pool({ connectionTimeoutMillis: 100, port: this.port, host: 'localhost' }, Client)
+
+ pool.connect((err, client, release) => {
+ expect(err).to.be(undefined)
+ expect(client).to.not.be(undefined)
+ expect(client.isConnected()).to.be(true)
+ done()
+ })
+ })
})
diff --git a/packages/pg-pool/test/ending.js b/packages/pg-pool/test/ending.js
index e1839b46c..d3aa81f05 100644
--- a/packages/pg-pool/test/ending.js
+++ b/packages/pg-pool/test/ending.js
@@ -37,4 +37,14 @@ describe('pool ending', () => {
expect(res.rows[0].name).to.equal('brianc')
})
)
+
+ it('pool.end() - finish pending queries', async () => {
+ const pool = new Pool({ max: 20 })
+ let completed = 0
+ for (let x = 1; x <= 20; x++) {
+ pool.query('SELECT $1::text as name', ['brianc']).then(() => completed++)
+ }
+ await pool.end()
+ expect(completed).to.equal(20)
+ })
})
diff --git a/packages/pg-pool/test/error-handling.js b/packages/pg-pool/test/error-handling.js
index fea1d1148..60354325c 100644
--- a/packages/pg-pool/test/error-handling.js
+++ b/packages/pg-pool/test/error-handling.js
@@ -37,6 +37,18 @@ describe('pool error handling', function () {
})
})
+ it('Catches errors in client.query', async function () {
+ let caught = false
+ const pool = new Pool()
+ try {
+ await pool.query(null)
+ } catch (e) {
+ caught = true
+ }
+ pool.end()
+ expect(caught).to.be(true)
+ })
+
describe('calling release more than once', () => {
it(
'should throw each time',
@@ -65,18 +77,6 @@ describe('pool error handling', function () {
})
})
- describe('calling connect after end', () => {
- it('should return an error', function* () {
- const pool = new Pool()
- const res = yield pool.query('SELECT $1::text as name', ['hi'])
- expect(res.rows[0].name).to.equal('hi')
- const wait = pool.end()
- pool.query('select now()')
- yield wait
- expect(() => pool.query('select now()')).to.reject()
- })
- })
-
describe('using an ended pool', () => {
it('rejects all additional promises', (done) => {
const pool = new Pool()
@@ -198,7 +198,7 @@ describe('pool error handling', function () {
co.wrap(function* () {
const pool = new Pool({ max: 1 })
const errors = []
- for (var i = 0; i < 20; i++) {
+ for (let i = 0; i < 20; i++) {
try {
yield pool.query('invalid sql')
} catch (err) {
diff --git a/packages/pg-pool/test/events.js b/packages/pg-pool/test/events.js
index 61979247d..809c2159a 100644
--- a/packages/pg-pool/test/events.js
+++ b/packages/pg-pool/test/events.js
@@ -60,6 +60,44 @@ describe('events', function () {
}, 100)
})
+ it('emits release every time a client is released', function (done) {
+ const pool = new Pool()
+ let releaseCount = 0
+ pool.on('release', function (err, client) {
+ expect(err instanceof Error).not.to.be(true)
+ expect(client).to.be.ok()
+ releaseCount++
+ })
+ const promises = []
+ for (let i = 0; i < 10; i++) {
+ pool.connect(function (err, client, release) {
+ if (err) return done(err)
+ release()
+ })
+ promises.push(pool.query('SELECT now()'))
+ }
+ Promise.all(promises).then(() => {
+ pool.end(() => {
+ expect(releaseCount).to.be(20)
+ done()
+ })
+ })
+ })
+
+ it('emits release with an error if client is released due to an error', function (done) {
+ const pool = new Pool()
+ pool.connect(function (err, client, release) {
+ expect(err).to.equal(undefined)
+ const releaseError = new Error('problem')
+ pool.once('release', function (err, errClient) {
+ expect(err).to.equal(releaseError)
+ expect(errClient).to.equal(client)
+ pool.end(done)
+ })
+ release(releaseError)
+ })
+ })
+
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) {
diff --git a/packages/pg-pool/test/idle-timeout-exit.js b/packages/pg-pool/test/idle-timeout-exit.js
new file mode 100644
index 000000000..7304bcff1
--- /dev/null
+++ b/packages/pg-pool/test/idle-timeout-exit.js
@@ -0,0 +1,19 @@
+// This test is meant to be spawned from idle-timeout.js
+if (module === require.main) {
+ const allowExitOnIdle = process.env.ALLOW_EXIT_ON_IDLE === '1'
+ const Pool = require('../index')
+
+ const pool = new Pool({
+ maxLifetimeSeconds: 2,
+ idleTimeoutMillis: 200,
+ ...(allowExitOnIdle ? { allowExitOnIdle: true } : {}),
+ })
+ pool.query('SELECT NOW()', (err, res) => console.log('completed first'))
+ pool.on('remove', () => {
+ console.log('removed')
+ })
+
+ setTimeout(() => {
+ pool.query('SELECT * from generate_series(0, 1000)', (err, res) => console.log('completed second'))
+ }, 50)
+}
diff --git a/packages/pg-pool/test/idle-timeout.js b/packages/pg-pool/test/idle-timeout.js
index fd9fba4a4..1063266d2 100644
--- a/packages/pg-pool/test/idle-timeout.js
+++ b/packages/pg-pool/test/idle-timeout.js
@@ -4,6 +4,8 @@ const expect = require('expect.js')
const describe = require('mocha').describe
const it = require('mocha').it
+const { fork } = require('child_process')
+const path = require('path')
const Pool = require('../')
@@ -26,11 +28,19 @@ describe('idle timeout', () => {
const pool = new Pool({ idleTimeoutMillis: 10 })
const clientA = yield pool.connect()
const clientB = yield pool.connect()
- clientA.release()
- clientB.release(new Error())
+ clientA.release() // this will put clientA in the idle pool
+ clientB.release(new Error()) // an error will cause clientB to be removed immediately
const removal = new Promise((resolve) => {
- pool.on('remove', () => {
+ pool.on('remove', (client) => {
+ // clientB's stream may take a while to close, so we may get a remove
+ // event for it
+ // we only want to handle the remove event for clientA when it times out
+ // due to being idle
+ if (client !== clientA) {
+ return
+ }
+
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
resolve()
@@ -52,8 +62,8 @@ describe('idle timeout', () => {
co.wrap(function* () {
const pool = new Pool({ idleTimeoutMillis: 1 })
const results = []
- for (var i = 0; i < 20; i++) {
- let query = pool.query('SELECT NOW()')
+ for (let i = 0; i < 20; i++) {
+ const query = pool.query('SELECT NOW()')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
results.push(yield query)
@@ -70,8 +80,8 @@ describe('idle timeout', () => {
co.wrap(function* () {
const pool = new Pool({ idleTimeoutMillis: 1 })
const results = []
- for (var i = 0; i < 20; i++) {
- let client = yield pool.connect()
+ for (let i = 0; i < 20; i++) {
+ const client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.idleCount).to.equal(0)
yield wait(10)
@@ -84,4 +94,35 @@ describe('idle timeout', () => {
return pool.end()
})
)
+
+ it('unrefs the connections and timeouts so the program can exit when idle when the allowExitOnIdle option is set', function (done) {
+ const child = fork(path.join(__dirname, 'idle-timeout-exit.js'), [], {
+ stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
+ env: { ...process.env, ALLOW_EXIT_ON_IDLE: '1' },
+ })
+ let result = ''
+ child.stdout.setEncoding('utf8')
+ child.stdout.on('data', (chunk) => (result += chunk))
+ child.on('error', (err) => done(err))
+ child.on('exit', (exitCode) => {
+ expect(exitCode).to.equal(0)
+ expect(result).to.equal('completed first\ncompleted second\n')
+ done()
+ })
+ })
+
+ it('keeps old behavior when allowExitOnIdle option is not set', function (done) {
+ const child = fork(path.join(__dirname, 'idle-timeout-exit.js'), [], {
+ stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
+ })
+ let result = ''
+ child.stdout.setEncoding('utf8')
+ child.stdout.on('data', (chunk) => (result += chunk))
+ child.on('error', (err) => done(err))
+ child.on('exit', (exitCode) => {
+ expect(exitCode).to.equal(0)
+ expect(result).to.equal('completed first\ncompleted second\nremoved\n')
+ done()
+ })
+ })
})
diff --git a/packages/pg-pool/test/lifecycle-hooks.js b/packages/pg-pool/test/lifecycle-hooks.js
new file mode 100644
index 000000000..05a706d95
--- /dev/null
+++ b/packages/pg-pool/test/lifecycle-hooks.js
@@ -0,0 +1,164 @@
+const describe = require('mocha').describe
+const it = require('mocha').it
+const expect = require('expect.js')
+
+const Pool = require('..')
+
+describe('lifecycle hooks', () => {
+ it('are called on connect', async () => {
+ const pool = new Pool({
+ onConnect: (client) => {
+ client.HOOK_CONNECT_COUNT = (client.HOOK_CONNECT_COUNT || 0) + 1
+ },
+ })
+ const client = await pool.connect()
+ expect(client.HOOK_CONNECT_COUNT).to.equal(1)
+ client.release()
+ const client2 = await pool.connect()
+ expect(client).to.equal(client2)
+ expect(client2.HOOK_CONNECT_COUNT).to.equal(1)
+ client.release()
+ await pool.end()
+ })
+
+ it('are called on connect with an async hook', async () => {
+ const pool = new Pool({
+ onConnect: async (client) => {
+ const res = await client.query('SELECT 1 AS num')
+ client.HOOK_CONNECT_RESULT = res.rows[0].num
+ },
+ })
+ const client = await pool.connect()
+ expect(client.HOOK_CONNECT_RESULT).to.equal(1)
+ const res = await client.query('SELECT 1 AS num')
+ expect(res.rows[0].num).to.equal(1)
+ client.release()
+ const client2 = await pool.connect()
+ expect(client).to.equal(client2)
+ expect(client2.HOOK_CONNECT_RESULT).to.equal(1)
+ client.release()
+ await pool.end()
+ })
+
+ it('errors out the connect call if the async connect hook rejects', async () => {
+ const pool = new Pool({
+ onConnect: async (client) => {
+ await client.query('SELECT INVALID HERE')
+ },
+ })
+ try {
+ await pool.connect()
+ throw new Error('Expected connect to throw')
+ } catch (err) {
+ expect(err.message).to.contain('invalid')
+ }
+ await pool.end()
+ })
+
+ it('calls onConnect when using pool.query', async () => {
+ const pool = new Pool({
+ onConnect: async (client) => {
+ const res = await client.query('SELECT 1 AS num')
+ client.HOOK_CONNECT_RESULT = res.rows[0].num
+ },
+ })
+ const res = await pool.query('SELECT $1::text AS name', ['brianc'])
+ expect(res.rows[0].name).to.equal('brianc')
+ const client = await pool.connect()
+ expect(client.HOOK_CONNECT_RESULT).to.equal(1)
+ client.release()
+ await pool.end()
+ })
+
+ it('recovers after a hook error', async () => {
+ let shouldError = true
+ const pool = new Pool({
+ onConnect: () => {
+ if (shouldError) {
+ throw new Error('connect hook error')
+ }
+ },
+ })
+ try {
+ await pool.connect()
+ throw new Error('Expected connect to throw')
+ } catch (err) {
+ expect(err.message).to.equal('connect hook error')
+ }
+ shouldError = false
+ const client = await pool.connect()
+ const res = await client.query('SELECT 1 AS num')
+ expect(res.rows[0].num).to.equal(1)
+ client.release()
+ await pool.end()
+ })
+
+ it('calls onConnect for each new client', async () => {
+ let connectCount = 0
+ const pool = new Pool({
+ max: 2,
+ onConnect: async (client) => {
+ connectCount++
+ await client.query('SELECT 1')
+ },
+ })
+ const client1 = await pool.connect()
+ const client2 = await pool.connect()
+ expect(connectCount).to.equal(2)
+ expect(client1).to.not.equal(client2)
+ client1.release()
+ client2.release()
+ await pool.end()
+ })
+
+ it('cleans up clients after repeated hook failures', async () => {
+ let errorCount = 0
+ const pool = new Pool({
+ max: 2,
+ onConnect: () => {
+ if (errorCount < 10) {
+ errorCount++
+ throw new Error('connect hook error')
+ }
+ },
+ })
+ for (let i = 0; i < 10; i++) {
+ let threw = false
+ try {
+ await pool.connect()
+ } catch (err) {
+ threw = true
+ expect(err.message).to.equal('connect hook error')
+ }
+ expect(threw).to.equal(true)
+ }
+ expect(errorCount).to.equal(10)
+ expect(pool.totalCount).to.equal(0)
+ expect(pool.idleCount).to.equal(0)
+ const client1 = await pool.connect()
+ const res1 = await client1.query('SELECT 1 AS num')
+ expect(res1.rows[0].num).to.equal(1)
+ const client2 = await pool.connect()
+ const res2 = await client2.query('SELECT 2 AS num')
+ expect(res2.rows[0].num).to.equal(2)
+ expect(pool.totalCount).to.equal(2)
+ client1.release()
+ client2.release()
+ await pool.end()
+ })
+
+ it('errors out the connect call if the connect hook throws', async () => {
+ const pool = new Pool({
+ onConnect: () => {
+ throw new Error('connect hook error')
+ },
+ })
+ try {
+ await pool.connect()
+ throw new Error('Expected connect to throw')
+ } catch (err) {
+ expect(err.message).to.equal('connect hook error')
+ }
+ await pool.end()
+ })
+})
diff --git a/packages/pg-pool/test/lifetime-timeout.js b/packages/pg-pool/test/lifetime-timeout.js
new file mode 100644
index 000000000..e9fa14f19
--- /dev/null
+++ b/packages/pg-pool/test/lifetime-timeout.js
@@ -0,0 +1,45 @@
+'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('lifetime timeout', () => {
+ it('connection lifetime should expire and remove the client', (done) => {
+ const pool = new Pool({ maxLifetimeSeconds: 1 })
+ pool.query('SELECT NOW()')
+ pool.on('remove', () => {
+ expect(pool.expiredCount).to.equal(0)
+ expect(pool.totalCount).to.equal(0)
+ done()
+ })
+ })
+ it('connection lifetime should expire and remove the client after the client is done working', (done) => {
+ const pool = new Pool({ maxLifetimeSeconds: 1 })
+ pool.query('SELECT pg_sleep(1.4)')
+ pool.on('remove', () => {
+ expect(pool.expiredCount).to.equal(0)
+ expect(pool.totalCount).to.equal(0)
+ done()
+ })
+ })
+ it(
+ 'can remove expired clients and recreate them',
+ co.wrap(function* () {
+ const pool = new Pool({ maxLifetimeSeconds: 1 })
+ const query = pool.query('SELECT pg_sleep(1.4)')
+ expect(pool.expiredCount).to.equal(0)
+ expect(pool.totalCount).to.equal(1)
+ yield query
+ yield new Promise((resolve) => setTimeout(resolve, 100))
+ expect(pool.expiredCount).to.equal(0)
+ expect(pool.totalCount).to.equal(0)
+ yield pool.query('SELECT NOW()')
+ expect(pool.expiredCount).to.equal(0)
+ expect(pool.totalCount).to.equal(1)
+ })
+ )
+})
diff --git a/packages/pg-pool/test/max-uses.js b/packages/pg-pool/test/max-uses.js
index c94ddec6b..42375c030 100644
--- a/packages/pg-pool/test/max-uses.js
+++ b/packages/pg-pool/test/max-uses.js
@@ -1,6 +1,5 @@
const expect = require('expect.js')
const co = require('co')
-const _ = require('lodash')
const describe = require('mocha').describe
const it = require('mocha').it
diff --git a/packages/pg-pool/test/releasing-clients.js b/packages/pg-pool/test/releasing-clients.js
index da8e09c16..ddfb44a44 100644
--- a/packages/pg-pool/test/releasing-clients.js
+++ b/packages/pg-pool/test/releasing-clients.js
@@ -1,7 +1,6 @@
const Pool = require('../')
const expect = require('expect.js')
-const net = require('net')
describe('releasing clients', () => {
it('removes a client which cannot be queried', async () => {
diff --git a/packages/pg-pool/test/sizing.js b/packages/pg-pool/test/sizing.js
index e7863ba07..0e93d7376 100644
--- a/packages/pg-pool/test/sizing.js
+++ b/packages/pg-pool/test/sizing.js
@@ -55,4 +55,88 @@ describe('pool size of 1', () => {
return yield pool.end()
})
)
+
+ it(
+ 'does not remove clients when at or below min',
+ co.wrap(function* () {
+ const pool = new Pool({ max: 1, min: 1, idleTimeoutMillis: 10 })
+ const client = yield pool.connect()
+ client.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ expect(pool.idleCount).to.equal(1)
+ return yield pool.end()
+ })
+ )
+
+ it(
+ 'does remove clients when at or below min if maxUses is reached',
+ co.wrap(function* () {
+ const pool = new Pool({ max: 1, min: 1, idleTimeoutMillis: 10, maxUses: 1 })
+ const client = yield pool.connect()
+ client.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ expect(pool.idleCount).to.equal(0)
+ return yield pool.end()
+ })
+ )
+
+ it(
+ 'does remove clients when at or below min if maxLifetimeSeconds is reached',
+ co.wrap(function* () {
+ const pool = new Pool({ max: 1, min: 1, idleTimeoutMillis: 10, maxLifetimeSeconds: 1 })
+ const client = yield pool.connect()
+ client.release()
+ yield new Promise((resolve) => setTimeout(resolve, 1020))
+ expect(pool.idleCount).to.equal(0)
+ return yield pool.end()
+ })
+ )
+})
+
+describe('pool size of 2', () => {
+ it(
+ 'does not remove clients when at or below min',
+ co.wrap(function* () {
+ const pool = new Pool({ max: 2, min: 2, idleTimeoutMillis: 10 })
+ const client = yield pool.connect()
+ const client2 = yield pool.connect()
+ client.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ client2.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ expect(pool.idleCount).to.equal(2)
+ return yield pool.end()
+ })
+ )
+
+ it(
+ 'does remove clients when above min',
+ co.wrap(function* () {
+ const pool = new Pool({ max: 2, min: 1, idleTimeoutMillis: 10 })
+ const client = yield pool.connect()
+ const client2 = yield pool.connect()
+ client.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ client2.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ expect(pool.idleCount).to.equal(1)
+ return yield pool.end()
+ })
+ )
+})
+
+describe('pool min size', () => {
+ it(
+ 'does not drop below min when clients released at same time',
+ co.wrap(function* () {
+ const pool = new Pool({ max: 2, min: 1, idleTimeoutMillis: 10 })
+ const client = yield pool.connect()
+ const client2 = yield pool.connect()
+ client.release()
+ client2.release()
+ yield new Promise((resolve) => setTimeout(resolve, 20))
+ expect(pool.idleCount).to.equal(1)
+ return yield pool.end()
+ })
+ )
})
diff --git a/packages/pg-pool/test/timeout.js b/packages/pg-pool/test/timeout.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/packages/pg-pool/test/verify.js b/packages/pg-pool/test/verify.js
index e7ae1dd88..9331e1a06 100644
--- a/packages/pg-pool/test/verify.js
+++ b/packages/pg-pool/test/verify.js
@@ -7,10 +7,9 @@ const it = require('mocha').it
const Pool = require('../')
describe('verify', () => {
- it('verifies a client with a callback', false, (done) => {
+ it('verifies a client with a callback', (done) => {
const pool = new Pool({
verify: (client, cb) => {
- client.release()
cb(new Error('nope'))
},
})
diff --git a/packages/pg-protocol/README.md b/packages/pg-protocol/README.md
new file mode 100644
index 000000000..8c52e40ec
--- /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/esm/index.js b/packages/pg-protocol/esm/index.js
new file mode 100644
index 000000000..c52807d63
--- /dev/null
+++ b/packages/pg-protocol/esm/index.js
@@ -0,0 +1,11 @@
+// ESM wrapper for pg-protocol
+import * as protocol from '../dist/index.js'
+
+// Re-export all the properties
+export const DatabaseError = protocol.DatabaseError
+export const SASL = protocol.SASL
+export const serialize = protocol.serialize
+export const parse = protocol.parse
+
+// Re-export the default
+export default protocol
diff --git a/packages/pg-protocol/package.json b/packages/pg-protocol/package.json
index 0a65e77d9..4ee3e3f17 100644
--- a/packages/pg-protocol/package.json
+++ b/packages/pg-protocol/package.json
@@ -1,19 +1,28 @@
{
"name": "pg-protocol",
- "version": "1.2.5",
+ "version": "1.14.0",
"description": "The postgres client/server binary protocol, implemented in TypeScript",
"main": "dist/index.js",
"types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./esm/index.js",
+ "require": "./dist/index.js",
+ "default": "./dist/index.js"
+ },
+ "./dist/*": "./dist/*.js",
+ "./dist/*.js": "./dist/*.js"
+ },
"license": "MIT",
"devDependencies": {
"@types/chai": "^4.2.7",
- "@types/mocha": "^5.2.7",
- "@types/node": "^12.12.21",
+ "@types/mocha": "^10.0.10",
+ "@types/node": "^16",
"chai": "^4.2.0",
"chunky": "^0.0.0",
- "mocha": "^7.1.2",
+ "mocha": "^11.7.5",
"ts-node": "^8.5.4",
- "typescript": "^3.7.3"
+ "typescript": "^6.0.3"
},
"scripts": {
"test": "mocha dist/**/*.test.js",
@@ -21,5 +30,15 @@
"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",
+ "/esm"
+ ]
}
diff --git a/packages/pg-protocol/src/b.ts b/packages/pg-protocol/src/b.ts
index 028b76393..c8a24113d 100644
--- a/packages/pg-protocol/src/b.ts
+++ b/packages/pg-protocol/src/b.ts
@@ -1,20 +1,17 @@
// 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 start = performance.now()
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)
+ console.log(performance.now() - start)
return
}
count++
diff --git a/packages/pg-protocol/src/buffer-reader.ts b/packages/pg-protocol/src/buffer-reader.ts
index 2305e130c..c9d9c2b66 100644
--- a/packages/pg-protocol/src/buffer-reader.ts
+++ b/packages/pg-protocol/src/buffer-reader.ts
@@ -1,10 +1,8 @@
-const emptyBuffer = Buffer.allocUnsafe(0)
-
export class BufferReader {
- private buffer: Buffer = emptyBuffer
+ private buffer: Buffer = Buffer.allocUnsafe(0)
// TODO(bmc): support non-utf8 encoding?
- private encoding: string = 'utf-8'
+ private encoding: BufferEncoding = 'utf-8'
constructor(private offset: number = 0) {}
@@ -31,6 +29,12 @@ export class BufferReader {
return result
}
+ public uint32(): number {
+ const result = this.buffer.readUInt32BE(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
@@ -40,6 +44,7 @@ export class BufferReader {
public cstring(): string {
const start = this.offset
let end = start
+ // eslint-disable-next-line no-empty
while (this.buffer[end++] !== 0) {}
this.offset = end
return this.buffer.toString(this.encoding, start, end - 1)
diff --git a/packages/pg-protocol/src/buffer-writer.ts b/packages/pg-protocol/src/buffer-writer.ts
index 3a8d80b30..cebb0d9ed 100644
--- a/packages/pg-protocol/src/buffer-writer.ts
+++ b/packages/pg-protocol/src/buffer-writer.ts
@@ -5,17 +5,17 @@ export class Writer {
private offset: number = 5
private headerPosition: number = 0
constructor(private size = 256) {
- this.buffer = Buffer.alloc(size)
+ this.buffer = Buffer.allocUnsafe(size)
}
private ensure(size: number): void {
- var remaining = this.buffer.length - this.offset
+ const remaining = this.buffer.length - this.offset
if (remaining < size) {
- var oldBuffer = this.buffer
+ const 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.alloc(newSize)
+ const newSize = oldBuffer.length + (oldBuffer.length >> 1) + size
+ this.buffer = Buffer.allocUnsafe(newSize)
oldBuffer.copy(this.buffer)
}
}
@@ -40,7 +40,7 @@ export class Writer {
if (!string) {
this.ensure(1)
} else {
- var len = Buffer.byteLength(string)
+ const len = Buffer.byteLength(string)
this.ensure(len + 1) // +1 for null terminator
this.buffer.write(string, this.offset, 'utf-8')
this.offset += len
@@ -51,7 +51,7 @@ export class Writer {
}
public addString(string: string = ''): Writer {
- var len = Buffer.byteLength(string)
+ const len = Buffer.byteLength(string)
this.ensure(len)
this.buffer.write(string, this.offset)
this.offset += len
@@ -76,7 +76,7 @@ export class Writer {
}
public flush(code?: number): Buffer {
- var result = this.join(code)
+ const result = this.join(code)
this.offset = 5
this.headerPosition = 0
this.buffer = Buffer.allocUnsafe(this.size)
diff --git a/packages/pg-protocol/src/inbound-parser.test.ts b/packages/pg-protocol/src/inbound-parser.test.ts
index 3fcbe410a..285f4bf2b 100644
--- a/packages/pg-protocol/src/inbound-parser.test.ts
+++ b/packages/pg-protocol/src/inbound-parser.test.ts
@@ -4,28 +4,18 @@ 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 = {
+import { Parser } from './parser'
+
+const authOkBuffer = buffers.authenticationOk()
+const paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8')
+const readyForQueryBuffer = buffers.readyForQuery()
+const backendKeyDataBuffer = buffers.backendKeyData(1, 2)
+const commandCompleteBuffer = buffers.commandComplete('SELECT 3')
+const parseCompleteBuffer = buffers.parseComplete()
+const bindCompleteBuffer = buffers.bindComplete()
+const portalSuspendedBuffer = buffers.portalSuspended()
+
+const row1 = {
name: 'id',
tableID: 1,
attributeNumber: 2,
@@ -34,10 +24,10 @@ var row1 = {
typeModifier: 5,
formatCode: 0,
}
-var oneRowDescBuff = buffers.rowDescription([row1])
+const oneRowDescBuff = buffers.rowDescription([row1])
row1.name = 'bang'
-var twoRowBuf = buffers.rowDescription([
+const twoRowBuf = buffers.rowDescription([
row1,
{
name: 'whoah',
@@ -50,58 +40,61 @@ var twoRowBuf = buffers.rowDescription([
},
])
-var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D')
-
-var emptyRowFieldBuf = buffers.dataRow([])
+const rowWithBigOids = {
+ name: 'bigoid',
+ tableID: 3000000001,
+ attributeNumber: 2,
+ dataTypeID: 3000000003,
+ dataTypeSize: 4,
+ typeModifier: 5,
+ formatCode: 0,
+}
+const bigOidDescBuff = buffers.rowDescription([rowWithBigOids])
-var oneFieldBuf = new BufferList()
- .addInt16(1) // number of fields
- .addInt32(5) // length of bytes of fields
- .addCString('test')
- .join(true, 'D')
+const emptyRowFieldBuf = buffers.dataRow([])
-var oneFieldBuf = buffers.dataRow(['test'])
+const oneFieldBuf = buffers.dataRow(['test'])
-var expectedAuthenticationOkayMessage = {
+const expectedAuthenticationOkayMessage = {
name: 'authenticationOk',
length: 8,
}
-var expectedParameterStatusMessage = {
+const expectedParameterStatusMessage = {
name: 'parameterStatus',
parameterName: 'client_encoding',
parameterValue: 'UTF8',
length: 25,
}
-var expectedBackendKeyDataMessage = {
+const expectedBackendKeyDataMessage = {
name: 'backendKeyData',
processID: 1,
secretKey: 2,
}
-var expectedReadyForQueryMessage = {
+const expectedReadyForQueryMessage = {
name: 'readyForQuery',
length: 5,
status: 'I',
}
-var expectedCommandCompleteMessage = {
+const expectedCommandCompleteMessage = {
name: 'commandComplete',
length: 13,
text: 'SELECT 3',
}
-var emptyRowDescriptionBuffer = new BufferList()
+const emptyRowDescriptionBuffer = new BufferList()
.addInt16(0) // number of fields
.join(true, 'T')
-var expectedEmptyRowDescriptionMessage = {
+const expectedEmptyRowDescriptionMessage = {
name: 'rowDescription',
length: 6,
fieldCount: 0,
fields: [],
}
-var expectedOneRowMessage = {
+const expectedOneRowMessage = {
name: 'rowDescription',
length: 27,
fieldCount: 1,
@@ -118,7 +111,7 @@ var expectedOneRowMessage = {
],
}
-var expectedTwoRowMessage = {
+const expectedTwoRowMessage = {
name: 'rowDescription',
length: 53,
fieldCount: 2,
@@ -143,9 +136,54 @@ var expectedTwoRowMessage = {
},
],
}
+const expectedBigOidMessage = {
+ name: 'rowDescription',
+ length: 31,
+ fieldCount: 1,
+ fields: [
+ {
+ name: 'bigoid',
+ tableID: 3000000001,
+ columnID: 2,
+ dataTypeID: 3000000003,
+ dataTypeSize: 4,
+ dataTypeModifier: 5,
+ format: 'text',
+ },
+ ],
+}
+
+const emptyParameterDescriptionBuffer = new BufferList()
+ .addInt16(0) // number of parameters
+ .join(true, 't')
+
+const oneParameterDescBuf = buffers.parameterDescription([1111])
+
+const twoParameterDescBuf = buffers.parameterDescription([2222, 3333])
+
+const expectedEmptyParameterDescriptionMessage = {
+ name: 'parameterDescription',
+ length: 6,
+ parameterCount: 0,
+ dataTypeIDs: [],
+}
+
+const expectedOneParameterMessage = {
+ name: 'parameterDescription',
+ length: 10,
+ parameterCount: 1,
+ dataTypeIDs: [1111],
+}
+
+const 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 testForMessage = function (buffer: Buffer, expectedMessage: any) {
+ it('receives and parses ' + expectedMessage.name, async () => {
const messages = await parseBuffers([buffer])
const [lastMessage] = messages
@@ -155,38 +193,38 @@ var testForMessage = function (buffer: Buffer, expectedMessage: any) {
})
}
-var plainPasswordBuffer = buffers.authenticationCleartextPassword()
-var md5PasswordBuffer = buffers.authenticationMD5Password()
-var SASLBuffer = buffers.authenticationSASL()
-var SASLContinueBuffer = buffers.authenticationSASLContinue()
-var SASLFinalBuffer = buffers.authenticationSASLFinal()
+const plainPasswordBuffer = buffers.authenticationCleartextPassword()
+const md5PasswordBuffer = buffers.authenticationMD5Password()
+const SASLBuffer = buffers.authenticationSASL()
+const SASLContinueBuffer = buffers.authenticationSASLContinue()
+const SASLFinalBuffer = buffers.authenticationSASLFinal()
-var expectedPlainPasswordMessage = {
+const expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword',
}
-var expectedMD5PasswordMessage = {
+const expectedMD5PasswordMessage = {
name: 'authenticationMD5Password',
salt: Buffer.from([1, 2, 3, 4]),
}
-var expectedSASLMessage = {
+const expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256'],
}
-var expectedSASLContinueMessage = {
+const expectedSASLContinueMessage = {
name: 'authenticationSASLContinue',
data: 'data',
}
-var expectedSASLFinalMessage = {
+const expectedSASLFinalMessage = {
name: 'authenticationSASLFinal',
data: 'data',
}
-var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
-var expectedNotificationResponseMessage = {
+const notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
+const expectedNotificationResponseMessage = {
name: 'notification',
processId: 4,
channel: 'hi',
@@ -243,6 +281,13 @@ describe('PgPacketStream', function () {
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
testForMessage(oneRowDescBuff, expectedOneRowMessage)
testForMessage(twoRowBuf, expectedTwoRowMessage)
+ testForMessage(bigOidDescBuff, expectedBigOidMessage)
+ })
+
+ describe('parameterDescription messages', function () {
+ testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage)
+ testForMessage(oneParameterDescBuf, expectedOneParameterMessage)
+ testForMessage(twoParameterDescBuf, expectedTwoParameterMessage)
})
describe('parsing rows', function () {
@@ -264,7 +309,7 @@ describe('PgPacketStream', function () {
describe('notice message', function () {
// this uses the same logic as error message
- var buff = buffers.notice([{ type: 'C', value: 'code' }])
+ const buff = buffers.notice([{ type: 'C', value: 'code' }])
testForMessage(buff, {
name: 'notice',
code: 'code',
@@ -276,7 +321,7 @@ describe('PgPacketStream', function () {
})
describe('with all the fields', function () {
- var buffer = buffers.error([
+ const buffer = buffers.error([
{
type: 'S',
value: 'ERROR',
@@ -422,7 +467,7 @@ describe('PgPacketStream', function () {
// 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, '!'])
+ const fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
it('parses when full buffer comes in', async function () {
const messages = await parseBuffers([fullBuffer])
@@ -435,12 +480,12 @@ describe('PgPacketStream', function () {
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)
+ const testMessageReceivedAfterSplitAt = async function (split: number) {
+ const firstBuffer = Buffer.alloc(fullBuffer.length - split)
+ const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
fullBuffer.copy(firstBuffer, 0, 0)
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
- const messages = await parseBuffers([fullBuffer])
+ const messages = await parseBuffers([firstBuffer, secondBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
@@ -451,28 +496,30 @@ describe('PgPacketStream', function () {
}
it('parses when split in the middle', function () {
- testMessageRecievedAfterSpiltAt(6)
+ return testMessageReceivedAfterSplitAt(6)
})
it('parses when split at end', function () {
- testMessageRecievedAfterSpiltAt(2)
+ return testMessageReceivedAfterSplitAt(2)
})
it('parses when split at beginning', function () {
- testMessageRecievedAfterSpiltAt(fullBuffer.length - 2)
- testMessageRecievedAfterSpiltAt(fullBuffer.length - 1)
- testMessageRecievedAfterSpiltAt(fullBuffer.length - 5)
+ return Promise.all([
+ testMessageReceivedAfterSplitAt(fullBuffer.length - 2),
+ testMessageReceivedAfterSplitAt(fullBuffer.length - 1),
+ testMessageReceivedAfterSplitAt(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)
+ const dataRowBuffer = buffers.dataRow(['!'])
+ const readyForQueryBuffer = buffers.readyForQuery()
+ const fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length)
dataRowBuffer.copy(fullBuffer, 0, 0)
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0)
- var verifyMessages = function (messages: any[]) {
+ const verifyMessages = function (messages: any[]) {
assert.strictEqual(messages.length, 2)
assert.deepEqual(messages[0], {
name: 'dataRow',
@@ -488,21 +535,21 @@ describe('PgPacketStream', function () {
})
}
// sanity check
- it('recieves both messages when packet is not split', async function () {
+ it('receives 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)
+ const splitAndVerifyTwoMessages = async function (split: number) {
+ const firstBuffer = Buffer.alloc(fullBuffer.length - split)
+ const 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 () {
+ describe('receives both messages when packet is split', function () {
it('in the middle', function () {
return splitAndVerifyTwoMessages(11)
})
@@ -519,4 +566,10 @@ describe('PgPacketStream', function () {
})
})
})
+
+ it('cleans up the reader after handling a packet', function () {
+ const parser = new Parser()
+ parser.parse(oneFieldBuf, () => {})
+ assert.strictEqual((parser as any).reader.buffer.byteLength, 0)
+ })
})
diff --git a/packages/pg-protocol/src/index.ts b/packages/pg-protocol/src/index.ts
index 486f79c86..703ff2e49 100644
--- a/packages/pg-protocol/src/index.ts
+++ b/packages/pg-protocol/src/index.ts
@@ -1,4 +1,4 @@
-import { BackendMessage } from './messages'
+import { DatabaseError } from './messages'
import { serialize } from './serializer'
import { Parser, MessageCallback } from './parser'
@@ -8,4 +8,4 @@ export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback):
return new Promise((resolve) => stream.on('end', () => resolve()))
}
-export { serialize }
+export { serialize, DatabaseError }
diff --git a/packages/pg-protocol/src/messages.ts b/packages/pg-protocol/src/messages.ts
index 03c2f61ea..c3fbbdd9b 100644
--- a/packages/pg-protocol/src/messages.ts
+++ b/packages/pg-protocol/src/messages.ts
@@ -1,33 +1,33 @@
export type Mode = 'text' | 'binary'
-export const enum MessageName {
- parseComplete = 'parseComplete',
- bindComplete = 'bindComplete',
- closeComplete = 'closeComplete',
- noData = 'noData',
- portalSuspended = 'portalSuspended',
- replicationStart = 'replicationStart',
- emptyQuery = 'emptyQuery',
- copyDone = 'copyDone',
- copyData = 'copyData',
- rowDescription = 'rowDescription',
- parameterStatus = 'parameterStatus',
- backendKeyData = 'backendKeyData',
- notification = 'notification',
- readyForQuery = 'readyForQuery',
- commandComplete = 'commandComplete',
- dataRow = 'dataRow',
- copyInResponse = 'copyInResponse',
- copyOutResponse = 'copyOutResponse',
- authenticationOk = 'authenticationOk',
- authenticationMD5Password = 'authenticationMD5Password',
- authenticationCleartextPassword = 'authenticationCleartextPassword',
- authenticationSASL = 'authenticationSASL',
- authenticationSASLContinue = 'authenticationSASLContinue',
- authenticationSASLFinal = 'authenticationSASLFinal',
- error = 'error',
- notice = 'notice',
-}
+export type MessageName =
+ | 'parseComplete'
+ | 'bindComplete'
+ | 'closeComplete'
+ | 'noData'
+ | 'portalSuspended'
+ | 'replicationStart'
+ | 'emptyQuery'
+ | 'copyDone'
+ | 'copyData'
+ | 'rowDescription'
+ | 'parameterDescription'
+ | 'parameterStatus'
+ | 'backendKeyData'
+ | 'notification'
+ | 'readyForQuery'
+ | 'commandComplete'
+ | 'dataRow'
+ | 'copyInResponse'
+ | 'copyOutResponse'
+ | 'authenticationOk'
+ | 'authenticationMD5Password'
+ | 'authenticationCleartextPassword'
+ | 'authenticationSASL'
+ | 'authenticationSASLContinue'
+ | 'authenticationSASLFinal'
+ | 'error'
+ | 'notice'
export interface BackendMessage {
name: MessageName
@@ -35,42 +35,42 @@ export interface BackendMessage {
}
export const parseComplete: BackendMessage = {
- name: MessageName.parseComplete,
+ name: 'parseComplete',
length: 5,
}
export const bindComplete: BackendMessage = {
- name: MessageName.bindComplete,
+ name: 'bindComplete',
length: 5,
}
export const closeComplete: BackendMessage = {
- name: MessageName.closeComplete,
+ name: 'closeComplete',
length: 5,
}
export const noData: BackendMessage = {
- name: MessageName.noData,
+ name: 'noData',
length: 5,
}
export const portalSuspended: BackendMessage = {
- name: MessageName.portalSuspended,
+ name: 'portalSuspended',
length: 5,
}
export const replicationStart: BackendMessage = {
- name: MessageName.replicationStart,
+ name: 'replicationStart',
length: 4,
}
export const emptyQuery: BackendMessage = {
- name: MessageName.emptyQuery,
+ name: 'emptyQuery',
length: 4,
}
export const copyDone: BackendMessage = {
- name: MessageName.copyDone,
+ name: 'copyDone',
length: 4,
}
@@ -111,14 +111,21 @@ export class DatabaseError extends Error implements NoticeOrError {
public file: string | undefined
public line: string | undefined
public routine: string | undefined
- constructor(message: string, public readonly length: number, public readonly name: MessageName) {
+ constructor(
+ message: string,
+ public readonly length: number,
+ public readonly name: MessageName
+ ) {
super(message)
}
}
export class CopyDataMessage {
- public readonly name = MessageName.copyData
- constructor(public readonly length: number, public readonly chunk: Buffer) {}
+ public readonly name = 'copyData'
+ constructor(
+ public readonly length: number,
+ public readonly chunk: Buffer
+ ) {}
}
export class CopyResponse {
@@ -146,15 +153,29 @@ export class Field {
}
export class RowDescriptionMessage {
- public readonly name: MessageName = MessageName.rowDescription
+ public readonly name: MessageName = 'rowDescription'
public readonly fields: Field[]
- constructor(public readonly length: number, public readonly fieldCount: number) {
+ 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 = MessageName.parameterStatus
+ public readonly name: MessageName = 'parameterStatus'
constructor(
public readonly length: number,
public readonly parameterName: string,
@@ -163,17 +184,24 @@ export class ParameterStatusMessage {
}
export class AuthenticationMD5Password implements BackendMessage {
- public readonly name: MessageName = MessageName.authenticationMD5Password
- constructor(public readonly length: number, public readonly salt: Buffer) {}
+ public readonly name: MessageName = 'authenticationMD5Password'
+ constructor(
+ public readonly length: number,
+ public readonly salt: Buffer
+ ) {}
}
export class BackendKeyDataMessage {
- public readonly name: MessageName = MessageName.backendKeyData
- constructor(public readonly length: number, public readonly processID: number, public readonly secretKey: number) {}
+ 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 = MessageName.notification
+ public readonly name: MessageName = 'notification'
constructor(
public readonly length: number,
public readonly processId: number,
@@ -183,26 +211,38 @@ export class NotificationResponseMessage {
}
export class ReadyForQueryMessage {
- public readonly name: MessageName = MessageName.readyForQuery
- constructor(public readonly length: number, public readonly status: string) {}
+ public readonly name: MessageName = 'readyForQuery'
+ constructor(
+ public readonly length: number,
+ public readonly status: string
+ ) {}
}
export class CommandCompleteMessage {
- public readonly name: MessageName = MessageName.commandComplete
- constructor(public readonly length: number, public readonly text: string) {}
+ 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 = MessageName.dataRow
- constructor(public length: number, public fields: any[]) {
+ 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 = MessageName.notice
+ 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
diff --git a/packages/pg-protocol/src/outbound-serializer.test.ts b/packages/pg-protocol/src/outbound-serializer.test.ts
index 06f20cf9c..0d3e387e4 100644
--- a/packages/pg-protocol/src/outbound-serializer.test.ts
+++ b/packages/pg-protocol/src/outbound-serializer.test.ts
@@ -46,7 +46,7 @@ describe('serializer', () => {
})
it('builds query message', function () {
- var txt = 'select * from boom'
+ const txt = 'select * from boom'
const actual = serialize.query(txt)
assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q'))
})
@@ -54,7 +54,7 @@ describe('serializer', () => {
describe('parse message', () => {
it('builds parse message', function () {
const actual = serialize.parse({ text: '!' })
- var expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
+ const expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
@@ -64,7 +64,7 @@ describe('serializer', () => {
text: 'select * from boom',
types: [],
})
- var expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P')
+ const expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
@@ -74,7 +74,7 @@ describe('serializer', () => {
text: 'select * from bang where name = $1',
types: [1, 2, 3, 4],
})
- var expected = new BufferList()
+ const expected = new BufferList()
.addCString('force')
.addCString('select * from bang where name = $1')
.addInt16(4)
@@ -91,11 +91,12 @@ describe('serializer', () => {
it('with no values', function () {
const actual = serialize.bind()
- var expectedBuffer = new BufferList()
+ const expectedBuffer = new BufferList()
.addCString('')
.addCString('')
.addInt16(0)
.addInt16(0)
+ .addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
@@ -107,9 +108,13 @@ describe('serializer', () => {
statement: 'woo',
values: ['1', 'hi', null, 'zing'],
})
- var expectedBuffer = new BufferList()
+ const 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)
@@ -119,19 +124,46 @@ describe('serializer', () => {
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing'))
+ .addInt16(1)
.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,
+ })
+ const 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(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()
+ const expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4) // value count
@@ -147,6 +179,7 @@ describe('serializer', () => {
.addInt32(-1)
.addInt32(4)
.add(Buffer.from('zing', 'utf-8'))
+ .addInt16(1)
.addInt16(0)
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
@@ -155,7 +188,7 @@ describe('serializer', () => {
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')
+ const expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E')
assert.deepEqual(actual, expectedBuffer)
})
@@ -164,39 +197,39 @@ describe('serializer', () => {
portal: 'my favorite portal',
rows: 100,
})
- var expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E')
+ const 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')
+ const 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')
+ const 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])
+ const 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')
+ const 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')
+ const expected = new BufferList().addChar('P').addCString('').join(true, 'D')
assert.deepEqual(actual, expected)
})
})
@@ -204,13 +237,13 @@ describe('serializer', () => {
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')
+ const 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')
+ const expected = new BufferList().addChar('P').addCString('').join(true, 'C')
assert.deepEqual(actual, expected)
})
})
diff --git a/packages/pg-protocol/src/parser.ts b/packages/pg-protocol/src/parser.ts
index a00dabec9..998077a00 100644
--- a/packages/pg-protocol/src/parser.ts
+++ b/packages/pg-protocol/src/parser.ts
@@ -15,6 +15,7 @@ import {
CopyResponse,
NotificationResponseMessage,
RowDescriptionMessage,
+ ParameterDescriptionMessage,
Field,
DataRowMessage,
ParameterStatusMessage,
@@ -26,7 +27,6 @@ import {
NoticeMessage,
} from './messages'
import { BufferReader } from './buffer-reader'
-import assert from 'assert'
// every message is prefixed with a single bye
const CODE_LENGTH = 1
@@ -36,6 +36,9 @@ const LEN_LENGTH = 4
const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH
+// A placeholder for a `BackendMessage`’s length value that will be set after construction.
+const LATEINIT_LENGTH = -1
+
export type Packet = {
code: number
packet: Buffer
@@ -62,6 +65,7 @@ const enum MessageCodes {
ErrorMessage = 0x45, // E
NoticeMessage = 0x4e, // N
RowDescriptionMessage = 0x54, // T
+ ParameterDescriptionMessage = 0x74, // t
PortalSuspended = 0x73, // s
ReplicationStart = 0x57, // W
EmptyQuery = 0x49, // I
@@ -151,227 +155,259 @@ export class Parser {
}
private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage {
+ const { reader } = this
+
+ // NOTE: This undesirably retains the buffer in `this.reader` if the `parse*Message` calls below throw. However, those should only throw in the case of a protocol error, which normally results in the reader being discarded.
+ reader.setBuffer(offset, bytes)
+
+ let message: BackendMessage
+
switch (code) {
case MessageCodes.BindComplete:
- return bindComplete
+ message = bindComplete
+ break
case MessageCodes.ParseComplete:
- return parseComplete
+ message = parseComplete
+ break
case MessageCodes.CloseComplete:
- return closeComplete
+ message = closeComplete
+ break
case MessageCodes.NoData:
- return noData
+ message = noData
+ break
case MessageCodes.PortalSuspended:
- return portalSuspended
+ message = portalSuspended
+ break
case MessageCodes.CopyDone:
- return copyDone
+ message = copyDone
+ break
case MessageCodes.ReplicationStart:
- return replicationStart
+ message = replicationStart
+ break
case MessageCodes.EmptyQuery:
- return emptyQuery
+ message = emptyQuery
+ break
case MessageCodes.DataRow:
- return this.parseDataRowMessage(offset, length, bytes)
+ message = parseDataRowMessage(reader)
+ break
case MessageCodes.CommandComplete:
- return this.parseCommandCompleteMessage(offset, length, bytes)
+ message = parseCommandCompleteMessage(reader)
+ break
case MessageCodes.ReadyForQuery:
- return this.parseReadyForQueryMessage(offset, length, bytes)
+ message = parseReadyForQueryMessage(reader)
+ break
case MessageCodes.NotificationResponse:
- return this.parseNotificationMessage(offset, length, bytes)
+ message = parseNotificationMessage(reader)
+ break
case MessageCodes.AuthenticationResponse:
- return this.parseAuthenticationResponse(offset, length, bytes)
+ message = parseAuthenticationResponse(reader, length)
+ break
case MessageCodes.ParameterStatus:
- return this.parseParameterStatusMessage(offset, length, bytes)
+ message = parseParameterStatusMessage(reader)
+ break
case MessageCodes.BackendKeyData:
- return this.parseBackendKeyData(offset, length, bytes)
+ message = parseBackendKeyData(reader)
+ break
case MessageCodes.ErrorMessage:
- return this.parseErrorMessage(offset, length, bytes, MessageName.error)
+ message = parseErrorMessage(reader, 'error')
+ break
case MessageCodes.NoticeMessage:
- return this.parseErrorMessage(offset, length, bytes, MessageName.notice)
+ message = parseErrorMessage(reader, 'notice')
+ break
case MessageCodes.RowDescriptionMessage:
- return this.parseRowDescriptionMessage(offset, length, bytes)
+ message = parseRowDescriptionMessage(reader)
+ break
+ case MessageCodes.ParameterDescriptionMessage:
+ message = parseParameterDescriptionMessage(reader)
+ break
case MessageCodes.CopyIn:
- return this.parseCopyInMessage(offset, length, bytes)
+ message = parseCopyInMessage(reader)
+ break
case MessageCodes.CopyOut:
- return this.parseCopyOutMessage(offset, length, bytes)
+ message = parseCopyOutMessage(reader)
+ break
case MessageCodes.CopyData:
- return this.parseCopyData(offset, length, bytes)
+ message = parseCopyData(reader, length)
+ break
default:
- assert.fail(`unknown message code: ${code.toString(16)}`)
+ return new DatabaseError('received invalid response: ' + code.toString(16), length, 'error')
}
- }
- private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) {
- this.reader.setBuffer(offset, bytes)
- const status = this.reader.string(1)
- return new ReadyForQueryMessage(length, status)
- }
+ reader.setBuffer(0, emptyBuffer)
- private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) {
- this.reader.setBuffer(offset, bytes)
- const text = this.reader.cstring()
- return new CommandCompleteMessage(length, text)
+ message.length = length
+ return message
}
+}
- private parseCopyData(offset: number, length: number, bytes: Buffer) {
- const chunk = bytes.slice(offset, offset + (length - 4))
- return new CopyDataMessage(length, chunk)
- }
+const parseReadyForQueryMessage = (reader: BufferReader) => {
+ const status = reader.string(1)
+ return new ReadyForQueryMessage(LATEINIT_LENGTH, status)
+}
- private parseCopyInMessage(offset: number, length: number, bytes: Buffer) {
- return this.parseCopyMessage(offset, length, bytes, MessageName.copyInResponse)
- }
+const parseCommandCompleteMessage = (reader: BufferReader) => {
+ const text = reader.cstring()
+ return new CommandCompleteMessage(LATEINIT_LENGTH, text)
+}
- private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) {
- return this.parseCopyMessage(offset, length, bytes, MessageName.copyOutResponse)
- }
+const parseCopyData = (reader: BufferReader, length: number) => {
+ const chunk = reader.bytes(length - 4)
+ return new CopyDataMessage(LATEINIT_LENGTH, chunk)
+}
- 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
- }
+const parseCopyInMessage = (reader: BufferReader) => parseCopyMessage(reader, 'copyInResponse')
- 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)
- }
+const parseCopyOutMessage = (reader: BufferReader) => parseCopyMessage(reader, 'copyOutResponse')
- 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
+const parseCopyMessage = (reader: BufferReader, messageName: MessageName) => {
+ const isBinary = reader.byte() !== 0
+ const columnCount = reader.int16()
+ const message = new CopyResponse(LATEINIT_LENGTH, messageName, isBinary, columnCount)
+ for (let i = 0; i < columnCount; i++) {
+ message.columnTypes[i] = reader.int16()
}
+ 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)
- }
+const parseNotificationMessage = (reader: BufferReader) => {
+ const processId = reader.int32()
+ const channel = reader.cstring()
+ const payload = reader.cstring()
+ return new NotificationResponseMessage(LATEINIT_LENGTH, processId, channel, payload)
+}
- 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)
+const parseRowDescriptionMessage = (reader: BufferReader) => {
+ const fieldCount = reader.int16()
+ const message = new RowDescriptionMessage(LATEINIT_LENGTH, fieldCount)
+ for (let i = 0; i < fieldCount; i++) {
+ message.fields[i] = parseField(reader)
}
+ return message
+}
- 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)
+const parseField = (reader: BufferReader) => {
+ const name = reader.cstring()
+ const tableID = reader.uint32()
+ const columnID = reader.int16()
+ const dataTypeID = reader.uint32()
+ const dataTypeSize = reader.int16()
+ const dataTypeModifier = reader.int32()
+ const mode = reader.int16() === 0 ? 'text' : 'binary'
+ return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode)
+}
+
+const parseParameterDescriptionMessage = (reader: BufferReader) => {
+ const parameterCount = reader.int16()
+ const message = new ParameterDescriptionMessage(LATEINIT_LENGTH, parameterCount)
+ for (let i = 0; i < parameterCount; i++) {
+ message.dataTypeIDs[i] = reader.int32()
}
+ return message
+}
- 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)
+const parseDataRowMessage = (reader: BufferReader) => {
+ const fieldCount = reader.int16()
+ const fields: any[] = new Array(fieldCount)
+ for (let i = 0; i < fieldCount; i++) {
+ const len = reader.int32()
+ // a -1 for length means the value of the field is null
+ fields[i] = len === -1 ? null : reader.string(len)
}
+ return new DataRowMessage(LATEINIT_LENGTH, fields)
+}
- 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: MessageName.authenticationOk,
- length,
- }
+const parseParameterStatusMessage = (reader: BufferReader) => {
+ const name = reader.cstring()
+ const value = reader.cstring()
+ return new ParameterStatusMessage(LATEINIT_LENGTH, name, value)
+}
- switch (code) {
- case 0: // AuthenticationOk
- break
- case 3: // AuthenticationCleartextPassword
- if (message.length === 8) {
- message.name = MessageName.authenticationCleartextPassword
- }
- break
- case 5: // AuthenticationMD5Password
- if (message.length === 12) {
- message.name = MessageName.authenticationMD5Password
- const salt = this.reader.bytes(4)
- return new AuthenticationMD5Password(length, salt)
- }
- break
- case 10: // AuthenticationSASL
- message.name = MessageName.authenticationSASL
+const parseBackendKeyData = (reader: BufferReader) => {
+ const processID = reader.int32()
+ const secretKey = reader.int32()
+ return new BackendKeyDataMessage(LATEINIT_LENGTH, processID, secretKey)
+}
+
+const parseAuthenticationResponse = (reader: BufferReader, length: number) => {
+ const code = reader.int32()
+ // TODO(bmc): maybe better types here
+ const message: BackendMessage & any = {
+ name: 'authenticationOk',
+ length,
+ }
+
+ 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 = reader.bytes(4)
+ return new AuthenticationMD5Password(LATEINIT_LENGTH, salt)
+ }
+ break
+ case 10: // AuthenticationSASL
+ {
+ message.name = 'authenticationSASL'
message.mechanisms = []
let mechanism: string
do {
- mechanism = this.reader.cstring()
-
+ mechanism = reader.cstring()
if (mechanism) {
message.mechanisms.push(mechanism)
}
} while (mechanism)
- break
- case 11: // AuthenticationSASLContinue
- message.name = MessageName.authenticationSASLContinue
- message.data = this.reader.string(length - 8)
- break
- case 12: // AuthenticationSASLFinal
- message.name = MessageName.authenticationSASLFinal
- message.data = this.reader.string(length - 8)
- break
- default:
- throw new Error('Unknown authenticationOk message type ' + code)
- }
- return message
+ }
+ break
+ case 11: // AuthenticationSASLContinue
+ message.name = 'authenticationSASLContinue'
+ message.data = reader.string(length - 8)
+ break
+ case 12: // AuthenticationSASLFinal
+ message.name = 'authenticationSASLFinal'
+ message.data = reader.string(length - 8)
+ break
+ 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 === MessageName.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
+const parseErrorMessage = (reader: BufferReader, name: MessageName) => {
+ const fields: Record = {}
+ let fieldType = reader.string(1)
+ while (fieldType !== '\0') {
+ fields[fieldType] = reader.cstring()
+ fieldType = reader.string(1)
}
+
+ const messageValue = fields.M
+
+ const message =
+ name === 'notice'
+ ? new NoticeMessage(LATEINIT_LENGTH, messageValue)
+ : new DatabaseError(messageValue, LATEINIT_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
index bff2fd332..bb0441f56 100644
--- a/packages/pg-protocol/src/serializer.ts
+++ b/packages/pg-protocol/src/serializer.ts
@@ -27,10 +27,10 @@ const startup = (opts: Record): Buffer => {
writer.addCString('client_encoding').addCString('UTF8')
- var bodyBuffer = writer.addCString('').flush()
+ const bodyBuffer = writer.addCString('').flush()
// this message is sent without a code
- var length = bodyBuffer.length + 4
+ const length = bodyBuffer.length + 4
return new Writer().addInt32(length).add(bodyBuffer).flush()
}
@@ -78,34 +78,67 @@ const parse = (query: ParseOpts): Buffer => {
// 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
+ const len = types.length
- var buffer = writer
+ const buffer = writer
.addCString(name) // name of query
.addCString(query.text) // actual query text
.addInt16(len)
- for (var i = 0; i < len; i++) {
+ for (let 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 => {
@@ -113,44 +146,21 @@ const bind = (config: BindOpts = {}): Buffer => {
const portal = config.portal || ''
const statement = config.statement || ''
const binary = config.binary || false
- var values = config.values || emptyArray
- var len = values.length
+ const values = config.values || emptyArray
+ const len = values.length
- var useBinary = false
- // TODO(bmc): all the loops in here aren't nice, we can do better
- for (var j = 0; j < len; j++) {
- useBinary = useBinary || values[j] instanceof Buffer
- }
+ writer.addCString(portal).addCString(statement)
+ writer.addInt16(len)
- var buffer = writer.addCString(portal).addCString(statement)
- if (!useBinary) {
- buffer.addInt16(0)
- } else {
- buffer.addInt16(len)
- for (j = 0; j < len; j++) {
- buffer.addInt16(values[j] instanceof Buffer ? 1 : 0)
- }
- }
- buffer.addInt16(len)
- for (var i = 0; i < len; i++) {
- var val = values[i]
- if (val === null || typeof val === 'undefined') {
- buffer.addInt32(-1)
- } else if (val instanceof Buffer) {
- buffer.addInt32(val.length)
- buffer.add(val)
- } else {
- buffer.addInt32(Buffer.byteLength(val))
- buffer.addString(val)
- }
- }
+ writeValues(values, config.valueMapper)
- if (binary) {
- buffer.addInt16(1) // format codes to use binary
- buffer.addInt16(1)
- } else {
- buffer.addInt16(0) // format codes to use text
- }
+ writer.addInt16(len)
+ writer.add(paramWriter.flush())
+
+ // all results use the same format code
+ writer.addInt16(1)
+ // format code
+ writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING)
return writer.flush(code.bind)
}
diff --git a/packages/pg-protocol/src/testing/buffer-list.ts b/packages/pg-protocol/src/testing/buffer-list.ts
index 15ac785cc..bef75d405 100644
--- a/packages/pg-protocol/src/testing/buffer-list.ts
+++ b/packages/pg-protocol/src/testing/buffer-list.ts
@@ -10,10 +10,10 @@ export default class BufferList {
return this.add(Buffer.from([val >>> 8, val >>> 0]), front)
}
- public getByteLength(initial?: number) {
+ public getByteLength() {
return this.buffers.reduce(function (previous, current) {
return previous + current.length
- }, initial || 0)
+ }, 0)
}
public addInt32(val: number, first?: boolean) {
@@ -24,16 +24,16 @@ export default class BufferList {
}
public addCString(val: string, front?: boolean) {
- var len = Buffer.byteLength(val)
- var buffer = Buffer.alloc(len + 1)
+ const len = Buffer.byteLength(val)
+ const 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)
+ const len = Buffer.byteLength(val)
+ const buffer = Buffer.alloc(len)
buffer.write(val)
return this.add(buffer, front)
}
@@ -47,7 +47,7 @@ export default class BufferList {
}
public join(appendLength?: boolean, char?: string): Buffer {
- var length = this.getByteLength()
+ let length = this.getByteLength()
if (appendLength) {
this.addInt32(length + 4, true)
return this.join(false, char)
@@ -56,20 +56,12 @@ export default class BufferList {
this.addChar(char, true)
length++
}
- var result = Buffer.alloc(length)
- var index = 0
+ const result = Buffer.alloc(length)
+ let 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
index 19ba16cce..1f0d71f2d 100644
--- a/packages/pg-protocol/src/testing/test-buffers.ts
+++ b/packages/pg-protocol/src/testing/test-buffers.ts
@@ -1,4 +1,4 @@
-// http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html
+// https://www.postgresql.org/docs/current/protocol-message-formats.html
import BufferList from './buffer-list'
const buffers = {
@@ -47,7 +47,7 @@ const buffers = {
rowDescription: function (fields: any[]) {
fields = fields || []
- var buf = new BufferList()
+ const buf = new BufferList()
buf.addInt16(fields.length)
fields.forEach(function (field) {
buf
@@ -62,15 +62,25 @@ const buffers = {
return buf.join(true, 'T')
},
+ parameterDescription: function (dataTypeIDs: number[]) {
+ dataTypeIDs = dataTypeIDs || []
+ const 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()
+ const buf = new BufferList()
buf.addInt16(columns.length)
columns.forEach(function (col) {
if (col == null) {
buf.addInt32(-1)
} else {
- var strBuf = Buffer.from(col, 'utf8')
+ const strBuf = Buffer.from(col, 'utf8')
buf.addInt32(strBuf.length)
buf.add(strBuf)
}
@@ -88,7 +98,7 @@ const buffers = {
errorOrNotice: function (fields: any) {
fields = fields || []
- var buf = new BufferList()
+ const buf = new BufferList()
fields.forEach(function (field: any) {
buf.addChar(field.type)
buf.addCString(field.value)
diff --git a/packages/pg-protocol/tsconfig.json b/packages/pg-protocol/tsconfig.json
index bdbe07a39..e09c03cd7 100644
--- a/packages/pg-protocol/tsconfig.json
+++ b/packages/pg-protocol/tsconfig.json
@@ -1,22 +1,27 @@
{
"compilerOptions": {
- "module": "commonjs",
+ "module": "node16",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"target": "es6",
"noImplicitAny": true,
- "moduleResolution": "node",
+ "moduleResolution": "node16",
"sourceMap": true,
"outDir": "dist",
- "baseUrl": ".",
+ "rootDir": "./src",
+ "incremental": true,
"declaration": true,
"paths": {
"*": [
- "node_modules/*",
- "src/types/*"
+ "./node_modules/*",
+ "./src/types/*"
]
- }
+ },
+ "types": [
+ "node",
+ "mocha"
+ ]
},
"include": [
"src/**/*"
diff --git a/packages/pg-query-stream/README.md b/packages/pg-query-stream/README.md
index 043928ad5..0441c7025 100644
--- a/packages/pg-query-stream/README.md
+++ b/packages/pg-query-stream/README.md
@@ -1,10 +1,7 @@
# pg-query-stream
-[](https://travis-ci.org/brianc/node-pg-query-stream)
-
Receive result rows from [pg](https://github.com/brianc/node-postgres) as a readable (object) stream.
-
## installation
```bash
@@ -14,20 +11,20 @@ $ npm install pg-query-stream --save
_requires pg>=2.8.1_
-
## use
```js
const pg = require('pg')
+const pool = new pg.Pool()
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;
+// pipe 1,000,000 rows to stdout without blowing up your memory usage
+pool.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
+ // release the client when the stream is finished
stream.on('end', done)
stream.pipe(JSONStream.stringify()).pipe(process.stdout)
})
@@ -35,13 +32,13 @@ pg.connect((err, client, done) => {
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.
+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 iteration 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?
+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
diff --git a/packages/pg-query-stream/esm/index.mjs b/packages/pg-query-stream/esm/index.mjs
new file mode 100644
index 000000000..34429f2e4
--- /dev/null
+++ b/packages/pg-query-stream/esm/index.mjs
@@ -0,0 +1,5 @@
+// ESM wrapper for pg-query-stream
+import QueryStream from '../dist/index.js'
+
+// Export as default only to match CJS module
+export default QueryStream
diff --git a/packages/pg-query-stream/package.json b/packages/pg-query-stream/package.json
index 34009afca..6704fcfb9 100644
--- a/packages/pg-query-stream/package.json
+++ b/packages/pg-query-stream/package.json
@@ -1,37 +1,59 @@
{
"name": "pg-query-stream",
- "version": "3.2.0",
+ "version": "4.15.0",
"description": "Postgres query result returned as readable stream",
- "main": "index.js",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./esm/index.mjs",
+ "require": "./dist/index.js",
+ "default": "./dist/index.js"
+ }
+ },
"scripts": {
- "test": "mocha"
+ "test": "mocha -r ts-node/register test/**/*.ts"
},
"repository": {
"type": "git",
- "url": "git://github.com/brianc/node-postgres.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",
+ "/esm"
+ ],
"author": "Brian M. Carlson",
"license": "MIT",
"bugs": {
"url": "https://github.com/brianc/node-postgres/issues"
},
"devDependencies": {
- "JSONStream": "~0.7.1",
+ "@types/chai": "^4.2.13",
+ "@types/mocha": "^10.0.10",
+ "@types/node": "^16.0.0",
+ "@types/pg": "^7.14.5",
+ "JSONStream": "~1.3.5",
"concat-stream": "~1.0.1",
- "eslint-plugin-promise": "^3.5.0",
- "mocha": "^7.1.2",
- "pg": "^8.3.0",
+ "eslint-plugin-promise": "^7.3.0",
+ "mocha": "^11.7.5",
+ "pg": "^8.21.0",
"stream-spec": "~0.3.5",
- "stream-tester": "0.0.5",
- "through": "~2.3.4"
+ "ts-node": "^8.5.4",
+ "typescript": "^6.0.3"
+ },
+ "peerDependencies": {
+ "pg": "^8"
},
"dependencies": {
- "pg-cursor": "^2.3.0"
+ "pg-cursor": "^2.20.0"
}
}
diff --git a/packages/pg-query-stream/index.js b/packages/pg-query-stream/src/index.ts
similarity index 50%
rename from packages/pg-query-stream/index.js
rename to packages/pg-query-stream/src/index.ts
index 3806e60aa..2a4509e09 100644
--- a/packages/pg-query-stream/index.js
+++ b/packages/pg-query-stream/src/index.ts
@@ -1,12 +1,39 @@
-const { Readable } = require('stream')
-const Cursor = require('pg-cursor')
+import { Readable } from 'stream'
+import { Submittable, Connection } from 'pg'
+import Cursor from 'pg-cursor'
-class PgQueryStream extends Readable {
- constructor(text, values, config = {}) {
+interface QueryStreamConfig {
+ batchSize?: number
+ highWaterMark?: number
+ rowMode?: 'array'
+ types?: any
+}
+
+class QueryStream extends Readable implements Submittable {
+ cursor: any
+ _result: any
+
+ callback: Function
+ 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
- // https://nodejs.org/api/stream.html#stream_new_stream_readable_options
- super({ objectMode: true, emitClose: true, autoDestroy: true, highWaterMark: batchSize || highWaterMark })
+
+ super({ objectMode: true, autoDestroy: true, highWaterMark: batchSize || highWaterMark })
this.cursor = new Cursor(text, values, config)
+ this.cursor
+ .on('end', (result) => {
+ this.callback && this.callback(null, result)
+ })
+ .on('error', (err) => {
+ this.callback && this.callback(err)
+ })
// delegate Submittable callbacks to cursor
this.handleRowDescription = this.cursor.handleRowDescription.bind(this.cursor)
@@ -21,19 +48,19 @@ class PgQueryStream extends Readable {
this._result = this.cursor._result
}
- submit(connection) {
+ public submit(connection: Connection): void {
this.cursor.submit(connection)
}
- _destroy(_err, cb) {
- this.cursor.close((err) => {
+ 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
- _read(size) {
- this.cursor.read(size, (err, rows, result) => {
+ 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)
@@ -45,4 +72,4 @@ class PgQueryStream extends Readable {
}
}
-module.exports = PgQueryStream
+export = QueryStream
diff --git a/packages/pg-query-stream/test/async-iterator.es6 b/packages/pg-query-stream/test/async-iterator.es6
deleted file mode 100644
index 47bda86d2..000000000
--- a/packages/pg-query-stream/test/async-iterator.es6
+++ /dev/null
@@ -1,112 +0,0 @@
-const QueryStream = require('../')
-const pg = require('pg')
-const assert = require('assert')
-
-const queryText = 'SELECT * FROM generate_series(0, 200) num'
-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/async-iterator.js b/packages/pg-query-stream/test/async-iterator.js
deleted file mode 100644
index 19718fe3b..000000000
--- a/packages/pg-query-stream/test/async-iterator.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// only newer versions of node support async iterator
-if (!process.version.startsWith('v8')) {
- require('./async-iterator.es6')
-}
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 000000000..e2f8a7552
--- /dev/null
+++ b/packages/pg-query-stream/test/async-iterator.ts
@@ -0,0 +1,133 @@
+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
+ }
+
+ try {
+ for await (const row of stream) {
+ rows.push(row)
+ }
+ for await (const row of stream) {
+ rows.push(row)
+ }
+ } catch (e) {
+ // swallow error - node 17 throws if stream is aborted
+ }
+ 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()
+ })
+
+ it('supports breaking with low watermark', async function () {
+ const pool = new pg.Pool({ max: 1 })
+ const client = await pool.connect()
+
+ for await (const _ of client.query(new QueryStream('select TRUE', [], { highWaterMark: 1 }))) break
+ for await (const _ of client.query(new QueryStream('select TRUE', [], { highWaterMark: 1 }))) break
+ for await (const _ of client.query(new QueryStream('select TRUE', [], { highWaterMark: 1 }))) break
+
+ client.release()
+ await pool.end()
+ })
+ })
+}
diff --git a/packages/pg-query-stream/test/client-options.js b/packages/pg-query-stream/test/client-options.ts
similarity index 62%
rename from packages/pg-query-stream/test/client-options.js
rename to packages/pg-query-stream/test/client-options.ts
index 3820d96b2..6646347fb 100644
--- a/packages/pg-query-stream/test/client-options.js
+++ b/packages/pg-query-stream/test/client-options.ts
@@ -1,17 +1,18 @@
-var pg = require('pg')
-var assert = require('assert')
-var QueryStream = require('../')
+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,
}
- var client = new pg.Client({ types })
+ //@ts-expect-error
+ const client = new pg.Client({ types })
client.connect()
- var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num')
- var query = client.query(stream)
- var result = []
+ 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)
})
diff --git a/packages/pg-query-stream/test/close.js b/packages/pg-query-stream/test/close.ts
similarity index 72%
rename from packages/pg-query-stream/test/close.js
rename to packages/pg-query-stream/test/close.ts
index 4a95464a7..97e4627d9 100644
--- a/packages/pg-query-stream/test/close.js
+++ b/packages/pg-query-stream/test/close.ts
@@ -1,16 +1,18 @@
-var assert = require('assert')
-var concat = require('concat-stream')
-
-var QueryStream = require('../')
-var helper = require('./helper')
+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) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [3], { batchSize: 2, highWaterMark: 2 })
- var query = client.query(stream)
+ 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)
})
@@ -18,12 +20,12 @@ if (process.version.startsWith('v8.')) {
helper('early close', function (client) {
it('can be closed early', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [20000], {
+ const stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [20000], {
batchSize: 2,
highWaterMark: 2,
})
- var query = client.query(stream)
- var readCount = 0
+ const query = client.query(stream)
+ let readCount = 0
query.on('readable', function () {
readCount++
query.read()
@@ -38,7 +40,7 @@ if (process.version.startsWith('v8.')) {
})
it('can destroy stream while reading', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)')
+ 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(() => {
@@ -48,7 +50,7 @@ if (process.version.startsWith('v8.')) {
})
it('emits an error when calling destroy with an error', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)')
+ 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(() => {
@@ -63,7 +65,7 @@ if (process.version.startsWith('v8.')) {
})
it('can destroy stream while reading an error', function (done) {
- var stream = new QueryStream('SELECT * from pg_sleep(1), basdfasdf;')
+ 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', () => {
@@ -74,7 +76,7 @@ if (process.version.startsWith('v8.')) {
})
it('does not crash when destroying the stream immediately after calling read', function (done) {
- var stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);')
+ 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()
@@ -82,7 +84,7 @@ if (process.version.startsWith('v8.')) {
})
it('does not crash when destroying the stream before its submitted', function (done) {
- var stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);')
+ 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.js b/packages/pg-query-stream/test/concat.js
deleted file mode 100644
index 6ce17a28e..000000000
--- a/packages/pg-query-stream/test/concat.js
+++ /dev/null
@@ -1,28 +0,0 @@
-var assert = require('assert')
-var concat = require('concat-stream')
-var through = require('through')
-var helper = require('./helper')
-
-var QueryStream = require('../')
-
-helper('concat', function (client) {
- it('concats correctly', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
- var query = client.query(stream)
- query
- .pipe(
- through(function (row) {
- this.push(row.num)
- })
- )
- .pipe(
- concat(function (result) {
- var 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/concat.ts b/packages/pg-query-stream/test/concat.ts
new file mode 100644
index 000000000..bdfa15862
--- /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.js b/packages/pg-query-stream/test/config.js
deleted file mode 100644
index 061fb1153..000000000
--- a/packages/pg-query-stream/test/config.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var assert = require('assert')
-var QueryStream = require('../')
-
-describe('stream config options', () => {
- // this is mostly for backwards compatability.
- it('sets readable.highWaterMark based on batch size', () => {
- var stream = new QueryStream('SELECT NOW()', [], {
- batchSize: 88,
- })
- assert.equal(stream._readableState.highWaterMark, 88)
- })
-
- it('sets readable.highWaterMark based on highWaterMark config', () => {
- var stream = new QueryStream('SELECT NOW()', [], {
- highWaterMark: 88,
- })
-
- assert.equal(stream._readableState.highWaterMark, 88)
- })
-
- it('defaults to 100 for highWaterMark', () => {
- var stream = new QueryStream('SELECT NOW()', [])
-
- assert.equal(stream._readableState.highWaterMark, 100)
- })
-})
diff --git a/packages/pg-query-stream/test/config.ts b/packages/pg-query-stream/test/config.ts
new file mode 100644
index 000000000..024b3d129
--- /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.js b/packages/pg-query-stream/test/empty-query.ts
similarity index 82%
rename from packages/pg-query-stream/test/empty-query.js
rename to packages/pg-query-stream/test/empty-query.ts
index 25f7d6956..68f137fe0 100644
--- a/packages/pg-query-stream/test/empty-query.js
+++ b/packages/pg-query-stream/test/empty-query.ts
@@ -1,6 +1,5 @@
-const assert = require('assert')
-const helper = require('./helper')
-const QueryStream = require('../')
+import helper from './helper'
+import QueryStream from '../src'
helper('empty-query', function (client) {
it('handles empty query', function (done) {
diff --git a/packages/pg-query-stream/test/error.js b/packages/pg-query-stream/test/error.js
deleted file mode 100644
index 0b732923d..000000000
--- a/packages/pg-query-stream/test/error.js
+++ /dev/null
@@ -1,24 +0,0 @@
-var assert = require('assert')
-var helper = require('./helper')
-
-var QueryStream = require('../')
-
-helper('error', function (client) {
- it('receives error on stream', function (done) {
- var stream = new QueryStream('SELECT * FROM asdf num', [])
- var 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)
- })
-})
diff --git a/packages/pg-query-stream/test/error.ts b/packages/pg-query-stream/test/error.ts
new file mode 100644
index 000000000..5f7a78565
--- /dev/null
+++ b/packages/pg-query-stream/test/error.ts
@@ -0,0 +1,193 @@
+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((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()
+ })
+ client.end()
+ })
+ })
+
+ it('should work if used after timeout error', async () => {
+ const pool = new Pool({ max: 1, connectionTimeoutMillis: 400, statement_timeout: 400 })
+
+ const res1 = await pool.query('SELECT 1 AS a')
+ assert.deepStrictEqual(res1.rows, [{ a: 1 }])
+
+ const query = new QueryStream('SELECT 2 AS b')
+ const client = await pool.connect()
+ const stream = await client.query(query)
+
+ await assert.rejects(() => pool.query('SELECT TRUE'), { message: 'timeout exceeded when trying to connect' })
+
+ await stream.destroy()
+ await client.release()
+
+ const res2 = await pool.query('SELECT 4 AS d')
+ assert.deepStrictEqual(res2.rows, [{ d: 4 }])
+
+ await pool.end()
+ })
+
+ it('should work if used after syntax error', async () => {
+ const pool = new Pool({ max: 1, statement_timeout: 100 }) // statement_timeout is required here, so maybe this is just another timeout error?
+
+ const res1 = await pool.query('SELECT 1 AS a')
+ assert.deepStrictEqual(res1.rows, [{ a: 1 }])
+
+ const query = new QueryStream('SELECT 2 AS b')
+ const client = await pool.connect()
+ const stream = await client.query(query)
+
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ await stream.destroy()
+ await client.release()
+
+ const res2 = await pool.query('SELECT 4 AS d')
+ assert.deepStrictEqual(res2.rows, [{ d: 4 }])
+
+ await pool.end()
+ })
+
+ it('should work after cancelling query', async () => {
+ const pool = new Pool()
+ const conn = await pool.connect()
+
+ // Get connection PID for sake of pg_cancel_backend() call
+ const result = await conn.query('SELECT pg_backend_pid() AS pid;')
+ const { pid } = result.rows[0]
+
+ const stream = conn.query(new QueryStream('SELECT pg_sleep(10);'))
+ stream.on('data', (chunk) => {
+ // Switches stream into readableFlowing === true mode
+ })
+ stream.on('error', (err) => {
+ // Errors are expected due to pg_cancel_backend() call
+ })
+
+ // Create a promise that is resolved when the stream is closed
+ const closed = new Promise((res) => {
+ stream.on('close', res)
+ })
+
+ // Wait 100ms before cancelling the query
+ await new Promise((res) => setTimeout(res, 100))
+
+ // Cancel pg_sleep(10) query
+ await pool.query('SELECT pg_cancel_backend($1);', [pid])
+
+ // Destroy stream and wait for it to be closed
+ stream.destroy()
+ await closed
+
+ // Subsequent query on same connection should succeed
+ const res = await conn.query('SELECT 1 AS a;')
+ assert.deepStrictEqual(res.rows, [{ a: 1 }])
+
+ conn.release()
+ await pool.end()
+ })
+
+ it('does not crash when query_timeout fires without callback', async () => {
+ const client = new Client({ query_timeout: 50 })
+ await client.connect()
+
+ const stream = new QueryStream('SELECT pg_sleep(10)')
+
+ // cursor.on('error') fires synchronously when handleError is called
+ // stream.on('error') fires asynchronously via destroy()
+ // Use cursor for immediate error detection
+ const errorPromise = new Promise((resolve) => {
+ stream.cursor.on('error', resolve)
+ })
+
+ client.query(stream)
+
+ const error = await errorPromise
+ assert.equal(error.message, 'Query read timeout')
+ await client.end()
+ })
+})
diff --git a/packages/pg-query-stream/test/fast-reader.js b/packages/pg-query-stream/test/fast-reader.ts
similarity index 69%
rename from packages/pg-query-stream/test/fast-reader.js
rename to packages/pg-query-stream/test/fast-reader.ts
index 4c6f31f95..5c0c0214a 100644
--- a/packages/pg-query-stream/test/fast-reader.js
+++ b/packages/pg-query-stream/test/fast-reader.ts
@@ -1,14 +1,14 @@
-var assert = require('assert')
-var helper = require('./helper')
-var QueryStream = require('../')
+import assert from 'assert'
+import helper from './helper'
+import QueryStream from '../src'
helper('fast reader', function (client) {
it('works', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
- var query = client.query(stream)
- var result = []
+ const stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
+ const query = client.query(stream)
+ const result = []
stream.on('readable', function () {
- var res = stream.read()
+ let res = stream.read()
while (res) {
if (result.length !== 201) {
assert(res, 'should not return null on evented reader')
@@ -24,7 +24,7 @@ helper('fast reader', function (client) {
}
})
stream.on('end', function () {
- var total = result.reduce(function (prev, cur) {
+ const total = result.reduce(function (prev, cur) {
return prev + cur
})
assert.equal(total, 20100)
diff --git a/packages/pg-query-stream/test/helper.js b/packages/pg-query-stream/test/helper.ts
similarity index 68%
rename from packages/pg-query-stream/test/helper.js
rename to packages/pg-query-stream/test/helper.ts
index ad21d6ea2..9e9b63a94 100644
--- a/packages/pg-query-stream/test/helper.js
+++ b/packages/pg-query-stream/test/helper.ts
@@ -1,7 +1,8 @@
-var pg = require('pg')
-module.exports = function (name, cb) {
+import pg from 'pg'
+
+export default function (name, cb) {
describe(name, function () {
- var client = new pg.Client()
+ const client = new pg.Client()
before(function (done) {
client.connect(done)
diff --git a/packages/pg-query-stream/test/instant.js b/packages/pg-query-stream/test/instant.js
deleted file mode 100644
index 0939753bb..000000000
--- a/packages/pg-query-stream/test/instant.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var assert = require('assert')
-var concat = require('concat-stream')
-
-var QueryStream = require('../')
-
-require('./helper')('instant', function (client) {
- it('instant', function (done) {
- var query = new QueryStream('SELECT pg_sleep(1)', [])
- var stream = client.query(query)
- stream.pipe(
- concat(function (res) {
- assert.equal(res.length, 1)
- done()
- })
- )
- })
-})
diff --git a/packages/pg-query-stream/test/instant.ts b/packages/pg-query-stream/test/instant.ts
new file mode 100644
index 000000000..da4fcad9e
--- /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.js b/packages/pg-query-stream/test/issue-3.ts
similarity index 73%
rename from packages/pg-query-stream/test/issue-3.js
rename to packages/pg-query-stream/test/issue-3.ts
index 7b467a3b3..8c2c04455 100644
--- a/packages/pg-query-stream/test/issue-3.js
+++ b/packages/pg-query-stream/test/issue-3.ts
@@ -1,8 +1,9 @@
-var pg = require('pg')
-var QueryStream = require('../')
+import pg from 'pg'
+import QueryStream from '../src'
+
describe('end semantics race condition', function () {
before(function (done) {
- var client = new pg.Client()
+ const client = new pg.Client()
client.connect()
client.on('drain', client.end.bind(client))
client.on('end', done)
@@ -10,14 +11,14 @@ describe('end semantics race condition', function () {
client.query('create table IF NOT EXISTS c(id int primary key references p)')
})
it('works', function (done) {
- var client1 = new pg.Client()
+ const client1 = new pg.Client()
client1.connect()
- var client2 = new pg.Client()
+ const client2 = new pg.Client()
client2.connect()
- var qr = new QueryStream('INSERT INTO p DEFAULT VALUES RETURNING id')
+ const qr = new QueryStream('INSERT INTO p DEFAULT VALUES RETURNING id')
client1.query(qr)
- var id = null
+ let id = null
qr.on('data', function (row) {
id = row.id
})
diff --git a/packages/pg-query-stream/test/passing-options.js b/packages/pg-query-stream/test/passing-options.ts
similarity index 62%
rename from packages/pg-query-stream/test/passing-options.js
rename to packages/pg-query-stream/test/passing-options.ts
index 858767de2..7aa924a04 100644
--- a/packages/pg-query-stream/test/passing-options.js
+++ b/packages/pg-query-stream/test/passing-options.ts
@@ -1,12 +1,12 @@
-var assert = require('assert')
-var helper = require('./helper')
-var QueryStream = require('../')
+import assert from 'assert'
+import helper from './helper'
+import QueryStream from '../src'
helper('passing options', function (client) {
it('passes row mode array', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { rowMode: 'array' })
- var query = client.query(stream)
- var result = []
+ 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)
})
@@ -21,9 +21,9 @@ helper('passing options', function (client) {
const types = {
getTypeParser: () => (string) => string,
}
- var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { types })
- var query = client.query(stream)
- var result = []
+ 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)
})
diff --git a/packages/pg-query-stream/test/pauses.js b/packages/pg-query-stream/test/pauses.js
deleted file mode 100644
index 3da9a0b07..000000000
--- a/packages/pg-query-stream/test/pauses.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var concat = require('concat-stream')
-var tester = require('stream-tester')
-var JSONStream = require('JSONStream')
-
-var QueryStream = require('../')
-
-require('./helper')('pauses', function (client) {
- it('pauses', function (done) {
- this.timeout(5000)
- var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [200], { batchSize: 2, highWaterMark: 2 })
- var query = client.query(stream)
- var 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/pauses.ts b/packages/pg-query-stream/test/pauses.ts
new file mode 100644
index 000000000..4906341f8
--- /dev/null
+++ b/packages/pg-query-stream/test/pauses.ts
@@ -0,0 +1,37 @@
+import helper from './helper'
+import concat from 'concat-stream'
+import JSONStream from 'JSONStream'
+import QueryStream from '../src'
+import { Transform } from 'stream'
+
+class PauseStream extends Transform {
+ constructor() {
+ super({ objectMode: true })
+ }
+
+ _transform(chunk, encoding, callback): void {
+ this.push(chunk, encoding)
+ setTimeout(callback, 1)
+ }
+}
+
+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 = new PauseStream()
+ query
+ .pipe(JSONStream.stringify())
+ .pipe(pauser)
+ .pipe(
+ concat(function (json) {
+ JSON.parse(json)
+ done()
+ })
+ )
+ })
+})
diff --git a/packages/pg-query-stream/test/pool.ts b/packages/pg-query-stream/test/pool.ts
new file mode 100644
index 000000000..06adf8e18
--- /dev/null
+++ b/packages/pg-query-stream/test/pool.ts
@@ -0,0 +1,17 @@
+import { Pool } from 'pg'
+import QueryStream from '../src'
+
+describe('pool', function () {
+ it('works', async function () {
+ const pool = new Pool()
+ const query = new QueryStream('SELECT * FROM generate_series(0, 10) num', [])
+ const q = pool.query(query)
+ query.on('data', (row) => {
+ // just consume the whole stream
+ })
+ await q
+ query.on('end', () => {
+ pool.end()
+ })
+ })
+})
diff --git a/packages/pg-query-stream/test/slow-reader.js b/packages/pg-query-stream/test/slow-reader.ts
similarity index 61%
rename from packages/pg-query-stream/test/slow-reader.js
rename to packages/pg-query-stream/test/slow-reader.ts
index 3978f3004..a62c0c20c 100644
--- a/packages/pg-query-stream/test/slow-reader.js
+++ b/packages/pg-query-stream/test/slow-reader.ts
@@ -1,10 +1,10 @@
-var helper = require('./helper')
-var QueryStream = require('../')
-var concat = require('concat-stream')
+import helper from './helper'
+import QueryStream from '../src'
+import concat from 'concat-stream'
-var Transform = require('stream').Transform
+import { Transform } from 'stream'
-var mapper = new Transform({ objectMode: true })
+const mapper = new Transform({ objectMode: true })
mapper._transform = function (obj, enc, cb) {
this.push(obj)
@@ -14,7 +14,7 @@ mapper._transform = function (obj, enc, cb) {
helper('slow reader', function (client) {
it('works', function (done) {
this.timeout(50000)
- var stream = new QueryStream('SELECT * FROM generate_series(0, 201) num', [], {
+ const stream = new QueryStream('SELECT * FROM generate_series(0, 201) num', [], {
highWaterMark: 100,
batchSize: 50,
})
diff --git a/packages/pg-query-stream/test/stream-tester-timestamp.js b/packages/pg-query-stream/test/stream-tester-timestamp.js
deleted file mode 100644
index ce989cc3f..000000000
--- a/packages/pg-query-stream/test/stream-tester-timestamp.js
+++ /dev/null
@@ -1,25 +0,0 @@
-var QueryStream = require('../')
-var spec = require('stream-spec')
-var assert = require('assert')
-
-require('./helper')('stream tester timestamp', function (client) {
- it('should not warn about max listeners', function (done) {
- var sql = "SELECT * FROM generate_series('1983-12-30 00:00'::timestamp, '2013-12-30 00:00', '1 years')"
- var stream = new QueryStream(sql, [])
- var ended = false
- var query = client.query(stream)
- query.on('end', function () {
- ended = true
- })
- spec(query).readable().pausable({ strict: true }).validateOnExit()
- var 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-timestamp.ts b/packages/pg-query-stream/test/stream-tester-timestamp.ts
new file mode 100644
index 000000000..9819ba491
--- /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.js b/packages/pg-query-stream/test/stream-tester.js
deleted file mode 100644
index f5ab2e372..000000000
--- a/packages/pg-query-stream/test/stream-tester.js
+++ /dev/null
@@ -1,12 +0,0 @@
-var spec = require('stream-spec')
-
-var QueryStream = require('../')
-
-require('./helper')('stream tester', function (client) {
- it('passes stream spec', function (done) {
- var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
- var query = client.query(stream)
- spec(query).readable().pausable({ strict: true }).validateOnExit()
- stream.on('end', done)
- })
-})
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 000000000..01c68275c
--- /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 000000000..e9c97b335
--- /dev/null
+++ b/packages/pg-query-stream/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "module": "node16",
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": false,
+ "target": "es6",
+ "noImplicitAny": false,
+ "moduleResolution": "node16",
+ "sourceMap": true,
+ "pretty": true,
+ "outDir": "dist",
+ "rootDir": "./src",
+ "incremental": true,
+ "declaration": true,
+ "types": [
+ "node",
+ "pg",
+ "mocha",
+ "chai"
+ ]
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/packages/pg/Makefile b/packages/pg/Makefile
index e05edbf49..2979dd750 100644
--- a/packages/pg/Makefile
+++ b/packages/pg/Makefile
@@ -6,8 +6,8 @@ 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
+.PHONY : test test-integration bench test-native \
+ publish update-npm
all:
npm install
@@ -17,7 +17,7 @@ help:
test: test-unit
-test-all: test-missing-native test-unit test-integration test-native
+test-all: test-unit test-integration test-native test-worker
update-npm:
@@ -27,35 +27,35 @@ bench:
@find benchmark -name "*-bench.js" | $(node-command)
test-unit:
+ @chmod 600 test/unit/client/pgpass.file
@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
+test-native:
@echo "***Testing native bindings***"
+ifeq ($(TEST_SKIP_NATIVE), true)
+ @echo "***Skipping tests***"
+else
@find test/native -name "*-tests.js" | $(node-command)
@find test/integration -name "*-tests.js" | $(node-command) native
+endif
-test-integration: test-connection
+test-integration:
@echo "***Testing Pure Javascript***"
@find test/integration -name "*-tests.js" | $(node-command)
-test-binary: test-connection
+test-binary:
@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
+
+test-worker:
+ # this command only runs in node 18.x and above since there are
+ # worker specific items missing from the node environment in lower versions
+ @if [[ $(shell node --version | sed 's/v//' | cut -d'.' -f1) -ge 18 ]]; then \
+ echo "***Testing Cloudflare Worker support***"; \
+ yarn vitest run -c test/vitest.config.mts test/cloudflare/ --no-watch -- $(params); \
+ else \
+ echo "Skipping test-worker: Node.js version is less than 18."; \
+ fi
diff --git a/packages/pg/README.md b/packages/pg/README.md
index ed4d7a626..75242374c 100644
--- a/packages/pg/README.md
+++ b/packages/pg/README.md
@@ -1,7 +1,6 @@
# node-postgres
[](http://travis-ci.org/brianc/node-postgres)
-[](https://david-dm.org/brianc/node-postgres?path=packages/pg)
@@ -43,20 +42,20 @@ When you open an issue please provide:
- version of Postgres
- smallest possible snippet of code to reproduce the problem
-You can also follow me [@briancarlson](https://twitter.com/briancarlson) if that's your thing. I try to always announce noteworthy changes & developments with node-postgres on Twitter.
+You can also follow me [@brianc](https://bsky.app/profile/brianc.bsky.social) on bluesky if that's your thing for updates on node-postgres with nearly zero non node-postgres content. My old twitter/x account is no longer used.
## Sponsorship :two_hearts:
-node-postgres's continued development has been made possible in part by generous finanical support from [the community](https://github.com/brianc/node-postgres/blob/master/SPONSORS.md) and these featured sponsors:
-
-