Gruber

Postgres

This module provides an abstraction over postgres called PostgresService along with utilities to integrate it with other Gruber modules.

There are methods like getPostgresMigrations, executePostgresMigration and postgresBootstrapMigration to help platforms like Deno and Node.js implement a Migrator.

There is also PostgresStore that implements the Store interface.

getPostgresMigrations

Query the postgres database to find migrations that have already been performed. Returning an array of PostgresMigrationRecord.

const sql // PostgresService
const records = await getPostgresMigrations(sql)

executePostgresMigration

Perform either the up or down postgres migration and record what happened. This will first start a transaction, so if anything goes wrong the whole operation is aborted. Within the transaction, it attempts the run the action (either up or down) as specified.

After the action is ran, it will follow up with updating the migration records. For an up action, it will create a new PostgresMigrationRecord and insert it into the database. For a down action, it will remove the corresponding PostgresMigrationRecord.

There is an edge case where it will not remove the record if running the postgresBootstrapMigration action, because that migration deletes the migration table itself so would be pointless.

const sql // PostgresService
const definition = definePostgresMigration(...)

await executePostgresMigration(definition, "up", sql)

PostgresMigrationRecord type

A record in a postgres database containing information about a migration that has been run.

const record = {
  name: '001-add-users-table.js',
  created: new Date()
}

postgresBootstrapMigration

This is a MigrationDefinition to bootstrap postgres migrations. It sets up the initial "migrations" table that all other migrations will be recorded in.

definePostgresMigration

A typed version of defineMigration that specalizes for a PostgresService. This is mostly useful to get a strongly typed sql parameter.

import { definePostgresMigration } from "gruber"

export default definePostgresMigration({
  async up(sql) {
    await sql.execute`
      CREATE TABLE users ...
    `
  },
  async down(sql) {
    await sql.execute`
      DROP TABLE users
    `
  }
})

PostgresService type

PostgresService is an abstraction around a connection to a running postgres database. Originally it was a shallow interface for postgres.js, but it is moving towards an agnostic layer that any SQL-based library could implement.

The idea is that platforms within Gruber implement a method that returns an object that conforms to this interface using a specific postgres library.

It might be interesting to think of this as an abstraction over any SQL-based server, but for now it is focussed on postgres.

const sql // PostgresService

// Run an SQL transaction and return the response
const result = await sql.transaction(async (trx) => {
  await trx.execute` INSERT INTO ... `
  return trx.execute` SELECT * FROM ... `
})

// Execute a statement
const users = await sql.execute` SELECT * FROM users WHERE id = ${5}`

// Prepare a value to be used in a query
const pets = await sql.execute`
  SELECT * FROM pets WHERE id IN ${sql.prepare([1, 2, 3, 4])}
`

// Write JSON to the database
await sql.execute`
  INSERT INTO bicycles (name, model)
  VALUES ("Big red", ${sql.json({ id: "xxx-yyy-zzz" })})
`

// Close the connection
await sql.dispose()

// Use Explicit Resource Management to automatically dispose the connection
function main() {
  using sql = getPostgresService(...)

  await sql.execute` SELECT * FROM ... `
}

dispose

Close the connection

await sql.dispose()

execute

Perform an SQL query against the database and return the results. Values are automatically escaped.

const users = await sql.execute`
  SELECT * FROM users WHERE id = ${5}
`

json

Prepare JSON to be serialized into a query

// Write JSON to the database
await sql.execute`
  INSERT INTO bicycles (name, model)
  VALUES ("Big red", ${sql.json({ id: "xxx-yyy-zzz", manufacturer: 'Cube' })})
`

prepare

Prepare a value to be used in an SQL query, for instance if you want to turn an array into a SQL set,

const pets = await sql.execute`
  SELECT * FROM pets WHERE id IN ${sql.prepare([1, 2, 3, 4])}
`

would become:

SELECT * FROM pets WHERE id IN (1,2,3,4)

transaction

Start a transaction with a BEGIN, then COMMIT if all goes well or ROLLBACK if not.

