From a0aec8bfae04e6726ad9397fc20a735f65b9d907 Mon Sep 17 00:00:00 2001 From: Automated Action Date: Sat, 21 Jun 2025 17:46:30 +0000 Subject: [PATCH] Configure application to use PostgreSQL instead of SQLite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated package.json to use PostgreSQL dependencies (pg, @types/pg) - Removed SQLite dependency - Updated TypeORM configuration for PostgreSQL connection - Modified database entities to use PostgreSQL-compatible column types (jsonb, timestamp) - Created comprehensive database migration script for initial schema - Updated environment configuration with PostgreSQL variables - Updated README with PostgreSQL setup instructions and database migration steps - Ensured storage directories are properly configured 🤖 Generated with BackendIM Co-Authored-By: Claude --- README.md | 44 +- node_modules/.package-lock.json | 294 ++++++++- node_modules/@types/pg/LICENSE | 21 + node_modules/@types/pg/README.md | 15 + node_modules/@types/pg/index.d.mts | 53 ++ node_modules/@types/pg/index.d.ts | 341 ++++++++++ node_modules/@types/pg/package.json | 38 ++ node_modules/pg-cloudflare/LICENSE | 21 + node_modules/pg-cloudflare/README.md | 107 ++++ node_modules/pg-cloudflare/esm/index.mjs | 3 + node_modules/pg-cloudflare/package.json | 38 ++ node_modules/pg-cloudflare/src/empty.ts | 3 + node_modules/pg-cloudflare/src/index.ts | 166 +++++ node_modules/pg-cloudflare/src/types.d.ts | 25 + node_modules/pg-connection-string/LICENSE | 21 + node_modules/pg-connection-string/README.md | 105 +++ .../pg-connection-string/esm/index.mjs | 8 + node_modules/pg-connection-string/index.d.ts | 36 ++ node_modules/pg-connection-string/index.js | 213 +++++++ .../pg-connection-string/package.json | 52 ++ node_modules/pg-int8/LICENSE | 13 + node_modules/pg-int8/README.md | 16 + node_modules/pg-int8/index.js | 100 +++ node_modules/pg-int8/package.json | 24 + node_modules/pg-pool/LICENSE | 21 + node_modules/pg-pool/README.md | 376 +++++++++++ node_modules/pg-pool/esm/index.mjs | 5 + node_modules/pg-pool/index.js | 479 ++++++++++++++ node_modules/pg-pool/package.json | 51 ++ node_modules/pg-protocol/LICENSE | 21 + node_modules/pg-protocol/README.md | 3 + node_modules/pg-protocol/esm/index.js | 11 + node_modules/pg-protocol/package.json | 48 ++ node_modules/pg-protocol/src/b.ts | 25 + node_modules/pg-protocol/src/buffer-reader.ts | 60 ++ node_modules/pg-protocol/src/buffer-writer.ts | 85 +++ .../pg-protocol/src/inbound-parser.test.ts | 568 +++++++++++++++++ node_modules/pg-protocol/src/index.ts | 11 + node_modules/pg-protocol/src/messages.ts | 262 ++++++++ .../src/outbound-serializer.test.ts | 276 ++++++++ node_modules/pg-protocol/src/parser.ts | 389 ++++++++++++ node_modules/pg-protocol/src/serializer.ts | 274 ++++++++ .../pg-protocol/src/testing/buffer-list.ts | 67 ++ .../pg-protocol/src/testing/test-buffers.ts | 166 +++++ .../pg-protocol/src/types/chunky.d.ts | 1 + node_modules/pg-types/.travis.yml | 7 + node_modules/pg-types/Makefile | 14 + node_modules/pg-types/README.md | 75 +++ node_modules/pg-types/index.d.ts | 137 ++++ node_modules/pg-types/index.js | 47 ++ node_modules/pg-types/index.test-d.ts | 21 + node_modules/pg-types/package.json | 42 ++ node_modules/pg-types/test/index.js | 24 + node_modules/pg-types/test/types.js | 597 ++++++++++++++++++ node_modules/pg/LICENSE | 21 + node_modules/pg/README.md | 95 +++ node_modules/pg/esm/index.mjs | 20 + node_modules/pg/package.json | 79 +++ node_modules/pgpass/README.md | 74 +++ node_modules/pgpass/package.json | 41 ++ node_modules/postgres-array/index.d.ts | 4 + node_modules/postgres-array/index.js | 97 +++ node_modules/postgres-array/license | 21 + node_modules/postgres-array/package.json | 35 + node_modules/postgres-array/readme.md | 43 ++ node_modules/postgres-bytea/index.js | 31 + node_modules/postgres-bytea/license | 21 + node_modules/postgres-bytea/package.json | 34 + node_modules/postgres-bytea/readme.md | 34 + node_modules/postgres-date/index.js | 116 ++++ node_modules/postgres-date/license | 21 + node_modules/postgres-date/package.json | 33 + node_modules/postgres-date/readme.md | 49 ++ node_modules/postgres-interval/index.d.ts | 20 + node_modules/postgres-interval/index.js | 125 ++++ node_modules/postgres-interval/license | 21 + node_modules/postgres-interval/package.json | 36 ++ node_modules/postgres-interval/readme.md | 48 ++ node_modules/split2/LICENSE | 13 + node_modules/split2/README.md | 85 +++ node_modules/split2/bench.js | 27 + node_modules/split2/index.js | 141 +++++ node_modules/split2/package.json | 39 ++ node_modules/split2/test.js | 409 ++++++++++++ package-lock.json | 297 ++++++++- package.json | 3 +- src/app.module.ts | 11 +- src/database/database.config.ts | 21 + src/database/entities/message.entity.ts | 2 +- src/database/entities/notification.entity.ts | 2 +- .../entities/organization-invite.entity.ts | 2 +- .../entities/organization-member.entity.ts | 6 +- src/database/entities/organization.entity.ts | 6 +- src/database/entities/user.entity.ts | 2 +- src/database/migrations/001-initial-schema.ts | 380 +++++++++++ 95 files changed, 8428 insertions(+), 57 deletions(-) create mode 100644 node_modules/@types/pg/LICENSE create mode 100644 node_modules/@types/pg/README.md create mode 100644 node_modules/@types/pg/index.d.mts create mode 100644 node_modules/@types/pg/index.d.ts create mode 100644 node_modules/@types/pg/package.json create mode 100644 node_modules/pg-cloudflare/LICENSE create mode 100644 node_modules/pg-cloudflare/README.md create mode 100644 node_modules/pg-cloudflare/esm/index.mjs create mode 100644 node_modules/pg-cloudflare/package.json create mode 100644 node_modules/pg-cloudflare/src/empty.ts create mode 100644 node_modules/pg-cloudflare/src/index.ts create mode 100644 node_modules/pg-cloudflare/src/types.d.ts create mode 100644 node_modules/pg-connection-string/LICENSE create mode 100644 node_modules/pg-connection-string/README.md create mode 100644 node_modules/pg-connection-string/esm/index.mjs create mode 100644 node_modules/pg-connection-string/index.d.ts create mode 100644 node_modules/pg-connection-string/index.js create mode 100644 node_modules/pg-connection-string/package.json create mode 100644 node_modules/pg-int8/LICENSE create mode 100644 node_modules/pg-int8/README.md create mode 100644 node_modules/pg-int8/index.js create mode 100644 node_modules/pg-int8/package.json create mode 100644 node_modules/pg-pool/LICENSE create mode 100644 node_modules/pg-pool/README.md create mode 100644 node_modules/pg-pool/esm/index.mjs create mode 100644 node_modules/pg-pool/index.js create mode 100644 node_modules/pg-pool/package.json create mode 100644 node_modules/pg-protocol/LICENSE create mode 100644 node_modules/pg-protocol/README.md create mode 100644 node_modules/pg-protocol/esm/index.js create mode 100644 node_modules/pg-protocol/package.json create mode 100644 node_modules/pg-protocol/src/b.ts create mode 100644 node_modules/pg-protocol/src/buffer-reader.ts create mode 100644 node_modules/pg-protocol/src/buffer-writer.ts create mode 100644 node_modules/pg-protocol/src/inbound-parser.test.ts create mode 100644 node_modules/pg-protocol/src/index.ts create mode 100644 node_modules/pg-protocol/src/messages.ts create mode 100644 node_modules/pg-protocol/src/outbound-serializer.test.ts create mode 100644 node_modules/pg-protocol/src/parser.ts create mode 100644 node_modules/pg-protocol/src/serializer.ts create mode 100644 node_modules/pg-protocol/src/testing/buffer-list.ts create mode 100644 node_modules/pg-protocol/src/testing/test-buffers.ts create mode 100644 node_modules/pg-protocol/src/types/chunky.d.ts create mode 100644 node_modules/pg-types/.travis.yml create mode 100644 node_modules/pg-types/Makefile create mode 100644 node_modules/pg-types/README.md create mode 100644 node_modules/pg-types/index.d.ts create mode 100644 node_modules/pg-types/index.js create mode 100644 node_modules/pg-types/index.test-d.ts create mode 100644 node_modules/pg-types/package.json create mode 100644 node_modules/pg-types/test/index.js create mode 100644 node_modules/pg-types/test/types.js create mode 100644 node_modules/pg/LICENSE create mode 100644 node_modules/pg/README.md create mode 100644 node_modules/pg/esm/index.mjs create mode 100644 node_modules/pg/package.json create mode 100644 node_modules/pgpass/README.md create mode 100644 node_modules/pgpass/package.json create mode 100644 node_modules/postgres-array/index.d.ts create mode 100644 node_modules/postgres-array/index.js create mode 100644 node_modules/postgres-array/license create mode 100644 node_modules/postgres-array/package.json create mode 100644 node_modules/postgres-array/readme.md create mode 100644 node_modules/postgres-bytea/index.js create mode 100644 node_modules/postgres-bytea/license create mode 100644 node_modules/postgres-bytea/package.json create mode 100644 node_modules/postgres-bytea/readme.md create mode 100644 node_modules/postgres-date/index.js create mode 100644 node_modules/postgres-date/license create mode 100644 node_modules/postgres-date/package.json create mode 100644 node_modules/postgres-date/readme.md create mode 100644 node_modules/postgres-interval/index.d.ts create mode 100644 node_modules/postgres-interval/index.js create mode 100644 node_modules/postgres-interval/license create mode 100644 node_modules/postgres-interval/package.json create mode 100644 node_modules/postgres-interval/readme.md create mode 100644 node_modules/split2/LICENSE create mode 100644 node_modules/split2/README.md create mode 100644 node_modules/split2/bench.js create mode 100644 node_modules/split2/index.js create mode 100644 node_modules/split2/package.json create mode 100644 node_modules/split2/test.js create mode 100644 src/database/database.config.ts create mode 100644 src/database/migrations/001-initial-schema.ts diff --git a/README.md b/README.md index 471ccec7..e65757dc 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ A comprehensive real-time chat API built with NestJS (TypeScript) featuring WebS ## Tech Stack - **Framework**: NestJS (TypeScript) -- **Database**: SQLite with TypeORM +- **Database**: PostgreSQL with TypeORM - **Real-time**: Socket.IO - **Authentication**: JWT with Passport - **File Storage**: Local filesystem @@ -91,18 +91,45 @@ src/ npm install ``` -### 2. Environment Configuration +### 2. Database Setup +First, ensure PostgreSQL is installed and running on your system. Create a database for the application: + +```bash +# Connect to PostgreSQL (adjust connection details as needed) +psql -U postgres -h localhost + +# Create database +CREATE DATABASE realtime_chat_db; + +# Exit PostgreSQL +\q +``` + +### 3. Environment Configuration Copy the example environment file and configure it: ```bash cp .env.example .env ``` Required environment variables: +- `DB_HOST`: PostgreSQL host (default: localhost) +- `DB_PORT`: PostgreSQL port (default: 5432) +- `DB_USERNAME`: PostgreSQL username (default: postgres) +- `DB_PASSWORD`: PostgreSQL password (default: postgres) +- `DB_NAME`: PostgreSQL database name (default: realtime_chat_db) - `PORT`: Server port (default: 3000) - `JWT_SECRET`: Secret key for JWT tokens +- `ENCRYPTION_KEY`: 32-character key for end-to-end encryption - `FIREBASE_SERVICE_ACCOUNT`: Firebase service account JSON for push notifications -### 3. Start the Application +### 4. Database Migration +Run the database migrations to set up the schema: +```bash +# Run migrations +npm run migration:run +``` + +### 5. Start the Application ```bash # Development npm run start:dev @@ -255,7 +282,7 @@ Socket.IO events are designed to map directly to Flutter state management patter ## Database Schema -The application uses SQLite with the following main entities: +The application uses PostgreSQL with the following main entities: - **Organizations**: Multi-tenant organization containers - **OrganizationMembers**: User membership in organizations with roles - **OrganizationInvites**: Email-based invitation system @@ -322,19 +349,26 @@ npm run build | Variable | Description | Required | Default | |----------|-------------|----------|---------| +| `DB_HOST` | PostgreSQL host | No | localhost | +| `DB_PORT` | PostgreSQL port | No | 5432 | +| `DB_USERNAME` | PostgreSQL username | No | postgres | +| `DB_PASSWORD` | PostgreSQL password | No | postgres | +| `DB_NAME` | PostgreSQL database name | No | realtime_chat_db | | `PORT` | Server port | No | 3000 | | `NODE_ENV` | Environment | No | development | | `JWT_SECRET` | JWT secret key | Yes | - | +| `ENCRYPTION_KEY` | 32-character encryption key | Yes | - | | `FIREBASE_SERVICE_ACCOUNT` | Firebase config JSON | No | - | ## Storage The application uses local filesystem storage: -- **Database**: `/app/storage/db/chat.sqlite` - **Media Files**: `/app/storage/media/` - **User Avatars**: `/app/storage/media/avatars/` - **Message Media**: `/app/storage/media/messages/` +Database is stored in PostgreSQL server as configured in environment variables. + ## Health Check The API includes a comprehensive health check endpoint at `/health` that reports: diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 60904394..1e2d9050 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -952,7 +952,8 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@google-cloud/firestore": { "version": "6.8.0", @@ -2393,6 +2394,7 @@ "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" @@ -2405,6 +2407,7 @@ "deprecated": "This functionality has been moved to @npmcli/fs", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -2419,6 +2422,7 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -2620,6 +2624,7 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 6" } @@ -3005,6 +3010,18 @@ "@types/passport": "*" } }, + "node_modules/@types/pg": { + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", + "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -3582,6 +3599,7 @@ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -3595,6 +3613,7 @@ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -4065,6 +4084,8 @@ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -4073,6 +4094,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "devOptional": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -4208,6 +4230,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -4266,6 +4289,7 @@ "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -4296,6 +4320,7 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4308,6 +4333,7 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4329,6 +4355,7 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4342,6 +4369,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4355,6 +4383,7 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -4587,6 +4616,7 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -5038,6 +5068,8 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -5067,6 +5099,8 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4.0.0" } @@ -5356,6 +5390,7 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -5366,6 +5401,7 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -5378,6 +5414,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", + "optional": true, "dependencies": { "once": "^1.4.0" } @@ -5486,6 +5523,7 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -5495,7 +5533,8 @@ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/error-ex": { "version": "1.3.2", @@ -6029,6 +6068,8 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -6315,7 +6356,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/filelist": { "version": "1.0.4", @@ -6572,7 +6615,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/fs-extra": { "version": "10.1.0", @@ -6782,7 +6827,9 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/glob": { "version": "10.4.5", @@ -7101,7 +7148,8 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause", - "optional": true + "optional": true, + "peer": true }, "node_modules/http-errors": { "version": "2.0.0", @@ -7131,6 +7179,7 @@ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@tootallnate/once": "1", "agent-base": "6", @@ -7169,6 +7218,7 @@ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -7268,6 +7318,7 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -7277,7 +7328,8 @@ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/inflight": { "version": "1.0.6", @@ -7300,7 +7352,9 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/inquirer": { "version": "8.2.6", @@ -7335,6 +7389,7 @@ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -7445,7 +7500,8 @@ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/is-number": { "version": "7.0.0", @@ -8447,7 +8503,8 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/jsdoc": { "version": "4.0.4", @@ -8957,6 +9014,7 @@ "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", @@ -8985,6 +9043,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9203,6 +9262,8 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -9250,6 +9311,7 @@ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9263,6 +9325,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9276,6 +9339,7 @@ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", @@ -9294,6 +9358,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9307,6 +9372,7 @@ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9320,6 +9386,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9333,6 +9400,7 @@ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9346,6 +9414,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9359,6 +9428,7 @@ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9372,6 +9442,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9420,7 +9491,9 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/moment": { "version": "2.30.1", @@ -9467,7 +9540,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -9497,6 +9572,8 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "semver": "^7.3.5" }, @@ -9562,6 +9639,7 @@ "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", @@ -9588,6 +9666,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -9602,6 +9681,7 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9614,6 +9694,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", @@ -9635,6 +9716,7 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9656,6 +9738,7 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9670,6 +9753,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", @@ -9685,7 +9769,8 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/node-int64": { "version": "0.4.0", @@ -9909,6 +9994,7 @@ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -10101,6 +10187,95 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/pg": { + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz", + "integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.2", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.6" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz", + "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.2.tgz", + "integrity": "sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10210,11 +10385,52 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -10314,7 +10530,8 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/promise-retry": { "version": "2.0.1", @@ -10322,6 +10539,7 @@ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -10336,6 +10554,7 @@ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -10483,6 +10702,8 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10597,6 +10818,8 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "peer": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10612,6 +10835,8 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11290,7 +11515,9 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -11311,6 +11538,8 @@ } ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -11340,6 +11569,7 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -11443,6 +11673,7 @@ "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -11458,6 +11689,7 @@ "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", @@ -11498,12 +11730,22 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause", - "optional": true + "optional": true, + "peer": true }, "node_modules/sql-highlight": { "version": "6.1.0", @@ -11527,6 +11769,8 @@ "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", "hasInstallScript": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", @@ -11549,7 +11793,9 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/ssri": { "version": "8.0.1", @@ -11557,6 +11803,7 @@ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.1.1" }, @@ -11570,6 +11817,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -11919,6 +12167,8 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -11930,13 +12180,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -12475,6 +12729,8 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -12769,6 +13025,7 @@ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "unique-slug": "^2.0.0" } @@ -12779,6 +13036,7 @@ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "imurmurhash": "^0.1.4" } diff --git a/node_modules/@types/pg/LICENSE b/node_modules/@types/pg/LICENSE new file mode 100644 index 00000000..9e841e7a --- /dev/null +++ b/node_modules/@types/pg/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + 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/node_modules/@types/pg/README.md b/node_modules/@types/pg/README.md new file mode 100644 index 00000000..94a8b19f --- /dev/null +++ b/node_modules/@types/pg/README.md @@ -0,0 +1,15 @@ +# Installation +> `npm install --save @types/pg` + +# Summary +This package contains type definitions for pg (https://github.com/brianc/node-postgres). + +# Details +Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/pg. + +### Additional Details + * Last updated: Sun, 01 Jun 2025 20:35:13 GMT + * Dependencies: [@types/node](https://npmjs.com/package/@types/node), [pg-protocol](https://npmjs.com/package/pg-protocol), [pg-types](https://npmjs.com/package/pg-types) + +# Credits +These definitions were written by [Phips Peter](https://github.com/pspeter3). diff --git a/node_modules/@types/pg/index.d.mts b/node_modules/@types/pg/index.d.mts new file mode 100644 index 00000000..9e596e1a --- /dev/null +++ b/node_modules/@types/pg/index.d.mts @@ -0,0 +1,53 @@ +import * as pg from "./index.js"; + +declare const PG: { + defaults: typeof pg.defaults; + Client: typeof pg.Client; + Query: typeof pg.Query; + Pool: typeof pg.Pool; + Connection: typeof pg.Connection; + types: typeof pg.types; + DatabaseError: typeof pg.DatabaseError; + TypeOverrides: typeof pg.TypeOverrides; + escapeIdentifier: typeof pg.escapeIdentifier; + escapeLiteral: typeof pg.escapeLiteral; + Result: typeof pg.Result; +}; + +declare namespace PG { + type QueryConfigValues = pg.QueryConfigValues; + type ClientConfig = pg.ClientConfig; + type ConnectionConfig = pg.ConnectionConfig; + type Defaults = pg.Defaults; + type PoolConfig = pg.PoolConfig; + type QueryConfig = pg.QueryConfig; + type CustomTypesConfig = pg.CustomTypesConfig; + type Submittable = pg.Submittable; + type QueryArrayConfig = pg.QueryArrayConfig; + type FieldDef = pg.FieldDef; + type QueryResultBase = pg.QueryResultBase; + type QueryResultRow = pg.QueryResultRow; + type QueryResult = pg.QueryResult; + type QueryArrayResult = pg.QueryArrayResult; + type Notification = pg.Notification; + type ResultBuilder = pg.ResultBuilder; + type QueryParse = pg.QueryParse; + type BindConfig = pg.BindConfig; + type ExecuteConfig = pg.ExecuteConfig; + type MessageConfig = pg.MessageConfig; + type PoolOptions = pg.PoolOptions; + type PoolClient = pg.PoolClient; + + type ClientBase = pg.ClientBase; + type Client = pg.Client; + type Query = pg.Query; + type Events = pg.Events; + type Pool = pg.Pool; + type Connection = pg.Connection; + type DatabaseError = pg.DatabaseError; + type TypeOverrides = pg.TypeOverrides; + type Result = pg.Result; +} + +export * from "./index.js"; +export default PG; diff --git a/node_modules/@types/pg/index.d.ts b/node_modules/@types/pg/index.d.ts new file mode 100644 index 00000000..c9c89ae1 --- /dev/null +++ b/node_modules/@types/pg/index.d.ts @@ -0,0 +1,341 @@ +/// + +import events = require("events"); +import stream = require("stream"); +import pgTypes = require("pg-types"); +import { NoticeMessage } from "pg-protocol/dist/messages.js"; + +import { ConnectionOptions } from "tls"; + +export type QueryConfigValues = T extends Array ? T : never; + +export interface ClientConfig { + user?: string | undefined; + database?: string | undefined; + password?: string | (() => string | Promise) | undefined; + port?: number | undefined; + host?: string | undefined; + connectionString?: string | undefined; + keepAlive?: boolean | undefined; + stream?: () => stream.Duplex | undefined; + statement_timeout?: false | number | undefined; + ssl?: boolean | ConnectionOptions | undefined; + query_timeout?: number | undefined; + lock_timeout?: number | undefined; + keepAliveInitialDelayMillis?: number | undefined; + idle_in_transaction_session_timeout?: number | undefined; + application_name?: string | undefined; + fallback_application_name?: string | undefined; + connectionTimeoutMillis?: number | undefined; + types?: CustomTypesConfig | undefined; + options?: string | undefined; + client_encoding?: string | undefined; +} + +export type ConnectionConfig = ClientConfig; + +export interface Defaults extends ClientConfig { + poolSize?: number | undefined; + poolIdleTimeout?: number | undefined; + reapIntervalMillis?: number | undefined; + binary?: boolean | undefined; + parseInt8?: boolean | undefined; + parseInputDatesAsUTC?: boolean | undefined; +} + +export interface PoolConfig extends ClientConfig { + // properties from module 'pg-pool' + max?: number | undefined; + min?: number | undefined; + idleTimeoutMillis?: number | undefined | null; + log?: ((...messages: any[]) => void) | undefined; + Promise?: PromiseConstructorLike | undefined; + allowExitOnIdle?: boolean | undefined; + maxUses?: number | undefined; + maxLifetimeSeconds?: number | undefined; + Client?: (new() => ClientBase) | undefined; +} + +export interface QueryConfig { + name?: string | undefined; + text: string; + values?: QueryConfigValues; + types?: CustomTypesConfig | undefined; +} + +export interface CustomTypesConfig { + getTypeParser: typeof pgTypes.getTypeParser; +} + +export interface Submittable { + submit: (connection: Connection) => void; +} + +export interface QueryArrayConfig extends QueryConfig { + rowMode: "array"; +} + +export interface FieldDef { + name: string; + tableID: number; + columnID: number; + dataTypeID: number; + dataTypeSize: number; + dataTypeModifier: number; + format: string; +} + +export interface QueryResultBase { + command: string; + rowCount: number | null; + oid: number; + fields: FieldDef[]; +} + +export interface QueryResultRow { + [column: string]: any; +} + +export interface QueryResult extends QueryResultBase { + rows: R[]; +} + +export interface QueryArrayResult extends QueryResultBase { + rows: R[]; +} + +export interface Notification { + processId: number; + channel: string; + payload?: string | undefined; +} + +export interface ResultBuilder extends QueryResult { + addRow(row: R): void; +} + +export interface QueryParse { + name: string; + text: string; + types: string[]; +} + +type ValueMapper = (param: any, index: number) => any; + +export interface BindConfig { + portal?: string | undefined; + statement?: string | undefined; + binary?: string | undefined; + values?: Array | undefined; + valueMapper?: ValueMapper | undefined; +} + +export interface ExecuteConfig { + portal?: string | undefined; + rows?: string | undefined; +} + +export interface MessageConfig { + type: string; + name?: string | undefined; +} + +export function escapeIdentifier(str: string): string; + +export function escapeLiteral(str: string): string; + +export class Connection extends events.EventEmitter { + readonly stream: stream.Duplex; + + constructor(config?: ConnectionConfig); + + bind(config: BindConfig | null, more: boolean): void; + execute(config: ExecuteConfig | null, more: boolean): void; + parse(query: QueryParse, more: boolean): void; + + query(text: string): void; + + describe(msg: MessageConfig, more: boolean): void; + close(msg: MessageConfig, more: boolean): void; + + flush(): void; + sync(): void; + end(): void; +} + +export interface PoolOptions extends PoolConfig { + max: number; + maxUses: number; + allowExitOnIdle: boolean; + maxLifetimeSeconds: number; + idleTimeoutMillis: number | null; +} + +/** + * {@link https://node-postgres.com/apis/pool} + */ +export class Pool extends events.EventEmitter { + /** + * 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. + */ + constructor(config?: PoolConfig); + + readonly totalCount: number; + readonly idleCount: number; + readonly waitingCount: number; + readonly expiredCount: number; + + readonly ending: boolean; + readonly ended: boolean; + + options: PoolOptions; + + connect(): Promise; + connect( + callback: (err: Error | undefined, client: PoolClient | undefined, done: (release?: any) => void) => void, + ): void; + + end(): Promise; + end(callback: () => void): void; + + query(queryStream: T): T; + // tslint:disable:no-unnecessary-generics + query( + queryConfig: QueryArrayConfig, + values?: QueryConfigValues, + ): Promise>; + query( + queryConfig: QueryConfig, + ): Promise>; + query( + queryTextOrConfig: string | QueryConfig, + values?: QueryConfigValues, + ): Promise>; + query( + queryConfig: QueryArrayConfig, + callback: (err: Error, result: QueryArrayResult) => void, + ): void; + query( + queryTextOrConfig: string | QueryConfig, + callback: (err: Error, result: QueryResult) => void, + ): void; + query( + queryText: string, + values: QueryConfigValues, + callback: (err: Error, result: QueryResult) => void, + ): void; + // tslint:enable:no-unnecessary-generics + + on(event: "release" | "error", listener: (err: Error, client: PoolClient) => void): this; + on(event: "connect" | "acquire" | "remove", listener: (client: PoolClient) => void): this; +} + +export class ClientBase extends events.EventEmitter { + constructor(config?: string | ClientConfig); + + connect(): Promise; + connect(callback: (err: Error) => void): void; + + query(queryStream: T): T; + // tslint:disable:no-unnecessary-generics + query( + queryConfig: QueryArrayConfig, + values?: QueryConfigValues, + ): Promise>; + query( + queryConfig: QueryConfig, + ): Promise>; + query( + queryTextOrConfig: string | QueryConfig, + values?: QueryConfigValues, + ): Promise>; + query( + queryConfig: QueryArrayConfig, + callback: (err: Error, result: QueryArrayResult) => void, + ): void; + query( + queryTextOrConfig: string | QueryConfig, + callback: (err: Error, result: QueryResult) => void, + ): void; + query( + queryText: string, + values: QueryConfigValues, + callback: (err: Error, result: QueryResult) => void, + ): void; + // tslint:enable:no-unnecessary-generics + + copyFrom(queryText: string): stream.Writable; + copyTo(queryText: string): stream.Readable; + + pauseDrain(): void; + resumeDrain(): void; + + escapeIdentifier: typeof escapeIdentifier; + escapeLiteral: typeof escapeLiteral; + setTypeParser: typeof pgTypes.setTypeParser; + getTypeParser: typeof pgTypes.getTypeParser; + + on(event: "drain", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "notice", listener: (notice: NoticeMessage) => void): this; + on(event: "notification", listener: (message: Notification) => void): this; + // tslint:disable-next-line unified-signatures + on(event: "end", listener: () => void): this; +} + +export class Client extends ClientBase { + user?: string | undefined; + database?: string | undefined; + port: number; + host: string; + password?: string | undefined; + ssl: boolean; + readonly connection: Connection; + + constructor(config?: string | ClientConfig); + + end(): Promise; + end(callback: (err: Error) => void): void; +} + +export interface PoolClient extends ClientBase { + release(err?: Error | boolean): void; +} + +export class Query extends events.EventEmitter + implements Submittable +{ + constructor(queryTextOrConfig?: string | QueryConfig, values?: QueryConfigValues); + submit: (connection: Connection) => void; + on(event: "row", listener: (row: R, result?: ResultBuilder) => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "end", listener: (result: ResultBuilder) => void): this; +} + +export class Events extends events.EventEmitter { + on(event: "error", listener: (err: Error, client: Client) => void): this; +} + +export const types: typeof pgTypes; + +export const defaults: Defaults & ClientConfig; + +import * as Pg from "."; + +export const native: typeof Pg | null; + +export { DatabaseError } from "pg-protocol"; +import TypeOverrides = require("./lib/type-overrides"); +export { TypeOverrides }; + +export class Result implements QueryResult { + command: string; + rowCount: number | null; + oid: number; + fields: FieldDef[]; + rows: R[]; + + constructor(rowMode: string, t: typeof types); +} diff --git a/node_modules/@types/pg/package.json b/node_modules/@types/pg/package.json new file mode 100644 index 00000000..7dfdabc5 --- /dev/null +++ b/node_modules/@types/pg/package.json @@ -0,0 +1,38 @@ +{ + "name": "@types/pg", + "version": "8.15.4", + "description": "TypeScript definitions for pg", + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/pg", + "license": "MIT", + "contributors": [ + { + "name": "Phips Peter", + "githubUsername": "pspeter3", + "url": "https://github.com/pspeter3" + } + ], + "main": "", + "types": "index.d.ts", + "exports": { + ".": { + "import": "./index.d.mts", + "require": "./index.d.ts" + }, + "./lib/*": "./lib/*.d.ts", + "./package.json": "./package.json" + }, + "repository": { + "type": "git", + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "directory": "types/pg" + }, + "scripts": {}, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + }, + "peerDependencies": {}, + "typesPublisherContentHash": "1df9604d3c0f5bd0713592a33b19b2d88034938fa9af44ab97f2112ff5efabe2", + "typeScriptVersion": "5.1" +} \ No newline at end of file diff --git a/node_modules/pg-cloudflare/LICENSE b/node_modules/pg-cloudflare/LICENSE new file mode 100644 index 00000000..5c140564 --- /dev/null +++ b/node_modules/pg-cloudflare/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010 - 2021 Brian Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/pg-cloudflare/README.md b/node_modules/pg-cloudflare/README.md new file mode 100644 index 00000000..32c3faef --- /dev/null +++ b/node_modules/pg-cloudflare/README.md @@ -0,0 +1,107 @@ +# 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: [..., "cloudflare"] }, + plugins: [ + // ignore cloudflare:sockets imports + new webpack.IgnorePlugin({ + resourceRegExp: /^cloudflare:sockets$/, + }), + ], + } + ``` +- `vite.config.js` + ```js + export default defineConfig({ + ..., + resolve: { + conditions: [..., "cloudflare"], + }, + build: { + ..., + // don't try to bundle cloudflare:sockets + rollupOptions: { + external: [..., 'cloudflare:sockets'], + }, + }, + }) + ``` +- `rollup.config.js` + ```js + export default defineConfig({ + ..., + plugins: [..., nodeResolve({ exportConditions: [..., 'cloudflare'] })], + // don't try to bundle cloudflare:sockets + external: [..., 'cloudflare:sockets'], + }) + ``` +- `esbuild.config.js` + ```js + await esbuild.build({ + ..., + conditions: [..., 'cloudflare'], + }) + ``` + +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/node_modules/pg-cloudflare/esm/index.mjs b/node_modules/pg-cloudflare/esm/index.mjs new file mode 100644 index 00000000..6384216f --- /dev/null +++ b/node_modules/pg-cloudflare/esm/index.mjs @@ -0,0 +1,3 @@ +import cf from '../dist/index.js' + +export const CloudflareSocket = cf.CloudflareSocket diff --git a/node_modules/pg-cloudflare/package.json b/node_modules/pg-cloudflare/package.json new file mode 100644 index 00000000..f0d27734 --- /dev/null +++ b/node_modules/pg-cloudflare/package.json @@ -0,0 +1,38 @@ +{ + "name": "pg-cloudflare", + "version": "1.2.6", + "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": "^4.0.3" + }, + "exports": { + ".": { + "cloudflare": { + "import": "./esm/index.mjs", + "require": "./dist/index.js" + }, + "default": "./dist/empty.js" + } + }, + "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" + ], + "gitHead": "cd877a57612a39335a97b593111710d26126279d" +} diff --git a/node_modules/pg-cloudflare/src/empty.ts b/node_modules/pg-cloudflare/src/empty.ts new file mode 100644 index 00000000..f1e6740d --- /dev/null +++ b/node_modules/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/node_modules/pg-cloudflare/src/index.ts b/node_modules/pg-cloudflare/src/index.ts new file mode 100644 index 00000000..d83882ef --- /dev/null +++ b/node_modules/pg-cloudflare/src/index.ts @@ -0,0 +1,166 @@ +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) { + const hex = Buffer.from(data).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/node_modules/pg-cloudflare/src/types.d.ts b/node_modules/pg-cloudflare/src/types.d.ts new file mode 100644 index 00000000..f6f1c3f2 --- /dev/null +++ b/node_modules/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/node_modules/pg-connection-string/LICENSE b/node_modules/pg-connection-string/LICENSE new file mode 100644 index 00000000..b068a6cb --- /dev/null +++ b/node_modules/pg-connection-string/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Iced Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/node_modules/pg-connection-string/README.md b/node_modules/pg-connection-string/README.md new file mode 100644 index 00000000..e47adc81 --- /dev/null +++ b/node_modules/pg-connection-string/README.md @@ -0,0 +1,105 @@ +pg-connection-string +==================== + +[![NPM](https://nodei.co/npm/pg-connection-string.png?compact=true)](https://nodei.co/npm/pg-connection-string/) + +Functions for dealing with a PostgresSQL connection string + +`parse` method taken from [node-postgres](https://github.com/brianc/node-postgres.git) +Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com) +MIT License + +## Usage + +```js +const parse = require('pg-connection-string').parse; + +const config = parse('postgres://someuser:somepassword@somehost:381/somedatabase') +``` + +The resulting config contains a subset of the following properties: + +* `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: + + * `socket:?` - UNIX domain socket + * `postgres://:@:/?` - TCP connection + +But see below for more details. + +### UNIX Domain Sockets + +When user and password are not given, the socket path follows `socket:`, as in `socket:/var/run/pgsql`. +This form can be shortened to just a path: `/var/run/pgsql`. + +When user and password are given, they are included in the typical URL positions, with an empty `host`, as in `socket://user:pass@/var/run/pgsql`. + +Query parameters follow a `?` character, including the following special query parameters: + + * `db=` - sets the database name (urlencoded) + * `encoding=` - sets the `client_encoding` property + +### TCP Connections + +TCP connections to the Postgres server are indicated with `pg:` or `postgres:` schemes (in fact, any scheme but `socket:` is accepted). +If username and password are included, they should be urlencoded. +The database name, however, should *not* be urlencoded. + +Query parameters follow a `?` character, including the following special query parameters: + * `host=` - sets `host` property, overriding the URL's host + * `encoding=` - sets the `client_encoding` property + * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly + * `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/node_modules/pg-connection-string/esm/index.mjs b/node_modules/pg-connection-string/esm/index.mjs new file mode 100644 index 00000000..7b390c51 --- /dev/null +++ b/node_modules/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/node_modules/pg-connection-string/index.d.ts b/node_modules/pg-connection-string/index.d.ts new file mode 100644 index 00000000..2ebe6753 --- /dev/null +++ b/node_modules/pg-connection-string/index.d.ts @@ -0,0 +1,36 @@ +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 + password?: string + user?: string + port?: string | null + database: string | null | undefined + client_encoding?: 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/node_modules/pg-connection-string/index.js b/node_modules/pg-connection-string/index.js new file mode 100644 index 00000000..4d83975e --- /dev/null +++ b/node_modules/pg-connection-string/index.js @@ -0,0 +1,213 @@ +'use strict' + +//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, options = {}) { + //unix socket + if (str.charAt(0) === '/') { + const config = str.split(' ') + return { host: config[0], database: config[1] } + } + + // Check for empty host in URL + + const config = {} + 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*****') + } + + // 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) + + if (result.protocol == 'socket:') { + config.host = decodeURI(result.pathname) + 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 = 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 + } + if (!config.port) { + // Only set the port if there is no equivalent query param. + config.port = result.port + } + + const pathname = result.pathname.slice(1) || null + config.database = pathname ? decodeURI(pathname) : null + + if (config.ssl === 'true' || config.ssl === '1') { + config.ssl = true + } + + if (config.ssl === '0') { + config.ssl = false + } + + if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) { + config.ssl = {} + } + + // 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() + } + + if (config.sslkey) { + config.ssl.key = fs.readFileSync(config.sslkey).toString() + } + + if (config.sslrootcert) { + 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': { + 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 + }, {}) + + 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 + }, {}) + + return poolConfig +} + +// parses a connection string into ClientConfig +function parseIntoClientConfig(str) { + return toClientConfig(parse(str)) +} + +module.exports = parse + +parse.parse = parse +parse.toClientConfig = toClientConfig +parse.parseIntoClientConfig = parseIntoClientConfig diff --git a/node_modules/pg-connection-string/package.json b/node_modules/pg-connection-string/package.json new file mode 100644 index 00000000..dbd69fbb --- /dev/null +++ b/node_modules/pg-connection-string/package.json @@ -0,0 +1,52 @@ +{ + "name": "pg-connection-string", + "version": "2.9.1", + "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": "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", + "directory": "packages/pg-connection-string" + }, + "keywords": [ + "pg", + "connection", + "string", + "parse" + ], + "author": "Blaine Bublitz (http://iceddev.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/brianc/node-postgres/issues" + }, + "homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string", + "devDependencies": { + "@types/pg": "^8.12.0", + "chai": "^4.1.1", + "coveralls": "^3.0.4", + "istanbul": "^0.4.5", + "mocha": "^10.5.2", + "nyc": "^15", + "tsx": "^4.19.4", + "typescript": "^4.0.3" + }, + "files": [ + "index.js", + "index.d.ts", + "esm" + ], + "gitHead": "cd877a57612a39335a97b593111710d26126279d" +} diff --git a/node_modules/pg-int8/LICENSE b/node_modules/pg-int8/LICENSE new file mode 100644 index 00000000..c56c9731 --- /dev/null +++ b/node_modules/pg-int8/LICENSE @@ -0,0 +1,13 @@ +Copyright © 2017, Charmander <~@charmander.me> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/pg-int8/README.md b/node_modules/pg-int8/README.md new file mode 100644 index 00000000..ef2e6084 --- /dev/null +++ b/node_modules/pg-int8/README.md @@ -0,0 +1,16 @@ +[![Build status][ci image]][ci] + +64-bit big-endian signed integer-to-string conversion designed for [pg][]. + +```js +const readInt8 = require('pg-int8'); + +readInt8(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7])) +// '283686952306183' +``` + + + [pg]: https://github.com/brianc/node-postgres + + [ci]: https://travis-ci.org/charmander/pg-int8 + [ci image]: https://api.travis-ci.org/charmander/pg-int8.svg diff --git a/node_modules/pg-int8/index.js b/node_modules/pg-int8/index.js new file mode 100644 index 00000000..db779750 --- /dev/null +++ b/node_modules/pg-int8/index.js @@ -0,0 +1,100 @@ +'use strict'; + +// selected so (BASE - 1) * 0x100000000 + 0xffffffff is a safe integer +var BASE = 1000000; + +function readInt8(buffer) { + var high = buffer.readInt32BE(0); + var low = buffer.readUInt32BE(4); + var sign = ''; + + if (high < 0) { + high = ~high + (low === 0); + low = (~low + 1) >>> 0; + sign = '-'; + } + + var result = ''; + var carry; + var t; + var digits; + var pad; + var l; + var i; + + { + carry = high % BASE; + high = high / BASE >>> 0; + + t = 0x100000000 * carry + low; + low = t / BASE >>> 0; + digits = '' + (t - BASE * low); + + if (low === 0 && high === 0) { + return sign + digits + result; + } + + pad = ''; + l = 6 - digits.length; + + for (i = 0; i < l; i++) { + pad += '0'; + } + + result = pad + digits + result; + } + + { + carry = high % BASE; + high = high / BASE >>> 0; + + t = 0x100000000 * carry + low; + low = t / BASE >>> 0; + digits = '' + (t - BASE * low); + + if (low === 0 && high === 0) { + return sign + digits + result; + } + + pad = ''; + l = 6 - digits.length; + + for (i = 0; i < l; i++) { + pad += '0'; + } + + result = pad + digits + result; + } + + { + carry = high % BASE; + high = high / BASE >>> 0; + + t = 0x100000000 * carry + low; + low = t / BASE >>> 0; + digits = '' + (t - BASE * low); + + if (low === 0 && high === 0) { + return sign + digits + result; + } + + pad = ''; + l = 6 - digits.length; + + for (i = 0; i < l; i++) { + pad += '0'; + } + + result = pad + digits + result; + } + + { + carry = high % BASE; + t = 0x100000000 * carry + low; + digits = '' + t % BASE; + + return sign + digits + result; + } +} + +module.exports = readInt8; diff --git a/node_modules/pg-int8/package.json b/node_modules/pg-int8/package.json new file mode 100644 index 00000000..4b937e1b --- /dev/null +++ b/node_modules/pg-int8/package.json @@ -0,0 +1,24 @@ +{ + "name": "pg-int8", + "version": "1.0.1", + "description": "64-bit big-endian signed integer-to-string conversion", + "bugs": "https://github.com/charmander/pg-int8/issues", + "license": "ISC", + "files": [ + "index.js" + ], + "repository": { + "type": "git", + "url": "https://github.com/charmander/pg-int8" + }, + "scripts": { + "test": "tap test" + }, + "devDependencies": { + "@charmander/eslint-config-base": "1.0.2", + "tap": "10.7.3" + }, + "engines": { + "node": ">=4.0.0" + } +} diff --git a/node_modules/pg-pool/LICENSE b/node_modules/pg-pool/LICENSE new file mode 100644 index 00000000..4e905814 --- /dev/null +++ b/node_modules/pg-pool/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/pg-pool/README.md b/node_modules/pg-pool/README.md new file mode 100644 index 00000000..3ff657fe --- /dev/null +++ b/node_modules/pg-pool/README.md @@ -0,0 +1,376 @@ +# pg-pool +[![Build Status](https://travis-ci.org/brianc/node-pg-pool.svg?branch=master)](https://travis-ci.org/brianc/node-pg-pool) + +A connection pool for node-postgres + +## install +```sh +npm i pg-pool pg +``` + +## use + +### create + +to use pg-pool you must first create an instance of a pool + +```js +const Pool = require('pg-pool') + +// by default the pool uses the same +// configuration as whatever `pg` version you have installed +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 +const pool2 = new Pool({ + database: 'postgres', + user: 'brianc', + password: 'secret!', + port: 5432, + ssl: true, + max: 20, // set pool max size to 20 + idleTimeoutMillis: 1000, // close idle clients after 1 second + connectionTimeoutMillis: 1000, // return an error after 1 second if connection could not be established + maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion) +}) + +// you can supply a custom client constructor +// if you want to use the native postgres client +const NativeClient = require('pg').native.Client +const nativePool = new Pool({ Client: NativeClient }) + +// 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 url = require('url') + +const params = url.parse(process.env.DATABASE_URL); +const auth = params.auth.split(':'); + +const config = { + user: auth[0], + password: auth[1], + host: params.hostname, + port: params.port, + database: params.pathname.split('/')[1], + ssl: true +}; + +const pool = new Pool(config); + +/* + Transforms, 'postgres://DBuser:secret@DBHost:#####/myDB', into + config = { + user: 'DBuser', + password: 'secret', + host: 'DBHost', + port: '#####', + database: 'myDB', + ssl: true + } +*/ +``` + +### acquire clients with a promise + +pg-pool supports a fully promise-based api for acquiring clients + +```js +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) + }) +}) +``` + +### plays nice with async/await + +this ends up looking much nicer if you're using [co](https://github.com/tj/co) or async/await: + +```js +// with async/await +(async () => { + const pool = new Pool() + const client = await pool.connect() + try { + 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)) + +// with co +co(function * () { + const client = yield pool.connect() + try { + 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)) +``` + +### your new favorite helper method + +because its so common to just run a query and return the client to the pool afterward pg-pool has this built-in: + +```js +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 +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 +have some other edge case like [streaming rows](https://github.com/brianc/node-pg-query-stream) or using a [cursor](https://github.com/brianc/node-pg-cursor) +you should almost always just use `pool.query`. Its easy, it does the right thing :tm:, and wont ever forget to return +clients back to the pool after the query is done. + +### drop-in backwards compatible + +pg-pool still and will always support the traditional callback api for acquiring a client. This is the exact API node-postgres has shipped with for years: + +```js +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', err.message, err.stack) + } + console.log('hello from', res.rows[0].name) + }) +}) +``` + +### shut it down + +When you are finished with the pool if all the clients are idle the pool will close them after `config.idleTimeoutMillis` and your app +will shutdown gracefully. If you don't want to wait for the timeout you can end the pool as follows: + +```js +const pool = new Pool() +const client = await pool.connect() +console.log(await client.query('select now()')) +client.release() +await pool.end() +``` + +### a note on instances + +The pool should be a __long-lived object__ in your application. Generally you'll want to instantiate one pool when your app starts up and use the same instance of the pool throughout the lifetime of your application. If you are frequently creating a new pool within your code you likely don't have your pool initialization code in the correct place. Example: + +```js +// assume this is a file in your program at ./your-app/lib/db.js + +// correct usage: create the pool and let it live +// 'globally' here, controlling access to it through exported methods +const pool = new pg.Pool() + +// this is the right way to export the query method +module.exports.query = (text, values) => { + console.log('query:', text, values) + return pool.query(text, values) +} + +// this would be the WRONG way to export the connect method +module.exports.connect = () => { + // notice how we would be creating a pool instance here + // every time we called 'connect' to get a new client? + // that's a bad thing & results in creating an unbounded + // number of pools & therefore connections + const aPool = new pg.Pool() + return aPool.connect() +} +``` + +### events + +Every instance of a `Pool` is an event emitter. These instances emit the following events: + +#### error + +Emitted whenever an idle client in the pool encounters an error. This is common when your PostgreSQL server shuts down, reboots, or a network partition otherwise causes it to become unavailable while your pool has connected clients. + +Example: + +```js +const Pool = require('pg-pool') +const pool = new Pool() + +// attach an error handler to the pool for when a connected, idle client +// receives an error by being disconnected, etc +pool.on('error', function(error, client) { + // handle this in the same way you would treat process.on('uncaughtException') + // it is supplied the error as well as the idle client which received the error +}) +``` + +#### connect + +Fired whenever the pool creates a __new__ `pg.Client` instance and successfully connects it to the backend. + +Example: + +```js +const Pool = require('pg-pool') +const pool = new Pool() + +const count = 0 + +pool.on('connect', client => { + client.count = count++ +}) + +pool + .connect() + .then(client => { + return client + .query('SELECT $1::int AS "clientCount"', [client.count]) + .then(res => console.log(res.rows[0].clientCount)) // outputs 0 + .then(() => client) + }) + .then(client => client.release()) + +``` + +#### acquire + +Fired whenever 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 +const Pool = require('pg-pool') +const pool = new Pool() + +const acquireCount = 0 +pool.on('acquire', function (client) { + acquireCount++ +}) + +const connectCount = 0 +pool.on('connect', function () { + connectCount++ +}) + +for (let i = 0; i < 200; i++) { + pool.query('SELECT NOW()') +} + +setTimeout(function () { + console.log('connect count:', connectCount) // output: connect count: 10 + console.log('acquire count:', acquireCount) // output: acquire count: 200 +}, 100) + +``` + +### environment variables + +pg-pool & node-postgres support some of the same environment variables as `psql` supports. The most common are: + +``` +PGDATABASE=my_db +PGUSER=username +PGPASSWORD="my awesome password" +PGPORT=5432 +PGSSLMODE=require +``` + +Usually I will export these into my local environment via a `.env` file with environment settings or export them in `~/.bash_profile` or something similar. This way I get configurability which works with both the postgres suite of tools (`psql`, `pg_dump`, `pg_restore`) and node, I can vary the environment variables locally and in production, and it supports the concept of a [12-factor app](http://12factor.net/) out of the box. + +## bring your own promise + +In versions of node `<=0.12.x` there is no native promise implementation available globally. You can polyfill the promise globally like this: + +```js +// first run `npm install promise-polyfill --save +if (typeof Promise == 'undefined') { + global.Promise = require('promise-polyfill') +} +``` + +You can use any other promise implementation you'd like. The pool also allows you to configure the promise implementation on a per-pool level: + +```js +const bluebirdPool = new Pool({ + Promise: require('bluebird') +}) +``` + +__please note:__ in node `<=0.12.x` the pool will throw if you do not provide a promise constructor in one of the two ways mentioned above. In node `>=4.0.0` the pool will use the native promise implementation by default; however, the two methods above still allow you to "bring your own." + +## maxUses and read-replica autoscaling (e.g. AWS Aurora) + +The maxUses config option can help an application instance rebalance load against a replica set that has been auto-scaled after the connection pool is already full of healthy connections. + +The mechanism here is that a connection is considered "expended" after it has been acquired and released `maxUses` number of times. Depending on the load on your system, this means there will be an approximate time in which any given connection will live, thus creating a window for rebalancing. + +Imagine a scenario where you have 10 app instances providing an API running against a replica cluster of 3 that are accessed via a round-robin DNS entry. Each instance runs a connection pool size of 20. With an ambient load of 50 requests per second, the connection pool will likely fill up in a few minutes with healthy connections. + +If you have weekly bursts of traffic which peak at 1,000 requests per second, you might want to grow your replicas to 10 during this period. Without setting `maxUses`, the new replicas will not be adopted by the app servers without an intervention -- namely, restarting each in turn in order to build up new connection pools that are balanced against all the replicas. Adding additional app server instances will help to some extent because they will adopt all the replicas in an even way, but the initial app servers will continue to focus additional load on the original replicas. + +This is where the `maxUses` configuration option comes into play. Setting `maxUses` to 7500 will ensure that over a period of 30 minutes or so the new replicas will be adopted as the pre-existing connections are closed and replaced with new ones, thus creating a window for eventual balance. + +You'll want to test based on your own scenarios, but one way to make a first guess at `maxUses` is to identify an acceptable window for rebalancing and then solve for the value: + +``` +maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize +``` + +In the example above, assuming we acquire and release 1 connection per request and we are aiming for a 30 minute rebalancing window: + +``` +maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize + 7200 = 1800 * 1000 / 10 / 25 +``` + +## tests + +To run tests clone the repo, `npm i` in the working dir, and then run `npm test` + +## contributions + +I love contributions. Please make sure they have tests, and submit a PR. If you're not sure if the issue is worth it or will be accepted it never hurts to open an issue to begin the conversation. If you're interested in keeping up with node-postgres releated stuff, you can follow me on twitter at [@briancarlson](https://twitter.com/briancarlson) - I generally announce any noteworthy updates there. + +## license + +The MIT License (MIT) +Copyright (c) 2016 Brian M. Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/pg-pool/esm/index.mjs b/node_modules/pg-pool/esm/index.mjs new file mode 100644 index 00000000..a97fb624 --- /dev/null +++ b/node_modules/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/node_modules/pg-pool/index.js b/node_modules/pg-pool/index.js new file mode 100644 index 00000000..b3d9ada9 --- /dev/null +++ b/node_modules/pg-pool/index.js @@ -0,0 +1,479 @@ +'use strict' +const EventEmitter = require('events').EventEmitter + +const NOOP = function () {} + +const removeWhere = (list, predicate) => { + const i = list.findIndex(predicate) + + return i === -1 ? undefined : list.splice(i, 1)[0] +} + +class IdleItem { + constructor(client, idleListener, timeoutId) { + this.client = client + this.idleListener = idleListener + this.timeoutId = timeoutId + } +} + +class PendingItem { + constructor(callback) { + this.callback = callback + } +} + +function throwOnDoubleRelease() { + throw new Error('Release called on client which has already been released to the pool.') +} + +function promisify(Promise, callback) { + if (callback) { + return { callback: callback, result: undefined } + } + let rej + let res + const cb = function (err, client) { + err ? rej(err) : res(client) + } + const result = new Promise(function (resolve, reject) { + res = resolve + rej = reject + }).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 } +} + +function makeIdleListener(pool, client) { + return function idleListener(err) { + err.client = client + + client.removeListener('error', idleListener) + client.on('error', () => { + pool.log('additional client error after disconnection due to error', err) + }) + pool._remove(client) + // TODO - document that once the pool emits an error + // the client has already been closed & purged and is unusable + pool.emit('error', err, client) + } +} + +class Pool extends EventEmitter { + constructor(options, Client) { + super() + this.options = Object.assign({}, options) + + if (options != null && 'password' in options) { + // "hiding" the password so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this.options, 'password', { + configurable: true, + enumerable: false, + writable: true, + value: options.password, + }) + } + if (options != null && options.ssl && options.ssl.key) { + // "hiding" the ssl->key so it doesn't show up in stack traces + // or if the client is console.logged + Object.defineProperty(this.options.ssl, 'key', { + enumerable: false, + }) + } + + this.options.max = this.options.max || this.options.poolSize || 10 + this.options.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 + + if (typeof this.options.idleTimeoutMillis === 'undefined') { + this.options.idleTimeoutMillis = 10000 + } + + this._clients = [] + this._idle = [] + this._expired = new WeakSet() + this._pendingQueue = [] + this._endCallback = undefined + this.ending = false + this.ended = false + } + + _isFull() { + return this._clients.length >= this.options.max + } + + _isAboveMin() { + return this._clients.length > this.options.min + } + + _pulseQueue() { + this.log('pulse queue') + if (this.ended) { + this.log('pulse queue ended') + return + } + if (this.ending) { + this.log('pulse queue on ending') + if (this._idle.length) { + this._idle.slice().map((item) => { + this._remove(item.client) + }) + } + if (!this._clients.length) { + this.ended = true + this._endCallback() + } + return + } + + // if we don't have any waiting, do nothing + if (!this._pendingQueue.length) { + this.log('no queued requests') + return + } + // if we don't have any idle clients and we have no more room do nothing + if (!this._idle.length && this._isFull()) { + return + } + const pendingItem = this._pendingQueue.shift() + if (this._idle.length) { + const idleItem = this._idle.pop() + clearTimeout(idleItem.timeoutId) + const client = idleItem.client + client.ref && client.ref() + const idleListener = idleItem.idleListener + + return this._acquireClient(client, pendingItem, idleListener, false) + } + if (!this._isFull()) { + return this.newClient(pendingItem) + } + throw new Error('unexpected condition') + } + + _remove(client, callback) { + const removed = removeWhere(this._idle, (item) => item.client === client) + + if (removed !== undefined) { + clearTimeout(removed.timeoutId) + } + + this._clients = this._clients.filter((c) => c !== client) + const context = this + client.end(() => { + context.emit('remove', client) + + if (typeof callback === 'function') { + callback() + } + }) + } + + connect(cb) { + if (this.ending) { + const err = new Error('Cannot use a pool after calling end on the pool') + return cb ? cb(err) : this.Promise.reject(err) + } + + const response = promisify(this.Promise, cb) + const result = response.result + + // if we don't have to connect a new client, don't do so + if (this._isFull() || this._idle.length) { + // if we have idle clients schedule a pulse immediately + if (this._idle.length) { + process.nextTick(() => this._pulseQueue()) + } + + if (!this.options.connectionTimeoutMillis) { + this._pendingQueue.push(new PendingItem(response.callback)) + return result + } + + const queueCallback = (err, res, done) => { + clearTimeout(tid) + response.callback(err, res, done) + } + + const pendingItem = new PendingItem(queueCallback) + + // set connection timeout on checking out an existing client + const tid = setTimeout(() => { + // remove the callback from pending waiters because + // we're going to call it with a timeout error + removeWhere(this._pendingQueue, (i) => i.callback === queueCallback) + pendingItem.timedOut = true + response.callback(new Error('timeout exceeded when trying to connect')) + }, this.options.connectionTimeoutMillis) + + if (tid.unref) { + tid.unref() + } + + this._pendingQueue.push(pendingItem) + return result + } + + this.newClient(new PendingItem(response.callback)) + + return result + } + + newClient(pendingItem) { + const client = new this.Client(this.options) + this._clients.push(client) + const idleListener = makeIdleListener(this, client) + + this.log('checking client timeout') + + // connection timeout logic + let tid + let timeoutHit = false + if (this.options.connectionTimeoutMillis) { + tid = setTimeout(() => { + this.log('ending client due to timeout') + timeoutHit = true + // force kill the node driver, and let libpq do its teardown + client.connection ? client.connection.stream.destroy() : client.end() + }, this.options.connectionTimeoutMillis) + } + + this.log('connecting new client') + client.connect((err) => { + if (tid) { + clearTimeout(tid) + } + client.on('error', idleListener) + if (err) { + this.log('client failed to connect', err) + // remove the dead client from our list of clients + this._clients = this._clients.filter((c) => c !== client) + if (timeoutHit) { + err = new Error('Connection terminated due to connection timeout', { cause: err }) + } + + // this client won’t be released, so move on immediately + this._pulseQueue() + + if (!pendingItem.timedOut) { + pendingItem.callback(err, undefined, NOOP) + } + } else { + this.log('new client connected') + + 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) { + this.emit('connect', client) + } + + this.emit('acquire', client) + + client.release = this._releaseOnce(client, idleListener) + + client.removeListener('error', idleListener) + + if (!pendingItem.timedOut) { + if (isNew && this.options.verify) { + this.options.verify(client, (err) => { + if (err) { + client.release(err) + return pendingItem.callback(err, undefined, NOOP) + } + + pendingItem.callback(undefined, client, client.release) + }) + } else { + pendingItem.callback(undefined, client, client.release) + } + } else { + if (isNew && this.options.verify) { + this.options.verify(client, client.release) + } else { + client.release() + } + } + } + + // returns a function that wraps _release and throws if called more than once + _releaseOnce(client, idleListener) { + let released = false + + return (err) => { + if (released) { + throwOnDoubleRelease() + } + + released = true + this._release(client, idleListener, err) + } + } + + // release a client back to the poll, include an error + // to remove it from the pool + _release(client, idleListener, err) { + client.on('error', idleListener) + + client._poolUseCount = (client._poolUseCount || 0) + 1 + + 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') + } + + 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 && this._isAboveMin()) { + tid = setTimeout(() => { + 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)) + this._pulseQueue() + } + + query(text, values, cb) { + // guard clause against passing a function as the first parameter + if (typeof text === 'function') { + const response = promisify(this.Promise, text) + setImmediate(function () { + return response.callback(new Error('Passing a function as the first parameter to pool.query is not supported')) + }) + return response.result + } + + // allow plain text query without values + if (typeof values === 'function') { + cb = values + values = undefined + } + const response = promisify(this.Promise, cb) + cb = response.callback + + this.connect((err, client) => { + if (err) { + return cb(err) + } + + let clientReleased = false + const onError = (err) => { + if (clientReleased) { + return + } + clientReleased = true + client.release(err) + cb(err) + } + + client.once('error', onError) + this.log('dispatching query') + 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 + } + + end(cb) { + this.log('ending') + if (this.ending) { + const err = new Error('Called end on pool more than once') + return cb ? cb(err) : this.Promise.reject(err) + } + this.ending = true + const promised = promisify(this.Promise, cb) + this._endCallback = promised.callback + this._pulseQueue() + return promised.result + } + + get waitingCount() { + return this._pendingQueue.length + } + + get idleCount() { + return this._idle.length + } + + get expiredCount() { + return this._clients.reduce((acc, client) => acc + (this._expired.has(client) ? 1 : 0), 0) + } + + get totalCount() { + return this._clients.length + } +} +module.exports = Pool diff --git a/node_modules/pg-pool/package.json b/node_modules/pg-pool/package.json new file mode 100644 index 00000000..bf69b241 --- /dev/null +++ b/node_modules/pg-pool/package.json @@ -0,0 +1,51 @@ +{ + "name": "pg-pool", + "version": "3.10.1", + "description": "Connection pool for node-postgres", + "main": "index.js", + "exports": { + ".": { + "import": "./esm/index.mjs", + "require": "./index.js", + "default": "./index.js" + } + }, + "directories": { + "test": "test" + }, + "scripts": { + "test": " node_modules/.bin/mocha" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-pool" + }, + "keywords": [ + "pg", + "postgres", + "pool", + "database" + ], + "author": "Brian M. Carlson", + "license": "MIT", + "bugs": { + "url": "https://github.com/brianc/node-pg-pool/issues" + }, + "homepage": "https://github.com/brianc/node-pg-pool#readme", + "devDependencies": { + "bluebird": "3.7.2", + "co": "4.6.0", + "expect.js": "0.3.1", + "lodash": "^4.17.11", + "mocha": "^10.5.2" + }, + "peerDependencies": { + "pg": ">=8.0" + }, + "files": [ + "index.js", + "esm" + ], + "gitHead": "cd877a57612a39335a97b593111710d26126279d" +} diff --git a/node_modules/pg-protocol/LICENSE b/node_modules/pg-protocol/LICENSE new file mode 100644 index 00000000..5c140564 --- /dev/null +++ b/node_modules/pg-protocol/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010 - 2021 Brian Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/pg-protocol/README.md b/node_modules/pg-protocol/README.md new file mode 100644 index 00000000..8c52e40e --- /dev/null +++ b/node_modules/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/node_modules/pg-protocol/esm/index.js b/node_modules/pg-protocol/esm/index.js new file mode 100644 index 00000000..c52807d6 --- /dev/null +++ b/node_modules/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/node_modules/pg-protocol/package.json b/node_modules/pg-protocol/package.json new file mode 100644 index 00000000..5110a541 --- /dev/null +++ b/node_modules/pg-protocol/package.json @@ -0,0 +1,48 @@ +{ + "name": "pg-protocol", + "version": "1.10.2", + "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/*": { + "import": "./dist/*", + "require": "./dist/*", + "default": "./dist/*" + } + }, + "license": "MIT", + "devDependencies": { + "@types/chai": "^4.2.7", + "@types/mocha": "^10.0.7", + "@types/node": "^12.12.21", + "chai": "^4.2.0", + "chunky": "^0.0.0", + "mocha": "^10.5.2", + "ts-node": "^8.5.4", + "typescript": "^4.0.3" + }, + "scripts": { + "test": "mocha dist/**/*.test.js", + "build": "tsc", + "build:watch": "tsc --watch", + "prepublish": "yarn build", + "pretest": "yarn build" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg-protocol" + }, + "files": [ + "/dist/*{js,ts,map}", + "/src", + "/esm" + ], + "gitHead": "1a25d128177dd7c54dc4652bbb92ac7986306e3f" +} diff --git a/node_modules/pg-protocol/src/b.ts b/node_modules/pg-protocol/src/b.ts new file mode 100644 index 00000000..c8a24113 --- /dev/null +++ b/node_modules/pg-protocol/src/b.ts @@ -0,0 +1,25 @@ +// file for microbenchmarking + +import { BufferReader } from './buffer-reader' + +const LOOPS = 1000 +let count = 0 +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(performance.now() - start) + return + } + count++ + for (let i = 0; i < LOOPS; i++) { + reader.setBuffer(0, buffer) + reader.cstring() + } + setImmediate(run) +} + +run() diff --git a/node_modules/pg-protocol/src/buffer-reader.ts b/node_modules/pg-protocol/src/buffer-reader.ts new file mode 100644 index 00000000..62b16a2e --- /dev/null +++ b/node_modules/pg-protocol/src/buffer-reader.ts @@ -0,0 +1,60 @@ +const emptyBuffer = Buffer.allocUnsafe(0) + +export class BufferReader { + private buffer: Buffer = emptyBuffer + + // TODO(bmc): support non-utf8 encoding? + private encoding: string = 'utf-8' + + constructor(private offset: number = 0) {} + + public setBuffer(offset: number, buffer: Buffer): void { + this.offset = offset + this.buffer = buffer + } + + public int16(): number { + const result = this.buffer.readInt16BE(this.offset) + this.offset += 2 + return result + } + + public byte(): number { + const result = this.buffer[this.offset] + this.offset++ + return result + } + + public int32(): number { + const result = this.buffer.readInt32BE(this.offset) + this.offset += 4 + return result + } + + public 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 + return result + } + + 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) + } + + public bytes(length: number): Buffer { + const result = this.buffer.slice(this.offset, this.offset + length) + this.offset += length + return result + } +} diff --git a/node_modules/pg-protocol/src/buffer-writer.ts b/node_modules/pg-protocol/src/buffer-writer.ts new file mode 100644 index 00000000..cebb0d9e --- /dev/null +++ b/node_modules/pg-protocol/src/buffer-writer.ts @@ -0,0 +1,85 @@ +//binary data writer tuned for encoding binary specific to the postgres binary protocol + +export class Writer { + private buffer: Buffer + private offset: number = 5 + private headerPosition: number = 0 + constructor(private size = 256) { + this.buffer = Buffer.allocUnsafe(size) + } + + private ensure(size: number): void { + const remaining = this.buffer.length - this.offset + if (remaining < size) { + const oldBuffer = this.buffer + // exponential growth factor of around ~ 1.5 + // https://stackoverflow.com/questions/2269063/buffer-growth-strategy + const newSize = oldBuffer.length + (oldBuffer.length >> 1) + size + this.buffer = Buffer.allocUnsafe(newSize) + oldBuffer.copy(this.buffer) + } + } + + public addInt32(num: number): Writer { + this.ensure(4) + this.buffer[this.offset++] = (num >>> 24) & 0xff + this.buffer[this.offset++] = (num >>> 16) & 0xff + this.buffer[this.offset++] = (num >>> 8) & 0xff + this.buffer[this.offset++] = (num >>> 0) & 0xff + return this + } + + public addInt16(num: number): Writer { + this.ensure(2) + this.buffer[this.offset++] = (num >>> 8) & 0xff + this.buffer[this.offset++] = (num >>> 0) & 0xff + return this + } + + public addCString(string: string): Writer { + if (!string) { + this.ensure(1) + } else { + const len = Buffer.byteLength(string) + this.ensure(len + 1) // +1 for null terminator + this.buffer.write(string, this.offset, 'utf-8') + this.offset += len + } + + this.buffer[this.offset++] = 0 // null terminator + return this + } + + public addString(string: string = ''): Writer { + const len = Buffer.byteLength(string) + this.ensure(len) + this.buffer.write(string, this.offset) + this.offset += len + return this + } + + public add(otherBuffer: Buffer): Writer { + this.ensure(otherBuffer.length) + otherBuffer.copy(this.buffer, this.offset) + this.offset += otherBuffer.length + return this + } + + private join(code?: number): Buffer { + if (code) { + this.buffer[this.headerPosition] = code + //length is everything in this packet minus the code + const length = this.offset - (this.headerPosition + 1) + this.buffer.writeInt32BE(length, this.headerPosition + 1) + } + return this.buffer.slice(code ? 0 : 5, this.offset) + } + + public flush(code?: number): Buffer { + const result = this.join(code) + this.offset = 5 + this.headerPosition = 0 + this.buffer = Buffer.allocUnsafe(this.size) + return result + } +} diff --git a/node_modules/pg-protocol/src/inbound-parser.test.ts b/node_modules/pg-protocol/src/inbound-parser.test.ts new file mode 100644 index 00000000..0575993d --- /dev/null +++ b/node_modules/pg-protocol/src/inbound-parser.test.ts @@ -0,0 +1,568 @@ +import buffers from './testing/test-buffers' +import BufferList from './testing/buffer-list' +import { parse } from '.' +import assert from 'assert' +import { PassThrough } from 'stream' +import { BackendMessage } from './messages' + +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, + dataTypeID: 3, + dataTypeSize: 4, + typeModifier: 5, + formatCode: 0, +} +const oneRowDescBuff = buffers.rowDescription([row1]) +row1.name = 'bang' + +const twoRowBuf = buffers.rowDescription([ + row1, + { + name: 'whoah', + tableID: 10, + attributeNumber: 11, + dataTypeID: 12, + dataTypeSize: 13, + typeModifier: 14, + formatCode: 0, + }, +]) + +const rowWithBigOids = { + name: 'bigoid', + tableID: 3000000001, + attributeNumber: 2, + dataTypeID: 3000000003, + dataTypeSize: 4, + typeModifier: 5, + formatCode: 0, +} +const bigOidDescBuff = buffers.rowDescription([rowWithBigOids]) + +const emptyRowFieldBuf = buffers.dataRow([]) + +const oneFieldBuf = buffers.dataRow(['test']) + +const expectedAuthenticationOkayMessage = { + name: 'authenticationOk', + length: 8, +} + +const expectedParameterStatusMessage = { + name: 'parameterStatus', + parameterName: 'client_encoding', + parameterValue: 'UTF8', + length: 25, +} + +const expectedBackendKeyDataMessage = { + name: 'backendKeyData', + processID: 1, + secretKey: 2, +} + +const expectedReadyForQueryMessage = { + name: 'readyForQuery', + length: 5, + status: 'I', +} + +const expectedCommandCompleteMessage = { + name: 'commandComplete', + length: 13, + text: 'SELECT 3', +} +const emptyRowDescriptionBuffer = new BufferList() + .addInt16(0) // number of fields + .join(true, 'T') + +const expectedEmptyRowDescriptionMessage = { + name: 'rowDescription', + length: 6, + fieldCount: 0, + fields: [], +} +const expectedOneRowMessage = { + name: 'rowDescription', + length: 27, + fieldCount: 1, + fields: [ + { + name: 'id', + tableID: 1, + columnID: 2, + dataTypeID: 3, + dataTypeSize: 4, + dataTypeModifier: 5, + format: 'text', + }, + ], +} + +const expectedTwoRowMessage = { + name: 'rowDescription', + length: 53, + fieldCount: 2, + fields: [ + { + name: 'bang', + tableID: 1, + columnID: 2, + dataTypeID: 3, + dataTypeSize: 4, + dataTypeModifier: 5, + format: 'text', + }, + { + name: 'whoah', + tableID: 10, + columnID: 11, + dataTypeID: 12, + dataTypeSize: 13, + dataTypeModifier: 14, + format: 'text', + }, + ], +} +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], +} + +const testForMessage = function (buffer: Buffer, expectedMessage: any) { + it('receives and parses ' + expectedMessage.name, async () => { + const messages = await parseBuffers([buffer]) + const [lastMessage] = messages + + for (const key in expectedMessage) { + assert.deepEqual((lastMessage as any)[key], expectedMessage[key]) + } + }) +} + +const plainPasswordBuffer = buffers.authenticationCleartextPassword() +const md5PasswordBuffer = buffers.authenticationMD5Password() +const SASLBuffer = buffers.authenticationSASL() +const SASLContinueBuffer = buffers.authenticationSASLContinue() +const SASLFinalBuffer = buffers.authenticationSASLFinal() + +const expectedPlainPasswordMessage = { + name: 'authenticationCleartextPassword', +} + +const expectedMD5PasswordMessage = { + name: 'authenticationMD5Password', + salt: Buffer.from([1, 2, 3, 4]), +} + +const expectedSASLMessage = { + name: 'authenticationSASL', + mechanisms: ['SCRAM-SHA-256'], +} + +const expectedSASLContinueMessage = { + name: 'authenticationSASLContinue', + data: 'data', +} + +const expectedSASLFinalMessage = { + name: 'authenticationSASLFinal', + data: 'data', +} + +const notificationResponseBuffer = buffers.notification(4, 'hi', 'boom') +const expectedNotificationResponseMessage = { + name: 'notification', + processId: 4, + channel: 'hi', + payload: 'boom', +} + +const parseBuffers = async (buffers: Buffer[]): Promise => { + const stream = new PassThrough() + for (const buffer of buffers) { + stream.write(buffer) + } + stream.end() + const msgs: BackendMessage[] = [] + await parse(stream, (msg) => msgs.push(msg)) + return msgs +} + +describe('PgPacketStream', function () { + testForMessage(authOkBuffer, expectedAuthenticationOkayMessage) + testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage) + testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage) + testForMessage(SASLBuffer, expectedSASLMessage) + testForMessage(SASLContinueBuffer, expectedSASLContinueMessage) + + // this exercises a found bug in the parser: + // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084 + // and adds a test which is deterministic, rather than relying on network packet chunking + const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])]) + testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage) + + testForMessage(SASLFinalBuffer, expectedSASLFinalMessage) + + // this exercises a found bug in the parser: + // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084 + // and adds a test which is deterministic, rather than relying on network packet chunking + const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])]) + testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage) + + testForMessage(paramStatusBuffer, expectedParameterStatusMessage) + testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage) + testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage) + testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage) + testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage) + testForMessage(buffers.emptyQuery(), { + name: 'emptyQuery', + length: 4, + }) + + testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), { + name: 'noData', + }) + + describe('rowDescription messages', function () { + testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage) + testForMessage(oneRowDescBuff, expectedOneRowMessage) + testForMessage(twoRowBuf, expectedTwoRowMessage) + testForMessage(bigOidDescBuff, expectedBigOidMessage) + }) + + describe('parameterDescription messages', function () { + testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage) + testForMessage(oneParameterDescBuf, expectedOneParameterMessage) + testForMessage(twoParameterDescBuf, expectedTwoParameterMessage) + }) + + describe('parsing rows', function () { + describe('parsing empty row', function () { + testForMessage(emptyRowFieldBuf, { + name: 'dataRow', + fieldCount: 0, + }) + }) + + describe('parsing data row with fields', function () { + testForMessage(oneFieldBuf, { + name: 'dataRow', + fieldCount: 1, + fields: ['test'], + }) + }) + }) + + describe('notice message', function () { + // this uses the same logic as error message + const buff = buffers.notice([{ type: 'C', value: 'code' }]) + testForMessage(buff, { + name: 'notice', + code: 'code', + }) + }) + + testForMessage(buffers.error([]), { + name: 'error', + }) + + describe('with all the fields', function () { + const buffer = buffers.error([ + { + type: 'S', + value: 'ERROR', + }, + { + type: 'C', + value: 'code', + }, + { + type: 'M', + value: 'message', + }, + { + type: 'D', + value: 'details', + }, + { + type: 'H', + value: 'hint', + }, + { + type: 'P', + value: '100', + }, + { + type: 'p', + value: '101', + }, + { + type: 'q', + value: 'query', + }, + { + type: 'W', + value: 'where', + }, + { + type: 'F', + value: 'file', + }, + { + type: 'L', + value: 'line', + }, + { + type: 'R', + value: 'routine', + }, + { + type: 'Z', // ignored + value: 'alsdkf', + }, + ]) + + testForMessage(buffer, { + name: 'error', + severity: 'ERROR', + code: 'code', + message: 'message', + detail: 'details', + hint: 'hint', + position: '100', + internalPosition: '101', + internalQuery: 'query', + where: 'where', + file: 'file', + line: 'line', + routine: 'routine', + }) + }) + + testForMessage(parseCompleteBuffer, { + name: 'parseComplete', + }) + + testForMessage(bindCompleteBuffer, { + name: 'bindComplete', + }) + + testForMessage(bindCompleteBuffer, { + name: 'bindComplete', + }) + + testForMessage(buffers.closeComplete(), { + name: 'closeComplete', + }) + + describe('parses portal suspended message', function () { + testForMessage(portalSuspendedBuffer, { + name: 'portalSuspended', + }) + }) + + describe('parses replication start message', function () { + testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), { + name: 'replicationStart', + length: 4, + }) + }) + + describe('copy', () => { + testForMessage(buffers.copyIn(0), { + name: 'copyInResponse', + length: 7, + binary: false, + columnTypes: [], + }) + + testForMessage(buffers.copyIn(2), { + name: 'copyInResponse', + length: 11, + binary: false, + columnTypes: [0, 1], + }) + + testForMessage(buffers.copyOut(0), { + name: 'copyOutResponse', + length: 7, + binary: false, + columnTypes: [], + }) + + testForMessage(buffers.copyOut(3), { + name: 'copyOutResponse', + length: 13, + binary: false, + columnTypes: [0, 1, 2], + }) + + testForMessage(buffers.copyDone(), { + name: 'copyDone', + length: 4, + }) + + testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), { + name: 'copyData', + length: 7, + chunk: Buffer.from([5, 6, 7]), + }) + }) + + // since the data message on a stream can randomly divide the incomming + // tcp packets anywhere, we need to make sure we can parse every single + // split on a tcp message + describe('split buffer, single message parsing', function () { + const fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!']) + + it('parses when full buffer comes in', async function () { + const messages = await parseBuffers([fullBuffer]) + const message = messages[0] as any + assert.equal(message.fields.length, 5) + assert.equal(message.fields[0], null) + assert.equal(message.fields[1], 'bang') + assert.equal(message.fields[2], 'zug zug') + assert.equal(message.fields[3], null) + assert.equal(message.fields[4], '!') + }) + + 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([firstBuffer, secondBuffer]) + const message = messages[0] as any + assert.equal(message.fields.length, 5) + assert.equal(message.fields[0], null) + assert.equal(message.fields[1], 'bang') + assert.equal(message.fields[2], 'zug zug') + assert.equal(message.fields[3], null) + assert.equal(message.fields[4], '!') + } + + it('parses when split in the middle', function () { + return testMessageReceivedAfterSplitAt(6) + }) + + it('parses when split at end', function () { + return testMessageReceivedAfterSplitAt(2) + }) + + it('parses when split at beginning', function () { + return Promise.all([ + testMessageReceivedAfterSplitAt(fullBuffer.length - 2), + testMessageReceivedAfterSplitAt(fullBuffer.length - 1), + testMessageReceivedAfterSplitAt(fullBuffer.length - 5), + ]) + }) + }) + + describe('split buffer, multiple message parsing', function () { + 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) + + const verifyMessages = function (messages: any[]) { + assert.strictEqual(messages.length, 2) + assert.deepEqual(messages[0], { + name: 'dataRow', + fieldCount: 1, + length: 11, + fields: ['!'], + }) + assert.equal(messages[0].fields[0], '!') + assert.deepEqual(messages[1], { + name: 'readyForQuery', + length: 5, + status: 'I', + }) + } + // sanity check + it('receives both messages when packet is not split', async function () { + const messages = await parseBuffers([fullBuffer]) + verifyMessages(messages) + }) + + 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('receives both messages when packet is split', function () { + it('in the middle', function () { + return splitAndVerifyTwoMessages(11) + }) + it('at the front', function () { + return Promise.all([ + splitAndVerifyTwoMessages(fullBuffer.length - 1), + splitAndVerifyTwoMessages(fullBuffer.length - 4), + splitAndVerifyTwoMessages(fullBuffer.length - 6), + ]) + }) + + it('at the end', function () { + return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]) + }) + }) + }) +}) diff --git a/node_modules/pg-protocol/src/index.ts b/node_modules/pg-protocol/src/index.ts new file mode 100644 index 00000000..703ff2e4 --- /dev/null +++ b/node_modules/pg-protocol/src/index.ts @@ -0,0 +1,11 @@ +import { DatabaseError } from './messages' +import { serialize } from './serializer' +import { Parser, MessageCallback } from './parser' + +export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise { + const parser = new Parser() + stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback)) + return new Promise((resolve) => stream.on('end', () => resolve())) +} + +export { serialize, DatabaseError } diff --git a/node_modules/pg-protocol/src/messages.ts b/node_modules/pg-protocol/src/messages.ts new file mode 100644 index 00000000..c3fbbdd9 --- /dev/null +++ b/node_modules/pg-protocol/src/messages.ts @@ -0,0 +1,262 @@ +export type Mode = 'text' | 'binary' + +export type MessageName = + | 'parseComplete' + | 'bindComplete' + | 'closeComplete' + | 'noData' + | 'portalSuspended' + | 'replicationStart' + | 'emptyQuery' + | 'copyDone' + | 'copyData' + | 'rowDescription' + | 'parameterDescription' + | 'parameterStatus' + | 'backendKeyData' + | 'notification' + | 'readyForQuery' + | 'commandComplete' + | 'dataRow' + | 'copyInResponse' + | 'copyOutResponse' + | 'authenticationOk' + | 'authenticationMD5Password' + | 'authenticationCleartextPassword' + | 'authenticationSASL' + | 'authenticationSASLContinue' + | 'authenticationSASLFinal' + | 'error' + | 'notice' + +export interface BackendMessage { + name: MessageName + length: number +} + +export const parseComplete: BackendMessage = { + name: 'parseComplete', + length: 5, +} + +export const bindComplete: BackendMessage = { + name: 'bindComplete', + length: 5, +} + +export const closeComplete: BackendMessage = { + name: 'closeComplete', + length: 5, +} + +export const noData: BackendMessage = { + name: 'noData', + length: 5, +} + +export const portalSuspended: BackendMessage = { + name: 'portalSuspended', + length: 5, +} + +export const replicationStart: BackendMessage = { + name: 'replicationStart', + length: 4, +} + +export const emptyQuery: BackendMessage = { + name: 'emptyQuery', + length: 4, +} + +export const copyDone: BackendMessage = { + name: 'copyDone', + length: 4, +} + +interface NoticeOrError { + message: string | undefined + severity: string | undefined + code: string | undefined + detail: string | undefined + hint: string | undefined + position: string | undefined + internalPosition: string | undefined + internalQuery: string | undefined + where: string | undefined + schema: string | undefined + table: string | undefined + column: string | undefined + dataType: string | undefined + constraint: string | undefined + file: string | undefined + line: string | undefined + routine: string | undefined +} + +export class DatabaseError extends Error implements NoticeOrError { + public severity: string | undefined + public code: string | undefined + public detail: string | undefined + public hint: string | undefined + public position: string | undefined + public internalPosition: string | undefined + public internalQuery: string | undefined + public where: string | undefined + public schema: string | undefined + public table: string | undefined + public column: string | undefined + public dataType: string | undefined + public constraint: string | undefined + public file: string | undefined + public line: string | undefined + public routine: string | undefined + constructor( + message: string, + public readonly length: number, + public readonly name: MessageName + ) { + super(message) + } +} + +export class CopyDataMessage { + public readonly name = 'copyData' + constructor( + public readonly length: number, + public readonly chunk: Buffer + ) {} +} + +export class CopyResponse { + public readonly columnTypes: number[] + constructor( + public readonly length: number, + public readonly name: MessageName, + public readonly binary: boolean, + columnCount: number + ) { + this.columnTypes = new Array(columnCount) + } +} + +export class Field { + constructor( + public readonly name: string, + public readonly tableID: number, + public readonly columnID: number, + public readonly dataTypeID: number, + public readonly dataTypeSize: number, + public readonly dataTypeModifier: number, + public readonly format: Mode + ) {} +} + +export class RowDescriptionMessage { + public readonly name: MessageName = 'rowDescription' + public readonly fields: Field[] + constructor( + public readonly length: number, + public readonly fieldCount: number + ) { + this.fields = new Array(this.fieldCount) + } +} + +export class ParameterDescriptionMessage { + public readonly name: MessageName = 'parameterDescription' + public readonly dataTypeIDs: number[] + constructor( + public readonly length: number, + public readonly parameterCount: number + ) { + this.dataTypeIDs = new Array(this.parameterCount) + } +} + +export class ParameterStatusMessage { + public readonly name: MessageName = 'parameterStatus' + constructor( + public readonly length: number, + public readonly parameterName: string, + public readonly parameterValue: string + ) {} +} + +export class AuthenticationMD5Password implements BackendMessage { + public readonly name: MessageName = 'authenticationMD5Password' + constructor( + public readonly length: number, + public readonly salt: Buffer + ) {} +} + +export class BackendKeyDataMessage { + public readonly name: MessageName = 'backendKeyData' + constructor( + public readonly length: number, + public readonly processID: number, + public readonly secretKey: number + ) {} +} + +export class NotificationResponseMessage { + public readonly name: MessageName = 'notification' + constructor( + public readonly length: number, + public readonly processId: number, + public readonly channel: string, + public readonly payload: string + ) {} +} + +export class ReadyForQueryMessage { + public readonly name: MessageName = 'readyForQuery' + constructor( + public readonly length: number, + public readonly status: string + ) {} +} + +export class CommandCompleteMessage { + public readonly name: MessageName = 'commandComplete' + constructor( + public readonly length: number, + public readonly text: string + ) {} +} + +export class DataRowMessage { + public readonly fieldCount: number + public readonly name: MessageName = 'dataRow' + constructor( + public length: number, + public fields: any[] + ) { + this.fieldCount = fields.length + } +} + +export class NoticeMessage implements BackendMessage, NoticeOrError { + constructor( + public readonly length: number, + public readonly message: string | undefined + ) {} + public readonly name = 'notice' + public severity: string | undefined + public code: string | undefined + public detail: string | undefined + public hint: string | undefined + public position: string | undefined + public internalPosition: string | undefined + public internalQuery: string | undefined + public where: string | undefined + public schema: string | undefined + public table: string | undefined + public column: string | undefined + public dataType: string | undefined + public constraint: string | undefined + public file: string | undefined + public line: string | undefined + public routine: string | undefined +} diff --git a/node_modules/pg-protocol/src/outbound-serializer.test.ts b/node_modules/pg-protocol/src/outbound-serializer.test.ts new file mode 100644 index 00000000..0d3e387e --- /dev/null +++ b/node_modules/pg-protocol/src/outbound-serializer.test.ts @@ -0,0 +1,276 @@ +import assert from 'assert' +import { serialize } from './serializer' +import BufferList from './testing/buffer-list' + +describe('serializer', () => { + it('builds startup message', function () { + const actual = serialize.startup({ + user: 'brian', + database: 'bang', + }) + assert.deepEqual( + actual, + new BufferList() + .addInt16(3) + .addInt16(0) + .addCString('user') + .addCString('brian') + .addCString('database') + .addCString('bang') + .addCString('client_encoding') + .addCString('UTF8') + .addCString('') + .join(true) + ) + }) + + it('builds password message', function () { + const actual = serialize.password('!') + assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p')) + }) + + it('builds request ssl message', function () { + const actual = serialize.requestSsl() + const expected = new BufferList().addInt32(80877103).join(true) + assert.deepEqual(actual, expected) + }) + + it('builds SASLInitialResponseMessage message', function () { + const actual = serialize.sendSASLInitialResponseMessage('mech', 'data') + assert.deepEqual(actual, new BufferList().addCString('mech').addInt32(4).addString('data').join(true, 'p')) + }) + + it('builds SCRAMClientFinalMessage message', function () { + const actual = serialize.sendSCRAMClientFinalMessage('data') + assert.deepEqual(actual, new BufferList().addString('data').join(true, 'p')) + }) + + it('builds query message', function () { + const txt = 'select * from boom' + const actual = serialize.query(txt) + assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q')) + }) + + describe('parse message', () => { + it('builds parse message', function () { + const actual = serialize.parse({ text: '!' }) + const expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P') + assert.deepEqual(actual, expected) + }) + + it('builds parse message with named query', function () { + const actual = serialize.parse({ + name: 'boom', + text: 'select * from boom', + types: [], + }) + const expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P') + assert.deepEqual(actual, expected) + }) + + it('with multiple parameters', function () { + const actual = serialize.parse({ + name: 'force', + text: 'select * from bang where name = $1', + types: [1, 2, 3, 4], + }) + const expected = new BufferList() + .addCString('force') + .addCString('select * from bang where name = $1') + .addInt16(4) + .addInt32(1) + .addInt32(2) + .addInt32(3) + .addInt32(4) + .join(true, 'P') + assert.deepEqual(actual, expected) + }) + }) + + describe('bind messages', function () { + it('with no values', function () { + const actual = serialize.bind() + + const expectedBuffer = new BufferList() + .addCString('') + .addCString('') + .addInt16(0) + .addInt16(0) + .addInt16(1) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + + it('with named statement, portal, and values', function () { + const actual = serialize.bind({ + portal: 'bang', + statement: 'woo', + values: ['1', 'hi', null, 'zing'], + }) + 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) + .add(Buffer.from('1')) + .addInt32(2) + .add(Buffer.from('hi')) + .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')], + }) + const expectedBuffer = new BufferList() + .addCString('bang') // portal name + .addCString('woo') // statement name + .addInt16(4) // value count + .addInt16(0) // string + .addInt16(0) // string + .addInt16(0) // string + .addInt16(1) // binary + .addInt16(4) + .addInt32(1) + .add(Buffer.from('1')) + .addInt32(2) + .add(Buffer.from('hi')) + .addInt32(-1) + .addInt32(4) + .add(Buffer.from('zing', 'utf-8')) + .addInt16(1) + .addInt16(0) + .join(true, 'B') + assert.deepEqual(actual, expectedBuffer) + }) + + describe('builds execute message', function () { + it('for unamed portal with no row limit', function () { + const actual = serialize.execute() + const expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E') + assert.deepEqual(actual, expectedBuffer) + }) + + it('for named portal with row limit', function () { + const actual = serialize.execute({ + portal: 'my favorite portal', + rows: 100, + }) + 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() + const expected = new BufferList().join(true, 'H') + assert.deepEqual(actual, expected) + }) + + it('builds sync command', function () { + const actual = serialize.sync() + const expected = new BufferList().join(true, 'S') + assert.deepEqual(actual, expected) + }) + + it('builds end command', function () { + const actual = serialize.end() + 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' }) + 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' }) + const expected = new BufferList().addChar('P').addCString('').join(true, 'D') + assert.deepEqual(actual, expected) + }) + }) + + describe('builds close command', function () { + it('describe statement', function () { + const actual = serialize.close({ type: 'S', name: 'bang' }) + 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' }) + const expected = new BufferList().addChar('P').addCString('').join(true, 'C') + assert.deepEqual(actual, expected) + }) + }) + + describe('copy messages', function () { + it('builds copyFromChunk', () => { + const actual = serialize.copyData(Buffer.from([1, 2, 3])) + const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd') + assert.deepEqual(actual, expected) + }) + + it('builds copy fail', () => { + const actual = serialize.copyFail('err!') + const expected = new BufferList().addCString('err!').join(true, 'f') + assert.deepEqual(actual, expected) + }) + + it('builds copy done', () => { + const actual = serialize.copyDone() + const expected = new BufferList().join(true, 'c') + assert.deepEqual(actual, expected) + }) + }) + + it('builds cancel message', () => { + const actual = serialize.cancel(3, 4) + const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true) + assert.deepEqual(actual, expected) + }) +}) diff --git a/node_modules/pg-protocol/src/parser.ts b/node_modules/pg-protocol/src/parser.ts new file mode 100644 index 00000000..f7313f23 --- /dev/null +++ b/node_modules/pg-protocol/src/parser.ts @@ -0,0 +1,389 @@ +import { TransformOptions } from 'stream' +import { + Mode, + bindComplete, + parseComplete, + closeComplete, + noData, + portalSuspended, + copyDone, + replicationStart, + emptyQuery, + ReadyForQueryMessage, + CommandCompleteMessage, + CopyDataMessage, + CopyResponse, + NotificationResponseMessage, + RowDescriptionMessage, + ParameterDescriptionMessage, + Field, + DataRowMessage, + ParameterStatusMessage, + BackendKeyDataMessage, + DatabaseError, + BackendMessage, + MessageName, + AuthenticationMD5Password, + NoticeMessage, +} from './messages' +import { BufferReader } from './buffer-reader' + +// every message is prefixed with a single bye +const CODE_LENGTH = 1 +// every message has an int32 length which includes itself but does +// NOT include the code in the length +const LEN_LENGTH = 4 + +const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH + +export type Packet = { + code: number + packet: Buffer +} + +const emptyBuffer = Buffer.allocUnsafe(0) + +type StreamOptions = TransformOptions & { + mode: Mode +} + +const enum MessageCodes { + DataRow = 0x44, // D + ParseComplete = 0x31, // 1 + BindComplete = 0x32, // 2 + CloseComplete = 0x33, // 3 + CommandComplete = 0x43, // C + ReadyForQuery = 0x5a, // Z + NoData = 0x6e, // n + NotificationResponse = 0x41, // A + AuthenticationResponse = 0x52, // R + ParameterStatus = 0x53, // S + BackendKeyData = 0x4b, // K + ErrorMessage = 0x45, // E + NoticeMessage = 0x4e, // N + RowDescriptionMessage = 0x54, // T + ParameterDescriptionMessage = 0x74, // t + PortalSuspended = 0x73, // s + ReplicationStart = 0x57, // W + EmptyQuery = 0x49, // I + CopyIn = 0x47, // G + CopyOut = 0x48, // H + CopyDone = 0x63, // c + CopyData = 0x64, // d +} + +export type MessageCallback = (msg: BackendMessage) => void + +export class Parser { + private buffer: Buffer = emptyBuffer + private bufferLength: number = 0 + private bufferOffset: number = 0 + private reader = new BufferReader() + private mode: Mode + + constructor(opts?: StreamOptions) { + if (opts?.mode === 'binary') { + throw new Error('Binary mode not supported yet') + } + this.mode = opts?.mode || 'text' + } + + public parse(buffer: Buffer, callback: MessageCallback) { + this.mergeBuffer(buffer) + const bufferFullLength = this.bufferOffset + this.bufferLength + let offset = this.bufferOffset + while (offset + HEADER_LENGTH <= bufferFullLength) { + // code is 1 byte long - it identifies the message type + const code = this.buffer[offset] + // length is 1 Uint32BE - it is the length of the message EXCLUDING the code + const length = this.buffer.readUInt32BE(offset + CODE_LENGTH) + const fullMessageLength = CODE_LENGTH + length + if (fullMessageLength + offset <= bufferFullLength) { + const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer) + callback(message) + offset += fullMessageLength + } else { + break + } + } + if (offset === bufferFullLength) { + // No more use for the buffer + this.buffer = emptyBuffer + this.bufferLength = 0 + this.bufferOffset = 0 + } else { + // Adjust the cursors of remainingBuffer + this.bufferLength = bufferFullLength - offset + this.bufferOffset = offset + } + } + + private mergeBuffer(buffer: Buffer): void { + if (this.bufferLength > 0) { + const newLength = this.bufferLength + buffer.byteLength + const newFullLength = newLength + this.bufferOffset + if (newFullLength > this.buffer.byteLength) { + // We can't concat the new buffer with the remaining one + let newBuffer: Buffer + if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) { + // We can move the relevant part to the beginning of the buffer instead of allocating a new buffer + newBuffer = this.buffer + } else { + // Allocate a new larger buffer + let newBufferLength = this.buffer.byteLength * 2 + while (newLength >= newBufferLength) { + newBufferLength *= 2 + } + newBuffer = Buffer.allocUnsafe(newBufferLength) + } + // Move the remaining buffer to the new one + this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength) + this.buffer = newBuffer + this.bufferOffset = 0 + } + // Concat the new buffer with the remaining one + buffer.copy(this.buffer, this.bufferOffset + this.bufferLength) + this.bufferLength = newLength + } else { + this.buffer = buffer + this.bufferOffset = 0 + this.bufferLength = buffer.byteLength + } + } + + private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage { + switch (code) { + case MessageCodes.BindComplete: + return bindComplete + case MessageCodes.ParseComplete: + return parseComplete + case MessageCodes.CloseComplete: + return closeComplete + case MessageCodes.NoData: + return noData + case MessageCodes.PortalSuspended: + return portalSuspended + case MessageCodes.CopyDone: + return copyDone + case MessageCodes.ReplicationStart: + return replicationStart + case MessageCodes.EmptyQuery: + return emptyQuery + case MessageCodes.DataRow: + return this.parseDataRowMessage(offset, length, bytes) + case MessageCodes.CommandComplete: + return this.parseCommandCompleteMessage(offset, length, bytes) + case MessageCodes.ReadyForQuery: + return this.parseReadyForQueryMessage(offset, length, bytes) + case MessageCodes.NotificationResponse: + return this.parseNotificationMessage(offset, length, bytes) + case MessageCodes.AuthenticationResponse: + return this.parseAuthenticationResponse(offset, length, bytes) + case MessageCodes.ParameterStatus: + return this.parseParameterStatusMessage(offset, length, bytes) + case MessageCodes.BackendKeyData: + return this.parseBackendKeyData(offset, length, bytes) + case MessageCodes.ErrorMessage: + return this.parseErrorMessage(offset, length, bytes, 'error') + case MessageCodes.NoticeMessage: + return this.parseErrorMessage(offset, length, bytes, 'notice') + case MessageCodes.RowDescriptionMessage: + return this.parseRowDescriptionMessage(offset, length, bytes) + case MessageCodes.ParameterDescriptionMessage: + return this.parseParameterDescriptionMessage(offset, length, bytes) + case MessageCodes.CopyIn: + return this.parseCopyInMessage(offset, length, bytes) + case MessageCodes.CopyOut: + return this.parseCopyOutMessage(offset, length, bytes) + case MessageCodes.CopyData: + return this.parseCopyData(offset, length, bytes) + default: + 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) + } + + private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const text = this.reader.cstring() + return new CommandCompleteMessage(length, text) + } + + private parseCopyData(offset: number, length: number, bytes: Buffer) { + const chunk = bytes.slice(offset, offset + (length - 4)) + return new CopyDataMessage(length, chunk) + } + + private parseCopyInMessage(offset: number, length: number, bytes: Buffer) { + return this.parseCopyMessage(offset, length, bytes, 'copyInResponse') + } + + private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) { + return this.parseCopyMessage(offset, length, bytes, 'copyOutResponse') + } + + private parseCopyMessage(offset: number, length: number, bytes: Buffer, messageName: MessageName) { + this.reader.setBuffer(offset, bytes) + const isBinary = this.reader.byte() !== 0 + const columnCount = this.reader.int16() + const message = new CopyResponse(length, messageName, isBinary, columnCount) + for (let i = 0; i < columnCount; i++) { + message.columnTypes[i] = this.reader.int16() + } + return message + } + + private parseNotificationMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const processId = this.reader.int32() + const channel = this.reader.cstring() + const payload = this.reader.cstring() + return new NotificationResponseMessage(length, processId, channel, payload) + } + + private parseRowDescriptionMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const fieldCount = this.reader.int16() + const message = new RowDescriptionMessage(length, fieldCount) + for (let i = 0; i < fieldCount; i++) { + message.fields[i] = this.parseField() + } + return message + } + + private parseField(): Field { + const name = this.reader.cstring() + const tableID = this.reader.uint32() + const columnID = this.reader.int16() + const dataTypeID = this.reader.uint32() + const dataTypeSize = this.reader.int16() + const dataTypeModifier = this.reader.int32() + const mode = this.reader.int16() === 0 ? 'text' : 'binary' + return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode) + } + + private parseParameterDescriptionMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const parameterCount = this.reader.int16() + const message = new ParameterDescriptionMessage(length, parameterCount) + for (let i = 0; i < parameterCount; i++) { + message.dataTypeIDs[i] = this.reader.int32() + } + return message + } + + private parseDataRowMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const fieldCount = this.reader.int16() + const fields: any[] = new Array(fieldCount) + for (let i = 0; i < fieldCount; i++) { + const len = this.reader.int32() + // a -1 for length means the value of the field is null + fields[i] = len === -1 ? null : this.reader.string(len) + } + return new DataRowMessage(length, fields) + } + + private parseParameterStatusMessage(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const name = this.reader.cstring() + const value = this.reader.cstring() + return new ParameterStatusMessage(length, name, value) + } + + private parseBackendKeyData(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const processID = this.reader.int32() + const secretKey = this.reader.int32() + return new BackendKeyDataMessage(length, processID, secretKey) + } + + public parseAuthenticationResponse(offset: number, length: number, bytes: Buffer) { + this.reader.setBuffer(offset, bytes) + const code = this.reader.int32() + // TODO(bmc): maybe better types here + const message: BackendMessage & any = { + name: 'authenticationOk', + length, + } + + switch (code) { + case 0: // AuthenticationOk + break + case 3: // AuthenticationCleartextPassword + if (message.length === 8) { + message.name = 'authenticationCleartextPassword' + } + break + case 5: // AuthenticationMD5Password + if (message.length === 12) { + message.name = 'authenticationMD5Password' + const salt = this.reader.bytes(4) + return new AuthenticationMD5Password(length, salt) + } + break + case 10: // AuthenticationSASL + { + message.name = 'authenticationSASL' + message.mechanisms = [] + let mechanism: string + do { + mechanism = this.reader.cstring() + if (mechanism) { + message.mechanisms.push(mechanism) + } + } while (mechanism) + } + break + case 11: // AuthenticationSASLContinue + message.name = 'authenticationSASLContinue' + message.data = this.reader.string(length - 8) + break + case 12: // AuthenticationSASLFinal + message.name = 'authenticationSASLFinal' + message.data = this.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 === 'notice' ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name) + + message.severity = fields.S + message.code = fields.C + message.detail = fields.D + message.hint = fields.H + message.position = fields.P + message.internalPosition = fields.p + message.internalQuery = fields.q + message.where = fields.W + message.schema = fields.s + message.table = fields.t + message.column = fields.c + message.dataType = fields.d + message.constraint = fields.n + message.file = fields.F + message.line = fields.L + message.routine = fields.R + return message + } +} diff --git a/node_modules/pg-protocol/src/serializer.ts b/node_modules/pg-protocol/src/serializer.ts new file mode 100644 index 00000000..bb0441f5 --- /dev/null +++ b/node_modules/pg-protocol/src/serializer.ts @@ -0,0 +1,274 @@ +import { Writer } from './buffer-writer' + +const enum code { + startup = 0x70, + query = 0x51, + parse = 0x50, + bind = 0x42, + execute = 0x45, + flush = 0x48, + sync = 0x53, + end = 0x58, + close = 0x43, + describe = 0x44, + copyFromChunk = 0x64, + copyDone = 0x63, + copyFail = 0x66, +} + +const writer = new Writer() + +const startup = (opts: Record): Buffer => { + // protocol version + writer.addInt16(3).addInt16(0) + for (const key of Object.keys(opts)) { + writer.addCString(key).addCString(opts[key]) + } + + writer.addCString('client_encoding').addCString('UTF8') + + const bodyBuffer = writer.addCString('').flush() + // this message is sent without a code + + const length = bodyBuffer.length + 4 + + return new Writer().addInt32(length).add(bodyBuffer).flush() +} + +const requestSsl = (): Buffer => { + const response = Buffer.allocUnsafe(8) + response.writeInt32BE(8, 0) + response.writeInt32BE(80877103, 4) + return response +} + +const password = (password: string): Buffer => { + return writer.addCString(password).flush(code.startup) +} + +const sendSASLInitialResponseMessage = function (mechanism: string, initialResponse: string): Buffer { + // 0x70 = 'p' + writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse) + + return writer.flush(code.startup) +} + +const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer { + return writer.addString(additionalData).flush(code.startup) +} + +const query = (text: string): Buffer => { + return writer.addCString(text).flush(code.query) +} + +type ParseOpts = { + name?: string + types?: number[] + text: string +} + +const emptyArray: any[] = [] + +const parse = (query: ParseOpts): Buffer => { + // expect something like this: + // { name: 'queryName', + // text: 'select * from blah', + // types: ['int8', 'bool'] } + + // normalize missing query names to allow for null + const name = query.name || '' + if (name.length > 63) { + 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') + } + + const types = query.types || emptyArray + + const len = types.length + + const buffer = writer + .addCString(name) // name of query + .addCString(query.text) // actual query text + .addInt16(len) + + 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 => { + // normalize config + const portal = config.portal || '' + const statement = config.statement || '' + const binary = config.binary || false + const values = config.values || emptyArray + const len = values.length + + writer.addCString(portal).addCString(statement) + writer.addInt16(len) + + writeValues(values, config.valueMapper) + + writer.addInt16(len) + writer.add(paramWriter.flush()) + + // all results use the same format code + writer.addInt16(1) + // format code + writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING) + return writer.flush(code.bind) +} + +type ExecOpts = { + portal?: string + rows?: number +} + +const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00]) + +const execute = (config?: ExecOpts): Buffer => { + // this is the happy path for most queries + if (!config || (!config.portal && !config.rows)) { + return emptyExecute + } + + const portal = config.portal || '' + const rows = config.rows || 0 + + const portalLength = Buffer.byteLength(portal) + const len = 4 + portalLength + 1 + 4 + // one extra bit for code + const buff = Buffer.allocUnsafe(1 + len) + buff[0] = code.execute + buff.writeInt32BE(len, 1) + buff.write(portal, 5, 'utf-8') + buff[portalLength + 5] = 0 // null terminate portal cString + buff.writeUInt32BE(rows, buff.length - 4) + return buff +} + +const cancel = (processID: number, secretKey: number): Buffer => { + const buffer = Buffer.allocUnsafe(16) + buffer.writeInt32BE(16, 0) + buffer.writeInt16BE(1234, 4) + buffer.writeInt16BE(5678, 6) + buffer.writeInt32BE(processID, 8) + buffer.writeInt32BE(secretKey, 12) + return buffer +} + +type PortalOpts = { + type: 'S' | 'P' + name?: string +} + +const cstringMessage = (code: code, string: string): Buffer => { + const stringLen = Buffer.byteLength(string) + const len = 4 + stringLen + 1 + // one extra bit for code + const buffer = Buffer.allocUnsafe(1 + len) + buffer[0] = code + buffer.writeInt32BE(len, 1) + buffer.write(string, 5, 'utf-8') + buffer[len] = 0 // null terminate cString + return buffer +} + +const emptyDescribePortal = writer.addCString('P').flush(code.describe) +const emptyDescribeStatement = writer.addCString('S').flush(code.describe) + +const describe = (msg: PortalOpts): Buffer => { + return msg.name + ? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`) + : msg.type === 'P' + ? emptyDescribePortal + : emptyDescribeStatement +} + +const close = (msg: PortalOpts): Buffer => { + const text = `${msg.type}${msg.name || ''}` + return cstringMessage(code.close, text) +} + +const copyData = (chunk: Buffer): Buffer => { + return writer.add(chunk).flush(code.copyFromChunk) +} + +const copyFail = (message: string): Buffer => { + return cstringMessage(code.copyFail, message) +} + +const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04]) + +const flushBuffer = codeOnlyBuffer(code.flush) +const syncBuffer = codeOnlyBuffer(code.sync) +const endBuffer = codeOnlyBuffer(code.end) +const copyDoneBuffer = codeOnlyBuffer(code.copyDone) + +const serialize = { + startup, + password, + requestSsl, + sendSASLInitialResponseMessage, + sendSCRAMClientFinalMessage, + query, + parse, + bind, + execute, + describe, + close, + flush: () => flushBuffer, + sync: () => syncBuffer, + end: () => endBuffer, + copyData, + copyDone: () => copyDoneBuffer, + copyFail, + cancel, +} + +export { serialize } diff --git a/node_modules/pg-protocol/src/testing/buffer-list.ts b/node_modules/pg-protocol/src/testing/buffer-list.ts new file mode 100644 index 00000000..bef75d40 --- /dev/null +++ b/node_modules/pg-protocol/src/testing/buffer-list.ts @@ -0,0 +1,67 @@ +export default class BufferList { + constructor(public buffers: Buffer[] = []) {} + + public add(buffer: Buffer, front?: boolean) { + this.buffers[front ? 'unshift' : 'push'](buffer) + return this + } + + public addInt16(val: number, front?: boolean) { + return this.add(Buffer.from([val >>> 8, val >>> 0]), front) + } + + public getByteLength() { + return this.buffers.reduce(function (previous, current) { + return previous + current.length + }, 0) + } + + public addInt32(val: number, first?: boolean) { + return this.add( + Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]), + first + ) + } + + public addCString(val: string, front?: boolean) { + 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) { + const len = Buffer.byteLength(val) + const buffer = Buffer.alloc(len) + buffer.write(val) + return this.add(buffer, front) + } + + public addChar(char: string, first?: boolean) { + return this.add(Buffer.from(char, 'utf8'), first) + } + + public addByte(byte: number) { + return this.add(Buffer.from([byte])) + } + + public join(appendLength?: boolean, char?: string): Buffer { + let length = this.getByteLength() + if (appendLength) { + this.addInt32(length + 4, true) + return this.join(false, char) + } + if (char) { + this.addChar(char, true) + length++ + } + const result = Buffer.alloc(length) + let index = 0 + this.buffers.forEach(function (buffer) { + buffer.copy(result, index, 0) + index += buffer.length + }) + return result + } +} diff --git a/node_modules/pg-protocol/src/testing/test-buffers.ts b/node_modules/pg-protocol/src/testing/test-buffers.ts new file mode 100644 index 00000000..1f0d71f2 --- /dev/null +++ b/node_modules/pg-protocol/src/testing/test-buffers.ts @@ -0,0 +1,166 @@ +// https://www.postgresql.org/docs/current/protocol-message-formats.html +import BufferList from './buffer-list' + +const buffers = { + readyForQuery: function () { + return new BufferList().add(Buffer.from('I')).join(true, 'Z') + }, + + authenticationOk: function () { + return new BufferList().addInt32(0).join(true, 'R') + }, + + authenticationCleartextPassword: function () { + return new BufferList().addInt32(3).join(true, 'R') + }, + + authenticationMD5Password: function () { + return new BufferList() + .addInt32(5) + .add(Buffer.from([1, 2, 3, 4])) + .join(true, 'R') + }, + + authenticationSASL: function () { + return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R') + }, + + authenticationSASLContinue: function () { + return new BufferList().addInt32(11).addString('data').join(true, 'R') + }, + + authenticationSASLFinal: function () { + return new BufferList().addInt32(12).addString('data').join(true, 'R') + }, + + parameterStatus: function (name: string, value: string) { + return new BufferList().addCString(name).addCString(value).join(true, 'S') + }, + + backendKeyData: function (processID: number, secretKey: number) { + return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K') + }, + + commandComplete: function (string: string) { + return new BufferList().addCString(string).join(true, 'C') + }, + + rowDescription: function (fields: any[]) { + fields = fields || [] + const buf = new BufferList() + buf.addInt16(fields.length) + fields.forEach(function (field) { + buf + .addCString(field.name) + .addInt32(field.tableID || 0) + .addInt16(field.attributeNumber || 0) + .addInt32(field.dataTypeID || 0) + .addInt16(field.dataTypeSize || 0) + .addInt32(field.typeModifier || 0) + .addInt16(field.formatCode || 0) + }) + return buf.join(true, 'T') + }, + + parameterDescription: function (dataTypeIDs: number[]) { + dataTypeIDs = dataTypeIDs || [] + 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 || [] + const buf = new BufferList() + buf.addInt16(columns.length) + columns.forEach(function (col) { + if (col == null) { + buf.addInt32(-1) + } else { + const strBuf = Buffer.from(col, 'utf8') + buf.addInt32(strBuf.length) + buf.add(strBuf) + } + }) + return buf.join(true, 'D') + }, + + error: function (fields: any) { + return buffers.errorOrNotice(fields).join(true, 'E') + }, + + notice: function (fields: any) { + return buffers.errorOrNotice(fields).join(true, 'N') + }, + + errorOrNotice: function (fields: any) { + fields = fields || [] + const buf = new BufferList() + fields.forEach(function (field: any) { + buf.addChar(field.type) + buf.addCString(field.value) + }) + return buf.add(Buffer.from([0])) // terminator + }, + + parseComplete: function () { + return new BufferList().join(true, '1') + }, + + bindComplete: function () { + return new BufferList().join(true, '2') + }, + + notification: function (id: number, channel: string, payload: string) { + return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A') + }, + + emptyQuery: function () { + return new BufferList().join(true, 'I') + }, + + portalSuspended: function () { + return new BufferList().join(true, 's') + }, + + closeComplete: function () { + return new BufferList().join(true, '3') + }, + + copyIn: function (cols: number) { + const list = new BufferList() + // text mode + .addByte(0) + // column count + .addInt16(cols) + for (let i = 0; i < cols; i++) { + list.addInt16(i) + } + return list.join(true, 'G') + }, + + copyOut: function (cols: number) { + const list = new BufferList() + // text mode + .addByte(0) + // column count + .addInt16(cols) + for (let i = 0; i < cols; i++) { + list.addInt16(i) + } + return list.join(true, 'H') + }, + + copyData: function (bytes: Buffer) { + return new BufferList().add(bytes).join(true, 'd') + }, + + copyDone: function () { + return new BufferList().join(true, 'c') + }, +} + +export default buffers diff --git a/node_modules/pg-protocol/src/types/chunky.d.ts b/node_modules/pg-protocol/src/types/chunky.d.ts new file mode 100644 index 00000000..7389bda6 --- /dev/null +++ b/node_modules/pg-protocol/src/types/chunky.d.ts @@ -0,0 +1 @@ +declare module 'chunky' diff --git a/node_modules/pg-types/.travis.yml b/node_modules/pg-types/.travis.yml new file mode 100644 index 00000000..dd6b0332 --- /dev/null +++ b/node_modules/pg-types/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - '4' + - 'lts/*' + - 'node' +env: + - PGUSER=postgres diff --git a/node_modules/pg-types/Makefile b/node_modules/pg-types/Makefile new file mode 100644 index 00000000..d7ec83d5 --- /dev/null +++ b/node_modules/pg-types/Makefile @@ -0,0 +1,14 @@ +.PHONY: publish-patch test + +test: + npm test + +patch: test + npm version patch -m "Bump version" + git push origin master --tags + npm publish + +minor: test + npm version minor -m "Bump version" + git push origin master --tags + npm publish diff --git a/node_modules/pg-types/README.md b/node_modules/pg-types/README.md new file mode 100644 index 00000000..54a3f2c6 --- /dev/null +++ b/node_modules/pg-types/README.md @@ -0,0 +1,75 @@ +# pg-types + +This is the code that turns all the raw text from postgres into JavaScript types for [node-postgres](https://github.com/brianc/node-postgres.git) + +## use + +This module is consumed and exported from the root `pg` object of node-postgres. To access it, do the following: + +```js +var types = require('pg').types +``` + +Generally what you'll want to do is override how a specific data-type is parsed and turned into a JavaScript type. By default the PostgreSQL backend server returns everything as strings. Every data type corresponds to a unique `OID` within the server, and these `OIDs` are sent back with the query response. So, you need to match a particluar `OID` to a function you'd like to use to take the raw text input and produce a valid JavaScript object as a result. `null` values are never parsed. + +Let's do something I commonly like to do on projects: return 64-bit integers `(int8)` as JavaScript integers. Because JavaScript doesn't have support for 64-bit integers node-postgres cannot confidently parse `int8` data type results as numbers because if you have a _huge_ number it will overflow and the result you'd get back from node-postgres would not be the result in the datbase. That would be a __very bad thing__ so node-postgres just returns `int8` results as strings and leaves the parsing up to you. Let's say that you know you don't and wont ever have numbers greater than `int4` in your database, but you're tired of recieving results from the `COUNT(*)` function as strings (because that function returns `int8`). You would do this: + +```js +var types = require('pg').types +types.setTypeParser(20, function(val) { + return parseInt(val) +}) +``` + +__boom__: now you get numbers instead of strings. + +Just as another example -- not saying this is a good idea -- let's say you want to return all dates from your database as [moment](http://momentjs.com/docs/) objects. Okay, do this: + +```js +var types = require('pg').types +var moment = require('moment') +var parseFn = function(val) { + return val === null ? null : moment(val) +} +types.setTypeParser(types.builtins.TIMESTAMPTZ, parseFn) +types.setTypeParser(types.builtins.TIMESTAMP, parseFn) +``` +_note: I've never done that with my dates, and I'm not 100% sure moment can parse all the date strings returned from postgres. It's just an example!_ + +If you're thinking "gee, this seems pretty handy, but how can I get a list of all the OIDs in the database and what they correspond to?!?!?!" worry not: + +```bash +$ psql -c "select typname, oid, typarray from pg_type order by oid" +``` + +If you want to find out the OID of a specific type: + +```bash +$ psql -c "select typname, oid, typarray from pg_type where typname = 'daterange' order by oid" +``` + +:smile: + +## 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/node_modules/pg-types/index.d.ts b/node_modules/pg-types/index.d.ts new file mode 100644 index 00000000..4bebcbe6 --- /dev/null +++ b/node_modules/pg-types/index.d.ts @@ -0,0 +1,137 @@ +export enum TypeId { + BOOL = 16, + BYTEA = 17, + CHAR = 18, + INT8 = 20, + INT2 = 21, + INT4 = 23, + REGPROC = 24, + TEXT = 25, + OID = 26, + TID = 27, + XID = 28, + CID = 29, + JSON = 114, + XML = 142, + PG_NODE_TREE = 194, + SMGR = 210, + PATH = 602, + POLYGON = 604, + CIDR = 650, + FLOAT4 = 700, + FLOAT8 = 701, + ABSTIME = 702, + RELTIME = 703, + TINTERVAL = 704, + CIRCLE = 718, + MACADDR8 = 774, + MONEY = 790, + MACADDR = 829, + INET = 869, + ACLITEM = 1033, + BPCHAR = 1042, + VARCHAR = 1043, + DATE = 1082, + TIME = 1083, + TIMESTAMP = 1114, + TIMESTAMPTZ = 1184, + INTERVAL = 1186, + TIMETZ = 1266, + BIT = 1560, + VARBIT = 1562, + NUMERIC = 1700, + REFCURSOR = 1790, + REGPROCEDURE = 2202, + REGOPER = 2203, + REGOPERATOR = 2204, + REGCLASS = 2205, + REGTYPE = 2206, + UUID = 2950, + TXID_SNAPSHOT = 2970, + PG_LSN = 3220, + PG_NDISTINCT = 3361, + PG_DEPENDENCIES = 3402, + TSVECTOR = 3614, + TSQUERY = 3615, + GTSVECTOR = 3642, + REGCONFIG = 3734, + REGDICTIONARY = 3769, + JSONB = 3802, + REGNAMESPACE = 4089, + REGROLE = 4096 +} + +export type builtinsTypes = + 'BOOL' | + 'BYTEA' | + 'CHAR' | + 'INT8' | + 'INT2' | + 'INT4' | + 'REGPROC' | + 'TEXT' | + 'OID' | + 'TID' | + 'XID' | + 'CID' | + 'JSON' | + 'XML' | + 'PG_NODE_TREE' | + 'SMGR' | + 'PATH' | + 'POLYGON' | + 'CIDR' | + 'FLOAT4' | + 'FLOAT8' | + 'ABSTIME' | + 'RELTIME' | + 'TINTERVAL' | + 'CIRCLE' | + 'MACADDR8' | + 'MONEY' | + 'MACADDR' | + 'INET' | + 'ACLITEM' | + 'BPCHAR' | + 'VARCHAR' | + 'DATE' | + 'TIME' | + 'TIMESTAMP' | + 'TIMESTAMPTZ' | + 'INTERVAL' | + 'TIMETZ' | + 'BIT' | + 'VARBIT' | + 'NUMERIC' | + 'REFCURSOR' | + 'REGPROCEDURE' | + 'REGOPER' | + 'REGOPERATOR' | + 'REGCLASS' | + 'REGTYPE' | + 'UUID' | + 'TXID_SNAPSHOT' | + 'PG_LSN' | + 'PG_NDISTINCT' | + 'PG_DEPENDENCIES' | + 'TSVECTOR' | + 'TSQUERY' | + 'GTSVECTOR' | + 'REGCONFIG' | + 'REGDICTIONARY' | + 'JSONB' | + 'REGNAMESPACE' | + 'REGROLE'; + +export type TypesBuiltins = {[key in builtinsTypes]: TypeId}; + +export type TypeFormat = 'text' | 'binary'; + +export const builtins: TypesBuiltins; + +export function setTypeParser (id: TypeId, parseFn: ((value: string) => any)): void; +export function setTypeParser (id: TypeId, format: TypeFormat, parseFn: (value: string) => any): void; + +export const getTypeParser: (id: TypeId, format?: TypeFormat) => any + +export const arrayParser: (source: string, transform: (entry: any) => any) => any[]; diff --git a/node_modules/pg-types/index.js b/node_modules/pg-types/index.js new file mode 100644 index 00000000..952d8c27 --- /dev/null +++ b/node_modules/pg-types/index.js @@ -0,0 +1,47 @@ +var textParsers = require('./lib/textParsers'); +var binaryParsers = require('./lib/binaryParsers'); +var arrayParser = require('./lib/arrayParser'); +var builtinTypes = require('./lib/builtins'); + +exports.getTypeParser = getTypeParser; +exports.setTypeParser = setTypeParser; +exports.arrayParser = arrayParser; +exports.builtins = builtinTypes; + +var typeParsers = { + text: {}, + binary: {} +}; + +//the empty parse function +function noParse (val) { + return String(val); +}; + +//returns a function used to convert a specific type (specified by +//oid) into a result javascript type +//note: the oid can be obtained via the following sql query: +//SELECT oid FROM pg_type WHERE typname = 'TYPE_NAME_HERE'; +function getTypeParser (oid, format) { + format = format || 'text'; + if (!typeParsers[format]) { + return noParse; + } + return typeParsers[format][oid] || noParse; +}; + +function setTypeParser (oid, format, parseFn) { + if(typeof format == 'function') { + parseFn = format; + format = 'text'; + } + typeParsers[format][oid] = parseFn; +}; + +textParsers.init(function(oid, converter) { + typeParsers.text[oid] = converter; +}); + +binaryParsers.init(function(oid, converter) { + typeParsers.binary[oid] = converter; +}); diff --git a/node_modules/pg-types/index.test-d.ts b/node_modules/pg-types/index.test-d.ts new file mode 100644 index 00000000..d530e6ef --- /dev/null +++ b/node_modules/pg-types/index.test-d.ts @@ -0,0 +1,21 @@ +import * as types from '.'; +import { expectType } from 'tsd'; + +// builtins +expectType(types.builtins); + +// getTypeParser +const noParse = types.getTypeParser(types.builtins.NUMERIC, 'text'); +const numericParser = types.getTypeParser(types.builtins.NUMERIC, 'binary'); +expectType(noParse('noParse')); +expectType(numericParser([200, 1, 0, 15])); + +// getArrayParser +const value = types.arrayParser('{1,2,3}', (num) => parseInt(num)); +expectType(value); + +//setTypeParser +types.setTypeParser(types.builtins.INT8, parseInt); +types.setTypeParser(types.builtins.FLOAT8, parseFloat); +types.setTypeParser(types.builtins.FLOAT8, 'binary', (data) => data[0]); +types.setTypeParser(types.builtins.FLOAT8, 'text', parseFloat); diff --git a/node_modules/pg-types/package.json b/node_modules/pg-types/package.json new file mode 100644 index 00000000..5f18026f --- /dev/null +++ b/node_modules/pg-types/package.json @@ -0,0 +1,42 @@ +{ + "name": "pg-types", + "version": "2.2.0", + "description": "Query result type converters for node-postgres", + "main": "index.js", + "scripts": { + "test": "tape test/*.js | tap-spec && npm run test-ts", + "test-ts": "if-node-version '>= 8' tsd" + }, + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-pg-types.git" + }, + "keywords": [ + "postgres", + "PostgreSQL", + "pg" + ], + "author": "Brian M. Carlson", + "license": "MIT", + "bugs": { + "url": "https://github.com/brianc/node-pg-types/issues" + }, + "homepage": "https://github.com/brianc/node-pg-types", + "devDependencies": { + "if-node-version": "^1.1.1", + "pff": "^1.0.0", + "tap-spec": "^4.0.0", + "tape": "^4.0.0", + "tsd": "^0.7.4" + }, + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } +} diff --git a/node_modules/pg-types/test/index.js b/node_modules/pg-types/test/index.js new file mode 100644 index 00000000..b7d05cd6 --- /dev/null +++ b/node_modules/pg-types/test/index.js @@ -0,0 +1,24 @@ + +var test = require('tape') +var printf = require('pff') +var getTypeParser = require('../').getTypeParser +var types = require('./types') + +test('types', function (t) { + Object.keys(types).forEach(function (typeName) { + var type = types[typeName] + t.test(typeName, function (t) { + var parser = getTypeParser(type.id, type.format) + type.tests.forEach(function (tests) { + var input = tests[0] + var expected = tests[1] + var result = parser(input) + if (typeof expected === 'function') { + return expected(t, result) + } + t.equal(result, expected) + }) + t.end() + }) + }) +}) diff --git a/node_modules/pg-types/test/types.js b/node_modules/pg-types/test/types.js new file mode 100644 index 00000000..af708a5c --- /dev/null +++ b/node_modules/pg-types/test/types.js @@ -0,0 +1,597 @@ +'use strict' + +exports['string/varchar'] = { + format: 'text', + id: 1043, + tests: [ + ['bang', 'bang'] + ] +} + +exports['integer/int4'] = { + format: 'text', + id: 23, + tests: [ + ['2147483647', 2147483647] + ] +} + +exports['smallint/int2'] = { + format: 'text', + id: 21, + tests: [ + ['32767', 32767] + ] +} + +exports['bigint/int8'] = { + format: 'text', + id: 20, + tests: [ + ['9223372036854775807', '9223372036854775807'] + ] +} + +exports.oid = { + format: 'text', + id: 26, + tests: [ + ['103', 103] + ] +} + +var bignum = '31415926535897932384626433832795028841971693993751058.16180339887498948482045868343656381177203091798057628' +exports.numeric = { + format: 'text', + id: 1700, + tests: [ + [bignum, bignum] + ] +} + +exports['real/float4'] = { + format: 'text', + id: 700, + tests: [ + ['123.456', 123.456] + ] +} + +exports['double precision / float 8'] = { + format: 'text', + id: 701, + tests: [ + ['12345678.12345678', 12345678.12345678] + ] +} + +exports.boolean = { + format: 'text', + id: 16, + tests: [ + ['TRUE', true], + ['t', true], + ['true', true], + ['y', true], + ['yes', true], + ['on', true], + ['1', true], + ['f', false], + [null, null] + ] +} + +exports.timestamptz = { + format: 'text', + id: 1184, + tests: [ + [ + '2010-10-31 14:54:13.74-05:30', + dateEquals(2010, 9, 31, 20, 24, 13, 740) + ], + [ + '2011-01-23 22:05:00.68-06', + dateEquals(2011, 0, 24, 4, 5, 0, 680) + ], + [ + '2010-10-30 14:11:12.730838Z', + dateEquals(2010, 9, 30, 14, 11, 12, 730) + ], + [ + '2010-10-30 13:10:01+05', + dateEquals(2010, 9, 30, 8, 10, 1, 0) + ] + ] +} + +exports.timestamp = { + format: 'text', + id: 1114, + tests: [ + [ + '2010-10-31 00:00:00', + function (t, value) { + t.equal( + value.toUTCString(), + new Date(2010, 9, 31, 0, 0, 0, 0, 0).toUTCString() + ) + t.equal( + value.toString(), + new Date(2010, 9, 31, 0, 0, 0, 0, 0, 0).toString() + ) + } + ] + ] +} + +exports.date = { + format: 'text', + id: 1082, + tests: [ + ['2010-10-31', function (t, value) { + var now = new Date(2010, 9, 31) + dateEquals( + 2010, + now.getUTCMonth(), + now.getUTCDate(), + now.getUTCHours(), 0, 0, 0)(t, value) + t.equal(value.getHours(), now.getHours()) + }] + ] +} + +exports.inet = { + format: 'text', + id: 869, + tests: [ + ['8.8.8.8', '8.8.8.8'], + ['2001:4860:4860::8888', '2001:4860:4860::8888'], + ['127.0.0.1', '127.0.0.1'], + ['fd00:1::40e', 'fd00:1::40e'], + ['1.2.3.4', '1.2.3.4'] + ] +} + +exports.cidr = { + format: 'text', + id: 650, + tests: [ + ['172.16.0.0/12', '172.16.0.0/12'], + ['fe80::/10', 'fe80::/10'], + ['fc00::/7', 'fc00::/7'], + ['192.168.0.0/24', '192.168.0.0/24'], + ['10.0.0.0/8', '10.0.0.0/8'] + ] +} + +exports.macaddr = { + format: 'text', + id: 829, + tests: [ + ['08:00:2b:01:02:03', '08:00:2b:01:02:03'], + ['16:10:9f:0d:66:00', '16:10:9f:0d:66:00'] + ] +} + +exports.numrange = { + format: 'text', + id: 3906, + tests: [ + ['[,]', '[,]'], + ['(,)', '(,)'], + ['(,]', '(,]'], + ['[1,)', '[1,)'], + ['[,1]', '[,1]'], + ['(1,2)', '(1,2)'], + ['(1,20.5]', '(1,20.5]'] + ] +} + +exports.interval = { + format: 'text', + id: 1186, + tests: [ + ['01:02:03', function (t, value) { + t.equal(value.toPostgres(), '3 seconds 2 minutes 1 hours') + t.deepEqual(value, {hours: 1, minutes: 2, seconds: 3}) + }], + ['01:02:03.456', function (t, value) { + t.deepEqual(value, {hours: 1, minutes:2, seconds: 3, milliseconds: 456}) + }], + ['1 year -32 days', function (t, value) { + t.equal(value.toPostgres(), '-32 days 1 years') + t.deepEqual(value, {years: 1, days: -32}) + }], + ['1 day -00:00:03', function (t, value) { + t.equal(value.toPostgres(), '-3 seconds 1 days') + t.deepEqual(value, {days: 1, seconds: -3}) + }] + ] +} + +exports.bytea = { + format: 'text', + id: 17, + tests: [ + ['foo\\000\\200\\\\\\377', function (t, value) { + var buffer = new Buffer([102, 111, 111, 0, 128, 92, 255]) + t.ok(buffer.equals(value)) + }], + ['', function (t, value) { + var buffer = new Buffer(0) + t.ok(buffer.equals(value)) + }] + ] +} + +exports['array/boolean'] = { + format: 'text', + id: 1000, + tests: [ + ['{true,false}', function (t, value) { + t.deepEqual(value, [true, false]) + }] + ] +} + +exports['array/char'] = { + format: 'text', + id: 1014, + tests: [ + ['{foo,bar}', function (t, value) { + t.deepEqual(value, ['foo', 'bar']) + }] + ] +} + +exports['array/varchar'] = { + format: 'text', + id: 1015, + tests: [ + ['{foo,bar}', function (t, value) { + t.deepEqual(value, ['foo', 'bar']) + }] + ] +} + +exports['array/text'] = { + format: 'text', + id: 1008, + tests: [ + ['{foo}', function (t, value) { + t.deepEqual(value, ['foo']) + }] + ] +} + +exports['array/bytea'] = { + format: 'text', + id: 1001, + tests: [ + ['{"\\\\x00000000"}', function (t, value) { + var buffer = new Buffer('00000000', 'hex') + t.ok(Array.isArray(value)) + t.equal(value.length, 1) + t.ok(buffer.equals(value[0])) + }], + ['{NULL,"\\\\x4e554c4c"}', function (t, value) { + var buffer = new Buffer('4e554c4c', 'hex') + t.ok(Array.isArray(value)) + t.equal(value.length, 2) + t.equal(value[0], null) + t.ok(buffer.equals(value[1])) + }], + ] +} + +exports['array/numeric'] = { + format: 'text', + id: 1231, + tests: [ + ['{1.2,3.4}', function (t, value) { + t.deepEqual(value, [1.2, 3.4]) + }] + ] +} + +exports['array/int2'] = { + format: 'text', + id: 1005, + tests: [ + ['{-32768, -32767, 32766, 32767}', function (t, value) { + t.deepEqual(value, [-32768, -32767, 32766, 32767]) + }] + ] +} + +exports['array/int4'] = { + format: 'text', + id: 1005, + tests: [ + ['{-2147483648, -2147483647, 2147483646, 2147483647}', function (t, value) { + t.deepEqual(value, [-2147483648, -2147483647, 2147483646, 2147483647]) + }] + ] +} + +exports['array/int8'] = { + format: 'text', + id: 1016, + tests: [ + [ + '{-9223372036854775808, -9223372036854775807, 9223372036854775806, 9223372036854775807}', + function (t, value) { + t.deepEqual(value, [ + '-9223372036854775808', + '-9223372036854775807', + '9223372036854775806', + '9223372036854775807' + ]) + } + ] + ] +} + +exports['array/json'] = { + format: 'text', + id: 199, + tests: [ + [ + '{{1,2},{[3],"[4,5]"},{null,NULL}}', + function (t, value) { + t.deepEqual(value, [ + [1, 2], + [[3], [4, 5]], + [null, null], + ]) + } + ] + ] +} + +exports['array/jsonb'] = { + format: 'text', + id: 3807, + tests: exports['array/json'].tests +} + +exports['array/point'] = { + format: 'text', + id: 1017, + tests: [ + ['{"(25.1,50.5)","(10.1,40)"}', function (t, value) { + t.deepEqual(value, [{x: 25.1, y: 50.5}, {x: 10.1, y: 40}]) + }] + ] +} + +exports['array/oid'] = { + format: 'text', + id: 1028, + tests: [ + ['{25864,25860}', function (t, value) { + t.deepEqual(value, [25864, 25860]) + }] + ] +} + +exports['array/float4'] = { + format: 'text', + id: 1021, + tests: [ + ['{1.2, 3.4}', function (t, value) { + t.deepEqual(value, [1.2, 3.4]) + }] + ] +} + +exports['array/float8'] = { + format: 'text', + id: 1022, + tests: [ + ['{-12345678.1234567, 12345678.12345678}', function (t, value) { + t.deepEqual(value, [-12345678.1234567, 12345678.12345678]) + }] + ] +} + +exports['array/date'] = { + format: 'text', + id: 1182, + tests: [ + ['{2014-01-01,2015-12-31}', function (t, value) { + var expecteds = [new Date(2014, 0, 1), new Date(2015, 11, 31)] + t.equal(value.length, 2) + value.forEach(function (date, index) { + var expected = expecteds[index] + dateEquals( + expected.getUTCFullYear(), + expected.getUTCMonth(), + expected.getUTCDate(), + expected.getUTCHours(), 0, 0, 0)(t, date) + }) + }] + ] +} + +exports['array/interval'] = { + format: 'text', + id: 1187, + tests: [ + ['{01:02:03,1 day -00:00:03}', function (t, value) { + var expecteds = [{hours: 1, minutes: 2, seconds: 3}, + {days: 1, seconds: -3}] + t.equal(value.length, 2) + t.deepEqual(value, expecteds); + }] + ] +} + +exports['array/inet'] = { + format: 'text', + id: 1041, + tests: [ + ['{8.8.8.8}', function (t, value) { + t.deepEqual(value, ['8.8.8.8']); + }], + ['{2001:4860:4860::8888}', function (t, value) { + t.deepEqual(value, ['2001:4860:4860::8888']); + }], + ['{127.0.0.1,fd00:1::40e,1.2.3.4}', function (t, value) { + t.deepEqual(value, ['127.0.0.1', 'fd00:1::40e', '1.2.3.4']); + }] + ] +} + +exports['array/cidr'] = { + format: 'text', + id: 651, + tests: [ + ['{172.16.0.0/12}', function (t, value) { + t.deepEqual(value, ['172.16.0.0/12']); + }], + ['{fe80::/10}', function (t, value) { + t.deepEqual(value, ['fe80::/10']); + }], + ['{10.0.0.0/8,fc00::/7,192.168.0.0/24}', function (t, value) { + t.deepEqual(value, ['10.0.0.0/8', 'fc00::/7', '192.168.0.0/24']); + }] + ] +} + +exports['array/macaddr'] = { + format: 'text', + id: 1040, + tests: [ + ['{08:00:2b:01:02:03,16:10:9f:0d:66:00}', function (t, value) { + t.deepEqual(value, ['08:00:2b:01:02:03', '16:10:9f:0d:66:00']); + }] + ] +} + +exports['array/numrange'] = { + format: 'text', + id: 3907, + tests: [ + ['{"[1,2]","(4.5,8)","[10,40)","(-21.2,60.3]"}', function (t, value) { + t.deepEqual(value, ['[1,2]', '(4.5,8)', '[10,40)', '(-21.2,60.3]']); + }], + ['{"[,20]","[3,]","[,]","(,35)","(1,)","(,)"}', function (t, value) { + t.deepEqual(value, ['[,20]', '[3,]', '[,]', '(,35)', '(1,)', '(,)']); + }], + ['{"[,20)","[3,)","[,)","[,35)","[1,)","[,)"}', function (t, value) { + t.deepEqual(value, ['[,20)', '[3,)', '[,)', '[,35)', '[1,)', '[,)']); + }] + ] +} + +exports['binary-string/varchar'] = { + format: 'binary', + id: 1043, + tests: [ + ['bang', 'bang'] + ] +} + +exports['binary-integer/int4'] = { + format: 'binary', + id: 23, + tests: [ + [[0, 0, 0, 100], 100] + ] +} + +exports['binary-smallint/int2'] = { + format: 'binary', + id: 21, + tests: [ + [[0, 101], 101] + ] +} + +exports['binary-bigint/int8'] = { + format: 'binary', + id: 20, + tests: [ + [new Buffer([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), '9223372036854775807'] + ] +} + +exports['binary-oid'] = { + format: 'binary', + id: 26, + tests: [ + [[0, 0, 0, 103], 103] + ] +} + +exports['binary-numeric'] = { + format: 'binary', + id: 1700, + tests: [ + [ + [0, 2, 0, 0, 0, 0, 0, hex('0x64'), 0, 12, hex('0xd'), hex('0x48'), 0, 0, 0, 0], + 12.34 + ] + ] +} + +exports['binary-real/float4'] = { + format: 'binary', + id: 700, + tests: [ + [['0x41', '0x48', '0x00', '0x00'].map(hex), 12.5] + ] +} + +exports['binary-boolean'] = { + format: 'binary', + id: 16, + tests: [ + [[1], true], + [[0], false], + [null, null] + ] +} + +exports['binary-string'] = { + format: 'binary', + id: 25, + tests: [ + [ + new Buffer(['0x73', '0x6c', '0x61', '0x64', '0x64', '0x61'].map(hex)), + 'sladda' + ] + ] +} + +exports.point = { + format: 'text', + id: 600, + tests: [ + ['(25.1,50.5)', function (t, value) { + t.deepEqual(value, {x: 25.1, y: 50.5}) + }] + ] +} + +exports.circle = { + format: 'text', + id: 718, + tests: [ + ['<(25,10),5>', function (t, value) { + t.deepEqual(value, {x: 25, y: 10, radius: 5}) + }] + ] +} + +function hex (string) { + return parseInt(string, 16) +} + +function dateEquals () { + var timestamp = Date.UTC.apply(Date, arguments) + return function (t, value) { + t.equal(value.toUTCString(), new Date(timestamp).toUTCString()) + } +} diff --git a/node_modules/pg/LICENSE b/node_modules/pg/LICENSE new file mode 100644 index 00000000..5c140564 --- /dev/null +++ b/node_modules/pg/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010 - 2021 Brian Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/pg/README.md b/node_modules/pg/README.md new file mode 100644 index 00000000..bf4effef --- /dev/null +++ b/node_modules/pg/README.md @@ -0,0 +1,95 @@ +# node-postgres + +[![Build Status](https://secure.travis-ci.org/brianc/node-postgres.svg?branch=master)](http://travis-ci.org/brianc/node-postgres) +NPM version +NPM downloads + +Non-blocking PostgreSQL client for Node.js. Pure JavaScript and optional native libpq bindings. + +## Install + +```sh +$ npm install pg +``` + +--- + +## :star: [Documentation](https://node-postgres.com) :star: + +### 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` + +### 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. +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! + +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. + +## Sponsorship :two_hearts: + +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! + +![medplum](https://raw.githubusercontent.com/medplum/medplum-logo/refs/heads/main/medplum-logo.png) + +## Contributing + +**:heart: contributions!** + +I will **happily** accept your pull request if it: + +- **has tests** +- looks reasonable +- does not break backwards compatibility + +If your change involves breaking backwards compatibility please please point that out in the pull request & we can discuss & plan when and how to release it and what type of documentation or communicate it will require. + +## Troubleshooting and FAQ + +The causes and solutions to common errors can be found among the [Frequently Asked Questions (FAQ)](https://github.com/brianc/node-postgres/wiki/FAQ) + +## License + +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. diff --git a/node_modules/pg/esm/index.mjs b/node_modules/pg/esm/index.mjs new file mode 100644 index 00000000..587d80c1 --- /dev/null +++ b/node_modules/pg/esm/index.mjs @@ -0,0 +1,20 @@ +// ESM wrapper for pg +import pg from '../lib/index.js' + +// Re-export all the properties +export const Client = pg.Client +export const Pool = pg.Pool +export const Connection = pg.Connection +export const types = pg.types +export const Query = pg.Query +export const DatabaseError = pg.DatabaseError +export const escapeIdentifier = pg.escapeIdentifier +export const escapeLiteral = pg.escapeLiteral +export const Result = pg.Result +export const TypeOverrides = pg.TypeOverrides + +// Also export the defaults +export const defaults = pg.defaults + +// Re-export the default +export default pg diff --git a/node_modules/pg/package.json b/node_modules/pg/package.json new file mode 100644 index 00000000..628fe41d --- /dev/null +++ b/node_modules/pg/package.json @@ -0,0 +1,79 @@ +{ + "name": "pg", + "version": "8.16.2", + "description": "PostgreSQL client - pure javascript & libpq with the same API", + "keywords": [ + "database", + "libpq", + "pg", + "postgre", + "postgres", + "postgresql", + "rdbms" + ], + "homepage": "https://github.com/brianc/node-postgres", + "repository": { + "type": "git", + "url": "git://github.com/brianc/node-postgres.git", + "directory": "packages/pg" + }, + "author": "Brian Carlson ", + "main": "./lib", + "exports": { + ".": { + "import": "./esm/index.mjs", + "require": "./lib/index.js", + "default": "./lib/index.js" + }, + "./package.json": { + "default": "./package.json" + }, + "./lib/*": { + "import": "./lib/*", + "require": "./lib/*", + "default": "./lib/*" + } + }, + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.2", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "0.8.23", + "@cloudflare/workers-types": "^4.20230404.0", + "async": "2.6.4", + "bluebird": "3.7.2", + "co": "4.6.0", + "pg-copy-streams": "0.3.0", + "typescript": "^4.0.3", + "vitest": "~3.0.9", + "wrangler": "^3.x" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.6" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + }, + "scripts": { + "test": "make test-all" + }, + "files": [ + "lib", + "esm", + "SPONSORS.md" + ], + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "gitHead": "1a25d128177dd7c54dc4652bbb92ac7986306e3f" +} diff --git a/node_modules/pgpass/README.md b/node_modules/pgpass/README.md new file mode 100644 index 00000000..bbc51939 --- /dev/null +++ b/node_modules/pgpass/README.md @@ -0,0 +1,74 @@ +# pgpass + +[![Build Status](https://github.com/hoegaarden/pgpass/workflows/CI/badge.svg?branch=master)](https://github.com/hoegaarden/pgpass/actions?query=workflow%3ACI+branch%3Amaster) + +## Install + +```sh +npm install pgpass +``` + +## Usage +```js +var pgPass = require('pgpass'); + +var connInfo = { + 'host' : 'pgserver' , + 'user' : 'the_user_name' , +}; + +pgPass(connInfo, function(pass){ + conn_info.password = pass; + // connect to postgresql server +}); +``` + +## Description + +This module tries to read the `~/.pgpass` file (or the equivalent for windows systems). If the environment variable `PGPASSFILE` is set, this file is used instead. If everything goes right, the password from said file is passed to the callback; if the password cannot be read `undefined` is passed to the callback. + +Cases where `undefined` is returned: + +- the environment variable `PGPASSWORD` is set +- the file cannot be read (wrong permissions, no such file, ...) +- for non windows systems: the file is write-/readable by the group or by other users +- there is no matching line for the given connection info + +There should be no need to use this module directly; it is already included in `node-postgres`. + +## Configuration + +The module reads the environment variable `PGPASS_NO_DEESCAPE` to decide if the the read tokens from the password file should be de-escaped or not. Default is to do de-escaping. For further information on this see [this commit](https://github.com/postgres/postgres/commit/8d15e3ec4fcb735875a8a70a09ec0c62153c3329). + + +## Tests + +There are tests in `./test/`; including linting and coverage testing. Running `npm test` runs: + +- `jshint` +- `mocha` tests +- `jscoverage` and `mocha -R html-cov` + +You can see the coverage report in `coverage.html`. + + +## Development, Patches, Bugs, ... + +If you find Bugs or have improvements, please feel free to open a issue on GitHub. If you provide a pull request, I'm more than happy to merge them, just make sure to add tests for your changes. + +## Links + +- https://github.com/hoegaarden/node-pgpass +- http://www.postgresql.org/docs/current/static/libpq-pgpass.html +- https://wiki.postgresql.org/wiki/Pgpass +- https://github.com/postgres/postgres/blob/master/src/interfaces/libpq/fe-connect.c + +## License + +Copyright (c) 2013-2016 Hannes Hörl + +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/node_modules/pgpass/package.json b/node_modules/pgpass/package.json new file mode 100644 index 00000000..22bfe84e --- /dev/null +++ b/node_modules/pgpass/package.json @@ -0,0 +1,41 @@ +{ + "name": "pgpass", + "version": "1.0.5", + "description": "Module for reading .pgpass", + "main": "lib/index", + "scripts": { + "pretest": "chmod 600 ./test/_pgpass", + "_hint": "jshint --exclude node_modules --verbose lib test", + "_test": "mocha --recursive -R list", + "_covered_test": "nyc --reporter html --reporter text \"$npm_execpath\" run _test", + "test": "\"$npm_execpath\" run _hint && \"$npm_execpath\" run _covered_test" + }, + "author": "Hannes Hörl ", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + }, + "devDependencies": { + "jshint": "^2.12.0", + "mocha": "^8.2.0", + "nyc": "^15.1.0", + "pg": "^8.4.1", + "pg-escape": "^0.2.0", + "pg-native": "3.0.0", + "resumer": "0.0.0", + "tmp": "^0.2.1", + "which": "^2.0.2" + }, + "keywords": [ + "postgres", + "pg", + "pgpass", + "password", + "postgresql" + ], + "bugs": "https://github.com/hoegaarden/pgpass/issues", + "repository": { + "type": "git", + "url": "https://github.com/hoegaarden/pgpass.git" + } +} diff --git a/node_modules/postgres-array/index.d.ts b/node_modules/postgres-array/index.d.ts new file mode 100644 index 00000000..88665bd9 --- /dev/null +++ b/node_modules/postgres-array/index.d.ts @@ -0,0 +1,4 @@ + +export function parse(source: string): string[]; +export function parse(source: string, transform: (value: string) => T): T[]; + diff --git a/node_modules/postgres-array/index.js b/node_modules/postgres-array/index.js new file mode 100644 index 00000000..18bfd163 --- /dev/null +++ b/node_modules/postgres-array/index.js @@ -0,0 +1,97 @@ +'use strict' + +exports.parse = function (source, transform) { + return new ArrayParser(source, transform).parse() +} + +class ArrayParser { + constructor (source, transform) { + this.source = source + this.transform = transform || identity + this.position = 0 + this.entries = [] + this.recorded = [] + this.dimension = 0 + } + + isEof () { + return this.position >= this.source.length + } + + nextCharacter () { + var character = this.source[this.position++] + if (character === '\\') { + return { + value: this.source[this.position++], + escaped: true + } + } + return { + value: character, + escaped: false + } + } + + record (character) { + this.recorded.push(character) + } + + newEntry (includeEmpty) { + var entry + if (this.recorded.length > 0 || includeEmpty) { + entry = this.recorded.join('') + if (entry === 'NULL' && !includeEmpty) { + entry = null + } + if (entry !== null) entry = this.transform(entry) + this.entries.push(entry) + this.recorded = [] + } + } + + consumeDimensions () { + if (this.source[0] === '[') { + while (!this.isEof()) { + var char = this.nextCharacter() + if (char.value === '=') break + } + } + } + + parse (nested) { + var character, parser, quote + this.consumeDimensions() + while (!this.isEof()) { + character = this.nextCharacter() + if (character.value === '{' && !quote) { + this.dimension++ + if (this.dimension > 1) { + parser = new ArrayParser(this.source.substr(this.position - 1), this.transform) + this.entries.push(parser.parse(true)) + this.position += parser.position - 2 + } + } else if (character.value === '}' && !quote) { + this.dimension-- + if (!this.dimension) { + this.newEntry() + if (nested) return this.entries + } + } else if (character.value === '"' && !character.escaped) { + if (quote) this.newEntry(true) + quote = !quote + } else if (character.value === ',' && !quote) { + this.newEntry() + } else { + this.record(character.value) + } + } + if (this.dimension !== 0) { + throw new Error('array dimension not balanced') + } + return this.entries + } +} + +function identity (value) { + return value +} diff --git a/node_modules/postgres-array/license b/node_modules/postgres-array/license new file mode 100644 index 00000000..25c62470 --- /dev/null +++ b/node_modules/postgres-array/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Ben Drucker (bendrucker.me) + +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/node_modules/postgres-array/package.json b/node_modules/postgres-array/package.json new file mode 100644 index 00000000..d6aa94e5 --- /dev/null +++ b/node_modules/postgres-array/package.json @@ -0,0 +1,35 @@ +{ + "name": "postgres-array", + "main": "index.js", + "version": "2.0.0", + "description": "Parse postgres array columns", + "license": "MIT", + "repository": "bendrucker/postgres-array", + "author": { + "name": "Ben Drucker", + "email": "bvdrucker@gmail.com", + "url": "bendrucker.me" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "standard && tape test.js" + }, + "types": "index.d.ts", + "keywords": [ + "postgres", + "array", + "parser" + ], + "dependencies": {}, + "devDependencies": { + "standard": "^12.0.1", + "tape": "^4.0.0" + }, + "files": [ + "index.js", + "index.d.ts", + "readme.md" + ] +} diff --git a/node_modules/postgres-array/readme.md b/node_modules/postgres-array/readme.md new file mode 100644 index 00000000..b74b369d --- /dev/null +++ b/node_modules/postgres-array/readme.md @@ -0,0 +1,43 @@ +# postgres-array [![Build Status](https://travis-ci.org/bendrucker/postgres-array.svg?branch=master)](https://travis-ci.org/bendrucker/postgres-array) + +> Parse postgres array columns + + +## Install + +``` +$ npm install --save postgres-array +``` + + +## Usage + +```js +var postgresArray = require('postgres-array') + +postgresArray.parse('{1,2,3}', (value) => parseInt(value, 10)) +//=> [1, 2, 3] +``` + +## API + +#### `parse(input, [transform])` -> `array` + +##### input + +*Required* +Type: `string` + +A Postgres array string. + +##### transform + +Type: `function` +Default: `identity` + +A function that transforms non-null values inserted into the array. + + +## License + +MIT © [Ben Drucker](http://bendrucker.me) diff --git a/node_modules/postgres-bytea/index.js b/node_modules/postgres-bytea/index.js new file mode 100644 index 00000000..d1107a01 --- /dev/null +++ b/node_modules/postgres-bytea/index.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = function parseBytea (input) { + if (/^\\x/.test(input)) { + // new 'hex' style response (pg >9.0) + return new Buffer(input.substr(2), 'hex') + } + var output = '' + var i = 0 + while (i < input.length) { + if (input[i] !== '\\') { + output += input[i] + ++i + } else { + if (/[0-7]{3}/.test(input.substr(i + 1, 3))) { + output += String.fromCharCode(parseInt(input.substr(i + 1, 3), 8)) + i += 4 + } else { + var backslashes = 1 + while (i + backslashes < input.length && input[i + backslashes] === '\\') { + backslashes++ + } + for (var k = 0; k < Math.floor(backslashes / 2); ++k) { + output += '\\' + } + i += Math.floor(backslashes / 2) * 2 + } + } + } + return new Buffer(output, 'binary') +} diff --git a/node_modules/postgres-bytea/license b/node_modules/postgres-bytea/license new file mode 100644 index 00000000..25c62470 --- /dev/null +++ b/node_modules/postgres-bytea/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Ben Drucker (bendrucker.me) + +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/node_modules/postgres-bytea/package.json b/node_modules/postgres-bytea/package.json new file mode 100644 index 00000000..cac17418 --- /dev/null +++ b/node_modules/postgres-bytea/package.json @@ -0,0 +1,34 @@ +{ + "name": "postgres-bytea", + "main": "index.js", + "version": "1.0.0", + "description": "Postgres bytea parser", + "license": "MIT", + "repository": "bendrucker/postgres-bytea", + "author": { + "name": "Ben Drucker", + "email": "bvdrucker@gmail.com", + "url": "bendrucker.me" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "standard && tape test.js" + }, + "keywords": [ + "bytea", + "postgres", + "binary", + "parser" + ], + "dependencies": {}, + "devDependencies": { + "tape": "^4.0.0", + "standard": "^4.0.0" + }, + "files": [ + "index.js", + "readme.md" + ] +} diff --git a/node_modules/postgres-bytea/readme.md b/node_modules/postgres-bytea/readme.md new file mode 100644 index 00000000..4939c3be --- /dev/null +++ b/node_modules/postgres-bytea/readme.md @@ -0,0 +1,34 @@ +# postgres-bytea [![Build Status](https://travis-ci.org/bendrucker/postgres-bytea.svg?branch=master)](https://travis-ci.org/bendrucker/postgres-bytea) + +> Postgres bytea parser + + +## Install + +``` +$ npm install --save postgres-bytea +``` + + +## Usage + +```js +var bytea = require('postgres-bytea'); +bytea('\\000\\100\\200') +//=> buffer +``` + +## API + +#### `bytea(input)` -> `buffer` + +##### input + +*Required* +Type: `string` + +A Postgres bytea binary string. + +## License + +MIT © [Ben Drucker](http://bendrucker.me) diff --git a/node_modules/postgres-date/index.js b/node_modules/postgres-date/index.js new file mode 100644 index 00000000..5dc73fbd --- /dev/null +++ b/node_modules/postgres-date/index.js @@ -0,0 +1,116 @@ +'use strict' + +var DATE_TIME = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?.*?( BC)?$/ +var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/ +var TIME_ZONE = /([Z+-])(\d{2})?:?(\d{2})?:?(\d{2})?/ +var INFINITY = /^-?infinity$/ + +module.exports = function parseDate (isoDate) { + if (INFINITY.test(isoDate)) { + // Capitalize to Infinity before passing to Number + return Number(isoDate.replace('i', 'I')) + } + var matches = DATE_TIME.exec(isoDate) + + if (!matches) { + // Force YYYY-MM-DD dates to be parsed as local time + return getDate(isoDate) || null + } + + var isBC = !!matches[8] + var year = parseInt(matches[1], 10) + if (isBC) { + year = bcYearToNegativeYear(year) + } + + var month = parseInt(matches[2], 10) - 1 + var day = matches[3] + var hour = parseInt(matches[4], 10) + var minute = parseInt(matches[5], 10) + var second = parseInt(matches[6], 10) + + var ms = matches[7] + ms = ms ? 1000 * parseFloat(ms) : 0 + + var date + var offset = timeZoneOffset(isoDate) + if (offset != null) { + date = new Date(Date.UTC(year, month, day, hour, minute, second, ms)) + + // Account for years from 0 to 99 being interpreted as 1900-1999 + // by Date.UTC / the multi-argument form of the Date constructor + if (is0To99(year)) { + date.setUTCFullYear(year) + } + + if (offset !== 0) { + date.setTime(date.getTime() - offset) + } + } else { + date = new Date(year, month, day, hour, minute, second, ms) + + if (is0To99(year)) { + date.setFullYear(year) + } + } + + return date +} + +function getDate (isoDate) { + var matches = DATE.exec(isoDate) + if (!matches) { + return + } + + var year = parseInt(matches[1], 10) + var isBC = !!matches[4] + if (isBC) { + year = bcYearToNegativeYear(year) + } + + var month = parseInt(matches[2], 10) - 1 + var day = matches[3] + // YYYY-MM-DD will be parsed as local time + var date = new Date(year, month, day) + + if (is0To99(year)) { + date.setFullYear(year) + } + + return date +} + +// match timezones: +// Z (UTC) +// -05 +// +06:30 +function timeZoneOffset (isoDate) { + if (isoDate.endsWith('+00')) { + return 0 + } + + var zone = TIME_ZONE.exec(isoDate.split(' ')[1]) + if (!zone) return + var type = zone[1] + + if (type === 'Z') { + return 0 + } + var sign = type === '-' ? -1 : 1 + var offset = parseInt(zone[2], 10) * 3600 + + parseInt(zone[3] || 0, 10) * 60 + + parseInt(zone[4] || 0, 10) + + return offset * sign * 1000 +} + +function bcYearToNegativeYear (year) { + // Account for numerical difference between representations of BC years + // See: https://github.com/bendrucker/postgres-date/issues/5 + return -(year - 1) +} + +function is0To99 (num) { + return num >= 0 && num < 100 +} diff --git a/node_modules/postgres-date/license b/node_modules/postgres-date/license new file mode 100644 index 00000000..25c62470 --- /dev/null +++ b/node_modules/postgres-date/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Ben Drucker (bendrucker.me) + +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/node_modules/postgres-date/package.json b/node_modules/postgres-date/package.json new file mode 100644 index 00000000..6fddec71 --- /dev/null +++ b/node_modules/postgres-date/package.json @@ -0,0 +1,33 @@ +{ + "name": "postgres-date", + "main": "index.js", + "version": "1.0.7", + "description": "Postgres date column parser", + "license": "MIT", + "repository": "bendrucker/postgres-date", + "author": { + "name": "Ben Drucker", + "email": "bvdrucker@gmail.com", + "url": "bendrucker.me" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "standard && tape test.js" + }, + "keywords": [ + "postgres", + "date", + "parser" + ], + "dependencies": {}, + "devDependencies": { + "standard": "^14.0.0", + "tape": "^5.0.0" + }, + "files": [ + "index.js", + "readme.md" + ] +} diff --git a/node_modules/postgres-date/readme.md b/node_modules/postgres-date/readme.md new file mode 100644 index 00000000..095431a0 --- /dev/null +++ b/node_modules/postgres-date/readme.md @@ -0,0 +1,49 @@ +# postgres-date [![Build Status](https://travis-ci.org/bendrucker/postgres-date.svg?branch=master)](https://travis-ci.org/bendrucker/postgres-date) [![Greenkeeper badge](https://badges.greenkeeper.io/bendrucker/postgres-date.svg)](https://greenkeeper.io/) + +> Postgres date output parser + +This package parses [date/time outputs](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT) from Postgres into Javascript `Date` objects. Its goal is to match Postgres behavior and preserve data accuracy. + +If you find a case where a valid Postgres output results in incorrect parsing (including loss of precision), please [create a pull request](https://github.com/bendrucker/postgres-date/compare) and provide a failing test. + +**Supported Postgres Versions:** `>= 9.6` + +All prior versions of Postgres are likely compatible but not officially supported. + +## Install + +``` +$ npm install --save postgres-date +``` + + +## Usage + +```js +var parse = require('postgres-date') +parse('2011-01-23 22:15:51Z') +// => 2011-01-23T22:15:51.000Z +``` + +## API + +#### `parse(isoDate)` -> `date` + +##### isoDate + +*Required* +Type: `string` + +A date string from Postgres. + +## Releases + +The following semantic versioning increments will be used for changes: + +* **Major**: Removal of support for Node.js versions or Postgres versions (not expected) +* **Minor**: Unused, since Postgres returns dates in standard ISO 8601 format +* **Patch**: Any fix for parsing behavior + +## License + +MIT © [Ben Drucker](http://bendrucker.me) diff --git a/node_modules/postgres-interval/index.d.ts b/node_modules/postgres-interval/index.d.ts new file mode 100644 index 00000000..f82b4c37 --- /dev/null +++ b/node_modules/postgres-interval/index.d.ts @@ -0,0 +1,20 @@ +declare namespace PostgresInterval { + export interface IPostgresInterval { + years?: number; + months?: number; + days?: number; + hours?: number; + minutes?: number; + seconds?: number; + milliseconds?: number; + + toPostgres(): string; + + toISO(): string; + toISOString(): string; + } +} + +declare function PostgresInterval(raw: string): PostgresInterval.IPostgresInterval; + +export = PostgresInterval; diff --git a/node_modules/postgres-interval/index.js b/node_modules/postgres-interval/index.js new file mode 100644 index 00000000..8ecca800 --- /dev/null +++ b/node_modules/postgres-interval/index.js @@ -0,0 +1,125 @@ +'use strict' + +var extend = require('xtend/mutable') + +module.exports = PostgresInterval + +function PostgresInterval (raw) { + if (!(this instanceof PostgresInterval)) { + return new PostgresInterval(raw) + } + extend(this, parse(raw)) +} +var properties = ['seconds', 'minutes', 'hours', 'days', 'months', 'years'] +PostgresInterval.prototype.toPostgres = function () { + var filtered = properties.filter(this.hasOwnProperty, this) + + // In addition to `properties`, we need to account for fractions of seconds. + if (this.milliseconds && filtered.indexOf('seconds') < 0) { + filtered.push('seconds') + } + + if (filtered.length === 0) return '0' + return filtered + .map(function (property) { + var value = this[property] || 0 + + // Account for fractional part of seconds, + // remove trailing zeroes. + if (property === 'seconds' && this.milliseconds) { + value = (value + this.milliseconds / 1000).toFixed(6).replace(/\.?0+$/, '') + } + + return value + ' ' + property + }, this) + .join(' ') +} + +var propertiesISOEquivalent = { + years: 'Y', + months: 'M', + days: 'D', + hours: 'H', + minutes: 'M', + seconds: 'S' +} +var dateProperties = ['years', 'months', 'days'] +var timeProperties = ['hours', 'minutes', 'seconds'] +// according to ISO 8601 +PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO = function () { + var datePart = dateProperties + .map(buildProperty, this) + .join('') + + var timePart = timeProperties + .map(buildProperty, this) + .join('') + + return 'P' + datePart + 'T' + timePart + + function buildProperty (property) { + var value = this[property] || 0 + + // Account for fractional part of seconds, + // remove trailing zeroes. + if (property === 'seconds' && this.milliseconds) { + value = (value + this.milliseconds / 1000).toFixed(6).replace(/0+$/, '') + } + + return value + propertiesISOEquivalent[property] + } +} + +var NUMBER = '([+-]?\\d+)' +var YEAR = NUMBER + '\\s+years?' +var MONTH = NUMBER + '\\s+mons?' +var DAY = NUMBER + '\\s+days?' +var TIME = '([+-])?([\\d]*):(\\d\\d):(\\d\\d)\\.?(\\d{1,6})?' +var INTERVAL = new RegExp([YEAR, MONTH, DAY, TIME].map(function (regexString) { + return '(' + regexString + ')?' +}) + .join('\\s*')) + +// Positions of values in regex match +var positions = { + years: 2, + months: 4, + days: 6, + hours: 9, + minutes: 10, + seconds: 11, + milliseconds: 12 +} +// We can use negative time +var negatives = ['hours', 'minutes', 'seconds', 'milliseconds'] + +function parseMilliseconds (fraction) { + // add omitted zeroes + var microseconds = fraction + '000000'.slice(fraction.length) + return parseInt(microseconds, 10) / 1000 +} + +function parse (interval) { + if (!interval) return {} + var matches = INTERVAL.exec(interval) + var isNegative = matches[8] === '-' + return Object.keys(positions) + .reduce(function (parsed, property) { + var position = positions[property] + var value = matches[position] + // no empty string + if (!value) return parsed + // milliseconds are actually microseconds (up to 6 digits) + // with omitted trailing zeroes. + value = property === 'milliseconds' + ? parseMilliseconds(value) + : parseInt(value, 10) + // no zeros + if (!value) return parsed + if (isNegative && ~negatives.indexOf(property)) { + value *= -1 + } + parsed[property] = value + return parsed + }, {}) +} diff --git a/node_modules/postgres-interval/license b/node_modules/postgres-interval/license new file mode 100644 index 00000000..25c62470 --- /dev/null +++ b/node_modules/postgres-interval/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Ben Drucker (bendrucker.me) + +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/node_modules/postgres-interval/package.json b/node_modules/postgres-interval/package.json new file mode 100644 index 00000000..95520a0e --- /dev/null +++ b/node_modules/postgres-interval/package.json @@ -0,0 +1,36 @@ +{ + "name": "postgres-interval", + "main": "index.js", + "version": "1.2.0", + "description": "Parse Postgres interval columns", + "license": "MIT", + "repository": "bendrucker/postgres-interval", + "author": { + "name": "Ben Drucker", + "email": "bvdrucker@gmail.com", + "url": "bendrucker.me" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "standard && tape test.js" + }, + "keywords": [ + "postgres", + "interval", + "parser" + ], + "dependencies": { + "xtend": "^4.0.0" + }, + "devDependencies": { + "tape": "^4.0.0", + "standard": "^12.0.1" + }, + "files": [ + "index.js", + "index.d.ts", + "readme.md" + ] +} diff --git a/node_modules/postgres-interval/readme.md b/node_modules/postgres-interval/readme.md new file mode 100644 index 00000000..53cda4ad --- /dev/null +++ b/node_modules/postgres-interval/readme.md @@ -0,0 +1,48 @@ +# postgres-interval [![Build Status](https://travis-ci.org/bendrucker/postgres-interval.svg?branch=master)](https://travis-ci.org/bendrucker/postgres-interval) [![Greenkeeper badge](https://badges.greenkeeper.io/bendrucker/postgres-interval.svg)](https://greenkeeper.io/) + +> Parse Postgres interval columns + + +## Install + +``` +$ npm install --save postgres-interval +``` + + +## Usage + +```js +var parse = require('postgres-interval') +var interval = parse('01:02:03') +//=> {hours: 1, minutes: 2, seconds: 3} +interval.toPostgres() +// 3 seconds 2 minutes 1 hours +interval.toISO() +// P0Y0M0DT1H2M3S +``` + +## API + +#### `parse(pgInterval)` -> `interval` + +##### pgInterval + +*Required* +Type: `string` + +A Postgres interval string. + +#### `interval.toPostgres()` -> `string` + +Returns an interval string. This allows the interval object to be passed into prepared statements. + +#### `interval.toISOString()` -> `string` + +Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string. + +Also available as `interval.toISO()` for backwards compatibility. + +## License + +MIT © [Ben Drucker](http://bendrucker.me) diff --git a/node_modules/split2/LICENSE b/node_modules/split2/LICENSE new file mode 100644 index 00000000..a91afe5b --- /dev/null +++ b/node_modules/split2/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2014-2018, Matteo Collina + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/split2/README.md b/node_modules/split2/README.md new file mode 100644 index 00000000..36f03ab6 --- /dev/null +++ b/node_modules/split2/README.md @@ -0,0 +1,85 @@ +# Split2(matcher, mapper, options) + +![ci](https://github.com/mcollina/split2/workflows/ci/badge.svg) + +Break up a stream and reassemble it so that each line is a chunk. +`split2` is inspired by [@dominictarr](https://github.com/dominictarr) [`split`](https://github.com/dominictarr/split) module, +and it is totally API compatible with it. +However, it is based on Node.js core [`Transform`](https://nodejs.org/api/stream.html#stream_new_stream_transform_options). + +`matcher` may be a `String`, or a `RegExp`. Example, read every line in a file ... + +``` js + fs.createReadStream(file) + .pipe(split2()) + .on('data', function (line) { + //each chunk now is a separate line! + }) + +``` + +`split` takes the same arguments as `string.split` except it defaults to '/\r?\n/', and the optional `limit` paremeter is ignored. +[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split) + +`split` takes an optional options object on it's third argument, which +is directly passed as a +[Transform](https://nodejs.org/api/stream.html#stream_new_stream_transform_options) +option. + +Additionally, the `.maxLength` and `.skipOverflow` options are implemented, which set limits on the internal +buffer size and the stream's behavior when the limit is exceeded. There is no limit unless `maxLength` is set. When +the internal buffer size exceeds `maxLength`, the stream emits an error by default. You may also set `skipOverflow` to +true to suppress the error and instead skip past any lines that cause the internal buffer to exceed `maxLength`. + +Calling `.destroy` will make the stream emit `close`. Use this to perform cleanup logic + +``` js +var splitFile = function(filename) { + var file = fs.createReadStream(filename) + + return file + .pipe(split2()) + .on('close', function() { + // destroy the file stream in case the split stream was destroyed + file.destroy() + }) +} + +var stream = splitFile('my-file.txt') + +stream.destroy() // will destroy the input file stream +``` + +# NDJ - Newline Delimited Json + +`split2` accepts a function which transforms each line. + +``` js +fs.createReadStream(file) + .pipe(split2(JSON.parse)) + .on('data', function (obj) { + //each chunk now is a js object + }) + .on("error", function(error) { + //handling parsing errors + }) +``` + +However, in [@dominictarr](https://github.com/dominictarr) [`split`](https://github.com/dominictarr/split) the mapper +is wrapped in a try-catch, while here it is not: if your parsing logic can throw, wrap it yourself. Otherwise, you can also use the stream error handling when mapper function throw. + +# License + +Copyright (c) 2014-2021, Matteo Collina + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/split2/bench.js b/node_modules/split2/bench.js new file mode 100644 index 00000000..15ec5df9 --- /dev/null +++ b/node_modules/split2/bench.js @@ -0,0 +1,27 @@ +'use strict' + +const split = require('./') +const bench = require('fastbench') +const binarySplit = require('binary-split') +const fs = require('fs') + +function benchSplit (cb) { + fs.createReadStream('package.json') + .pipe(split()) + .on('end', cb) + .resume() +} + +function benchBinarySplit (cb) { + fs.createReadStream('package.json') + .pipe(binarySplit()) + .on('end', cb) + .resume() +} + +const run = bench([ + benchSplit, + benchBinarySplit +], 10000) + +run(run) diff --git a/node_modules/split2/index.js b/node_modules/split2/index.js new file mode 100644 index 00000000..9b59f6ce --- /dev/null +++ b/node_modules/split2/index.js @@ -0,0 +1,141 @@ +/* +Copyright (c) 2014-2021, Matteo Collina + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +'use strict' + +const { Transform } = require('stream') +const { StringDecoder } = require('string_decoder') +const kLast = Symbol('last') +const kDecoder = Symbol('decoder') + +function transform (chunk, enc, cb) { + let list + if (this.overflow) { // Line buffer is full. Skip to start of next line. + const buf = this[kDecoder].write(chunk) + list = buf.split(this.matcher) + + if (list.length === 1) return cb() // Line ending not found. Discard entire chunk. + + // Line ending found. Discard trailing fragment of previous line and reset overflow state. + list.shift() + this.overflow = false + } else { + this[kLast] += this[kDecoder].write(chunk) + list = this[kLast].split(this.matcher) + } + + this[kLast] = list.pop() + + for (let i = 0; i < list.length; i++) { + try { + push(this, this.mapper(list[i])) + } catch (error) { + return cb(error) + } + } + + this.overflow = this[kLast].length > this.maxLength + if (this.overflow && !this.skipOverflow) { + cb(new Error('maximum buffer reached')) + return + } + + cb() +} + +function flush (cb) { + // forward any gibberish left in there + this[kLast] += this[kDecoder].end() + + if (this[kLast]) { + try { + push(this, this.mapper(this[kLast])) + } catch (error) { + return cb(error) + } + } + + cb() +} + +function push (self, val) { + if (val !== undefined) { + self.push(val) + } +} + +function noop (incoming) { + return incoming +} + +function split (matcher, mapper, options) { + // Set defaults for any arguments not supplied. + matcher = matcher || /\r?\n/ + mapper = mapper || noop + options = options || {} + + // Test arguments explicitly. + switch (arguments.length) { + case 1: + // If mapper is only argument. + if (typeof matcher === 'function') { + mapper = matcher + matcher = /\r?\n/ + // If options is only argument. + } else if (typeof matcher === 'object' && !(matcher instanceof RegExp) && !matcher[Symbol.split]) { + options = matcher + matcher = /\r?\n/ + } + break + + case 2: + // If mapper and options are arguments. + if (typeof matcher === 'function') { + options = mapper + mapper = matcher + matcher = /\r?\n/ + // If matcher and options are arguments. + } else if (typeof mapper === 'object') { + options = mapper + mapper = noop + } + } + + options = Object.assign({}, options) + options.autoDestroy = true + options.transform = transform + options.flush = flush + options.readableObjectMode = true + + const stream = new Transform(options) + + stream[kLast] = '' + stream[kDecoder] = new StringDecoder('utf8') + stream.matcher = matcher + stream.mapper = mapper + stream.maxLength = options.maxLength + stream.skipOverflow = options.skipOverflow || false + stream.overflow = false + stream._destroy = function (err, cb) { + // Weird Node v12 bug that we need to work around + this._writableState.errorEmitted = false + cb(err) + } + + return stream +} + +module.exports = split diff --git a/node_modules/split2/package.json b/node_modules/split2/package.json new file mode 100644 index 00000000..e04bcc81 --- /dev/null +++ b/node_modules/split2/package.json @@ -0,0 +1,39 @@ +{ + "name": "split2", + "version": "4.2.0", + "description": "split a Text Stream into a Line Stream, using Stream 3", + "main": "index.js", + "scripts": { + "lint": "standard --verbose", + "unit": "nyc --lines 100 --branches 100 --functions 100 --check-coverage --reporter=text tape test.js", + "coverage": "nyc --reporter=html --reporter=cobertura --reporter=text tape test/test.js", + "test:report": "npm run lint && npm run unit:report", + "test": "npm run lint && npm run unit", + "legacy": "tape test.js" + }, + "pre-commit": [ + "test" + ], + "website": "https://github.com/mcollina/split2", + "repository": { + "type": "git", + "url": "https://github.com/mcollina/split2.git" + }, + "bugs": { + "url": "http://github.com/mcollina/split2/issues" + }, + "engines": { + "node": ">= 10.x" + }, + "author": "Matteo Collina ", + "license": "ISC", + "devDependencies": { + "binary-split": "^1.0.3", + "callback-stream": "^1.1.0", + "fastbench": "^1.0.0", + "nyc": "^15.0.1", + "pre-commit": "^1.1.2", + "standard": "^17.0.0", + "tape": "^5.0.0" + } +} diff --git a/node_modules/split2/test.js b/node_modules/split2/test.js new file mode 100644 index 00000000..a7f98385 --- /dev/null +++ b/node_modules/split2/test.js @@ -0,0 +1,409 @@ +'use strict' + +const test = require('tape') +const split = require('./') +const callback = require('callback-stream') +const strcb = callback.bind(null, { decodeStrings: false }) +const objcb = callback.bind(null, { objectMode: true }) + +test('split two lines on end', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello\nworld') +}) + +test('split two lines on two writes', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.write('hello') + input.write('\nworld') + input.end() +}) + +test('split four lines on three writes', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world', 'bye', 'world']) + })) + + input.write('hello\nwor') + input.write('ld\nbye\nwo') + input.write('rld') + input.end() +}) + +test('accumulate multiple writes', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['helloworld']) + })) + + input.write('hello') + input.write('world') + input.end() +}) + +test('split using a custom string matcher', function (t) { + t.plan(2) + + const input = split('~') + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello~world') +}) + +test('split using a custom regexp matcher', function (t) { + t.plan(2) + + const input = split(/~/) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello~world') +}) + +test('support an option argument', function (t) { + t.plan(2) + + const input = split({ highWaterMark: 2 }) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello\nworld') +}) + +test('support a mapper function', function (t) { + t.plan(2) + + const a = { a: '42' } + const b = { b: '24' } + + const input = split(JSON.parse) + + input.pipe(objcb(function (err, list) { + t.error(err) + t.deepEqual(list, [a, b]) + })) + + input.write(JSON.stringify(a)) + input.write('\n') + input.end(JSON.stringify(b)) +}) + +test('split lines windows-style', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello\r\nworld') +}) + +test('splits a buffer', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end(Buffer.from('hello\nworld')) +}) + +test('do not end on undefined', function (t) { + t.plan(2) + + const input = split(function (line) { }) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, []) + })) + + input.end(Buffer.from('hello\nworld')) +}) + +test('has destroy method', function (t) { + t.plan(1) + + const input = split(function (line) { }) + + input.on('close', function () { + t.ok(true, 'close emitted') + t.end() + }) + + input.destroy() +}) + +test('support custom matcher and mapper', function (t) { + t.plan(4) + + const a = { a: '42' } + const b = { b: '24' } + const input = split('~', JSON.parse) + + t.equal(input.matcher, '~') + t.equal(typeof input.mapper, 'function') + + input.pipe(objcb(function (err, list) { + t.notOk(err, 'no errors') + t.deepEqual(list, [a, b]) + })) + + input.write(JSON.stringify(a)) + input.write('~') + input.end(JSON.stringify(b)) +}) + +test('support custom matcher and options', function (t) { + t.plan(6) + + const input = split('~', { highWaterMark: 1024 }) + + t.equal(input.matcher, '~') + t.equal(typeof input.mapper, 'function') + t.equal(input._readableState.highWaterMark, 1024) + t.equal(input._writableState.highWaterMark, 1024) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello~world') +}) + +test('support mapper and options', function (t) { + t.plan(6) + + const a = { a: '42' } + const b = { b: '24' } + const input = split(JSON.parse, { highWaterMark: 1024 }) + + t.ok(input.matcher instanceof RegExp, 'matcher is RegExp') + t.equal(typeof input.mapper, 'function') + t.equal(input._readableState.highWaterMark, 1024) + t.equal(input._writableState.highWaterMark, 1024) + + input.pipe(objcb(function (err, list) { + t.error(err) + t.deepEqual(list, [a, b]) + })) + + input.write(JSON.stringify(a)) + input.write('\n') + input.end(JSON.stringify(b)) +}) + +test('split utf8 chars', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['烫烫烫', '锟斤拷']) + })) + + const buf = Buffer.from('烫烫烫\r\n锟斤拷', 'utf8') + for (let i = 0; i < buf.length; ++i) { + input.write(buf.slice(i, i + 1)) + } + input.end() +}) + +test('split utf8 chars 2by2', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['烫烫烫', '烫烫烫']) + })) + + const str = '烫烫烫\r\n烫烫烫' + const buf = Buffer.from(str, 'utf8') + for (let i = 0; i < buf.length; i += 2) { + input.write(buf.slice(i, i + 2)) + } + input.end() +}) + +test('split lines when the \n comes at the end of a chunk', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.write('hello\n') + input.end('world') +}) + +test('truncated utf-8 char', function (t) { + t.plan(2) + + const input = split() + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['烫' + Buffer.from('e7', 'hex').toString()]) + })) + + const str = '烫烫' + const buf = Buffer.from(str, 'utf8') + + input.write(buf.slice(0, 3)) + input.end(buf.slice(3, 4)) +}) + +test('maximum buffer limit', function (t) { + t.plan(1) + + const input = split({ maxLength: 2 }) + input.on('error', function (err) { + t.ok(err) + }) + + input.resume() + + input.write('hey') +}) + +test('readable highWaterMark', function (t) { + const input = split() + t.equal(input._readableState.highWaterMark, 16) + t.end() +}) + +test('maxLength < chunk size', function (t) { + t.plan(2) + + const input = split({ maxLength: 2 }) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['a', 'b']) + })) + + input.end('a\nb') +}) + +test('maximum buffer limit w/skip', function (t) { + t.plan(2) + + const input = split({ maxLength: 2, skipOverflow: true }) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['a', 'b', 'c']) + })) + + input.write('a\n123') + input.write('456') + input.write('789\nb\nc') + input.end() +}) + +test("don't modify the options object", function (t) { + t.plan(2) + + const options = {} + const input = split(options) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.same(options, {}) + })) + + input.end() +}) + +test('mapper throws flush', function (t) { + t.plan(1) + const error = new Error() + const input = split(function () { + throw error + }) + + input.on('error', (err, list) => { + t.same(err, error) + }) + input.end('hello') +}) + +test('mapper throws on transform', function (t) { + t.plan(1) + + const error = new Error() + const input = split(function (l) { + throw error + }) + + input.on('error', (err) => { + t.same(err, error) + }) + input.write('a') + input.write('\n') + input.end('b') +}) + +test('supports Symbol.split', function (t) { + t.plan(2) + + const input = split({ + [Symbol.split] (str) { + return str.split('~') + } + }) + + input.pipe(strcb(function (err, list) { + t.error(err) + t.deepEqual(list, ['hello', 'world']) + })) + + input.end('hello~world') +}) diff --git a/package-lock.json b/package-lock.json index c4efa27d..1757a95b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,10 +31,10 @@ "passport": "^0.6.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", + "pg": "^8.11.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "socket.io": "^4.7.0", - "sqlite3": "^5.1.6", "typeorm": "^0.3.17", "uuid": "^9.0.0" }, @@ -50,6 +50,7 @@ "@types/node": "^20.3.1", "@types/passport-jwt": "^3.0.9", "@types/passport-local": "^1.0.35", + "@types/pg": "^8.10.2", "@types/supertest": "^2.0.12", "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^6.0.0", @@ -1016,7 +1017,8 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@google-cloud/firestore": { "version": "6.8.0", @@ -2457,6 +2459,7 @@ "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" @@ -2469,6 +2472,7 @@ "deprecated": "This functionality has been moved to @npmcli/fs", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -2483,6 +2487,7 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -2684,6 +2689,7 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 6" } @@ -3069,6 +3075,18 @@ "@types/passport": "*" } }, + "node_modules/@types/pg": { + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", + "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -3646,6 +3664,7 @@ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -3659,6 +3678,7 @@ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -4129,6 +4149,8 @@ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -4137,6 +4159,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "devOptional": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -4272,6 +4295,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -4330,6 +4354,7 @@ "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -4360,6 +4385,7 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4372,6 +4398,7 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4393,6 +4420,7 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4406,6 +4434,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4419,6 +4448,7 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -4651,6 +4681,7 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -5102,6 +5133,8 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -5131,6 +5164,8 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4.0.0" } @@ -5420,6 +5455,7 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -5430,6 +5466,7 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -5442,6 +5479,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", + "optional": true, "dependencies": { "once": "^1.4.0" } @@ -5550,6 +5588,7 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -5559,7 +5598,8 @@ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/error-ex": { "version": "1.3.2", @@ -6093,6 +6133,8 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -6379,7 +6421,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/filelist": { "version": "1.0.4", @@ -6636,7 +6680,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/fs-extra": { "version": "10.1.0", @@ -6861,7 +6907,9 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/glob": { "version": "10.4.5", @@ -7180,7 +7228,8 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause", - "optional": true + "optional": true, + "peer": true }, "node_modules/http-errors": { "version": "2.0.0", @@ -7210,6 +7259,7 @@ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@tootallnate/once": "1", "agent-base": "6", @@ -7248,6 +7298,7 @@ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -7347,6 +7398,7 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -7356,7 +7408,8 @@ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/inflight": { "version": "1.0.6", @@ -7379,7 +7432,9 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/inquirer": { "version": "8.2.6", @@ -7414,6 +7469,7 @@ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -7524,7 +7580,8 @@ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/is-number": { "version": "7.0.0", @@ -8526,7 +8583,8 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/jsdoc": { "version": "4.0.4", @@ -9036,6 +9094,7 @@ "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", @@ -9064,6 +9123,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9282,6 +9342,8 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -9329,6 +9391,7 @@ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9342,6 +9405,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9355,6 +9419,7 @@ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", @@ -9373,6 +9438,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9386,6 +9452,7 @@ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9399,6 +9466,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9412,6 +9480,7 @@ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9425,6 +9494,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9438,6 +9508,7 @@ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9451,6 +9522,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9499,7 +9571,9 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/moment": { "version": "2.30.1", @@ -9546,7 +9620,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -9576,6 +9652,8 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "semver": "^7.3.5" }, @@ -9641,6 +9719,7 @@ "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", @@ -9667,6 +9746,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -9681,6 +9761,7 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9693,6 +9774,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", @@ -9714,6 +9796,7 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9735,6 +9818,7 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9749,6 +9833,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", @@ -9764,7 +9849,8 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/node-int64": { "version": "0.4.0", @@ -9988,6 +10074,7 @@ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -10180,6 +10267,95 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/pg": { + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz", + "integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.2", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.6" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz", + "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.2.tgz", + "integrity": "sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10289,11 +10465,52 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -10393,7 +10610,8 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/promise-retry": { "version": "2.0.1", @@ -10401,6 +10619,7 @@ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -10415,6 +10634,7 @@ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -10562,6 +10782,8 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10676,6 +10898,8 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "peer": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10691,6 +10915,8 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11369,7 +11595,9 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -11390,6 +11618,8 @@ } ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -11419,6 +11649,7 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -11522,6 +11753,7 @@ "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -11537,6 +11769,7 @@ "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", @@ -11577,12 +11810,22 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause", - "optional": true + "optional": true, + "peer": true }, "node_modules/sql-highlight": { "version": "6.1.0", @@ -11606,6 +11849,8 @@ "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", "hasInstallScript": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", @@ -11628,7 +11873,9 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/ssri": { "version": "8.0.1", @@ -11636,6 +11883,7 @@ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.1.1" }, @@ -11649,6 +11897,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -11998,6 +12247,8 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -12009,13 +12260,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -12554,6 +12809,8 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -12848,6 +13105,7 @@ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "unique-slug": "^2.0.0" } @@ -12858,6 +13116,7 @@ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "imurmurhash": "^0.1.4" } diff --git a/package.json b/package.json index f8558bf8..15388bc5 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@nestjs/serve-static": "^4.0.0", "@nestjs/schedule": "^4.0.0", "typeorm": "^0.3.17", - "sqlite3": "^5.1.6", + "pg": "^8.11.0", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", @@ -67,6 +67,7 @@ "@types/multer": "^1.4.7", "@types/crypto-js": "^4.1.1", "@types/uuid": "^9.0.2", + "@types/pg": "^8.10.2", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", diff --git a/src/app.module.ts b/src/app.module.ts index 373018e2..97c66679 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -19,12 +19,10 @@ import { OrganizationModule } from './organization/organization.module'; // Ensure storage directories exist const storageDir = '/app/storage'; -const dbDir = '/app/storage/db'; const mediaDir = '/app/storage/media'; try { mkdirSync(storageDir, { recursive: true }); - mkdirSync(dbDir, { recursive: true }); mkdirSync(mediaDir, { recursive: true }); } catch (error) { // Directory already exists or other error @@ -38,11 +36,16 @@ try { TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ - type: 'sqlite', - database: '/app/storage/db/chat.sqlite', + type: 'postgres', + host: configService.get('DB_HOST') || 'localhost', + port: configService.get('DB_PORT') || 5432, + username: configService.get('DB_USERNAME') || 'postgres', + password: configService.get('DB_PASSWORD') || 'postgres', + database: configService.get('DB_NAME') || 'realtime_chat_db', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: process.env.NODE_ENV !== 'production', logging: process.env.NODE_ENV === 'development', + ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, }), inject: [ConfigService], }), diff --git a/src/database/database.config.ts b/src/database/database.config.ts new file mode 100644 index 00000000..1d5d862b --- /dev/null +++ b/src/database/database.config.ts @@ -0,0 +1,21 @@ +import { DataSource } from 'typeorm'; +import { ConfigService } from '@nestjs/config'; +import { config } from 'dotenv'; + +config(); + +const configService = new ConfigService(); + +export default new DataSource({ + type: 'postgres', + host: configService.get('DB_HOST') || 'localhost', + port: configService.get('DB_PORT') || 5432, + username: configService.get('DB_USERNAME') || 'postgres', + password: configService.get('DB_PASSWORD') || 'postgres', + database: configService.get('DB_NAME') || 'realtime_chat_db', + entities: [__dirname + '/entities/**/*.entity{.ts,.js}'], + migrations: [__dirname + '/migrations/**/*{.ts,.js}'], + synchronize: false, + logging: configService.get('NODE_ENV') === 'development', + ssl: configService.get('NODE_ENV') === 'production' ? { rejectUnauthorized: false } : false, +}); \ No newline at end of file diff --git a/src/database/entities/message.entity.ts b/src/database/entities/message.entity.ts index c519f93a..d6632a4b 100644 --- a/src/database/entities/message.entity.ts +++ b/src/database/entities/message.entity.ts @@ -49,7 +49,7 @@ export class Message { @Column({ default: false }) isDeleted: boolean; - @Column({ type: 'datetime', nullable: true }) + @Column({ type: 'timestamp', nullable: true }) editedAt: Date; @CreateDateColumn() diff --git a/src/database/entities/notification.entity.ts b/src/database/entities/notification.entity.ts index ffbfedc4..1a08b9c2 100644 --- a/src/database/entities/notification.entity.ts +++ b/src/database/entities/notification.entity.ts @@ -34,7 +34,7 @@ export class Notification { @Column({ type: 'text' }) body: string; - @Column({ type: 'json', nullable: true }) + @Column({ type: 'jsonb', nullable: true }) data: any; @Column({ default: false }) diff --git a/src/database/entities/organization-invite.entity.ts b/src/database/entities/organization-invite.entity.ts index 8ba1cefe..794e5464 100644 --- a/src/database/entities/organization-invite.entity.ts +++ b/src/database/entities/organization-invite.entity.ts @@ -43,7 +43,7 @@ export class OrganizationInvite { @Column() token: string; - @Column({ type: 'datetime' }) + @Column({ type: 'timestamp' }) expiresAt: Date; @Column({ type: 'text', nullable: true }) diff --git a/src/database/entities/organization-member.entity.ts b/src/database/entities/organization-member.entity.ts index f39ab916..efc8b2d7 100644 --- a/src/database/entities/organization-member.entity.ts +++ b/src/database/entities/organization-member.entity.ts @@ -43,7 +43,7 @@ export class OrganizationMember { }) status: MemberStatus; - @Column({ type: 'json', nullable: true }) + @Column({ type: 'jsonb', nullable: true }) permissions: { canCreateChannels?: boolean; canDeleteMessages?: boolean; @@ -53,10 +53,10 @@ export class OrganizationMember { canAccessAnalytics?: boolean; }; - @Column({ type: 'datetime', nullable: true }) + @Column({ type: 'timestamp', nullable: true }) joinedAt: Date; - @Column({ type: 'datetime', nullable: true }) + @Column({ type: 'timestamp', nullable: true }) lastActiveAt: Date; @CreateDateColumn() diff --git a/src/database/entities/organization.entity.ts b/src/database/entities/organization.entity.ts index ea8ecb87..189c048a 100644 --- a/src/database/entities/organization.entity.ts +++ b/src/database/entities/organization.entity.ts @@ -61,7 +61,7 @@ export class Organization { }) status: OrganizationStatus; - @Column({ type: 'json', nullable: true }) + @Column({ type: 'jsonb', nullable: true }) settings: { allowPublicSignup?: boolean; requireInviteApproval?: boolean; @@ -72,7 +72,7 @@ export class Organization { customBranding?: boolean; }; - @Column({ type: 'json', nullable: true }) + @Column({ type: 'jsonb', nullable: true }) limits: { maxUsers?: number; maxChatRooms?: number; @@ -80,7 +80,7 @@ export class Organization { maxMonthlyMessages?: number; }; - @Column({ type: 'datetime', nullable: true }) + @Column({ type: 'timestamp', nullable: true }) subscriptionExpiry: Date; @ManyToOne(() => User) diff --git a/src/database/entities/user.entity.ts b/src/database/entities/user.entity.ts index 1abb3532..58585f72 100644 --- a/src/database/entities/user.entity.ts +++ b/src/database/entities/user.entity.ts @@ -38,7 +38,7 @@ export class User { @Column({ default: true }) isOnline: boolean; - @Column({ type: 'datetime', nullable: true }) + @Column({ type: 'timestamp', nullable: true }) lastSeen: Date; @Column({ type: 'text', nullable: true }) diff --git a/src/database/migrations/001-initial-schema.ts b/src/database/migrations/001-initial-schema.ts new file mode 100644 index 00000000..b716ea5d --- /dev/null +++ b/src/database/migrations/001-initial-schema.ts @@ -0,0 +1,380 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InitialSchema1700000000001 implements MigrationInterface { + name = 'InitialSchema1700000000001'; + + public async up(queryRunner: QueryRunner): Promise { + // Create users table + await queryRunner.query(` + CREATE TABLE "users" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "email" character varying NOT NULL, + "username" character varying NOT NULL, + "password" character varying NOT NULL, + "avatar" character varying, + "bio" text, + "isOnline" boolean NOT NULL DEFAULT true, + "lastSeen" timestamp, + "publicKey" text, + "privateKey" text, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "UQ_users_email" UNIQUE ("email"), + CONSTRAINT "PK_users" PRIMARY KEY ("id") + ) + `); + + // Create organizations table + await queryRunner.query(` + CREATE TABLE "organizations" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" character varying NOT NULL, + "slug" character varying NOT NULL, + "description" text, + "logo" character varying, + "website" character varying, + "contactEmail" character varying, + "plan" character varying NOT NULL DEFAULT 'free', + "status" character varying NOT NULL DEFAULT 'active', + "settings" jsonb, + "limits" jsonb, + "subscriptionExpiry" timestamp, + "ownerId" uuid, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "UQ_organizations_name" UNIQUE ("name"), + CONSTRAINT "UQ_organizations_slug" UNIQUE ("slug"), + CONSTRAINT "PK_organizations" PRIMARY KEY ("id") + ) + `); + + // Create organization_members table + await queryRunner.query(` + CREATE TABLE "organization_members" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "role" character varying NOT NULL DEFAULT 'member', + "status" character varying NOT NULL DEFAULT 'active', + "permissions" jsonb, + "joinedAt" timestamp, + "lastActiveAt" timestamp, + "organizationId" uuid NOT NULL, + "userId" uuid NOT NULL, + "invitedById" uuid, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "UQ_organization_members_org_user" UNIQUE ("organizationId", "userId"), + CONSTRAINT "PK_organization_members" PRIMARY KEY ("id") + ) + `); + + // Create organization_invites table + await queryRunner.query(` + CREATE TABLE "organization_invites" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "email" character varying NOT NULL, + "role" character varying NOT NULL DEFAULT 'member', + "status" character varying NOT NULL DEFAULT 'pending', + "token" character varying NOT NULL, + "expiresAt" timestamp NOT NULL, + "message" text, + "organizationId" uuid NOT NULL, + "invitedById" uuid NOT NULL, + "acceptedById" uuid, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_organization_invites" PRIMARY KEY ("id") + ) + `); + + // Create chat_rooms table + await queryRunner.query(` + CREATE TABLE "chat_rooms" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" character varying, + "description" text, + "type" character varying NOT NULL DEFAULT 'direct', + "avatar" character varying, + "isEncrypted" boolean NOT NULL DEFAULT false, + "encryptionKey" text, + "organizationId" uuid NOT NULL, + "createdById" uuid, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_chat_rooms" PRIMARY KEY ("id") + ) + `); + + // Create messages table + await queryRunner.query(` + CREATE TABLE "messages" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "content" text NOT NULL, + "type" character varying NOT NULL DEFAULT 'text', + "isEncrypted" boolean NOT NULL DEFAULT false, + "encryptedContent" text, + "isEdited" boolean NOT NULL DEFAULT false, + "isDeleted" boolean NOT NULL DEFAULT false, + "editedAt" timestamp, + "replyToId" uuid, + "senderId" uuid NOT NULL, + "chatRoomId" uuid NOT NULL, + "organizationId" uuid NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_messages" PRIMARY KEY ("id") + ) + `); + + // Create message_media table + await queryRunner.query(` + CREATE TABLE "message_media" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "filename" character varying NOT NULL, + "originalName" character varying NOT NULL, + "mimeType" character varying NOT NULL, + "size" integer NOT NULL, + "url" character varying NOT NULL, + "type" character varying NOT NULL DEFAULT 'other', + "thumbnailUrl" character varying, + "width" integer, + "height" integer, + "duration" integer, + "messageId" uuid NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_message_media" PRIMARY KEY ("id") + ) + `); + + // Create message_mentions table + await queryRunner.query(` + CREATE TABLE "message_mentions" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "messageId" uuid NOT NULL, + "mentionedUserId" uuid NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_message_mentions" PRIMARY KEY ("id") + ) + `); + + // Create user_devices table + await queryRunner.query(` + CREATE TABLE "user_devices" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "deviceId" character varying NOT NULL, + "deviceType" character varying NOT NULL, + "platform" character varying NOT NULL, + "fcmToken" text, + "isActive" boolean NOT NULL DEFAULT true, + "userId" uuid NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + "updatedAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_user_devices" PRIMARY KEY ("id") + ) + `); + + // Create notifications table + await queryRunner.query(` + CREATE TABLE "notifications" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "type" character varying NOT NULL, + "title" character varying NOT NULL, + "body" text NOT NULL, + "data" jsonb, + "isRead" boolean NOT NULL DEFAULT false, + "isSent" boolean NOT NULL DEFAULT false, + "recipientId" uuid NOT NULL, + "messageId" uuid, + "organizationId" uuid NOT NULL, + "createdAt" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_notifications" PRIMARY KEY ("id") + ) + `); + + // Create chat_room_members table (many-to-many) + await queryRunner.query(` + CREATE TABLE "chat_room_members" ( + "chatRoomId" uuid NOT NULL, + "userId" uuid NOT NULL, + CONSTRAINT "PK_chat_room_members" PRIMARY KEY ("chatRoomId", "userId") + ) + `); + + // Add foreign key constraints + await queryRunner.query(` + ALTER TABLE "organizations" + ADD CONSTRAINT "FK_organizations_owner" + FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE SET NULL + `); + + await queryRunner.query(` + ALTER TABLE "organization_members" + ADD CONSTRAINT "FK_organization_members_organization" + FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "organization_members" + ADD CONSTRAINT "FK_organization_members_user" + FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "organization_members" + ADD CONSTRAINT "FK_organization_members_invitedBy" + FOREIGN KEY ("invitedById") REFERENCES "users"("id") ON DELETE SET NULL + `); + + await queryRunner.query(` + ALTER TABLE "organization_invites" + ADD CONSTRAINT "FK_organization_invites_organization" + FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "organization_invites" + ADD CONSTRAINT "FK_organization_invites_invitedBy" + FOREIGN KEY ("invitedById") REFERENCES "users"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "organization_invites" + ADD CONSTRAINT "FK_organization_invites_acceptedBy" + FOREIGN KEY ("acceptedById") REFERENCES "users"("id") ON DELETE SET NULL + `); + + await queryRunner.query(` + ALTER TABLE "chat_rooms" + ADD CONSTRAINT "FK_chat_rooms_organization" + FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "chat_rooms" + ADD CONSTRAINT "FK_chat_rooms_createdBy" + FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE SET NULL + `); + + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_messages_sender" + FOREIGN KEY ("senderId") REFERENCES "users"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_messages_chatRoom" + FOREIGN KEY ("chatRoomId") REFERENCES "chat_rooms"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_messages_organization" + FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_messages_replyTo" + FOREIGN KEY ("replyToId") REFERENCES "messages"("id") ON DELETE SET NULL + `); + + await queryRunner.query(` + ALTER TABLE "message_media" + ADD CONSTRAINT "FK_message_media_message" + FOREIGN KEY ("messageId") REFERENCES "messages"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "message_mentions" + ADD CONSTRAINT "FK_message_mentions_message" + FOREIGN KEY ("messageId") REFERENCES "messages"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "message_mentions" + ADD CONSTRAINT "FK_message_mentions_mentionedUser" + FOREIGN KEY ("mentionedUserId") REFERENCES "users"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "user_devices" + ADD CONSTRAINT "FK_user_devices_user" + FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "notifications" + ADD CONSTRAINT "FK_notifications_recipient" + FOREIGN KEY ("recipientId") REFERENCES "users"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "notifications" + ADD CONSTRAINT "FK_notifications_message" + FOREIGN KEY ("messageId") REFERENCES "messages"("id") ON DELETE SET NULL + `); + + await queryRunner.query(` + ALTER TABLE "notifications" + ADD CONSTRAINT "FK_notifications_organization" + FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "chat_room_members" + ADD CONSTRAINT "FK_chat_room_members_chatRoom" + FOREIGN KEY ("chatRoomId") REFERENCES "chat_rooms"("id") ON DELETE CASCADE + `); + + await queryRunner.query(` + ALTER TABLE "chat_room_members" + ADD CONSTRAINT "FK_chat_room_members_user" + FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE + `); + + // Create indexes for better performance + await queryRunner.query(`CREATE INDEX "IDX_users_email" ON "users" ("email")`); + await queryRunner.query(`CREATE INDEX "IDX_organizations_slug" ON "organizations" ("slug")`); + await queryRunner.query(`CREATE INDEX "IDX_organization_members_org" ON "organization_members" ("organizationId")`); + await queryRunner.query(`CREATE INDEX "IDX_organization_members_user" ON "organization_members" ("userId")`); + await queryRunner.query(`CREATE INDEX "IDX_organization_invites_email" ON "organization_invites" ("email")`); + await queryRunner.query(`CREATE INDEX "IDX_organization_invites_token" ON "organization_invites" ("token")`); + await queryRunner.query(`CREATE INDEX "IDX_chat_rooms_organization" ON "chat_rooms" ("organizationId")`); + await queryRunner.query(`CREATE INDEX "IDX_messages_chatRoom" ON "messages" ("chatRoomId")`); + await queryRunner.query(`CREATE INDEX "IDX_messages_sender" ON "messages" ("senderId")`); + await queryRunner.query(`CREATE INDEX "IDX_messages_organization" ON "messages" ("organizationId")`); + await queryRunner.query(`CREATE INDEX "IDX_messages_createdAt" ON "messages" ("createdAt")`); + await queryRunner.query(`CREATE INDEX "IDX_notifications_recipient" ON "notifications" ("recipientId")`); + await queryRunner.query(`CREATE INDEX "IDX_notifications_organization" ON "notifications" ("organizationId")`); + } + + public async down(queryRunner: QueryRunner): Promise { + // Drop indexes + await queryRunner.query(`DROP INDEX "IDX_notifications_organization"`); + await queryRunner.query(`DROP INDEX "IDX_notifications_recipient"`); + await queryRunner.query(`DROP INDEX "IDX_messages_createdAt"`); + await queryRunner.query(`DROP INDEX "IDX_messages_organization"`); + await queryRunner.query(`DROP INDEX "IDX_messages_sender"`); + await queryRunner.query(`DROP INDEX "IDX_messages_chatRoom"`); + await queryRunner.query(`DROP INDEX "IDX_chat_rooms_organization"`); + await queryRunner.query(`DROP INDEX "IDX_organization_invites_token"`); + await queryRunner.query(`DROP INDEX "IDX_organization_invites_email"`); + await queryRunner.query(`DROP INDEX "IDX_organization_members_user"`); + await queryRunner.query(`DROP INDEX "IDX_organization_members_org"`); + await queryRunner.query(`DROP INDEX "IDX_organizations_slug"`); + await queryRunner.query(`DROP INDEX "IDX_users_email"`); + + // Drop tables in reverse order + await queryRunner.query(`DROP TABLE "chat_room_members"`); + await queryRunner.query(`DROP TABLE "notifications"`); + await queryRunner.query(`DROP TABLE "user_devices"`); + await queryRunner.query(`DROP TABLE "message_mentions"`); + await queryRunner.query(`DROP TABLE "message_media"`); + await queryRunner.query(`DROP TABLE "messages"`); + await queryRunner.query(`DROP TABLE "chat_rooms"`); + await queryRunner.query(`DROP TABLE "organization_invites"`); + await queryRunner.query(`DROP TABLE "organization_members"`); + await queryRunner.query(`DROP TABLE "organizations"`); + await queryRunner.query(`DROP TABLE "users"`); + } +} \ No newline at end of file