0.1.5Updated 6 months ago
import { Pool, Client, type ConnectionString } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import { Sluggify } from "@infinity-beyond/utils/slug.ts";

export class PostgresClient {
  private _setup_client!: Client
  pool!: Pool
  readonly connection_string!: string
  readonly database!: string

  private SETUP_FINISHED = false;

  constructor(connection_string: ConnectionString) {
    if(Deno.args.includes('build')) return this;

    const [ _, extracted_connection_string, database ] = connection_string.match(/((?:postgresql|postgres):\/\/(?:[^:@\s]*(?::[^@\s]*)?@)?(?:[^\/\?\s]+))\b(?:\/(.+))?/) ?? [];

    if(!extracted_connection_string) throw new Error('PostgresClient_Invalid connection_string provided')

    this.connection_string = extracted_connection_string;
    this.database = Sluggify(database);

    this._setup_client = new Client(`${this.connection_string}/postgres`);
  }

  async CreateTable<T = Record<string, string>>(name: string, fields: { [P in keyof Required<T>]: string }, { onCreate }: CreateTableOptions = {}) {
    const fields_array: string[] = Object.entries(fields).map(([ name, type ]) => `${name} ${type}`);

    const query = `CREATE TABLE IF NOT EXISTS "${name}" (${fields_array.join(',')})`;

    const response = await this.query(query);

    if(response.warnings.length === 0) {
      onCreate?.();
    }

    return response;
  }

  async Setup() {
    if(this.SETUP_FINISHED) return this;
    await this.EnsureDatabaseExists(this.database);

    this.SETUP_FINISHED = true;

    await this._setup_client.end();

    this.pool = new Pool(`${this.connection_string}/${this.database}`, 20, true);

    return this;
  }

  private async EnsureDatabaseExists(name: string) {
    if(this.SETUP_FINISHED) return;

    const { rowCount = 0 } = await this._setup_client.queryObject(`SELECT datname FROM pg_catalog.pg_database where datname=$1`, [name]);

    if(rowCount === 0) {
      await this._setup_client.queryObject(`CREATE DATABASE "${name}"`);
    }
  }

  async query<T = any>(query: string, args?: any[]) {
    const client = await this.pool.connect();
    try {
      if(Deno.env.get('LOGGING_QUERIES')) console.log(query);

      return await client.queryObject<T>(query, args);
    } finally {
      client.release()
    }
  }

  get end() { return this.pool.end.bind(this.pool); }
}

interface CreateTableOptions {
  onCreate?: () => Promise<void>
}