@pg-access/postgres
v0.1.0
Published
Compiles @pg-access/core's AST into real PostgreSQL Row-Level Security SQL (CREATE POLICY, GRANT, migrations).
Maintainers
Readme
@pg-access/postgres
Compiles the AuthNode AST from @pg-access/core into real PostgreSQL
Row-Level Security SQL: ALTER TABLE ... ENABLE ROW LEVEL SECURITY plus
one CREATE POLICY per declared operation, with a Supabase-flavored
default dialect (auth.uid(), the authenticated role). It also reads a
live database's applied policies and diffs them against a config.
Most projects use this through @pg-access/cli rather than directly.
compile
import { defineAuth, owner } from "@pg-access/core";
import { compile } from "@pg-access/postgres";
const auth = defineAuth({
projects: { rows: { select: owner("user_id") } },
});
const { sql, statements } = compile(auth);
// sql: the full DDL, statements separated by a blank line
// statements: the same, split into individually executable strings- Each policy is preceded by a
drop policy if existsfor its own name, so a regenerated migration applies cleanly on a database that already has the previous version. - A table with no row policies produces no statements; RLS is never force-enabled with zero policies (which would silently deny everything).
- Pass
existingPolicies(fromlistManagedPolicies()) andcompile()also emits drops for managed policies no longer in the config. - Pass
dialectto target a non-Supabase setup;postgresDialectis the default and theDialecttype is exported.
generateMigration
import { generateMigration } from "@pg-access/postgres";
const { fileName, sql } = generateMigration(auth);
// fileName: `<yyyyMMddHHmmss>_pg_access.sql`, matching the Supabase CLIWraps compile() output as a named, timestamped migration body with a
"generated by pg-access" header. Writing it to disk is the caller's job.
Introspection and diffing
import { diffPolicies, listManagedPolicies } from "@pg-access/postgres";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const applied = await listManagedPolicies(pool, ["projects", "posts"]);
const { missing, orphaned } = diffPolicies(auth, applied);listManagedPolicies(queryable, tables) reads pg_policies for
pg-access-named policies (<table>_<operation>) on the given tables.
diffPolicies(auth, applied) returns which declared policies aren't
applied yet (missing) and which applied managed policies are no longer
declared (orphaned). Orphan detection only covers tables still present in
the config; a table removed entirely isn't visible to it.
Lower-level renderers (compilePolicy, renderCreatePolicy,
renderDropPolicy, policyName, quoteIdent / quoteLiteral /
quoteRole) are exported for tooling that needs them.
See the pg-access docs for the end-to-end guide.
