supabase / postgres-meta

A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries
https://supabase.com
Apache License 2.0
856 stars 112 forks source link

feat: bring-your-own-pg-client™ (browser support) #782

Open gregnr opened 3 weeks ago

gregnr commented 3 weeks ago

Adds a PostgresMetaBase class that allows you to bring-your-own-pg-client™. Instead of depending on pg, the base class accepts custom query() and end() functions allowing you to choose how these methods are implemented.

The existing PostgresMeta class now extends PostgresMetaBase and implements query() and end() using pg exactly as before without breaking changes.

Why?

Browser support 🤓 ElectricSQL's pglite gives us a working Postgres instance in the browser (via wasm). Offering a platform-agnostic version of this lib means we can use it in any environment including the browser.

Example using PGlite

import { PGlite } from '@electric-sql/pglite'
import {
  PostgresMetaBase,
  PostgresMetaErr,
  PostgresTable,
  wrapError,
  wrapResult,
} from '@supabase/postgres-meta/base' // note '/base' - see below

const db = new PGlite()

// Implement `query` and `end` for PGlite
const pgMeta = new PostgresMetaBase({
  query: async (sql) => {
    try {
      const res = await db.query(sql)
      return wrapResult<any[]>(res.rows)
    } catch (error) {
      return wrapError(error, sql)
    }
  },
  // PGlite is single user/connection so doesn't require an end function
  end: async () => {},
})

const { data, error } = await pgMeta.tables.list({ includedSchemas: ['public'] })
// `data` contains public tables in PGlite 🚀

Other important notes

In order to make this platform-agnostic, all code needed to be pure TS (no native deps). We do 3 things:

  1. Introduce PostgresMetaBase class which doesn't import the pg dependency
  2. Create a new entrypoint base.ts with a package export at /base. This means you can import { PostgresMetaBase } from '@supabase/postgres-meta/base' without importing pg.

    Importing @supabase/postgres-meta will continue to use pg as before without breaking changes.

  3. Create custom loader for .sql files that live under ./src/lib/sql. Previously these were loaded using Node's fs API. Now they are imported directly like import tablesSql from './sql/tables.sql'.

    This custom import is accomplished by adding a lightweight bundler to the build step: tsup (esbuild bundler under the hood). So now tsup/esbuild run the build process instead of pure tsc. I did my best to make sure the dist outputs were consistent with previous tsc builds, but worth double checking (there are some differences, like bundling into single files). Also outputs both ESM and CJS outputs which should provide more flexibility to consumers.

All tests continue to pass. Only change needed was adding a custom vitest plugin to load .sql files, similar to what we do with tsup.

soedirgo commented 2 weeks ago

This is great! 💯

We're also slowly moving all pg-meta functionality to the main Supabase repo, which is pure-JS and is simply a SQL builder - would that be a better place for this?

There's a lot of work that still needs to be done for that though - feature parity, publishing the package somewhere, tests, the works.

soedirgo commented 2 weeks ago

Or maybe we keep this feature here for now and switch to the pure-JS version once we reach parity? Wdyt?