Loading...
Loading...
Compare original and translation side by side
Edit your data contract. Prisma handles the rest.
supabase()编辑你的数据契约,其余工作由Prisma处理。
supabase()policy_select@@rlsauth.uid()asUser(jwt)asAnon()asServiceRole()auth.usersauth.*storage.*policy_select@@rlsauth.uid()asUser(jwt)asAnon()asServiceRole()auth.usersauth.*storage.*prisma-next-contractdb.tsprisma-next-runtimeprisma-next-queriesdbprisma-next-migrationsprisma-next-contractdb.tsprisma-next-runtimeprisma-next-queriesdbprisma-next-migrationsexternal@prisma-next/extension-supabase/packauthstorageanonauthenticatedservice_roleexternalextensionsdb verifymanagedroles = [authenticated]not-foundsupabase()SupabaseDbdb.sqldb.ormawait db.asUser(jwt)db.asAnon()db.asServiceRole()RoleBoundDb.sql.orm.raw.execute(plan).transaction(fn)set_config('role', …)set_config('request.jwt.claims', …)RESET ALLauth.uid()auth.jwt()GRANTGRANTpublicservice_roleauth.*storage.*jwksUrlasUser(jwt)josejwksUrljwtSecretSUPABASE.CONFIG_INVALIDSUPABASE.JWT_INVALIDmeta.reasonjwtSecretjwksUrlroleauthenticatedsupabase statusJWT_SECRETauth.*storage.*service_roledb.asServiceRole().supabase.sql.orm.nativeEnums.executeservice_roleservice_roleauth.*storage.*USAGEpostgresasUserasAnon.supabaseasServiceRole().sql.ormexternal@prisma-next/extension-supabase/packauthstorageanonauthenticatedservice_roleexternalextensionsdb verifymanagedroles = [authenticated]verifynot-foundsupabase()SupabaseDbdb.sqldb.ormawait db.asUser(jwt)db.asAnon()db.asServiceRole()RoleBoundDb.sql.orm.raw.execute(plan).transaction(fn)set_config('role', …)set_config('request.jwt.claims', …)RESET ALLauth.uid()auth.jwt()GRANTGRANTpublicservice_roleauth.*storage.*jwksUrlasUser(jwt)josejwksUrljwtSecretSUPABASE.CONFIG_INVALIDSUPABASE.JWT_INVALIDmeta.reasonjwtSecretjwksUrlroleauthenticatedsupabase statusJWT_SECRETservice_roleauth.*storage.*db.asServiceRole().supabase.sql.orm.nativeEnums.executeservice_roleservice_roleauth.*storage.*USAGEpostgresasUserasAnon.supabaseasServiceRole().sql.orm/controldefineConfig({ extensions: [...] })extensionsexamples/supabase/prisma-next.config.ts// prisma-next.config.ts
import postgresAdapter from '@prisma-next/adapter-postgres/control';
import { defineConfig } from '@prisma-next/cli/config-types';
import postgresDriver from '@prisma-next/driver-postgres/control';
import supabasePack from '@prisma-next/extension-supabase/pack';
import sql from '@prisma-next/family-sql/control';
import { prismaContract } from '@prisma-next/sql-contract-psl/provider';
import postgres from '@prisma-next/target-postgres/control';
import postgresPackRef from '@prisma-next/target-postgres/pack';
import { postgresCreateNamespace } from '@prisma-next/target-postgres/types';
export default defineConfig({
family: sql,
target: postgres,
adapter: postgresAdapter,
driver: postgresDriver,
extensions: [supabasePack],
contract: prismaContract('./src/contract.prisma', {
output: 'src/contract.json',
target: postgresPackRef,
createNamespace: postgresCreateNamespace,
}),
migrations: { dir: 'migrations' },
});/controldefineConfig({ extensions: [...] })extensionsexamples/supabase/prisma-next.config.ts// prisma-next.config.ts
import postgresAdapter from '@prisma-next/adapter-postgres/control';
import { defineConfig } from '@prisma-next/cli/config-types';
import postgresDriver from '@prisma-next/driver-postgres/control';
import supabasePack from '@prisma-next/extension-supabase/pack';
import sql from '@prisma-next/family-sql/control';
import { prismaContract } from '@prisma-next/sql-contract-psl/provider';
import postgres from '@prisma-next/target-postgres/control';
import postgresPackRef from '@prisma-next/target-postgres/pack';
import { postgresCreateNamespace } from '@prisma-next/target-postgres/types';
export default defineConfig({
family: sql,
target: postgres,
adapter: postgresAdapter,
driver: postgresDriver,
extensions: [supabasePack],
contract: prismaContract('./src/contract.prisma', {
output: 'src/contract.json',
target: postgresPackRef,
createNamespace: postgresCreateNamespace,
}),
migrations: { dir: 'migrations' },
});auth.usersauth.userspublicauthstoragesupabase:auth.AuthUserREFERENCES "auth"."users"("id")policy_<operation>@@rlsexamples/supabase/src/contract.prismatypes {
Uuid = String @db.Uuid
}
namespace public {
model Profile {
id Uuid @id @default(uuid())
username String
userId Uuid @unique
user supabase:auth.AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("profile")
@@rls
}
// authenticated may read only their own profile.
policy_select profile_owner_read {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
}
// anon may read every profile (a public directory listing).
policy_select profile_public_read {
target = Profile
roles = [anon]
using = "true"
}
// authenticated may update only their own profile, and may not
// reassign it to another owner (WITH CHECK).
policy_update profile_owner_write {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
withCheck = "\"userId\"::uuid = auth.uid()"
}
}policy_selectpolicy_insertpolicy_updatepolicy_deletepolicy_allkey = valuetargetrolesanonauthenticatedservice_roleusingwithCheck(target, operation)@@rlspolicy_*@@rlsPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE@@rls\"userId\"auth.uid()uuid@prisma-next/postgres/contract-builderpolicySelectpolicyInsertpolicyUpdatepolicyDeletepolicyAllrlsEnabled(Model)role('anon')prisma-next contract emitprisma-next-migrationsENABLE ROW LEVEL SECURITYCREATE POLICYauth.*publicauthstoragesupabase:auth.AuthUserREFERENCES "auth"."users"("id")policy_<operation>@@rlsexamples/supabase/src/contract.prismatypes {
Uuid = String @db.Uuid
}
namespace public {
model Profile {
id Uuid @id @default(uuid())
username String
userId Uuid @unique
user supabase:auth.AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("profile")
@@rls
}
// 已认证用户仅可读取自己的个人资料
policy_select profile_owner_read {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
}
// 匿名用户可读取所有个人资料(公开目录列表)
policy_select profile_public_read {
target = Profile
roles = [anon]
using = "true"
}
// 已认证用户仅可更新自己的个人资料,且不可将其重新分配给其他所有者(WITH CHECK)
policy_update profile_owner_write {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
withCheck = "\"userId\"::uuid = auth.uid()"
}
}policy_selectpolicy_insertpolicy_updatepolicy_deletepolicy_allkey = valuetargetrolesanonauthenticatedservice_roleusingwithCheck(target, operation)@@rlspolicy_*@@rlsPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE@@rls\"userId\"auth.uid()uuid@prisma-next/postgres/contract-builderpolicySelectpolicyInsertpolicyUpdatepolicyDeletepolicyAllrlsEnabled(Model)role('anon')prisma-next contract emitprisma-next-migrationsENABLE ROW LEVEL SECURITYCREATE POLICYauth.*db.tssupabase()supabase()db.tspostgres()supabase()/runtimejwksUrl// src/prisma/db.ts
import { supabase } from '@prisma-next/extension-supabase/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };
export const db = await supabase<Contract>({
contractJson,
url: process.env['DATABASE_URL'], // direct Postgres connection — see pitfalls
jwksUrl: process.env['SUPABASE_JWKS_URL'], // https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json
// Legacy HS256 projects use jwtSecret: process.env['SUPABASE_JWT_SECRET'] instead — exactly one of the two.
});middlewarepostgres()prisma-next-runtimeset_configpoolOptionspgpg.Poolpg.Clienturlawait db.close()await usingprisma-next-runtimepostgres()/runtimesupabase()jwksUrl// src/prisma/db.ts
import { supabase } from '@prisma-next/extension-supabase/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };
export const db = await supabase<Contract>({
contractJson,
url: process.env['DATABASE_URL'], // 直接Postgres连接——注意陷阱
jwksUrl: process.env['SUPABASE_JWKS_URL'], // https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json
// 旧版HS256项目使用jwtSecret: process.env['SUPABASE_JWT_SECRET']替代——二选一,不可同时配置
});middlewarepostgres()prisma-next-runtimeset_configpoolOptionspgpg.Poolpg.Clienturlprisma-next-runtimeawait db.close()await usingRoleBoundDbprisma-next-queries// A signed-in user: rows are RLS-scoped to the JWT's auth.uid().
const userDb = await db.asUser(jwt); // async — rejects with code SUPABASE.JWT_INVALID on a bad/expired token
const mine = await userDb.orm.public.Profile.select('id', 'username').all();
// The anon role: sees what anon policies permit.
const listing = await db.asAnon().orm.public.Profile.select('id', 'username').all();
// service_role: BYPASSRLS — sees everything in YOUR contract.
const all = await db.asServiceRole().orm.public.Profile.select('id', 'username').all();
// Writes ride the same surfaces; RLS filters them too. An UPDATE against
// another owner's row affects 0 rows; a withCheck violation raises an error.
const updated = await userDb.orm.public.Profile
.where({ userId: me })
.updateAndCount({ username: 'new-name' });asAnon()asServiceRole()asUserorm.public.Profilesql.public.profileprisma-next-queriesRoleBoundDb.transaction(fn)RoleBoundDbprisma-next-queries// 已登录用户:行数据受JWT的auth.uid()范围限制
const userDb = await db.asUser(jwt); // 异步操作——令牌无效/过期时会抛出SUPABASE.JWT_INVALID错误
const mine = await userDb.orm.public.Profile.select('id', 'username').all();
// 匿名角色:仅能看到匿名策略允许的数据
const listing = await db.asAnon().orm.public.Profile.select('id', 'username').all();
// service_role:绕过RLS——可查看你的契约中的所有数据
const all = await db.asServiceRole().orm.public.Profile.select('id', 'username').all();
// 写入操作使用相同接口;同样受RLS过滤。更新其他所有者的行时,影响行数为0;违反withCheck规则会抛出错误
const updated = await userDb.orm.public.Profile
.where({ userId: me })
.updateAndCount({ username: 'new-name' });asAnon()asServiceRole()asUserorm.public.Profilesql.public.profileprisma-next-queriesRoleBoundDb.transaction(fn)auth.*storage.*auth.*storage.*service_roledb.asServiceRole().supabaseconst admin = db.asServiceRole();
// SQL builder over the pack contract:
const users = await admin.supabase
.execute(admin.supabase.sql.auth.users.select('id', 'email').build())
.toArray();
// ORM over the pack contract:
const sessions = await admin.supabase.orm.auth.AuthSession.select('id', 'aal').all();
// Native enum values (e.g. auth.aal_level) come typed:
type AalLevel = (typeof admin.supabase.nativeEnums.auth.AalLevel)['Value'];service_roleauth.*storage.*permission denied for table users42501GRANT USAGE ON SCHEMA auth TO service_role;
GRANT SELECT ON TABLE auth.users TO service_role;asUserasAnon.supabase.transactionservice_roleservice_roledb.asServiceRole().supabaseconst admin = db.asServiceRole();
// 基于扩展包契约的SQL构建器:
const users = await admin.supabase
.execute(admin.supabase.sql.auth.users.select('id', 'email').build())
.toArray();
// 基于扩展包契约的ORM:
const sessions = await admin.supabase.orm.auth.AuthSession.select('id', 'aal').all();
// 原生枚举值(如auth.aal_level)带有类型:
type AalLevel = (typeof admin.supabase.nativeEnums.auth.AalLevel)['Value'];service_roleauth.*storage.*permission denied for table users42501GRANT USAGE ON SCHEMA auth TO service_role;
GRANT SELECT ON TABLE auth.users TO service_role;asUserasAnon.supabase.transactionservice_roleGRANTpermission deniedpublicALTER DEFAULT PRIVILEGESpublicprisma-next db initmigrateanonauthenticatedservice_roleservice_roleauth.*storage.*GRANT USAGEGRANT SELECTpsqlpermission denied for table …42501GRANTpermission deniedpublicpublicALTER DEFAULT PRIVILEGESprisma-next db initmigrateanonauthenticatedservice_roleservice_roleauth.*storage.*GRANT USAGEGRANT SELECTpsqlpermission denied for table …42501set_configRESET ALLaws-0-<region>.pooler.supabase.com:5432postgres.<project-ref>db.<project-ref>.supabase.co:5432.envDATABASE_URLSUPABASE_JWKS_URLhttps://<project-ref>.supabase.co/auth/v1/.well-known/jwks.jsonhttp://127.0.0.1:54321/auth/v1/.well-known/jwks.jsonSUPABASE_JWT_SECRETsupabase statusJWT_SECRETalgset_configRESET ALLaws-0-<region>.pooler.supabase.com:5432postgres.<project-ref>db.<project-ref>.supabase.co:5432.envDATABASE_URLSUPABASE_JWKS_URLhttps://<project-ref>.supabase.co/auth/v1/.well-known/jwks.jsonhttp://127.0.0.1:54321/auth/v1/.well-known/jwks.jsonSUPABASE_JWT_SECRETsupabase statusJWT_SECRETalgjwtSecretsupabase statusJWT_SECRETasUserSUPABASE.JWT_INVALIDjwksUrlSUPABASE_JWKS_URLjwtSecretpublicauth.*service_rolepermission denieddb.sqldb.ormdbRoleBoundDbawaitsupabase()asUser(jwt)asAnon()asServiceRole().supabaseasUserasAnonauth.*service_roleservice_rolepolicy_*@@rlsPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE@@rls"userId"auth.uid()::uuiduuidjwksUrljwtSecretsupabase()SUPABASE.CONFIG_INVALIDawait.catchUPDATEwithChecksupabase statusJWT_SECRETjwtSecretasUserSUPABASE.JWT_INVALIDjwksUrlSUPABASE_JWKS_URLjwtSecretpublicservice_roleauth.*permission denieddbdb.sqldb.ormRoleBoundDbawaitsupabase()asUser(jwt)asAnon()asServiceRole()awaitasUserasAnon.supabaseservice_roleauth.*service_rolepolicy_*@@rlsPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE@@rls"userId"uuid::uuidauth.uid()jwksUrljwtSecretsupabase()SUPABASE.CONFIG_INVALIDawait.catchwithCheck/controldefineConfig({ extensions: [...] })extensionsprisma-next-feedbackGRANTservice_roleauth.*psqlprisma-next-feedback.supabaseauth.usersauth.uid()@supabase/supabase-js/controldefineConfig({ extensions: [...] })extensionsprisma-next-feedbackservice_roleauth.*psqlprisma-next-feedback.supabaseauth.usersauth.uid()@supabase/supabase-jsexamples/supabasedb.tspackages/3-extensions/supabase/README.mdpackages/3-extensions/supabase/src/runtime/supabase.tsSupabaseOptionsRoleBoundDbServiceRoleDbexamples/supabasedb.tspackages/3-extensions/supabase/README.mdpackages/3-extensions/supabase/src/runtime/supabase.tsSupabaseOptionsRoleBoundDbServiceRoleDbextensions: [supabasePack]defineConfig/controlsupabase:auth.AuthUserfieldsreferencesonDelete@@rlsauth.uid()db.tsawait supabase<Contract>({ contractJson, url, jwksUrl | jwtSecret })jwksUrljwtSecretRoleBoundDbasUserasAnonasServiceRoleasUserauth.*storage.*asServiceRole().supabaseGRANT USAGE ON SCHEMA authGRANT SELECTpublic/controldb.sql.supabasedefineConfigextensions: [supabasePack]/controlsupabase:auth.AuthUserfieldsreferencesonDelete@@rlsauth.uid()db.tsawait supabase<Contract>({ contractJson, url, jwksUrl | jwtSecret })jwksUrljwtSecretasUserasAnonasServiceRoleRoleBoundDbasUserawaitauth.*storage.*asServiceRole().supabaseGRANT USAGE ON SCHEMA authGRANT SELECTpublic/controldb.sql.supabase