# All files / src/drivers postgres-js.ts

100% Statements 23/23
90% Branches 9/10
100% Functions 4/4
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104

    1x
    12x
    12x
    117x

    116x
    116x
    116x
    117x

    117x
    117x
    117x

    12x

    12x
    12x

    12x

    7x
    7x
    5x
    4x
    4x

    7x
    12x
    12x
     /**
     * `postgres.js` adapter for `pgrls-test`.
     *
     * Usage:
     *
     * ```ts
     * import postgres from 'postgres';
     * import { PgrlsTestClient, postgresJsDriver } from 'pgrls-test';
     *
     * const sql = postgres(process.env.DATABASE_URL);
     * const client = new PgrlsTestClient(postgresJsDriver(sql));
     * ```
     *
     * The adapter exists because `postgres.js`'s primary API is
     * tagged-template literals (`sql\`SELECT * FROM x\``), which
     * doesn't fit the "execute this raw SQL string with these
     * positional params" shape `pgrls-test` needs. Internally we
     * use `sql.unsafe(text, params)` — postgres.js's escape hatch
     * for exactly that. Users keep their existing `postgres()`
     * connection pool and tagged-template usage everywhere else;
     * the escape hatch is confined to this adapter.
     *
     * `postgres` is a peer dependency. Users who don't use it
     * never pull it in.
     */
    import type { Driver, QueryResult } from './types.js';

    /**
     * Subset of `postgres.Sql` the adapter needs. Defined
     * structurally so we don't have to `import postgres from
     * 'postgres'` at module-load time (that would make `postgres`
     * a runtime dep instead of a peer dep).
     *
     * `postgres.js`'s `unsafe(text, params)` returns a
     * `PendingQuery<Row[]>` — awaiting it yields an array of rows
     * with `command` and `count` properties attached. Modeling
     * that shape here.
     */
    export interface PostgresJsSql {
      unsafe<TRow = Record<string, unknown>>(
        text: string,
        params?: readonly unknown[],
      ): Promise<PostgresJsResult<TRow>>;
    }

    /**
     * Result shape postgres.js returns from `sql.unsafe(...)`.
     *
     * The result IS the array of rows (numerically indexed) but
     * also has `command` and `count` properties tacked on — a
     * postgres.js convention. We treat it as both via a hybrid
     * type.
     */
    export type PostgresJsResult<TRow> = readonly TRow[] & {
      command?: string;
      count?: number;
    };

    /**
     * Wrap a `postgres.Sql` instance (or any object satisfying
     * `PostgresJsSql`) into the `Driver` shape the rest of
     * `pgrls-test` consumes.
     */
    export function postgresJsDriver(sql: PostgresJsSql): Driver {
      return {
        async query(sqlText: string, params?: readonly unknown[]): Promise<QueryResult> {
          const result = await sql.unsafe<Record<string, unknown>>(sqlText, params);
          // postgres.js's result is an array-like with `command`
          // and `count` extras. Spread into a plain array so
          // callers can't mutate the underlying driver state, and
          // surface command/count under the QueryResult shape.
          const rows: Record<string, unknown>[] = Array.from(result);
          return {
            rows,
            command: (result.command ?? '').toUpperCase(),
            // For SELECT and RETURNING-bearing DML, count is the
            // number of rows. For plain UPDATE/DELETE/INSERT, count
            // is the affected-row count. Map an explicit `undefined`
            // (defensive — postgres.js sets it on every result) to
            // rows.length as the safest fallback.
            rowCount: result.count ?? rows.length,
          };
        },

        async rollback(): Promise<void> {
          // ROLLBACK accepted in any transaction state, including
          // an aborted one.
          await sql.unsafe('ROLLBACK');
        },

        isInsufficientPrivilege(error: unknown): boolean {
          // postgres.js wraps server errors as `PostgresError`
          // instances with `code` set to the 5-char SQLSTATE.
          // 42501 is insufficient_privilege.
          return (
            typeof error === 'object' &&
            error !== null &&
            'code' in error &&
            (error as { code?: unknown }).code === '42501'
          );
        },
      };
    }