Supabase
Supabase is Postgres, so the PostgreSQL page applies unchanged. What is specific to Supabase is which endpoint you connect to and how tenant scoping meets RLS.
npm install uql-orm pgWhich connection string
Section titled “Which connection string”| Endpoint | Port | Use it for |
|---|---|---|
db.<ref>. |
5432 | Migrations, pg_, long-lived servers with IPv6 |
aws-<region>. (session mode) |
5432 | The same, from an IPv4-only network |
aws-<region>. (transaction mode) |
6543 | Serverless and many short-lived connections |
The direct hostname is IPv6-only unless you buy the IPv4 add-on, which is the usual cause of ENETUNREACH from CI or a container.
import { PgQuerierPool } from 'uql-orm/postgres';
export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL, max: 10,});Transaction mode hands you a different backend connection between statements, so session state does not survive and prepared statements fail. UQL prepares none of its own; if you enable them in pg, turn them off for that endpoint.
Run migrations on the direct endpoint:
import type { Config } from 'uql-orm';import { PgQuerierPool } from 'uql-orm/postgres';import { Company, Invoice } from './entities.js';
export default { pool: new PgQuerierPool({ connectionString: process.env.DIRECT_DATABASE_URL, }), entities: [Company, Invoice], migrationsPath: './migrations',} satisfies Config;Row-level security
Section titled “Row-level security”RLS policies read auth.uid() and auth.jwt(), which Supabase’s API layer fills. Connecting with pg skips that layer: as the postgres role, no policy applies. Two layers restore scoping, and they stack.
A UQL security filter is AND-merged into every query the ORM builds, cannot be turned off from the wire, and fails closed when the context is missing:
import { Entity } from 'uql-orm';
@Entity({ filters: { tenant: { where: (ctx) => ctx?.orgId != null ? { organizationId: ctx.orgId } : undefined, security: true, }, },})export class Invoice {}RLS is the backstop for raw SQL and anything else that skips the ORM. Connect as a role the policies apply to, and set the claims inside the transaction:
await pool.transaction(async (querier) => { await querier.run`SELECT set_config('request.jwt.claims', ${JSON.stringify(claims)}, true)`; return querier.findMany(Invoice, { $limit: 50 });});The rest of Supabase
Section titled “The rest of Supabase”UQL replaces supabase-js for data access only; Auth, Storage and Realtime keep working, and verified Auth claims can become the UQL context. pgvector is installed on every project, so semantic search needs only the index your migration creates.
Edge Functions run on Deno without TCP, so pg cannot connect there. For functions that freeze between invocations, see Serverless.