// Run an SQL transaction and return the response
const result = await sql.transaction(async (trx) => {
  await trx.execute` INSERT INTO ... `
  return trx.execute` SELECT * FROM ... `
})
debug
{
  "getPostgresMigrations": {
    "entrypoint": "postgres/mod.ts",
    "id": "getPostgresMigrations",
    "name": "getPostgresMigrations",
    "content": "Query the postgres database to find migrations that have already been performed.\nReturning an array of `PostgresMigrationRecord`.\n\n```js\nconst sql // PostgresService\nconst records = await getPostgresMigrations(sql)\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "members": {}
  },
  "executePostgresMigration": {
    "entrypoint": "postgres/mod.ts",
    "id": "executePostgresMigration",
    "name": "executePostgresMigration",
    "content": "Perform either the **up** or **down** postgres migration and record what happened.\nThis will first start a transaction, so if anything goes wrong the whole operation is aborted.\nWithin the transaction, it attempts the run the action (either **up** or **down**) as specified.\n\nAfter the action is ran, it will follow up with updating the migration records.\nFor an **up** action, it will create a new `PostgresMigrationRecord`\nand insert it into the database.\nFor a **down** action, it will remove the corresponding `PostgresMigrationRecord`.\n\n> There is an edge case where it will not remove the record if running the `postgresBootstrapMigration` action,\n> because that migration deletes the migration table itself so would be pointless.\n\n```js\nconst sql // PostgresService\nconst definition = definePostgresMigration(...)\n\nawait executePostgresMigration(definition, \"up\", sql)\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "members": {}
  },
  "PostgresMigrationRecord": {
    "entrypoint": "postgres/mod.ts",
    "id": "PostgresMigrationRecord",
    "name": "PostgresMigrationRecord",
    "content": "A record in a postgres database containing information about a migration that has been run.\n\n```js\nconst record = {\n  name: '001-add-users-table.js',\n  created: new Date()\n}\n```",
    "tags": {
      "group": "Miscellaneous",
      "type": "true"
    },
    "members": {}
  },
  "postgresBootstrapMigration": {
    "entrypoint": "postgres/mod.ts",
    "id": "postgresBootstrapMigration",
    "name": "postgresBootstrapMigration",
    "content": "This is a `MigrationDefinition` to bootstrap postgres migrations.\nIt sets up the initial \"migrations\" table that all other\nmigrations will be recorded in.",
    "tags": {
      "group": "Miscellaneous"
    },
    "members": {}
  },
  "definePostgresMigration": {
    "entrypoint": "postgres/mod.ts",
    "id": "definePostgresMigration",
    "name": "definePostgresMigration",
    "content": "A typed version of `defineMigration` that specalizes for a ` PostgresService`.\nThis is mostly useful to get a strongly typed `sql` parameter.\n\n```js\nimport { definePostgresMigration } from \"gruber\"\n\nexport default definePostgresMigration({\n  async up(sql) {\n    await sql.execute`\n      CREATE TABLE users ...\n    `\n  },\n  async down(sql) {\n    await sql.execute`\n      DROP TABLE users\n    `\n  }\n})\n```",
    "tags": {
      "group": "Miscellaneous"
    },
    "members": {}
  },
  "PostgresService": {
    "entrypoint": "postgres/mod.ts",
    "id": "PostgresService",
    "name": "PostgresService",
    "content": "`PostgresService` is an abstraction around a connection to a running postgres database.\nOriginally it was a shallow interface for [postgres.js](https://github.com/porsager/postgres),\nbut it is moving towards an agnostic layer that any SQL-based library could implement.\n\nThe idea is that platforms within Gruber implement a method that returns an object that conforms to this interface\nusing a specific postgres library.\n\nIt might be interesting to think of this as an abstraction over any SQL-based server, but for now it is focussed on postgres.\n\n```js\nconst sql // PostgresService\n\n// Run an SQL transaction and return the response\nconst result = await sql.transaction(async (trx) => {\n  await trx.execute` INSERT INTO ... `\n  return trx.execute` SELECT * FROM ... `\n})\n\n// Execute a statement\nconst users = await sql.execute` SELECT * FROM users WHERE id = ${5}`\n\n// Prepare a value to be used in a query\nconst pets = await sql.execute`\n  SELECT * FROM pets WHERE id IN ${sql.prepare([1, 2, 3, 4])}\n`\n\n// Write JSON to the database\nawait sql.execute`\n  INSERT INTO bicycles (name, model)\n  VALUES (\"Big red\", ${sql.json({ id: \"xxx-yyy-zzz\" })})\n`\n\n// Close the connection\nawait sql.dispose()\n\n// Use Explicit Resource Management to automatically dispose the connection\nfunction main() {\n  using sql = getPostgresService(...)\n\n  await sql.execute` SELECT * FROM ... `\n}\n```",
    "tags": {
      "group": "Miscellaneous",
      "type": "true"
    },
    "members": {
      "transaction": {
        "id": "PostgresService_transaction",
        "name": "transaction",
        "content": "Start a transaction with a `BEGIN`, then `COMMIT` if all goes well or `ROLLBACK` if not.\n\n```js\n// Run an SQL transaction and return the response\nconst result = await sql.transaction(async (trx) => {\n  await trx.execute` INSERT INTO ... `\n  return trx.execute` SELECT * FROM ... `\n})\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "members": {}
      },
      "execute": {
        "id": "PostgresService_execute",
        "name": "execute",
        "content": "Perform an SQL query against the database and return the results.\nValues are automatically escaped.\n\n```js\nconst users = await sql.execute`\n  SELECT * FROM users WHERE id = ${5}\n`",
        "tags": {
          "group": "Miscellaneous"
        },
        "members": {}
      },
      "prepare": {
        "id": "PostgresService_prepare",
        "name": "prepare",
        "content": "Prepare a value to be used in an SQL query,\nfor instance if you want to turn an array into a SQL set,\n\n```js\nconst pets = await sql.execute`\n  SELECT * FROM pets WHERE id IN ${sql.prepare([1, 2, 3, 4])}\n`\n```\n\nwould become:\n\n```sql\nSELECT * FROM pets WHERE id IN (1,2,3,4)\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "members": {}
      },
      "json": {
        "id": "PostgresService_json",
        "name": "json",
        "content": "Prepare JSON to be serialized into a query\n\n```js\n// Write JSON to the database\nawait sql.execute`\n  INSERT INTO bicycles (name, model)\n  VALUES (\"Big red\", ${sql.json({ id: \"xxx-yyy-zzz\", manufacturer: 'Cube' })})\n`\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "members": {}
      },
      "dispose": {
        "id": "PostgresService_dispose",
        "name": "dispose",
        "content": "Close the connection\n\n```js\nawait sql.dispose()\n```",
        "tags": {
          "group": "Miscellaneous"
        },
        "members": {}
      }
    }
  }
}