From 1c2ffd47225bd1beb5a54a2b56913c48115bd552 Mon Sep 17 00:00:00 2001 From: Stea <147100792+stea-uw@users.noreply.github.com> Date: Thu, 29 Aug 2024 05:40:35 -0700 Subject: [PATCH 01/10] Log errors when TLS fails (#484) This error gets lost when TLS enforcement is true. --- connection/connection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connection/connection.ts b/connection/connection.ts index 7ce3d38d..bc5d9cfe 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -393,7 +393,8 @@ export class Connection { if (e instanceof Deno.errors.InvalidData && tls_enabled) { if (tls_enforced) { throw new Error( - "The certificate used to secure the TLS connection is invalid.", + "The certificate used to secure the TLS connection is invalid: " + + e.message, ); } else { console.error( From a2ee14ea969d2ba7bb10d41a3d66599de5fc3f1c Mon Sep 17 00:00:00 2001 From: wackbyte Date: Thu, 29 Aug 2024 08:41:12 -0400 Subject: [PATCH 02/10] chore: fix build status badge (#479) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e480c2e1..d45e0510 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # deno-postgres -![Build Status](https://img.shields.io/github/workflow/status/denodrivers/postgres/ci?label=Build&logo=github&style=flat-square) +![Build Status](https://img.shields.io/github/actions/workflow/status/denodrivers/postgres/ci.yml?branch=main&label=Build&logo=github&style=flat-square) [![Discord server](https://img.shields.io/discord/768918486575480863?color=blue&label=Ask%20for%20help%20here&logo=discord&style=flat-square)](https://discord.gg/HEdTCvZUSf) [![Manual](https://img.shields.io/github/v/release/denodrivers/postgres?color=orange&label=Manual&logo=deno&style=flat-square)](https://deno-postgres.com) [![Documentation](https://img.shields.io/github/v/release/denodrivers/postgres?color=yellow&label=Documentation&logo=deno&style=flat-square)](https://doc.deno.land/https/deno.land/x/postgres/mod.ts) From 21997a7655fb1a86da81382fe0e183ac9ff4027a Mon Sep 17 00:00:00 2001 From: Hector Ayala Date: Mon, 23 Sep 2024 12:31:02 -0400 Subject: [PATCH 03/10] Fix docker compose usage in Github Actions (#490) * use docker compose * fix lint issues with deno lint --fix * remove obsolete version property from docker-compose.yml * add casting to prevent docs fail * add link to type issue * add link to type issue --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish_jsr.yml | 4 ++-- README.md | 4 ++-- client/error.ts | 2 +- connection/connection.ts | 8 +++++--- connection/connection_params.ts | 6 +++--- docker-compose.yml | 2 -- query/decode.ts | 4 ++-- query/query.ts | 4 ++-- query/transaction.ts | 2 +- tests/README.md | 4 ++-- tests/config.ts | 2 +- tests/helpers.ts | 2 +- tests/query_client_test.ts | 4 ++-- tests/utils_test.ts | 2 +- 15 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f72fe3e3..f5a8f843 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,16 +30,16 @@ jobs: uses: actions/checkout@master - name: Build tests container - run: docker-compose build tests + run: docker compose build tests - name: Run tests - run: docker-compose run tests + run: docker compose run tests - name: Run tests without typechecking id: no_typecheck uses: mathiasvr/command-output@v2.0.0 with: - run: docker-compose run no_check_tests + run: docker compose run no_check_tests continue-on-error: true - name: Report no typechecking tests status diff --git a/.github/workflows/publish_jsr.yml b/.github/workflows/publish_jsr.yml index 50285a66..1548c848 100644 --- a/.github/workflows/publish_jsr.yml +++ b/.github/workflows/publish_jsr.yml @@ -39,10 +39,10 @@ jobs: run: deno test --doc client.ts mod.ts pool.ts client/ connection/ query/ utils/ - name: Build tests container - run: docker-compose build tests + run: docker compose build tests - name: Run tests - run: docker-compose run tests + run: docker compose run tests - name: Publish (dry run) if: startsWith(github.ref, 'refs/tags/') == false diff --git a/README.md b/README.md index d45e0510..aeb63820 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,8 @@ result assertions. To run the tests, run the following commands: -1. `docker-compose build tests` -2. `docker-compose run tests` +1. `docker compose build tests` +2. `docker compose run tests` The build step will check linting and formatting as well and report it to the command line diff --git a/client/error.ts b/client/error.ts index 7fc4cccd..35d05993 100644 --- a/client/error.ts +++ b/client/error.ts @@ -1,4 +1,4 @@ -import { type Notice } from "../connection/message.ts"; +import type { Notice } from "../connection/message.ts"; /** * A connection error diff --git a/connection/connection.ts b/connection/connection.ts index bc5d9cfe..88cfa0f1 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -54,7 +54,7 @@ import { type QueryResult, ResultType, } from "../query/query.ts"; -import { type ClientConfiguration } from "./connection_params.ts"; +import type { ClientConfiguration } from "./connection_params.ts"; import * as scram from "./scram.ts"; import { ConnectionError, @@ -295,7 +295,7 @@ export class Connection { } async #openTlsConnection( - connection: Deno.Conn, + connection: Deno.TcpConn, options: { hostname: string; caCerts: string[] }, ) { this.#conn = await Deno.startTls(connection, options); @@ -354,7 +354,9 @@ export class Connection { // https://www.postgresql.org/docs/14/protocol-flow.html#id-1.10.5.7.11 if (accepts_tls) { try { - await this.#openTlsConnection(this.#conn, { + // TODO: handle connection type without castinggaa + // https://github.com/denoland/deno/issues/10200 + await this.#openTlsConnection(this.#conn as Deno.TcpConn, { hostname, caCerts: caCertificates, }); diff --git a/connection/connection_params.ts b/connection/connection_params.ts index ac4f650e..d59b9ac7 100644 --- a/connection/connection_params.ts +++ b/connection/connection_params.ts @@ -1,9 +1,9 @@ import { parseConnectionUri } from "../utils/utils.ts"; import { ConnectionParamsError } from "../client/error.ts"; import { fromFileUrl, isAbsolute } from "../deps.ts"; -import { OidType } from "../query/oid.ts"; -import { DebugControls } from "../debug.ts"; -import { ParseArrayFunction } from "../query/array_parser.ts"; +import type { OidType } from "../query/oid.ts"; +import type { DebugControls } from "../debug.ts"; +import type { ParseArrayFunction } from "../query/array_parser.ts"; /** * The connection string must match the following URI structure. All parameters but database and user are optional diff --git a/docker-compose.yml b/docker-compose.yml index be919039..e49dc016 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - x-database-env: &database-env POSTGRES_DB: "postgres" diff --git a/query/decode.ts b/query/decode.ts index 38df157e..cb5d9fc7 100644 --- a/query/decode.ts +++ b/query/decode.ts @@ -1,4 +1,4 @@ -import { Oid, OidType, OidTypes, OidValue } from "./oid.ts"; +import { Oid, type OidType, OidTypes, type OidValue } from "./oid.ts"; import { bold, yellow } from "../deps.ts"; import { decodeBigint, @@ -35,7 +35,7 @@ import { decodeTid, decodeTidArray, } from "./decoders.ts"; -import { ClientControls } from "../connection/connection_params.ts"; +import type { ClientControls } from "../connection/connection_params.ts"; import { parseArray } from "./array_parser.ts"; export class Column { diff --git a/query/query.ts b/query/query.ts index 58977459..ba02a5d9 100644 --- a/query/query.ts +++ b/query/query.ts @@ -1,7 +1,7 @@ import { encodeArgument, type EncodedArg } from "./encode.ts"; import { type Column, decode } from "./decode.ts"; -import { type Notice } from "../connection/message.ts"; -import { type ClientControls } from "../connection/connection_params.ts"; +import type { Notice } from "../connection/message.ts"; +import type { ClientControls } from "../connection/connection_params.ts"; // TODO // Limit the type of parameters that can be passed diff --git a/query/transaction.ts b/query/transaction.ts index 3dadd33a..02ba0197 100644 --- a/query/transaction.ts +++ b/query/transaction.ts @@ -1,4 +1,4 @@ -import { type QueryClient } from "../client.ts"; +import type { QueryClient } from "../client.ts"; import { Query, type QueryArguments, diff --git a/tests/README.md b/tests/README.md index c8c3e4e9..38cc8c41 100644 --- a/tests/README.md +++ b/tests/README.md @@ -16,8 +16,8 @@ From within the project directory, run: deno test --allow-read --allow-net --allow-env # run in docker container -docker-compose build --no-cache -docker-compose run tests +docker compose build --no-cache +docker compose run tests ``` ## Docker Configuration diff --git a/tests/config.ts b/tests/config.ts index 17bf701c..4a6784cf 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -1,4 +1,4 @@ -import { +import type { ClientConfiguration, ClientOptions, } from "../connection/connection_params.ts"; diff --git a/tests/helpers.ts b/tests/helpers.ts index d1630d3e..e26a7f27 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,6 +1,6 @@ import { Client } from "../client.ts"; import { Pool } from "../pool.ts"; -import { type ClientOptions } from "../connection/connection_params.ts"; +import type { ClientOptions } from "../connection/connection_params.ts"; export function generateSimpleClientTest( client_options: ClientOptions, diff --git a/tests/query_client_test.ts b/tests/query_client_test.ts index c096049a..abc7332f 100644 --- a/tests/query_client_test.ts +++ b/tests/query_client_test.ts @@ -14,8 +14,8 @@ import { assertThrows, } from "./test_deps.ts"; import { getMainConfiguration } from "./config.ts"; -import { PoolClient, QueryClient } from "../client.ts"; -import { ClientOptions } from "../connection/connection_params.ts"; +import type { PoolClient, QueryClient } from "../client.ts"; +import type { ClientOptions } from "../connection/connection_params.ts"; import { Oid } from "../query/oid.ts"; function withClient( diff --git a/tests/utils_test.ts b/tests/utils_test.ts index d5e418d3..1491831c 100644 --- a/tests/utils_test.ts +++ b/tests/utils_test.ts @@ -1,5 +1,5 @@ import { assertEquals, assertThrows } from "./test_deps.ts"; -import { parseConnectionUri, Uri } from "../utils/utils.ts"; +import { parseConnectionUri, type Uri } from "../utils/utils.ts"; import { DeferredAccessStack, DeferredStack } from "../utils/deferred.ts"; class LazilyInitializedObject { From c627326ab1c870485fb20d347c65e06bee5c0a7c Mon Sep 17 00:00:00 2001 From: predetermined Date: Mon, 23 Sep 2024 19:59:07 +0200 Subject: [PATCH 04/10] fix: add CREATE to CommandType (#489) --- query/query.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/query/query.ts b/query/query.ts index ba02a5d9..fa4eae8a 100644 --- a/query/query.ts +++ b/query/query.ts @@ -38,7 +38,8 @@ export type CommandType = | "SELECT" | "MOVE" | "FETCH" - | "COPY"; + | "COPY" + | "CREATE"; /** Type of a query result */ export enum ResultType { From 256fb860ea8d959872421950e6158910e9fe6c0f Mon Sep 17 00:00:00 2001 From: Daniel Staudigel Date: Mon, 6 Jan 2025 05:55:08 -0800 Subject: [PATCH 05/10] Update to allow NOTICEs on connect. (#496) --- connection/connection.ts | 2 ++ connection/message_code.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/connection/connection.ts b/connection/connection.ts index 88cfa0f1..cc9d2871 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -434,6 +434,8 @@ export class Connection { } case INCOMING_AUTHENTICATION_MESSAGES.PARAMETER_STATUS: break; + case INCOMING_AUTHENTICATION_MESSAGES.NOTICE: + break; default: throw new Error(`Unknown response for startup: ${message.type}`); } diff --git a/connection/message_code.ts b/connection/message_code.ts index ede4ed09..979fc1a3 100644 --- a/connection/message_code.ts +++ b/connection/message_code.ts @@ -24,6 +24,7 @@ export const INCOMING_AUTHENTICATION_MESSAGES = { BACKEND_KEY: "K", PARAMETER_STATUS: "S", READY: "Z", + NOTICE: "N", } as const; export const INCOMING_TLS_MESSAGES = { From 9dea70a8e753488d7b0b7caead511cf6c212499f Mon Sep 17 00:00:00 2001 From: Andrew Calder Date: Thu, 30 Jan 2025 23:57:22 +0000 Subject: [PATCH 06/10] fix(cd): allow publish of formatted/converted code (#498) Current JSR package is lagging behind because this job has been failing due to the uncommitted change check, which clashes with formatting & converting steps. --- .github/workflows/publish_jsr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_jsr.yml b/.github/workflows/publish_jsr.yml index 1548c848..c61fd054 100644 --- a/.github/workflows/publish_jsr.yml +++ b/.github/workflows/publish_jsr.yml @@ -46,8 +46,8 @@ jobs: - name: Publish (dry run) if: startsWith(github.ref, 'refs/tags/') == false - run: deno publish --dry-run + run: deno publish --dry-run --allow-dirty - name: Publish (real) if: startsWith(github.ref, 'refs/tags/') - run: deno publish + run: deno publish --allow-dirty From 15e40d0adb52651b8074c72f5b3ba77eba9b6151 Mon Sep 17 00:00:00 2001 From: James Hollowell Date: Thu, 30 Jan 2025 19:17:31 -0500 Subject: [PATCH 07/10] fix: Deno 2 permissions system update for ENV (#492) Updates usages of of `Deno.errors.PermissionDenied` to also support Deno v2's new `Deno.errors.NotCapable`. Fixes #491 --- connection/connection_params.ts | 5 ++++- tests/config.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/connection/connection_params.ts b/connection/connection_params.ts index d59b9ac7..ef37479c 100644 --- a/connection/connection_params.ts +++ b/connection/connection_params.ts @@ -420,7 +420,10 @@ export function createParams( try { pgEnv = getPgEnv(); } catch (e) { - if (e instanceof Deno.errors.PermissionDenied) { + // In Deno v1, Deno permission errors resulted in a Deno.errors.PermissionDenied exception. In Deno v2, a new + // Deno.errors.NotCapable exception was added to replace this. The "in" check makes this code safe for both Deno + // 1 and Deno 2 + if (e instanceof Deno.errors.PermissionDenied || ('NotCapable' in Deno.errors && e instanceof Deno.errors.NotCapable)) { has_env_access = false; } else { throw e; diff --git a/tests/config.ts b/tests/config.ts index 4a6784cf..a3366625 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -15,7 +15,7 @@ let DEV_MODE: string | undefined; try { DEV_MODE = Deno.env.get("DENO_POSTGRES_DEVELOPMENT"); } catch (e) { - if (e instanceof Deno.errors.PermissionDenied) { + if (e instanceof Deno.errors.PermissionDenied || ('NotCapable' in Deno.errors && e instanceof Deno.errors.NotCapable)) { throw new Error( "You need to provide ENV access in order to run the test suite", ); From b22f7432856c51e9ae6104a48f5cfee9aefe26d1 Mon Sep 17 00:00:00 2001 From: jersey Date: Sun, 20 Apr 2025 21:22:44 -0400 Subject: [PATCH 08/10] move to jsr for dependencies and modernize some things (#487) * modernize code * remove error type assertions * bump dependency versions * fix every test except the last one * format code * fix tests * update docs to point to jsr, fix docs tests * format * actually fix github action * make the test table if it doesn't exist, should fix gh action * update documentation --- .github/workflows/ci.yml | 57 +++------- .github/workflows/publish_jsr.yml | 13 ++- Dockerfile | 3 +- LICENSE | 2 +- README.md | 92 ++++++++-------- client.ts | 83 ++++++++++----- client/error.ts | 2 +- connection/auth.ts | 5 +- connection/connection.ts | 112 +++++++++++--------- connection/connection_params.ts | 16 +-- connection/packet.ts | 2 +- connection/scram.ts | 16 ++- deno.json | 15 ++- deps.ts | 18 ---- docker-compose.yml | 18 +++- docs/README.md | 39 ++++--- docs/index.html | 47 +++++---- mod.ts | 7 +- pool.ts | 32 +++--- query/array_parser.ts | 2 +- query/decode.ts | 6 +- query/decoders.ts | 33 ++++-- query/encode.ts | 24 +++-- query/query.ts | 15 ++- query/transaction.ts | 169 ++++++++++++++++++++---------- tests/auth_test.ts | 6 +- tests/config.ts | 5 +- tests/connection_params_test.ts | 3 +- tests/connection_test.ts | 30 ++---- tests/data_types_test.ts | 15 +-- tests/decode_test.ts | 2 +- tests/encode_test.ts | 2 +- tests/pool_test.ts | 10 +- tests/query_client_test.ts | 2 +- tests/test_deps.ts | 5 +- tests/utils_test.ts | 2 +- tools/convert_to_jsr.ts | 38 ------- utils/deferred.ts | 4 +- utils/utils.ts | 9 +- 39 files changed, 511 insertions(+), 450 deletions(-) delete mode 100644 deps.ts delete mode 100644 tools/convert_to_jsr.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5a8f843..cebfff81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: ci -on: [ push, pull_request, release ] +on: [push, pull_request, release] jobs: code_quality: @@ -12,18 +12,15 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v1.x - + deno-version: v2.x + - name: Format run: deno fmt --check - + - name: Lint run: deno lint - - name: Documentation tests - run: deno test --doc client.ts mod.ts pool.ts client/ connection/ query/ utils/ - - test: + test_docs: runs-on: ubuntu-latest steps: - name: Clone repo @@ -31,43 +28,21 @@ jobs: - name: Build tests container run: docker compose build tests - - - name: Run tests - run: docker compose run tests - - - name: Run tests without typechecking - id: no_typecheck - uses: mathiasvr/command-output@v2.0.0 - with: - run: docker compose run no_check_tests - continue-on-error: true - - name: Report no typechecking tests status - id: no_typecheck_status - if: steps.no_typecheck.outcome == 'success' - run: echo "name=status::success" >> $GITHUB_OUTPUT - outputs: - no_typecheck: ${{ steps.no_typecheck.outputs.stdout }} - no_typecheck_status: ${{ steps.no_typecheck_status.outputs.status }} + - name: Run doc tests + run: docker compose run doc_tests - report_warnings: - needs: [ code_quality, test ] + test: runs-on: ubuntu-latest steps: - - name: Set no-typecheck fail comment - if: ${{ needs.test.outputs.no_typecheck_status != 'success' && github.event_name == 'push' }} - uses: peter-evans/commit-comment@v3 - with: - body: | - # No typecheck tests failure + - name: Clone repo + uses: actions/checkout@master - This error was most likely caused by incorrect type stripping from the SWC crate + - name: Build tests container + run: docker compose build tests - Please report the following failure to https://github.com/denoland/deno with a reproduction of the current commit + - name: Run tests + run: docker compose run tests -
- Failure log -

-            ${{ needs.test.outputs.no_typecheck }}
-              
-
\ No newline at end of file + - name: Run tests without typechecking + run: docker compose run no_check_tests diff --git a/.github/workflows/publish_jsr.yml b/.github/workflows/publish_jsr.yml index c61fd054..11fe11b2 100644 --- a/.github/workflows/publish_jsr.yml +++ b/.github/workflows/publish_jsr.yml @@ -22,16 +22,15 @@ jobs: - name: Set up Deno uses: denoland/setup-deno@v1 - + with: + deno-version: v2.x + - name: Check Format run: deno fmt --check - - name: Convert to JSR package - run: deno run -A tools/convert_to_jsr.ts - - - name: Format converted code + - name: Format run: deno fmt - + - name: Lint run: deno lint @@ -40,7 +39,7 @@ jobs: - name: Build tests container run: docker compose build tests - + - name: Run tests run: docker compose run tests diff --git a/Dockerfile b/Dockerfile index c3bcd7c1..2ae96eaa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM denoland/deno:alpine-1.40.3 +FROM denoland/deno:alpine-2.2.11 WORKDIR /app # Install wait utility @@ -11,7 +11,6 @@ USER deno # Cache external libraries # Test deps caches all main dependencies as well COPY tests/test_deps.ts tests/test_deps.ts -COPY deps.ts deps.ts RUN deno cache tests/test_deps.ts ADD . . diff --git a/LICENSE b/LICENSE index cc4afa44..43c89de1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2022 Bartłomiej Iwańczuk and Steven Guerrero +Copyright (c) 2018-2025 Bartłomiej Iwańczuk, Steven Guerrero, and Hector Ayala Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index aeb63820..610ece78 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,22 @@ ![Build Status](https://img.shields.io/github/actions/workflow/status/denodrivers/postgres/ci.yml?branch=main&label=Build&logo=github&style=flat-square) [![Discord server](https://img.shields.io/discord/768918486575480863?color=blue&label=Ask%20for%20help%20here&logo=discord&style=flat-square)](https://discord.gg/HEdTCvZUSf) +[![JSR](https://jsr.io/badges/@db/postgres?style=flat-square)](https://jsr.io/@db/postgres) +[![JSR Score](https://jsr.io/badges/@db/postgres/score?style=flat-square)](https://jsr.io/@db/postgres) [![Manual](https://img.shields.io/github/v/release/denodrivers/postgres?color=orange&label=Manual&logo=deno&style=flat-square)](https://deno-postgres.com) -[![Documentation](https://img.shields.io/github/v/release/denodrivers/postgres?color=yellow&label=Documentation&logo=deno&style=flat-square)](https://doc.deno.land/https/deno.land/x/postgres/mod.ts) +[![Documentation](https://img.shields.io/github/v/release/denodrivers/postgres?color=yellow&label=Documentation&logo=deno&style=flat-square)](https://jsr.io/@db/postgres/doc) [![License](https://img.shields.io/github/license/denodrivers/postgres?color=yellowgreen&label=License&style=flat-square)](LICENSE) A lightweight PostgreSQL driver for Deno focused on developer experience. -`deno-postgres` is being developed inspired by the excellent work of +`deno-postgres` is inspired by the excellent work of [node-postgres](https://github.com/brianc/node-postgres) and [pq](https://github.com/lib/pq). ## Documentation -The documentation is available on the `deno-postgres` website -[https://deno-postgres.com/](https://deno-postgres.com/) +The documentation is available on the +[`deno-postgres` website](https://deno-postgres.com/). Join the [Discord](https://discord.gg/HEdTCvZUSf) as well! It's a good place to discuss bugs and features before opening issues. @@ -24,7 +26,7 @@ discuss bugs and features before opening issues. ```ts // deno run --allow-net --allow-read mod.ts -import { Client } from "https://deno.land/x/postgres/mod.ts"; +import { Client } from "jsr:@db/postgres"; const client = new Client({ user: "user", @@ -32,6 +34,7 @@ const client = new Client({ hostname: "localhost", port: 5432, }); + await client.connect(); { @@ -59,6 +62,40 @@ await client.connect(); await client.end(); ``` +## Deno compatibility + +Due to breaking changes introduced in the unstable APIs `deno-postgres` uses, +there has been some fragmentation regarding what versions of Deno can be used +alongside the driver. + +This situation will stabilize as `deno-postgres` approach version 1.0. + +| Deno version | Min driver version | Max version | Note | +| ------------- | ------------------ | ------------------- | ------------------------------------------------------------------------------ | +| 1.8.x | 0.5.0 | 0.10.0 | | +| 1.9.0 | 0.11.0 | 0.11.1 | | +| 1.9.1 and up | 0.11.2 | 0.11.3 | | +| 1.11.0 and up | 0.12.0 | 0.12.0 | | +| 1.14.0 and up | 0.13.0 | 0.13.0 | | +| 1.16.0 | 0.14.0 | 0.14.3 | | +| 1.17.0 | 0.15.0 | 0.17.1 | | +| 1.40.0 | 0.17.2 | currently supported | 0.17.2 [on JSR](https://jsr.io/@bartlomieju/postgres) | +| 2.0.0 and up | 0.19.4 | currently supported | All versions available as [`@db/postgres` on JSR](https://jsr.io/@db/postgres) | + +## Breaking changes + +Although `deno-postgres` is reasonably stable and robust, it is a WIP, and we're +still exploring the design. Expect some breaking changes as we reach version 1.0 +and enhance the feature set. Please check the Releases for more info on breaking +changes. Please reach out if there are any undocumented breaking changes. + +## Found issues? + +Please +[file an issue](https://github.com/denodrivers/postgres/issues/new/choose) with +any problems with the driver. If you would like to help, please look at the +issues as well. You can pick up one of them and try to implement it. + ## Contributing ### Prerequisites @@ -73,8 +110,8 @@ await client.end(); it to run the linter and formatter locally - https://deno.land/ - - `deno upgrade --version 1.40.0` - - `dvm install 1.40.0 && dvm use 1.40.0` + - `deno upgrade stable` + - `dvm install stable && dvm use stable` - You don't need to install Postgres locally on your machine to test the library; it will run as a service in the Docker container when you build it @@ -96,8 +133,8 @@ It is recommended that you don't rely on any previously initialized data for your tests instead create all the data you need at the moment of running the tests -For example, the following test will create a temporal table that will disappear -once the test has been completed +For example, the following test will create a temporary table that will +disappear once the test has been completed ```ts Deno.test("INSERT works correctly", async () => { @@ -134,41 +171,6 @@ a local testing environment, as shown in the following steps: 3. Run the tests manually by using the command\ `deno test -A` -## Deno compatibility - -Due to breaking changes introduced in the unstable APIs `deno-postgres` uses, -there has been some fragmentation regarding what versions of Deno can be used -alongside the driver. - -This situation will stabilize as `std` and `deno-postgres` approach version 1.0. - -| Deno version | Min driver version | Max driver version | Note | -| ------------- | ------------------ | ------------------ | -------------------- | -| 1.8.x | 0.5.0 | 0.10.0 | | -| 1.9.0 | 0.11.0 | 0.11.1 | | -| 1.9.1 and up | 0.11.2 | 0.11.3 | | -| 1.11.0 and up | 0.12.0 | 0.12.0 | | -| 1.14.0 and up | 0.13.0 | 0.13.0 | | -| 1.16.0 | 0.14.0 | 0.14.3 | | -| 1.17.0 | 0.15.0 | 0.17.1 | | -| 1.40.0 | 0.17.2 | | Now available on JSR | - -## Breaking changes - -Although `deno-postgres` is reasonably stable and robust, it is a WIP, and we're -still exploring the design. Expect some breaking changes as we reach version 1.0 -and enhance the feature set. Please check the Releases for more info on breaking -changes. Please reach out if there are any undocumented breaking changes. - -## Found issues? - -Please -[file an issue](https://github.com/denodrivers/postgres/issues/new/choose) with -any problems with the driver in this repository's issue section. If you would -like to help, please look at the -[issues](https://github.com/denodrivers/postgres/issues) as well. You can pick -up one of them and try to implement it. - ## Contributing guidelines When contributing to the repository, make sure to: @@ -194,5 +196,5 @@ preserved their individual licenses and copyrights. Everything is licensed under the MIT License. -All additional work is copyright 2018 - 2024 — Bartłomiej Iwańczuk, Steven +All additional work is copyright 2018 - 2025 — Bartłomiej Iwańczuk, Steven Guerrero, Hector Ayala — All rights reserved. diff --git a/client.ts b/client.ts index d254188d..f064e976 100644 --- a/client.ts +++ b/client.ts @@ -105,47 +105,57 @@ export abstract class QueryClient { * In order to create a transaction, use the `createTransaction` method in your client as follows: * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("my_transaction_name"); * * await transaction.begin(); * // All statements between begin and commit will happen inside the transaction * await transaction.commit(); // All changes are saved + * await client.end(); * ``` * * All statements that fail in query execution will cause the current transaction to abort and release * the client without applying any of the changes that took place inside it * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("cool_transaction"); * * await transaction.begin(); - * await transaction.queryArray`INSERT INTO MY_TABLE (X) VALUES ${"some_value"}`; + * * try { - * await transaction.queryArray`SELECT []`; // Invalid syntax, transaction aborted, changes won't be applied - * }catch(e){ - * await transaction.commit(); // Will throw, current transaction has already finished + * try { + * await transaction.queryArray`SELECT []`; // Invalid syntax, transaction aborted, changes won't be applied + * } catch (e) { + * await transaction.commit(); // Will throw, current transaction has already finished + * } + * } catch (e) { + * console.log(e); * } + * + * await client.end(); * ``` * * This however, only happens if the error is of execution in nature, validation errors won't abort * the transaction * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("awesome_transaction"); * * await transaction.begin(); - * await transaction.queryArray`INSERT INTO MY_TABLE (X) VALUES ${"some_value"}`; + * * try { * await transaction.rollback("unexistent_savepoint"); // Validation error - * } catch(e) { + * } catch (e) { + * console.log(e); * await transaction.commit(); // Transaction will end, changes will be saved * } + * + * await client.end(); * ``` * * A transaction has many options to ensure modifications made to the database are safe and @@ -160,7 +170,7 @@ export abstract class QueryClient { * - Repeatable read: This isolates the transaction in a way that any external changes to the data we are reading * won't be visible inside the transaction until it has finished * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = await client.createTransaction("my_transaction", { isolation_level: "repeatable_read" }); * ``` @@ -168,7 +178,7 @@ export abstract class QueryClient { * - Serializable: This isolation level prevents the current transaction from making persistent changes * if the data they were reading at the beginning of the transaction has been modified (recommended) * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = await client.createTransaction("my_transaction", { isolation_level: "serializable" }); * ``` @@ -181,7 +191,7 @@ export abstract class QueryClient { * is to in conjuction with the repeatable read isolation, ensuring the data you are reading does not change * during the transaction, specially useful for data extraction * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = await client.createTransaction("my_transaction", { read_only: true }); * ``` @@ -192,14 +202,19 @@ export abstract class QueryClient { * you can do the following: * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client_1 = new Client(); * const client_2 = new Client(); * const transaction_1 = client_1.createTransaction("transaction_1"); * + * await transaction_1.begin(); + * * const snapshot = await transaction_1.getSnapshot(); * const transaction_2 = client_2.createTransaction("new_transaction", { isolation_level: "repeatable_read", snapshot }); * // transaction_2 now shares the same starting state that transaction_1 had + * + * await client_1.end(); + * await client_2.end(); * ``` * * https://www.postgresql.org/docs/14/tutorial-transactions.html @@ -260,9 +275,14 @@ export abstract class QueryClient { * Execute queries and retrieve the data as array entries. It supports a generic in order to type the entries retrieved by the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * + * await my_client.queryArray`CREATE TABLE IF NOT EXISTS CLIENTS ( + * id SERIAL PRIMARY KEY, + * name TEXT NOT NULL + * )` + * * const { rows: rows1 } = await my_client.queryArray( * "SELECT ID, NAME FROM CLIENTS" * ); // Array @@ -270,6 +290,8 @@ export abstract class QueryClient { * const { rows: rows2 } = await my_client.queryArray<[number, string]>( * "SELECT ID, NAME FROM CLIENTS" * ); // Array<[number, string]> + * + * await my_client.end(); * ``` */ async queryArray>( @@ -280,12 +302,13 @@ export abstract class QueryClient { * Use the configuration object for more advance options to execute the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * const { rows } = await my_client.queryArray<[number, string]>({ * text: "SELECT ID, NAME FROM CLIENTS", * name: "select_clients", * }); // Array<[number, string]> + * await my_client.end(); * ``` */ async queryArray>( @@ -295,12 +318,14 @@ export abstract class QueryClient { * Execute prepared statements with template strings * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * * const id = 12; * // Array<[number, string]> * const {rows} = await my_client.queryArray<[number, string]>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`; + * + * await my_client.end(); * ``` */ async queryArray>( @@ -343,7 +368,7 @@ export abstract class QueryClient { * Executed queries and retrieve the data as object entries. It supports a generic in order to type the entries retrieved by the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * * const { rows: rows1 } = await my_client.queryObject( @@ -353,6 +378,8 @@ export abstract class QueryClient { * const { rows: rows2 } = await my_client.queryObject<{id: number, name: string}>( * "SELECT ID, NAME FROM CLIENTS" * ); // Array<{id: number, name: string}> + * + * await my_client.end(); * ``` */ async queryObject( @@ -363,7 +390,7 @@ export abstract class QueryClient { * Use the configuration object for more advance options to execute the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * * const { rows: rows1 } = await my_client.queryObject( @@ -376,6 +403,8 @@ export abstract class QueryClient { * fields: ["personal_id", "complete_name"], * }); * console.log(rows2); // [{personal_id: 78, complete_name: "Frank"}, {personal_id: 15, complete_name: "Sarah"}] + * + * await my_client.end(); * ``` */ async queryObject( @@ -385,11 +414,12 @@ export abstract class QueryClient { * Execute prepared statements with template strings * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * const id = 12; * // Array<{id: number, name: string}> * const { rows } = await my_client.queryObject<{id: number, name: string}>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`; + * await my_client.end(); * ``` */ async queryObject( @@ -447,10 +477,10 @@ export abstract class QueryClient { * statements asynchronously * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * await client.connect(); - * await client.queryArray`UPDATE MY_TABLE SET MY_FIELD = 0`; + * await client.queryArray`SELECT * FROM CLIENTS`; * await client.end(); * ``` * @@ -458,18 +488,17 @@ export abstract class QueryClient { * for concurrency capabilities check out connection pools * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client_1 = new Client(); * await client_1.connect(); * // Even if operations are not awaited, they will be executed in the order they were * // scheduled - * client_1.queryArray`UPDATE MY_TABLE SET MY_FIELD = 0`; - * client_1.queryArray`DELETE FROM MY_TABLE`; + * client_1.queryArray`DELETE FROM CLIENTS`; * * const client_2 = new Client(); * await client_2.connect(); * // `client_2` will execute it's queries in parallel to `client_1` - * const {rows: result} = await client_2.queryArray`SELECT * FROM MY_TABLE`; + * const {rows: result} = await client_2.queryArray`SELECT * FROM CLIENTS`; * * await client_1.end(); * await client_2.end(); diff --git a/client/error.ts b/client/error.ts index 35d05993..fa759980 100644 --- a/client/error.ts +++ b/client/error.ts @@ -20,7 +20,7 @@ export class ConnectionParamsError extends Error { /** * Create a new ConnectionParamsError */ - constructor(message: string, cause?: Error) { + constructor(message: string, cause?: unknown) { super(message, { cause }); this.name = "ConnectionParamsError"; } diff --git a/connection/auth.ts b/connection/auth.ts index c32e7b88..e77b8830 100644 --- a/connection/auth.ts +++ b/connection/auth.ts @@ -1,9 +1,10 @@ -import { crypto, hex } from "../deps.ts"; +import { crypto } from "@std/crypto/crypto"; +import { encodeHex } from "@std/encoding/hex"; const encoder = new TextEncoder(); async function md5(bytes: Uint8Array): Promise { - return hex.encodeHex(await crypto.subtle.digest("MD5", bytes)); + return encodeHex(await crypto.subtle.digest("MD5", bytes)); } // AuthenticationMD5Password diff --git a/connection/connection.ts b/connection/connection.ts index cc9d2871..9c0e66a2 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -26,15 +26,8 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { - bold, - BufReader, - BufWriter, - delay, - joinPath, - rgb24, - yellow, -} from "../deps.ts"; +import { join as joinPath } from "@std/path"; +import { bold, rgb24, yellow } from "@std/fmt/colors"; import { DeferredStack } from "../utils/deferred.ts"; import { getSocketName, readUInt32BE } from "../utils/utils.ts"; import { PacketWriter } from "./packet.ts"; @@ -127,8 +120,6 @@ const encoder = new TextEncoder(); // - Refactor properties to not be lazily initialized // or to handle their undefined value export class Connection { - #bufReader!: BufReader; - #bufWriter!: BufWriter; #conn!: Deno.Conn; connected = false; #connection_params: ClientConfiguration; @@ -142,6 +133,7 @@ export class Connection { #secretKey?: number; #tls?: boolean; #transport?: "tcp" | "socket"; + #connWritable!: WritableStreamDefaultWriter; get pid(): number | undefined { return this.#pid; @@ -165,13 +157,39 @@ export class Connection { this.#onDisconnection = disconnection_callback; } + /** + * Read p.length bytes into the buffer + */ + async #readFull(p: Uint8Array): Promise { + let bytes_read = 0; + while (bytes_read < p.length) { + try { + const read_result = await this.#conn.read(p.subarray(bytes_read)); + if (read_result === null) { + if (bytes_read === 0) { + return; + } else { + throw new ConnectionError("Failed to read bytes from socket"); + } + } + bytes_read += read_result; + } catch (e) { + if (e instanceof Deno.errors.ConnectionReset) { + throw new ConnectionError("The session was terminated unexpectedly"); + } + throw e; + } + } + } + /** * Read single message sent by backend */ async #readMessage(): Promise { // Clear buffer before reading the message type this.#message_header.fill(0); - await this.#bufReader.readFull(this.#message_header); + await this.#readFull(this.#message_header); + const type = decoder.decode(this.#message_header.slice(0, 1)); // TODO // Investigate if the ascii terminator is the best way to check for a broken @@ -187,7 +205,7 @@ export class Connection { } const length = readUInt32BE(this.#message_header, 1) - 4; const body = new Uint8Array(length); - await this.#bufReader.readFull(body); + await this.#readFull(body); return new Message(type, length, body); } @@ -197,8 +215,7 @@ export class Connection { writer.clear(); writer.addInt32(8).addInt32(80877103).join(); - await this.#bufWriter.write(writer.flush()); - await this.#bufWriter.flush(); + await this.#connWritable.write(writer.flush()); const response = new Uint8Array(1); await this.#conn.read(response); @@ -254,8 +271,7 @@ export class Connection { const finalBuffer = writer.addInt32(bodyLength).add(bodyBuffer).join(); - await this.#bufWriter.write(finalBuffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(finalBuffer); return await this.#readMessage(); } @@ -264,8 +280,7 @@ export class Connection { // @ts-expect-error This will throw in runtime if the options passed to it are socket related and deno is running // on stable this.#conn = await Deno.connect(options); - this.#bufWriter = new BufWriter(this.#conn); - this.#bufReader = new BufReader(this.#conn); + this.#connWritable = this.#conn.writable.getWriter(); } async #openSocketConnection(path: string, port: number) { @@ -299,8 +314,7 @@ export class Connection { options: { hostname: string; caCerts: string[] }, ) { this.#conn = await Deno.startTls(connection, options); - this.#bufWriter = new BufWriter(this.#conn); - this.#bufReader = new BufReader(this.#conn); + this.#connWritable = this.#conn.writable.getWriter(); } #resetConnectionMetadata() { @@ -338,7 +352,7 @@ export class Connection { this.#tls = undefined; this.#transport = "socket"; } else { - // A BufWriter needs to be available in order to check if the server accepts TLS connections + // A writer needs to be available in order to check if the server accepts TLS connections await this.#openConnection({ hostname, port, transport: "tcp" }); this.#tls = false; this.#transport = "tcp"; @@ -365,7 +379,7 @@ export class Connection { if (!tls_enforced) { console.error( bold(yellow("TLS connection failed with message: ")) + - e.message + + (e instanceof Error ? e.message : e) + "\n" + bold("Defaulting to non-encrypted connection"), ); @@ -392,7 +406,10 @@ export class Connection { } catch (e) { // Make sure to close the connection before erroring or reseting this.#closeConnection(); - if (e instanceof Deno.errors.InvalidData && tls_enabled) { + if ( + (e instanceof Deno.errors.InvalidData || + e instanceof Deno.errors.BadResource) && tls_enabled + ) { if (tls_enforced) { throw new Error( "The certificate used to secure the TLS connection is invalid: " + @@ -468,7 +485,7 @@ export class Connection { let reconnection_attempts = 0; const max_reconnections = this.#connection_params.connection.attempts; - let error: Error | undefined; + let error: unknown | undefined; // If no connection has been established and the reconnection attempts are // set to zero, attempt to connect at least once if (!is_reconnection && this.#connection_params.connection.attempts === 0) { @@ -492,7 +509,7 @@ export class Connection { } if (interval > 0) { - await delay(interval); + await new Promise((resolve) => setTimeout(resolve, interval)); } } try { @@ -566,8 +583,7 @@ export class Connection { const password = this.#connection_params.password || ""; const buffer = this.#packetWriter.addCString(password).flush(0x70); - await this.#bufWriter.write(buffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(buffer); return this.#readMessage(); } @@ -588,8 +604,7 @@ export class Connection { ); const buffer = this.#packetWriter.addCString(password).flush(0x70); - await this.#bufWriter.write(buffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(buffer); return this.#readMessage(); } @@ -616,8 +631,7 @@ export class Connection { this.#packetWriter.addCString("SCRAM-SHA-256"); this.#packetWriter.addInt32(clientFirstMessage.length); this.#packetWriter.addString(clientFirstMessage); - this.#bufWriter.write(this.#packetWriter.flush(0x70)); - this.#bufWriter.flush(); + this.#connWritable.write(this.#packetWriter.flush(0x70)); const maybe_sasl_continue = await this.#readMessage(); switch (maybe_sasl_continue.type) { @@ -644,8 +658,7 @@ export class Connection { this.#packetWriter.clear(); this.#packetWriter.addString(await client.composeResponse()); - this.#bufWriter.write(this.#packetWriter.flush(0x70)); - this.#bufWriter.flush(); + this.#connWritable.write(this.#packetWriter.flush(0x70)); const maybe_sasl_final = await this.#readMessage(); switch (maybe_sasl_final.type) { @@ -681,8 +694,7 @@ export class Connection { const buffer = this.#packetWriter.addCString(query.text).flush(0x51); - await this.#bufWriter.write(buffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(buffer); let result; if (query.result_type === ResultType.ARRAY) { @@ -691,7 +703,7 @@ export class Connection { result = new QueryObjectResult(query); } - let error: Error | undefined; + let error: unknown | undefined; let current_message = await this.#readMessage(); // Process messages until ready signal is sent @@ -771,7 +783,7 @@ export class Connection { .addCString(query.text) .addInt16(0) .flush(0x50); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } async #appendArgumentsToMessage(query: Query) { @@ -788,16 +800,16 @@ export class Connection { if (hasBinaryArgs) { this.#packetWriter.addInt16(query.args.length); - query.args.forEach((arg) => { + for (const arg of query.args) { this.#packetWriter.addInt16(arg instanceof Uint8Array ? 1 : 0); - }); + } } else { this.#packetWriter.addInt16(0); } this.#packetWriter.addInt16(query.args.length); - query.args.forEach((arg) => { + for (const arg of query.args) { if (arg === null || typeof arg === "undefined") { this.#packetWriter.addInt32(-1); } else if (arg instanceof Uint8Array) { @@ -808,11 +820,11 @@ export class Connection { this.#packetWriter.addInt32(byteLength); this.#packetWriter.addString(arg); } - }); + } this.#packetWriter.addInt16(0); const buffer = this.#packetWriter.flush(0x42); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } /** @@ -823,7 +835,7 @@ export class Connection { this.#packetWriter.clear(); const buffer = this.#packetWriter.addCString("P").flush(0x44); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } async #appendExecuteToMessage() { @@ -833,14 +845,14 @@ export class Connection { .addCString("") // unnamed portal .addInt32(0) .flush(0x45); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } async #appendSyncToMessage() { this.#packetWriter.clear(); const buffer = this.#packetWriter.flush(0x53); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } // TODO @@ -878,8 +890,6 @@ export class Connection { // The execute response contains the portal in which the query will be run and how many rows should it return await this.#appendExecuteToMessage(); await this.#appendSyncToMessage(); - // send all messages to backend - await this.#bufWriter.flush(); let result; if (query.result_type === ResultType.ARRAY) { @@ -888,7 +898,7 @@ export class Connection { result = new QueryObjectResult(query); } - let error: Error | undefined; + let error: unknown | undefined; let current_message = await this.#readMessage(); while (current_message.type !== INCOMING_QUERY_MESSAGES.READY) { @@ -1002,9 +1012,9 @@ export class Connection { async end(): Promise { if (this.connected) { const terminationMessage = new Uint8Array([0x58, 0x00, 0x00, 0x00, 0x04]); - await this.#bufWriter.write(terminationMessage); + await this.#connWritable.write(terminationMessage); try { - await this.#bufWriter.flush(); + await this.#connWritable.ready; } catch (_e) { // This steps can fail if the underlying connection was closed ungracefully } finally { diff --git a/connection/connection_params.ts b/connection/connection_params.ts index ef37479c..a55fb804 100644 --- a/connection/connection_params.ts +++ b/connection/connection_params.ts @@ -1,6 +1,6 @@ import { parseConnectionUri } from "../utils/utils.ts"; import { ConnectionParamsError } from "../client/error.ts"; -import { fromFileUrl, isAbsolute } from "../deps.ts"; +import { fromFileUrl, isAbsolute } from "@std/path"; import type { OidType } from "../query/oid.ts"; import type { DebugControls } from "../debug.ts"; import type { ParseArrayFunction } from "../query/array_parser.ts"; @@ -149,15 +149,14 @@ export type ClientControls = { * * @example * ```ts - * import dayjs from 'https://esm.sh/dayjs'; - * import { Oid,Decoders } from '../mod.ts' + * import { Oid, Decoders } from '../mod.ts' * * { * const decoders: Decoders = { * // 16 = Oid.bool : convert all boolean values to numbers * '16': (value: string) => value === 't' ? 1 : 0, - * // 1082 = Oid.date : convert all dates to dayjs objects - * 1082: (value: string) => dayjs(value), + * // 1082 = Oid.date : convert all dates to Date objects + * 1082: (value: string) => new Date(value), * // 23 = Oid.int4 : convert all integers to positive numbers * [Oid.int4]: (value: string) => Math.max(0, parseInt(value || '0', 10)), * } @@ -423,7 +422,12 @@ export function createParams( // In Deno v1, Deno permission errors resulted in a Deno.errors.PermissionDenied exception. In Deno v2, a new // Deno.errors.NotCapable exception was added to replace this. The "in" check makes this code safe for both Deno // 1 and Deno 2 - if (e instanceof Deno.errors.PermissionDenied || ('NotCapable' in Deno.errors && e instanceof Deno.errors.NotCapable)) { + if ( + e instanceof + ("NotCapable" in Deno.errors + ? Deno.errors.NotCapable + : Deno.errors.PermissionDenied) + ) { has_env_access = false; } else { throw e; diff --git a/connection/packet.ts b/connection/packet.ts index 36abae18..2d93f695 100644 --- a/connection/packet.ts +++ b/connection/packet.ts @@ -25,7 +25,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { copy } from "../deps.ts"; +import { copy } from "@std/bytes/copy"; import { readInt16BE, readInt32BE } from "../utils/utils.ts"; export class PacketReader { diff --git a/connection/scram.ts b/connection/scram.ts index 1ef2661e..e4e18c32 100644 --- a/connection/scram.ts +++ b/connection/scram.ts @@ -1,4 +1,4 @@ -import { base64 } from "../deps.ts"; +import { decodeBase64, encodeBase64 } from "@std/encoding/base64"; /** Number of random bytes used to generate a nonce */ const defaultNonceSize = 16; @@ -132,7 +132,7 @@ function escape(str: string): string { } function generateRandomNonce(size: number): string { - return base64.encodeBase64(crypto.getRandomValues(new Uint8Array(size))); + return encodeBase64(crypto.getRandomValues(new Uint8Array(size))); } function parseScramAttributes(message: string): Record { @@ -144,10 +144,8 @@ function parseScramAttributes(message: string): Record { throw new Error(Reason.BadMessage); } - // TODO - // Replace with String.prototype.substring - const key = entry.substr(0, pos); - const value = entry.substr(pos + 1); + const key = entry.substring(0, pos); + const value = entry.slice(pos + 1); attrs[key] = value; } @@ -221,7 +219,7 @@ export class Client { throw new Error(Reason.BadSalt); } try { - salt = base64.decodeBase64(attrs.s); + salt = decodeBase64(attrs.s); } catch { throw new Error(Reason.BadSalt); } @@ -261,7 +259,7 @@ export class Client { this.#auth_message += "," + responseWithoutProof; - const proof = base64.encodeBase64( + const proof = encodeBase64( computeScramProof( await computeScramSignature( this.#auth_message, @@ -294,7 +292,7 @@ export class Client { throw new Error(attrs.e ?? Reason.Rejected); } - const verifier = base64.encodeBase64( + const verifier = encodeBase64( await computeScramSignature( this.#auth_message, this.#key_signatures.server, diff --git a/deno.json b/deno.json index f4697e7c..028b90e6 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,13 @@ { - "lock": false, - "name": "@bartlomieju/postgres", - "version": "0.19.3", - "exports": "./mod.ts" + "name": "@db/postgres", + "version": "0.19.4", + "exports": "./mod.ts", + "imports": { + "@std/bytes": "jsr:@std/bytes@^1.0.5", + "@std/crypto": "jsr:@std/crypto@^1.0.4", + "@std/encoding": "jsr:@std/encoding@^1.0.9", + "@std/fmt": "jsr:@std/fmt@^1.0.6", + "@std/path": "jsr:@std/path@^1.0.8" + }, + "lock": false } diff --git a/deps.ts b/deps.ts deleted file mode 100644 index 3d10e31c..00000000 --- a/deps.ts +++ /dev/null @@ -1,18 +0,0 @@ -export * as base64 from "https://deno.land/std@0.214.0/encoding/base64.ts"; -export * as hex from "https://deno.land/std@0.214.0/encoding/hex.ts"; -export { parse as parseDate } from "https://deno.land/std@0.214.0/datetime/parse.ts"; -export { BufReader } from "https://deno.land/std@0.214.0/io/buf_reader.ts"; -export { BufWriter } from "https://deno.land/std@0.214.0/io/buf_writer.ts"; -export { copy } from "https://deno.land/std@0.214.0/bytes/copy.ts"; -export { crypto } from "https://deno.land/std@0.214.0/crypto/crypto.ts"; -export { delay } from "https://deno.land/std@0.214.0/async/delay.ts"; -export { - bold, - rgb24, - yellow, -} from "https://deno.land/std@0.214.0/fmt/colors.ts"; -export { - fromFileUrl, - isAbsolute, - join as joinPath, -} from "https://deno.land/std@0.214.0/path/mod.ts"; diff --git a/docker-compose.yml b/docker-compose.yml index e49dc016..a665103d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ x-test-env: WAIT_HOSTS: "postgres_clear:6000,postgres_md5:6001,postgres_scram:6002" # Wait fifteen seconds after database goes online # for database metadata initialization - WAIT_AFTER_HOSTS: "15" + WAIT_AFTER: "15" x-test-volumes: &test-volumes @@ -79,3 +79,19 @@ services: <<: *test-env NO_COLOR: "true" volumes: *test-volumes + + doc_tests: + image: postgres/tests + command: sh -c "/wait && deno test -A --doc client.ts mod.ts pool.ts client/ connection/ query/ utils/" + depends_on: + - postgres_clear + - postgres_md5 + - postgres_scram + environment: + <<: *test-env + PGDATABASE: "postgres" + PGPASSWORD: "postgres" + PGUSER: "postgres" + PGHOST: "postgres_md5" + PGPORT: 6001 + volumes: *test-volumes diff --git a/docs/README.md b/docs/README.md index aad68e46..65538ce2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,17 +1,19 @@ # deno-postgres -![Build Status](https://img.shields.io/github/workflow/status/denodrivers/postgres/ci?label=Build&logo=github&style=flat-square) +![Build Status](https://img.shields.io/github/actions/workflow/status/denodrivers/postgres/ci.yml?branch=main&label=Build&logo=github&style=flat-square) [![Discord server](https://img.shields.io/discord/768918486575480863?color=blue&label=Ask%20for%20help%20here&logo=discord&style=flat-square)](https://discord.gg/HEdTCvZUSf) -![Manual](https://img.shields.io/github/v/release/denodrivers/postgres?color=orange&label=Manual&logo=deno&style=flat-square) -[![Documentation](https://img.shields.io/github/v/release/denodrivers/postgres?color=yellow&label=Documentation&logo=deno&style=flat-square)](https://doc.deno.land/https/deno.land/x/postgres/mod.ts) -![License](https://img.shields.io/github/license/denodrivers/postgres?color=yellowgreen&label=License&style=flat-square) +[![JSR](https://jsr.io/badges/@db/postgres?style=flat-square)](https://jsr.io/@db/postgres) +[![JSR Score](https://jsr.io/badges/@db/postgres/score?style=flat-square)](https://jsr.io/@db/postgres) +[![Manual](https://img.shields.io/github/v/release/denodrivers/postgres?color=orange&label=Manual&logo=deno&style=flat-square)](https://deno-postgres.com) +[![Documentation](https://img.shields.io/github/v/release/denodrivers/postgres?color=yellow&label=Documentation&logo=deno&style=flat-square)](https://jsr.io/@db/postgres/doc) +[![License](https://img.shields.io/github/license/denodrivers/postgres?color=yellowgreen&label=License&style=flat-square)](LICENSE) `deno-postgres` is a lightweight PostgreSQL driver for Deno focused on user experience. It provides abstractions for most common operations such as typed queries, prepared statements, connection pools, and transactions. ```ts -import { Client } from "https://deno.land/x/postgres/mod.ts"; +import { Client } from "jsr:@db/postgres"; const client = new Client({ user: "user", @@ -38,7 +40,7 @@ All `deno-postgres` clients provide the following options to authenticate and manage your connections ```ts -import { Client } from "https://deno.land/x/postgres/mod.ts"; +import { Client } from "jsr:@db/postgres"; let config; @@ -114,7 +116,7 @@ of search parameters such as the following: - password: If password is not specified in the url, this will be taken instead - port: If port is not specified in the url, this will be taken instead - options: This parameter can be used by other database engines usable through - the Postgres protocol (such as Cockroachdb for example) to send additional + the Postgres protocol (such as CockroachDB for example) to send additional values for connection (ej: options=--cluster=your_cluster_name) - sslmode: Allows you to specify the tls configuration for your client; the allowed values are the following: @@ -231,9 +233,6 @@ instead of TCP by providing the route to the socket file your Postgres database creates automatically. You can manually set the protocol used with the `host_type` property in the client options -**Note**: This functionality is only available on UNIX systems under the -`--unstable` flag - In order to connect to the socket you can pass the path as a host in the client initialization. Alternatively, you can specify the port the database is listening on and the parent folder of the socket as a host (The equivalent of @@ -343,8 +342,8 @@ certificate to encrypt your connection that, if not taken care of, can render your certificate invalid. When using a self-signed certificate, make sure to specify the PEM encoded CA -certificate using the `--cert` option when starting Deno (Deno 1.12.2 or later) -or in the `tls.caCertificates` option when creating a client (Deno 1.15.0 later) +certificate using the `--cert` option when starting Deno or in the +`tls.caCertificates` option when creating a client ```ts const client = new Client({ @@ -381,7 +380,7 @@ https://www.postgresql.org/docs/14/libpq-envars.html) ```ts // PGUSER=user PGPASSWORD=admin PGDATABASE=test deno run --allow-net --allow-env database.js -import { Client } from "https://deno.land/x/postgres/mod.ts"; +import { Client } from "jsr:@db/postgres"; const client = new Client(); await client.connect(); @@ -1127,16 +1126,16 @@ const transaction = client.createTransaction("abortable"); await transaction.begin(); let savepoint; -try{ +try { // Oops, savepoints can't start with a number // Validation error, transaction won't be ended savepoint = await transaction.savepoint("1"); -}catch(e){ +} catch (e) { // We validate the error was not related to transaction execution - if(!(e instance of TransactionError)){ + if (!(e instanceof TransactionError)) { // We create a good savepoint we can use savepoint = await transaction.savepoint("a_valid_name"); - }else{ + } else { throw e; } } @@ -1452,7 +1451,7 @@ await transaction.commit(); The driver can provide different types of logs if as needed. By default, logs are disabled to keep your environment as uncluttered as possible. Logging can be enabled by using the `debug` option in the Client `controls` parameter. Pass -`true` to enable all logs, or turn on logs granulary by enabling the following +`true` to enable all logs, or turn on logs granularity by enabling the following options: - `queries` : Logs all SQL queries executed by the client @@ -1465,7 +1464,7 @@ options: ```ts // debug_test.ts -import { Client } from "./mod.ts"; +import { Client } from "jsr:@db/postgres"; const client = new Client({ user: "postgres", @@ -1497,7 +1496,7 @@ AS $function$ BEGIN RAISE INFO 'This function generates a random UUID :)'; RAISE NOTICE 'A UUID takes up 128 bits in memory.'; - RAISE WARNING 'UUIDs must follow a specific format and lenght in order to be valid!'; + RAISE WARNING 'UUIDs must follow a specific format and length in order to be valid!'; RETURN gen_random_uuid(); END; $function$;; diff --git a/docs/index.html b/docs/index.html index 4ce33e9f..2fc96d36 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,22 +1,31 @@ - - - Deno Postgres - - - - - - -
- - - - + + + Deno Postgres + + + + + + +
+ + + + diff --git a/mod.ts b/mod.ts index be4ee055..13499468 100644 --- a/mod.ts +++ b/mod.ts @@ -5,12 +5,7 @@ export { TransactionError, } from "./client/error.ts"; export { Pool } from "./pool.ts"; -export { Oid, OidTypes } from "./query/oid.ts"; - -// TODO -// Remove the following reexports after https://doc.deno.land -// supports two level depth exports -export type { OidType, OidValue } from "./query/oid.ts"; +export { Oid, type OidType, OidTypes, type OidValue } from "./query/oid.ts"; export type { ClientOptions, ConnectionOptions, diff --git a/pool.ts b/pool.ts index ae2b58e6..16713d53 100644 --- a/pool.ts +++ b/pool.ts @@ -14,18 +14,19 @@ import { DeferredAccessStack } from "./utils/deferred.ts"; * with their PostgreSQL database * * ```ts - * import { Pool } from "https://deno.land/x/postgres/mod.ts"; + * import { Pool } from "jsr:@db/postgres"; * const pool = new Pool({ - * database: "database", - * hostname: "hostname", - * password: "password", - * port: 5432, - * user: "user", + * database: Deno.env.get("PGDATABASE"), + * hostname: Deno.env.get("PGHOST"), + * password: Deno.env.get("PGPASSWORD"), + * port: Deno.env.get("PGPORT"), + * user: Deno.env.get("PGUSER"), * }, 10); // Creates a pool with 10 available connections * * const client = await pool.connect(); * await client.queryArray`SELECT 1`; * client.release(); + * await pool.end(); * ``` * * You can also opt to not initialize all your connections at once by passing the `lazy` @@ -34,7 +35,7 @@ import { DeferredAccessStack } from "./utils/deferred.ts"; * available connections in the pool * * ```ts - * import { Pool } from "https://deno.land/x/postgres/mod.ts"; + * import { Pool } from "jsr:@db/postgres"; * // Creates a pool with 10 max available connections * // Connection with the database won't be established until the user requires it * const pool = new Pool({}, 10, true); @@ -53,6 +54,7 @@ import { DeferredAccessStack } from "./utils/deferred.ts"; * const client_3 = await pool.connect(); * client_2.release(); * client_3.release(); + * await pool.end(); * ``` */ export class Pool { @@ -117,11 +119,12 @@ export class Pool { * with the database if no other connections are available * * ```ts - * import { Pool } from "https://deno.land/x/postgres/mod.ts"; + * import { Pool } from "jsr:@db/postgres"; * const pool = new Pool({}, 10); * const client = await pool.connect(); - * await client.queryArray`UPDATE MY_TABLE SET X = 1`; + * await client.queryArray`SELECT * FROM CLIENTS`; * client.release(); + * await pool.end(); * ``` */ async connect(): Promise { @@ -138,24 +141,29 @@ export class Pool { * This will close all open connections and set a terminated status in the pool * * ```ts - * import { Pool } from "https://deno.land/x/postgres/mod.ts"; + * import { Pool } from "jsr:@db/postgres"; * const pool = new Pool({}, 10); * * await pool.end(); * console.assert(pool.available === 0, "There are connections available after ending the pool"); - * await pool.end(); // An exception will be thrown, pool doesn't have any connections to close + * try { + * await pool.end(); // An exception will be thrown, pool doesn't have any connections to close + * } catch (e) { + * console.log(e); + * } * ``` * * However, a terminated pool can be reused by using the "connect" method, which * will reinitialize the connections according to the original configuration of the pool * * ```ts - * import { Pool } from "https://deno.land/x/postgres/mod.ts"; + * import { Pool } from "jsr:@db/postgres"; * const pool = new Pool({}, 10); * await pool.end(); * const client = await pool.connect(); * await client.queryArray`SELECT 1`; // Works! * client.release(); + * await pool.end(); * ``` */ async end(): Promise { diff --git a/query/array_parser.ts b/query/array_parser.ts index 60e27a25..8ca9175f 100644 --- a/query/array_parser.ts +++ b/query/array_parser.ts @@ -89,7 +89,7 @@ class ArrayParser { this.dimension++; if (this.dimension > 1) { parser = new ArrayParser( - this.source.substr(this.position - 1), + this.source.substring(this.position - 1), this.transform, this.separator, ); diff --git a/query/decode.ts b/query/decode.ts index cb5d9fc7..c0311910 100644 --- a/query/decode.ts +++ b/query/decode.ts @@ -1,5 +1,5 @@ import { Oid, type OidType, OidTypes, type OidValue } from "./oid.ts"; -import { bold, yellow } from "../deps.ts"; +import { bold, yellow } from "@std/fmt/colors"; import { decodeBigint, decodeBigintArray, @@ -196,10 +196,10 @@ function decodeText(value: string, typeOid: number) { // them as they see fit return value; } - } catch (_e) { + } catch (e) { console.error( bold(yellow(`Error decoding type Oid ${typeOid} value`)) + - _e.message + + (e instanceof Error ? e.message : e) + "\n" + bold("Defaulting to null."), ); diff --git a/query/decoders.ts b/query/decoders.ts index 4edbb03a..58356d76 100644 --- a/query/decoders.ts +++ b/query/decoders.ts @@ -1,4 +1,3 @@ -import { parseDate } from "../deps.ts"; import { parseArray } from "./array_parser.ts"; import type { Box, @@ -64,7 +63,9 @@ export function decodeBox(value: string): Box { b: decodePoint(b), }; } catch (e) { - throw new Error(`Invalid Box: "${value}" : ${e.message}`); + throw new Error( + `Invalid Box: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } } @@ -93,8 +94,8 @@ function decodeByteaEscape(byteaStr: string): Uint8Array { bytes.push(byteaStr.charCodeAt(i)); ++i; } else { - if (/[0-7]{3}/.test(byteaStr.substr(i + 1, 3))) { - bytes.push(parseInt(byteaStr.substr(i + 1, 3), 8)); + if (/[0-7]{3}/.test(byteaStr.substring(i + 1, i + 4))) { + bytes.push(parseInt(byteaStr.substring(i + 1, i + 4), 8)); i += 4; } else { let backslashes = 1; @@ -140,7 +141,9 @@ export function decodeCircle(value: string): Circle { radius: radius, }; } catch (e) { - throw new Error(`Invalid Circle: "${value}" : ${e.message}`); + throw new Error( + `Invalid Circle: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } } @@ -157,7 +160,7 @@ export function decodeDate(dateStr: string): Date | number { return Number(-Infinity); } - return parseDate(dateStr, "yyyy-MM-dd"); + return new Date(dateStr); } export function decodeDateArray(value: string) { @@ -249,13 +252,13 @@ export function decodeLine(value: string): Line { ); } - equationConsts.forEach((c) => { + for (const c of equationConsts) { if (Number.isNaN(parseFloat(c))) { throw new Error( `Invalid Line: "${value}". Line constant "${c}" must be a valid number.`, ); } - }); + } const [a, b, c] = equationConsts; @@ -287,7 +290,11 @@ export function decodeLineSegment(value: string): LineSegment { b: decodePoint(b), }; } catch (e) { - throw new Error(`Invalid Line Segment: "${value}" : ${e.message}`); + throw new Error( + `Invalid Line Segment: "${value}" : ${(e instanceof Error + ? e.message + : e)}`, + ); } } @@ -304,7 +311,9 @@ export function decodePath(value: string): Path { try { return decodePoint(point); } catch (e) { - throw new Error(`Invalid Path: "${value}" : ${e.message}`); + throw new Error( + `Invalid Path: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } }); } @@ -348,7 +357,9 @@ export function decodePolygon(value: string): Polygon { try { return decodePath(value); } catch (e) { - throw new Error(`Invalid Polygon: "${value}" : ${e.message}`); + throw new Error( + `Invalid Polygon: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } } diff --git a/query/encode.ts b/query/encode.ts index 36407bf2..94cf2b60 100644 --- a/query/encode.ts +++ b/query/encode.ts @@ -50,24 +50,23 @@ function escapeArrayElement(value: unknown): string { function encodeArray(array: Array): string { let encodedArray = "{"; - array.forEach((element, index) => { + for (let index = 0; index < array.length; index++) { if (index > 0) { encodedArray += ","; } + const element = array[index]; if (element === null || typeof element === "undefined") { encodedArray += "NULL"; } else if (Array.isArray(element)) { encodedArray += encodeArray(element); } else if (element instanceof Uint8Array) { - // TODO - // Should it be encoded as bytea? - throw new Error("Can't encode array of buffers."); + encodedArray += encodeBytes(element); } else { const encodedElement = encodeArgument(element); encodedArray += escapeArrayElement(encodedElement as string); } - }); + } encodedArray += "}"; return encodedArray; @@ -91,15 +90,18 @@ export type EncodedArg = null | string | Uint8Array; export function encodeArgument(value: unknown): EncodedArg { if (value === null || typeof value === "undefined") { return null; - } else if (value instanceof Uint8Array) { + } + if (value instanceof Uint8Array) { return encodeBytes(value); - } else if (value instanceof Date) { + } + if (value instanceof Date) { return encodeDate(value); - } else if (value instanceof Array) { + } + if (value instanceof Array) { return encodeArray(value); - } else if (value instanceof Object) { + } + if (value instanceof Object) { return JSON.stringify(value); - } else { - return String(value); } + return String(value); } diff --git a/query/query.ts b/query/query.ts index fa4eae8a..bdf0276e 100644 --- a/query/query.ts +++ b/query/query.ts @@ -15,13 +15,14 @@ import type { ClientControls } from "../connection/connection_params.ts"; * They will take the position according to the order in which they were provided * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * - * await my_client.queryArray("SELECT ID, NAME FROM PEOPLE WHERE AGE > $1 AND AGE < $2", [ - * 10, // $1 - * 20, // $2 + * await my_client.queryArray("SELECT ID, NAME FROM CLIENTS WHERE NAME = $1", [ + * "John", // $1 * ]); + * + * await my_client.end(); * ``` */ @@ -155,7 +156,7 @@ export interface QueryObjectOptions extends QueryOptions { /** * This class is used to handle the result of a query */ -export class QueryResult { +export abstract class QueryResult { /** * Type of query executed for this result */ @@ -225,9 +226,7 @@ export class QueryResult { * * This function can throw on validation, so any errors must be handled in the message loop accordingly */ - insertRow(_row: Uint8Array[]): void { - throw new Error("No implementation for insertRow is defined"); - } + abstract insertRow(_row: Uint8Array[]): void; } /** diff --git a/query/transaction.ts b/query/transaction.ts index 02ba0197..2b8dd6ea 100644 --- a/query/transaction.ts +++ b/query/transaction.ts @@ -60,26 +60,36 @@ export class Savepoint { * Releasing a savepoint will remove it's last instance in the transaction * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * + * await transaction.begin(); * const savepoint = await transaction.savepoint("n1"); * await savepoint.release(); - * transaction.rollback(savepoint); // Error, can't rollback because the savepoint was released + * + * try { + * await transaction.rollback(savepoint); // Error, can't rollback because the savepoint was released + * } catch (e) { + * console.log(e); + * } + * + * await client.end(); * ``` * * It will also allow you to set the savepoint to the position it had before the last update * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction1"); * + * await transaction.begin(); * const savepoint = await transaction.savepoint("n1"); * await savepoint.update(); * await savepoint.release(); // This drops the update of the last statement - * transaction.rollback(savepoint); // Will rollback to the first instance of the savepoint + * await transaction.rollback(savepoint); // Will rollback to the first instance of the savepoint + * await client.end(); * ``` * * This function will throw if there are no savepoint instances to drop @@ -97,29 +107,33 @@ export class Savepoint { * Updating a savepoint will update its position in the transaction execution * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction1"); * - * const my_value = "some value"; + * await transaction.begin(); * * const savepoint = await transaction.savepoint("n1"); - * transaction.queryArray`INSERT INTO MY_TABLE (X) VALUES (${my_value})`; + * transaction.queryArray`DELETE FROM CLIENTS`; * await savepoint.update(); // Rolling back will now return you to this point on the transaction + * await client.end(); * ``` * * You can also undo a savepoint update by using the `release` method * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction1"); + * + * await transaction.begin(); * * const savepoint = await transaction.savepoint("n1"); - * transaction.queryArray`DELETE FROM VERY_IMPORTANT_TABLE`; + * transaction.queryArray`DELETE FROM CLIENTS`; * await savepoint.update(); // Oops, shouldn't have updated the savepoint * await savepoint.release(); // This will undo the last update and return the savepoint to the first instance * await transaction.rollback(); // Will rollback before the table was deleted + * await client.end(); * ``` */ async update() { @@ -197,13 +211,14 @@ export class Transaction { * The begin method will officially begin the transaction, and it must be called before * any query or transaction operation is executed in order to lock the session * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction_name"); * * await transaction.begin(); // Session is locked, transaction operations are now safe * // Important operations * await transaction.commit(); // Session is unlocked, external operations can now take place + * await client.end(); * ``` * https://www.postgresql.org/docs/14/sql-begin.html */ @@ -257,9 +272,8 @@ export class Transaction { } catch (e) { if (e instanceof PostgresError) { throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } this.#updateClientLock(this.name); @@ -273,27 +287,31 @@ export class Transaction { * current transaction and end the current transaction * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * await transaction.begin(); * // Important operations * await transaction.commit(); // Will terminate the transaction and save all changes + * await client.end(); * ``` * * The commit method allows you to specify a "chain" option, that allows you to both commit the current changes and * start a new with the same transaction parameters in a single statement * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction1"); + * + * await transaction.begin(); * * // Transaction operations I want to commit * await transaction.commit({ chain: true }); // All changes are saved, following statements will be executed inside a transaction - * await transaction.queryArray`DELETE SOMETHING FROM SOMEWHERE`; // Still inside the transaction + * await transaction.queryArray`DELETE FROM CLIENTS`; // Still inside the transaction * await transaction.commit(); // The transaction finishes for good + * await client.end(); * ``` * * https://www.postgresql.org/docs/14/sql-commit.html @@ -312,9 +330,8 @@ export class Transaction { } catch (e) { if (e instanceof PostgresError) { throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } @@ -346,14 +363,19 @@ export class Transaction { * the snapshot state between two transactions * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client_1 = new Client(); * const client_2 = new Client(); * const transaction_1 = client_1.createTransaction("transaction"); * + * await transaction_1.begin(); + * * const snapshot = await transaction_1.getSnapshot(); * const transaction_2 = client_2.createTransaction("new_transaction", { isolation_level: "repeatable_read", snapshot }); * // transaction_2 now shares the same starting state that transaction_1 had + * + * await client_1.end(); + * await client_2.end(); * ``` * https://www.postgresql.org/docs/14/functions-admin.html#FUNCTIONS-SNAPSHOT-SYNCHRONIZATION */ @@ -371,36 +393,48 @@ export class Transaction { * It supports a generic interface in order to type the entries retrieved by the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * + * await transaction.begin(); + * * const {rows} = await transaction.queryArray( * "SELECT ID, NAME FROM CLIENTS" * ); // Array + * + * await client.end(); * ``` * * You can pass type arguments to the query in order to hint TypeScript what the return value will be * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * + * await transaction.begin(); + * * const { rows } = await transaction.queryArray<[number, string]>( * "SELECT ID, NAME FROM CLIENTS" * ); // Array<[number, string]> + * + * await client.end(); * ``` * * It also allows you to execute prepared stamements with template strings * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * + * await transaction.begin(); + * * const id = 12; * // Array<[number, string]> * const { rows } = await transaction.queryArray<[number, string]>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`; + * + * await client.end(); * ``` */ async queryArray>( @@ -411,12 +445,13 @@ export class Transaction { * Use the configuration object for more advance options to execute the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * const { rows } = await my_client.queryArray<[number, string]>({ * text: "SELECT ID, NAME FROM CLIENTS", * name: "select_clients", * }); // Array<[number, string]> + * await my_client.end(); * ``` */ async queryArray>( @@ -426,12 +461,14 @@ export class Transaction { * Execute prepared statements with template strings * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * * const id = 12; * // Array<[number, string]> * const {rows} = await my_client.queryArray<[number, string]>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`; + * + * await my_client.end(); * ``` */ async queryArray>( @@ -467,9 +504,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } @@ -477,7 +513,7 @@ export class Transaction { * Executed queries and retrieve the data as object entries. It supports a generic in order to type the entries retrieved by the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * * const { rows: rows1 } = await my_client.queryObject( @@ -487,6 +523,8 @@ export class Transaction { * const { rows: rows2 } = await my_client.queryObject<{id: number, name: string}>( * "SELECT ID, NAME FROM CLIENTS" * ); // Array<{id: number, name: string}> + * + * await my_client.end(); * ``` */ async queryObject( @@ -497,7 +535,7 @@ export class Transaction { * Use the configuration object for more advance options to execute the query * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * * const { rows: rows1 } = await my_client.queryObject( @@ -510,6 +548,8 @@ export class Transaction { * fields: ["personal_id", "complete_name"], * }); * console.log(rows2); // [{personal_id: 78, complete_name: "Frank"}, {personal_id: 15, complete_name: "Sarah"}] + * + * await my_client.end(); * ``` */ async queryObject( @@ -519,11 +559,12 @@ export class Transaction { * Execute prepared statements with template strings * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const my_client = new Client(); * const id = 12; * // Array<{id: number, name: string}> * const { rows } = await my_client.queryObject<{id: number, name: string}>`SELECT ID, NAME FROM CLIENTS WHERE ID = ${id}`; + * await my_client.end(); * ``` */ async queryObject( @@ -565,9 +606,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } @@ -578,12 +618,15 @@ export class Transaction { * Calling a rollback without arguments will terminate the current transaction and undo all changes. * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * + * await transaction.begin(); + * * // Very very important operations that went very, very wrong * await transaction.rollback(); // Like nothing ever happened + * await client.end(); * ``` * * https://www.postgresql.org/docs/14/sql-rollback.html @@ -593,13 +636,15 @@ export class Transaction { * Savepoints can be used to rollback specific changes part of a transaction. * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction1"); + * + * await transaction.begin(); * * // Important operations I don't want to rollback * const savepoint = await transaction.savepoint("before_disaster"); - * await transaction.queryArray`UPDATE MY_TABLE SET X = 0`; // Oops, update without where + * await transaction.queryArray`DELETE FROM CLIENTS`; // Oops, deleted the wrong thing * * // These are all the same, everything that happened between the savepoint and the rollback gets undone * await transaction.rollback(savepoint); @@ -607,6 +652,7 @@ export class Transaction { * await transaction.rollback({ savepoint: 'before_disaster'}) * * await transaction.commit(); // Commits all other changes + * await client.end(); * ``` */ async rollback( @@ -616,14 +662,17 @@ export class Transaction { * The `chain` option allows you to undo the current transaction and restart it with the same parameters in a single statement * * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction2"); + * + * await transaction.begin(); * * // Transaction operations I want to undo * await transaction.rollback({ chain: true }); // All changes are undone, but the following statements will be executed inside a transaction as well - * await transaction.queryArray`DELETE SOMETHING FROM SOMEWHERE`; // Still inside the transaction + * await transaction.queryArray`DELETE FROM CLIENTS`; // Still inside the transaction * await transaction.commit(); // The transaction finishes for good + * await client.end(); * ``` */ async rollback(options?: { chain?: boolean }): Promise; @@ -701,9 +750,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } this.#resetTransaction(); @@ -725,42 +773,51 @@ export class Transaction { * * A savepoint can be easily created like this * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); * const transaction = client.createTransaction("transaction"); * + * await transaction.begin(); + * * const savepoint = await transaction.savepoint("MY_savepoint"); // returns a `Savepoint` with name "my_savepoint" * await transaction.rollback(savepoint); * await savepoint.release(); // The savepoint will be removed + * await client.end(); * ``` * All savepoints can have multiple positions in a transaction, and you can change or update * this positions by using the `update` and `release` methods * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction1"); + * + * await transaction.begin(); * * const savepoint = await transaction.savepoint("n1"); - * await transaction.queryArray`INSERT INTO MY_TABLE VALUES (${'A'}, ${2})`; + * await transaction.queryArray`DELETE FROM CLIENTS`; * await savepoint.update(); // The savepoint will continue from here - * await transaction.queryArray`DELETE FROM MY_TABLE`; - * await transaction.rollback(savepoint); // The transaction will rollback before the delete, but after the insert + * await transaction.queryArray`DELETE FROM CLIENTS`; + * await transaction.rollback(savepoint); // The transaction will rollback before the secpmd delete * await savepoint.release(); // The last savepoint will be removed, the original one will remain - * await transaction.rollback(savepoint); // It rolls back before the insert + * await transaction.rollback(savepoint); // It rolls back before the delete * await savepoint.release(); // All savepoints are released + * await client.end(); * ``` * * Creating a new savepoint with an already used name will return you a reference to * the original savepoint * ```ts - * import { Client } from "https://deno.land/x/postgres/mod.ts"; + * import { Client } from "jsr:@db/postgres"; * const client = new Client(); - * const transaction = client.createTransaction("transaction"); + * const transaction = client.createTransaction("transaction2"); + * + * await transaction.begin(); * * const savepoint_a = await transaction.savepoint("a"); - * await transaction.queryArray`DELETE FROM MY_TABLE`; + * await transaction.queryArray`DELETE FROM CLIENTS`; * const savepoint_b = await transaction.savepoint("a"); // They will be the same savepoint, but the savepoint will be updated to this position * await transaction.rollback(savepoint_a); // Rolls back to savepoint_b + * await client.end(); * ``` * https://www.postgresql.org/docs/14/sql-savepoint.html */ @@ -792,9 +849,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } else { savepoint = new Savepoint( @@ -813,9 +869,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } this.#savepoints.push(savepoint); } diff --git a/tests/auth_test.ts b/tests/auth_test.ts index f7ed38db..4b06120e 100644 --- a/tests/auth_test.ts +++ b/tests/auth_test.ts @@ -1,4 +1,8 @@ -import { assertEquals, assertNotEquals, assertRejects } from "./test_deps.ts"; +import { + assertEquals, + assertNotEquals, + assertRejects, +} from "jsr:@std/assert@1.0.10"; import { Client as ScramClient, Reason } from "../connection/scram.ts"; Deno.test("Scram client reproduces RFC 7677 example", async () => { diff --git a/tests/config.ts b/tests/config.ts index a3366625..0fb0507a 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -15,7 +15,10 @@ let DEV_MODE: string | undefined; try { DEV_MODE = Deno.env.get("DENO_POSTGRES_DEVELOPMENT"); } catch (e) { - if (e instanceof Deno.errors.PermissionDenied || ('NotCapable' in Deno.errors && e instanceof Deno.errors.NotCapable)) { + if ( + e instanceof Deno.errors.PermissionDenied || + ("NotCapable" in Deno.errors && e instanceof Deno.errors.NotCapable) + ) { throw new Error( "You need to provide ENV access in order to run the test suite", ); diff --git a/tests/connection_params_test.ts b/tests/connection_params_test.ts index d5138784..94df4338 100644 --- a/tests/connection_params_test.ts +++ b/tests/connection_params_test.ts @@ -1,4 +1,5 @@ -import { assertEquals, assertThrows, fromFileUrl } from "./test_deps.ts"; +import { assertEquals, assertThrows } from "jsr:@std/assert@1.0.10"; +import { fromFileUrl } from "@std/path"; import { createParams } from "../connection/connection_params.ts"; import { ConnectionParamsError } from "../client/error.ts"; diff --git a/tests/connection_test.ts b/tests/connection_test.ts index 5cc85539..50cc7dd9 100644 --- a/tests/connection_test.ts +++ b/tests/connection_test.ts @@ -1,9 +1,5 @@ -import { - assertEquals, - assertRejects, - copyStream, - joinPath, -} from "./test_deps.ts"; +import { assertEquals, assertRejects } from "jsr:@std/assert@1.0.10"; +import { join as joinPath } from "@std/path"; import { getClearConfiguration, getClearSocketConfiguration, @@ -25,26 +21,20 @@ function createProxy( const proxy = (async () => { for await (const conn of target) { - let aborted = false; - const outbound = await Deno.connect({ hostname: source.hostname, port: source.port, }); + aborter.signal.addEventListener("abort", () => { conn.close(); outbound.close(); - aborted = true; }); + await Promise.all([ - copyStream(conn, outbound), - copyStream(outbound, conn), + conn.readable.pipeTo(outbound.writable), + outbound.readable.pipeTo(conn.writable), ]).catch(() => {}); - - if (!aborted) { - conn.close(); - outbound.close(); - } } })(); @@ -399,7 +389,7 @@ Deno.test("Closes connection on bad TLS availability verification", async functi await client.connect(); } catch (e) { if ( - e instanceof Error || + e instanceof Error && e.message.startsWith("Could not check if server accepts SSL connections") ) { bad_tls_availability_message = true; @@ -586,19 +576,19 @@ Deno.test("Attempts reconnection on socket disconnection", async () => { // TODO // Find a way to unlink the socket to simulate unexpected socket disconnection -Deno.test("Attempts reconnection when connection is lost", async function () { +Deno.test("Attempts reconnection when connection is lost", async () => { const cfg = getMainConfiguration(); const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }); const { aborter, proxy } = createProxy(listener, { hostname: cfg.hostname, - port: Number(cfg.port), + port: cfg.port, }); const client = new Client({ ...cfg, hostname: "127.0.0.1", - port: (listener.addr as Deno.NetAddr).port, + port: listener.addr.port, tls: { enabled: false, }, diff --git a/tests/data_types_test.ts b/tests/data_types_test.ts index d4d56103..1dc1c463 100644 --- a/tests/data_types_test.ts +++ b/tests/data_types_test.ts @@ -1,4 +1,5 @@ -import { assertEquals, base64, formatDate, parseDate } from "./test_deps.ts"; +import { assertEquals } from "jsr:@std/assert@1.0.10"; +import { decodeBase64, encodeBase64 } from "@std/encoding/base64"; import { getMainConfiguration } from "./config.ts"; import { generateSimpleClientTest } from "./helpers.ts"; import type { @@ -34,7 +35,7 @@ function generateRandomPoint(max_value = 100): Point { const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; function randomBase64(): string { - return base64.encodeBase64( + return encodeBase64( Array.from( { length: Math.ceil(Math.random() * 256) }, () => CHARS[Math.floor(Math.random() * CHARS.length)], @@ -671,7 +672,7 @@ Deno.test( `SELECT decode('${base64_string}','base64')`, ); - assertEquals(result.rows[0][0], base64.decodeBase64(base64_string)); + assertEquals(result.rows[0][0], decodeBase64(base64_string)); }), ); @@ -691,7 +692,7 @@ Deno.test( assertEquals( result.rows[0][0], - strings.map(base64.decodeBase64), + strings.map(decodeBase64), ); }), ); @@ -931,7 +932,7 @@ Deno.test( ); assertEquals(result.rows[0], [ - parseDate(date_text, "yyyy-MM-dd"), + new Date(date_text), Infinity, ]); }), @@ -941,7 +942,7 @@ Deno.test( "date array", testClient(async (client) => { await client.queryArray(`SET SESSION TIMEZONE TO '${timezone}'`); - const dates = ["2020-01-01", formatDate(new Date(), "yyyy-MM-dd")]; + const dates = ["2020-01-01", (new Date()).toISOString().split("T")[0]]; const { rows: result } = await client.queryArray<[[Date, Date]]>( "SELECT ARRAY[$1::DATE, $2]", @@ -950,7 +951,7 @@ Deno.test( assertEquals( result[0][0], - dates.map((d) => parseDate(d, "yyyy-MM-dd")), + dates.map((d) => new Date(d)), ); }), ); diff --git a/tests/decode_test.ts b/tests/decode_test.ts index 06512911..b2f0657f 100644 --- a/tests/decode_test.ts +++ b/tests/decode_test.ts @@ -17,7 +17,7 @@ import { decodePoint, decodeTid, } from "../query/decoders.ts"; -import { assertEquals, assertThrows } from "./test_deps.ts"; +import { assertEquals, assertThrows } from "jsr:@std/assert@1.0.10"; import { Oid } from "../query/oid.ts"; Deno.test("decodeBigint", function () { diff --git a/tests/encode_test.ts b/tests/encode_test.ts index 784fdaab..eab21868 100644 --- a/tests/encode_test.ts +++ b/tests/encode_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "./test_deps.ts"; +import { assertEquals } from "jsr:@std/assert@1.0.10"; import { encodeArgument } from "../query/encode.ts"; // internally `encodeArguments` uses `getTimezoneOffset` to encode Date diff --git a/tests/pool_test.ts b/tests/pool_test.ts index c8ecac91..3acf920e 100644 --- a/tests/pool_test.ts +++ b/tests/pool_test.ts @@ -1,4 +1,4 @@ -import { assertEquals, delay } from "./test_deps.ts"; +import { assertEquals } from "jsr:@std/assert@1.0.10"; import { getMainConfiguration } from "./config.ts"; import { generatePoolClientTest } from "./helpers.ts"; @@ -11,7 +11,7 @@ Deno.test( assertEquals(POOL.available, 10); const client = await POOL.connect(); const p = client.queryArray("SELECT pg_sleep(0.1) is null, -1 AS id"); - await delay(1); + await new Promise((resolve) => setTimeout(resolve, 1)); assertEquals(POOL.available, 9); assertEquals(POOL.size, 10); await p; @@ -28,7 +28,7 @@ Deno.test( return query; }); const qsPromises = Promise.all(qsThunks); - await delay(1); + await new Promise((resolve) => setTimeout(resolve, 1)); assertEquals(POOL.available, 0); const qs = await qsPromises; assertEquals(POOL.available, 10); @@ -52,7 +52,7 @@ Deno.test( const client_2 = await POOL.connect(); const p = client_2.queryArray("SELECT pg_sleep(0.1) is null, -1 AS id"); - await delay(1); + await new Promise((resolve) => setTimeout(resolve, 1)); assertEquals(POOL.size, size); assertEquals(POOL.available, size - 1); assertEquals(await POOL.initialized(), 0); @@ -75,7 +75,7 @@ Deno.test( }, ); const qsPromises = Promise.all(qsThunks); - await delay(1); + await new Promise((resolve) => setTimeout(resolve, 1)); assertEquals(POOL.available, 0); assertEquals(await POOL.initialized(), 0); const qs = await qsPromises; diff --git a/tests/query_client_test.ts b/tests/query_client_test.ts index abc7332f..26966de4 100644 --- a/tests/query_client_test.ts +++ b/tests/query_client_test.ts @@ -12,7 +12,7 @@ import { assertObjectMatch, assertRejects, assertThrows, -} from "./test_deps.ts"; +} from "jsr:@std/assert@1.0.10"; import { getMainConfiguration } from "./config.ts"; import type { PoolClient, QueryClient } from "../client.ts"; import type { ClientOptions } from "../connection/connection_params.ts"; diff --git a/tests/test_deps.ts b/tests/test_deps.ts index 3ec05aaa..cb56ee54 100644 --- a/tests/test_deps.ts +++ b/tests/test_deps.ts @@ -1,4 +1,3 @@ -export * from "../deps.ts"; export { assert, assertEquals, @@ -7,6 +6,4 @@ export { assertObjectMatch, assertRejects, assertThrows, -} from "https://deno.land/std@0.214.0/assert/mod.ts"; -export { format as formatDate } from "https://deno.land/std@0.214.0/datetime/format.ts"; -export { copy as copyStream } from "https://deno.land/std@0.214.0/io/copy.ts"; +} from "jsr:@std/assert@1.0.10"; diff --git a/tests/utils_test.ts b/tests/utils_test.ts index 1491831c..40542ea7 100644 --- a/tests/utils_test.ts +++ b/tests/utils_test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertThrows } from "./test_deps.ts"; +import { assertEquals, assertThrows } from "jsr:@std/assert@1.0.10"; import { parseConnectionUri, type Uri } from "../utils/utils.ts"; import { DeferredAccessStack, DeferredStack } from "../utils/deferred.ts"; diff --git a/tools/convert_to_jsr.ts b/tools/convert_to_jsr.ts deleted file mode 100644 index 9843f572..00000000 --- a/tools/convert_to_jsr.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { walk } from "https://deno.land/std@0.214.0/fs/walk.ts"; -import denoConfig from "../deno.json" with { type: "json" }; - -const STD_SPECIFIER_REGEX = - /https:\/\/deno\.land\/std@(\d+\.\d+\.\d+)\/(\w+)\/(.+)\.ts/g; -const POSTGRES_X_SPECIFIER = "https://deno.land/x/postgres/mod.ts"; -const POSTGRES_JSR_SPECIFIER = `jsr:${denoConfig.name}`; - -function toStdJsrSpecifier( - _full: string, - _version: string, - module: string, - path: string, -): string { - /** - * @todo(iuioiua) Restore the dynamic use of the `version` argument - * once 0.214.0 is released. - */ - const version = "0.213.1"; - return path === "mod" - ? `jsr:@std/${module}@${version}` - : `jsr:@std/${module}@${version}/${path}`; -} - -for await ( - const entry of walk(".", { - includeDirs: false, - exts: [".ts", ".md"], - skip: [/docker/, /.github/, /tools/], - followSymlinks: false, - }) -) { - const text = await Deno.readTextFile(entry.path); - const newText = text - .replaceAll(STD_SPECIFIER_REGEX, toStdJsrSpecifier) - .replaceAll(POSTGRES_X_SPECIFIER, POSTGRES_JSR_SPECIFIER); - await Deno.writeTextFile(entry.path, newText); -} diff --git a/utils/deferred.ts b/utils/deferred.ts index f4b4c10a..9d650d90 100644 --- a/utils/deferred.ts +++ b/utils/deferred.ts @@ -22,7 +22,9 @@ export class DeferredStack { async pop(): Promise { if (this.#elements.length > 0) { return this.#elements.pop()!; - } else if (this.#size < this.#max_size && this.#creator) { + } + + if (this.#size < this.#max_size && this.#creator) { this.#size++; return await this.#creator(); } diff --git a/utils/utils.ts b/utils/utils.ts index ae7ccee8..f0280fb7 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1,4 +1,4 @@ -import { bold, yellow } from "../deps.ts"; +import { bold, yellow } from "@std/fmt/colors"; export function readInt16BE(buffer: Uint8Array, offset: number): number { offset = offset >>> 0; @@ -93,7 +93,7 @@ export function parseConnectionUri(uri: string): Uri { } } catch (_e) { console.error( - bold(yellow("Failed to decode URL host") + "\nDefaulting to raw host"), + bold(`${yellow("Failed to decode URL host")}\nDefaulting to raw host`), ); } @@ -108,8 +108,9 @@ export function parseConnectionUri(uri: string): Uri { } catch (_e) { console.error( bold( - yellow("Failed to decode URL password") + - "\nDefaulting to raw password", + `${ + yellow("Failed to decode URL password") + }\nDefaulting to raw password`, ), ); } From d2a9c81ade407061733eb49f971060ad2e1ba0f0 Mon Sep 17 00:00:00 2001 From: jersey Date: Sun, 20 Apr 2025 21:41:52 -0400 Subject: [PATCH 09/10] fix JSR publish workflow (#499) --- .github/workflows/publish_jsr.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_jsr.yml b/.github/workflows/publish_jsr.yml index 11fe11b2..1afeb605 100644 --- a/.github/workflows/publish_jsr.yml +++ b/.github/workflows/publish_jsr.yml @@ -34,15 +34,15 @@ jobs: - name: Lint run: deno lint - - name: Documentation tests - run: deno test --doc client.ts mod.ts pool.ts client/ connection/ query/ utils/ - - name: Build tests container run: docker compose build tests - name: Run tests run: docker compose run tests + - name: Run doc tests + run: docker compose run doc_tests + - name: Publish (dry run) if: startsWith(github.ref, 'refs/tags/') == false run: deno publish --dry-run --allow-dirty From 8a9b60d4d41f06181e52dde3058a20720e34ba6a Mon Sep 17 00:00:00 2001 From: hector Date: Wed, 23 Apr 2025 22:01:50 -0400 Subject: [PATCH 10/10] chore: add mit license to deno.json file --- deno.json | 1 + 1 file changed, 1 insertion(+) diff --git a/deno.json b/deno.json index 028b90e6..63fbcc32 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,7 @@ { "name": "@db/postgres", "version": "0.19.4", + "license": "MIT", "exports": "./mod.ts", "imports": { "@std/bytes": "jsr:@std/bytes@^1.0.5",