Core Concepts
The foundational, adapter‑agnostic ideas behind RowGate.
Core Concepts
RowGate provides application‑level row‑level authorization.
This document explains the fundamental ideas behind RowGate without referencing any adapter, ORM, or query builder.
All adapters (Kysely, Prisma, Drizzle, raw SQL, etc.) plug into these same core concepts.
Example:
const db = withRowgate({
context: z.object({ userId: z.string() }),
adapter: someAdapter(rawDb),
policy: (ctx) => ({
Post: {
select: { filter: (qb) => qb.where("authorId", "=", ctx.userId) },
insert: { check: (row) => row.authorId === ctx.userId },
update: {
filter: (qb) => qb.where("authorId", "=", ctx.userId),
check: (row) => row.authorId === ctx.userId,
},
delete: { filter: (qb) => qb.where("authorId", "=", ctx.userId) },
},
}),
});
// Ungated queries are unvalidated
await db.ungated().insert("Post", { title: "Hello, world!" });
// RowGate enforces the Post policy on this query
await db.gated(ctx).select("Post").where("id", "=", id);What RowGate Solves
Applications often implement authorization by scattering checks across controllers, services, or database calls:
- “Only return rows where
userId = ctx.userId” - “Only update rows you own”
- “Admins can see everything”
This leads to:
- duplicated business logic
- inconsistent enforcement
- authorization bugs caused by forgetting one
WHEREclause
RowGate centralizes all row‑level authorization in policies, and ensures they apply to all operations through the selected adapter.
Context
A context represents the actor performing an operation.
Examples:
type Context = {
userId: string;
};
type Context = {
teamId: string;
};A few important characteristics:
- The context is fully controlled by your application.
- It is passed explicitly into RowGate, never read implicitly.
- RowGate does not define what a context contains — you do.
What is an Operation?
RowGate defines an adapter‑agnostic operation model:
- A read operation represents “fetch rows from table X with constraints”
- A write operation represents “modify rows of table X”
- A row insert operation represents “insert this data into table X”
Adapters convert ORM‑specific details into this internal operation shape.
Policies consume and return these internal operation representations — not ORM-native constructs.
This is why policies remain portable across Kysely, Prisma, Drizzle, and future drivers.
The Root Table
Every database request has a root table — the table whose rows are being acted upon.
Examples:
- Selecting posts → root table =
Post - Updating users → root table =
User - Deleting comments → root table =
Comment
The root table determines:
- which policy applies
- how constraints propagate into nested logic (joins, relations, implicit filters, etc.)
While each adapter determines how to identify the root table, RowGate defines the concept.
How Enforcement Works (Adapter‑Agnostic)
Even though adapters implement different mechanisms, the enforcement flow is always:
1. You call an ORM / query-layer function
(e.g. “fetch posts”, “update user 5”, “insert comment”)
2. The adapter transforms this into a RowGate Operation
- operation type: read / insert / update / delete
- table name
- data or condition set
- context
3. RowGate Core loads the registered policies
RowGate ensures:
- the table has a defined policy (unless unguarded)
- the policy has the required handlers
4. RowGate calls the appropriate policy handler
Policy modifies:
- the row (insert)
- the operation constraints (read/update/delete)
5. The adapter executes the modified operation
The adapter translates RowGate’s normalized operation back into ORM syntax and executes it.
Unguarded Tables
You may choose not to define a policy for a table.
These tables are:
- still accessible
- not filtered or restricted
- not subject to row-level authorization
Typical examples:
- lookup tables
- static configuration
- non-sensitive reference data
Strictness & Errors
RowGate prefers explicit errors over silent authorization failures.
RowGatePolicyError
Thrown when:
- a required policy is missing for a table
- a policy handler violates its contract
RowGateNotSupportedError
Thrown when the adapter detects a database operation it cannot enforce safely. You can still run the query using db.ungated() (not recommended!).
This ensures authorization is predictable and reliable.
Summary
RowGate Core provides:
- Context → actor identity
- Policies → rules for each table
- Root table → determines policy scope
- Operations → normalized adapter‑agnostic abstraction
- Enforcement → rewriting / sanitizing operations
- Strictness → errors instead of unsafe behavior
Everything in this file applies to all adapters — present and future.
Continue to:
- Learn how to define policies →
../general/policies.mdx - Install RowGate Core →
../core/getting-started.mdx - Use a specific database layer →
<ADAPTER>-adapter/getting-started