@btravstack/prisma / rls
rls
Classes
TenantListingInvalid
Defined in: packages/prisma/src/tenant-listing.ts:6
A tenant-listing declaration names an invalid identifier or shares its privileged owner with its caller.
Extends
TaggedErrorInstance<"TenantListingInvalid", {field: keyofTenantListingOptions;reason:"invalid-identifier"|"shared-role"; }>
Constructors
Constructor
new TenantListingInvalid(args): TenantListingInvalid;Defined in: node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2386
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("TenantListingInvalid")<{ readonly field: keyof TenantListingOptions; readonly reason: "invalid-identifier" | "shared-role"; }>.constructor
Properties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
_tag | readonly | "TenantListingInvalid" | TaggedError("TenantListingInvalid")._tag | node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2363 |
cause? | public | unknown | TaggedError("TenantListingInvalid").cause | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24 |
field | readonly | keyof TenantListingOptions | TaggedError("TenantListingInvalid").field | packages/prisma/src/tenant-listing.ts:7 |
message | public | string | TaggedError("TenantListingInvalid").message | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1075 |
name | public | string | TaggedError("TenantListingInvalid").name | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074 |
reason | readonly | "invalid-identifier" | "shared-role" | TaggedError("TenantListingInvalid").reason | packages/prisma/src/tenant-listing.ts:8 |
stack? | public | string | TaggedError("TenantListingInvalid").stack | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076 |
Type Aliases
TenantListing
type TenantListing = object;Defined in: packages/prisma/src/tenant-listing.ts:28
A declaration's migration, ready for Prisma 8's rawSql, and its tenant-id-only query.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
list | readonly | (db, after?) => AsyncResult<readonly string[], SqlError> | At most 100 IDs in bytewise order. Resume with the last ID; stop on an empty page. | packages/prisma/src/tenant-listing.ts:47 |
migration | readonly | object | - | packages/prisma/src/tenant-listing.ts:29 |
migration.execute | readonly | readonly Statement[] | - | packages/prisma/src/tenant-listing.ts:43 |
migration.id | readonly | string | - | packages/prisma/src/tenant-listing.ts:30 |
migration.label | readonly | string | - | packages/prisma/src/tenant-listing.ts:31 |
migration.operationClass | readonly | "additive" | - | packages/prisma/src/tenant-listing.ts:32 |
migration.postcheck | readonly | readonly Statement[] | - | packages/prisma/src/tenant-listing.ts:44 |
migration.precheck | readonly | readonly Statement[] | - | packages/prisma/src/tenant-listing.ts:42 |
migration.target | readonly | object | - | packages/prisma/src/tenant-listing.ts:33 |
migration.target.details | readonly | object | - | packages/prisma/src/tenant-listing.ts:35 |
migration.target.details.name | readonly | string | - | packages/prisma/src/tenant-listing.ts:39 |
migration.target.details.objectType | readonly | "rlsPolicy" | - | packages/prisma/src/tenant-listing.ts:37 |
migration.target.details.schema | readonly | string | - | packages/prisma/src/tenant-listing.ts:36 |
migration.target.details.table | readonly | string | - | packages/prisma/src/tenant-listing.ts:38 |
migration.target.id | readonly | "postgres" | - | packages/prisma/src/tenant-listing.ts:34 |
TenantListingOptions
type TenantListingOptions = object;Defined in: packages/prisma/src/tenant-listing.ts:12
Physical database names and existing roles for a narrow tenant-listing capability.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
name | readonly | string | A new paged function in schema; also names its select policy. | packages/prisma/src/tenant-listing.ts:18 |
owner | readonly | string | Trusted function owner with no payload SELECT, superuser or BYPASSRLS; needs schema CREATE. | packages/prisma/src/tenant-listing.ts:20 |
role | readonly | string | Application role granted execution, with no membership in owner and no schema CREATE privilege. | packages/prisma/src/tenant-listing.ts:22 |
schema | readonly | string | - | packages/prisma/src/tenant-listing.ts:13 |
table | readonly | string | - | packages/prisma/src/tenant-listing.ts:14 |
tenantColumn? | readonly | string | The physical tenant column. Default tenantId. | packages/prisma/src/tenant-listing.ts:16 |
TenantPinnedOptions
type TenantPinnedOptions<S> = QualifyOptions<S> & object;Defined in: packages/prisma/src/rls.ts:16
Options of tenantPinned: raises, as tryQuery takes it, and the setting the pin writes.
Type Parameters
| Type Parameter | Default type |
|---|---|
S extends string | never |
Type Declaration
| Name | Type | Description | Defined in |
|---|---|---|---|
setting? | string | The PostgreSQL run-time setting the policy reads. Defaults to app.tenant_id, matching current_setting('app.tenant_id', true). | packages/prisma/src/rls.ts:21 |
Functions
tenantListing()
function tenantListing(options): Result<TenantListing, TenantListingInvalid>;Defined in: packages/prisma/src/tenant-listing.ts:97
Declare a paged SECURITY DEFINER function returning distinct, non-null tenant IDs as text, and a typed query calling only that function. Each page holds at most 100 IDs in bytewise order; resume after its last ID until empty.
Pass migration to Prisma 8's rawSql inside a reviewed migration. Its transaction installs an owner-only SELECT policy, grants the owner access to the tenant column, fixes the function's search path, revokes PUBLIC execution and grants execution to role. Existing table policies and FORCE ROW LEVEL SECURITY are left intact. No application table privilege is added.
Roles and the table must already exist. The migrator must be able to SET ROLE to the owner; provision that membership for an ordinary migration role. The migration refuses a privileged application or function-owner role, caller membership in the owner, schema CREATE for the caller, owner payload SELECT, or any same-name function overload. Its postcheck verifies the function, policy and execution grants before Prisma records it as applied. Apply the operation transactionally; the function is initially private only once the REVOKE runs. Do not grant the application the owner role afterwards.
Parameters
| Parameter | Type |
|---|---|
options | TenantListingOptions |
Returns
Result<TenantListing, TenantListingInvalid>
tenantPinned()
function tenantPinned<Tx, R, S>(
db,
tenant,
work,
options?
): AsyncResult<R, SqlError | Raised<S>>;Defined in: packages/prisma/src/rls.ts:86
Runs work in a transaction whose connection is pinned to tenant, so a row-level-security policy reading current_setting('app.tenant_id', true) admits that tenant's rows and no others.
The pin is set_config(setting, tenant, true) — transaction-local, which is the whole reason this opens a transaction rather than issuing the pin and handing back a client. A session-scoped pin followed by a separate query works only while the pool happens to hand back the same connection, and fails the first time it does not; it is the shape a middleware would take, and it is why there is no middleware here.
It answers an AsyncResult, qualified by tryQuery — with raises as it takes it: the pin and the work run on one connection and fail on one channel, so a caller never wraps this in a second combinator. work still hands back a PromiseLike, because that is what Prisma's own transaction callback is.
Type Parameters
| Type Parameter | Default type |
|---|---|
Tx extends Queryable | - |
R | - |
S extends string | never |
Parameters
| Parameter | Type |
|---|---|
db | Pinnable<Tx> |
tenant | string |
work | (tx) => PromiseLike<R> |
options? | TenantPinnedOptions<S> |
Returns
AsyncResult<R, SqlError | Raised<S>>
Remarks
The policy must read the same setting name. A tenantPinned(db, tenant, work, { setting }) whose policy names a different one denies every row and every write, which looks exactly like row security working.
Example
const orders = await tenantPinned(db, tenant, (tx) => tx.orm.orders.Order.all());