mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
minor changes to database and realtime reference fiels after docs analysis
This commit is contained in:
@@ -39,7 +39,9 @@ const prisma = new PrismaClient({
|
||||
|
||||
## Session Mode (Port 5432)
|
||||
|
||||
Best for: Long-running servers, apps needing prepared statements.
|
||||
Alternative to direct connection when IPv4 is required. Supports prepared
|
||||
statements, SET commands, and LISTEN/NOTIFY. Recommended for migrations
|
||||
when direct connection is unavailable.
|
||||
|
||||
```bash
|
||||
## Session mode (via pooler for IPv4)
|
||||
@@ -48,11 +50,21 @@ postgres://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:5432/pos
|
||||
|
||||
## Direct Connection (Port 5432)
|
||||
|
||||
Best for: Migrations, admin tasks, persistent servers.
|
||||
Best for: Admin tasks, persistent servers.
|
||||
|
||||
```bash
|
||||
## Direct connection (IPv6 only unless IPv4 add-on enabled)
|
||||
postgres://postgres.[ref]:[password]@db.[ref].supabase.co:5432/postgres
|
||||
postgres://postgres:[password]@db.[ref].supabase.co:5432/postgres
|
||||
```
|
||||
|
||||
## Dedicated Pooler (PgBouncer)
|
||||
|
||||
For paying customers. Co-located with Postgres for best latency. Transaction
|
||||
mode only. Requires IPv6 or IPv4 add-on.
|
||||
|
||||
```bash
|
||||
## Dedicated pooler (port 6543)
|
||||
postgres://postgres.[ref]:[password]@db.[ref].supabase.co:6543/postgres
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
@@ -77,16 +77,18 @@ npx supabase db diff --linked -f sync_remote_changes
|
||||
- Indexes
|
||||
- Constraints
|
||||
- Functions and triggers
|
||||
- RLS policies
|
||||
- RLS policies (new policies only; `alter policy` may not diff correctly)
|
||||
- Extensions
|
||||
|
||||
## What diff Does NOT Capture
|
||||
|
||||
- Publications
|
||||
- Storage buckets
|
||||
- Views with `security_invoker` attributes
|
||||
- DML (INSERT, UPDATE, DELETE)
|
||||
- View ownership changes
|
||||
- Materialized views
|
||||
- Partitions
|
||||
- Comments
|
||||
|
||||
**Caveat:** `alter policy` changes may not be captured correctly. Use versioned
|
||||
migrations for RLS policy modifications.
|
||||
|
||||
For these, write manual migrations.
|
||||
|
||||
|
||||
@@ -38,15 +38,7 @@ create index if not exists idx_users_email on users(email);
|
||||
|
||||
```sql
|
||||
-- Add column only if it doesn't exist
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1 from information_schema.columns
|
||||
where table_name = 'users' and column_name = 'phone'
|
||||
) then
|
||||
alter table users add column phone text;
|
||||
end if;
|
||||
end $$;
|
||||
alter table users add column if not exists phone text;
|
||||
```
|
||||
|
||||
## Idempotent Drops
|
||||
|
||||
@@ -72,18 +72,21 @@ on conflict (id) do nothing;
|
||||
# Apply all pending migrations
|
||||
npx supabase migration up
|
||||
|
||||
# Check migration status
|
||||
# Check migration status (requires supabase link for remote)
|
||||
npx supabase migration list
|
||||
```
|
||||
|
||||
## Repair Failed Migration
|
||||
|
||||
If a migration partially fails:
|
||||
If local and remote migration histories diverge, use `migration repair` to
|
||||
manually update the remote history table without re-executing migrations:
|
||||
|
||||
```bash
|
||||
# Fix the migration file
|
||||
# Then repair the migration history
|
||||
# Mark a migration as applied (inserts record without running it)
|
||||
npx supabase migration repair --status applied 20240315120000
|
||||
|
||||
# Mark a migration as reverted (removes record from history)
|
||||
npx supabase migration repair --status reverted 20240315120000
|
||||
```
|
||||
|
||||
## Inspect Database State
|
||||
|
||||
@@ -33,7 +33,9 @@ create index idx_logs_created on logs using brin(created_at);
|
||||
create index idx_events_id on events using brin(id);
|
||||
```
|
||||
|
||||
**When to use:** Tables with millions of rows where data is inserted in order.
|
||||
**When to use:** Tables with millions of rows where data is inserted in order and
|
||||
updated infrequently. Ideal for `created_at` on append-only tables like orders
|
||||
or logs. Routinely 10x+ smaller than equivalent B-tree indexes.
|
||||
|
||||
## GIN (Generalized Inverted Index)
|
||||
|
||||
|
||||
@@ -125,21 +125,35 @@ const { count } = await supabase
|
||||
|
||||
## Debug Query Performance
|
||||
|
||||
`explain()` is disabled by default to protect sensitive database information.
|
||||
Enable it first:
|
||||
|
||||
```sql
|
||||
alter role authenticator set pgrst.db_plan_enabled to 'true';
|
||||
notify pgrst, 'reload config';
|
||||
```
|
||||
|
||||
Then use it in queries:
|
||||
|
||||
```javascript
|
||||
// Get query execution plan
|
||||
// Get query execution plan (text format by default)
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.eq("author_id", userId)
|
||||
.explain({ analyze: true, verbose: true });
|
||||
|
||||
console.log(data); // Shows execution plan
|
||||
// JSON format
|
||||
const { data: jsonPlan } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.explain({ analyze: true, format: "json" });
|
||||
```
|
||||
|
||||
Enable explain in database:
|
||||
Disable after use:
|
||||
|
||||
```sql
|
||||
alter role authenticator set pgrst.db_plan_enabled to true;
|
||||
alter role authenticator set pgrst.db_plan_enabled to 'false';
|
||||
notify pgrst, 'reload config';
|
||||
```
|
||||
|
||||
|
||||
@@ -37,13 +37,16 @@ alter table profiles enable row level security;
|
||||
create policy "Users can view own profile"
|
||||
on profiles for select
|
||||
to authenticated
|
||||
using (auth.uid() = user_id);
|
||||
using ((select auth.uid()) = user_id);
|
||||
```
|
||||
|
||||
Tables created via Dashboard have RLS enabled by default. Tables created via SQL
|
||||
require manual enablement. Supabase sends daily warnings for tables without RLS.
|
||||
|
||||
**Note:** Service role key bypasses ALL RLS policies. Never expose it to browsers.
|
||||
**Note:** Service role key bypasses RLS policies only when no user is signed in.
|
||||
If a user is authenticated, Supabase adheres to that user's RLS policies even
|
||||
when the client is initialized with the service role key. Never expose the
|
||||
service role key to browsers.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -90,12 +90,15 @@ using (
|
||||
-- Function in private schema
|
||||
create function private.user_team_ids()
|
||||
returns setof uuid
|
||||
language sql
|
||||
language plpgsql
|
||||
security definer
|
||||
stable
|
||||
set search_path = ''
|
||||
as $$
|
||||
select team_id from team_members
|
||||
where user_id = (select auth.uid())
|
||||
begin
|
||||
return query select team_id from public.team_members
|
||||
where user_id = (select auth.uid());
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Policy uses cached function result
|
||||
|
||||
@@ -30,14 +30,16 @@ as
|
||||
from profiles;
|
||||
```
|
||||
|
||||
**Correct (Older Postgres):**
|
||||
**Correct (Pre-Postgres 15):**
|
||||
|
||||
`security_invoker` is not available before PG15. Use one of these alternatives:
|
||||
|
||||
```sql
|
||||
-- Option 1: Revoke direct access, create RLS on view
|
||||
revoke all on public_profiles from anon, authenticated;
|
||||
-- Option 1: Create a public table synced via trigger instead of a view
|
||||
-- This avoids the security definer bypass entirely
|
||||
|
||||
-- Option 2: Create view in unexposed schema
|
||||
create schema private;
|
||||
create schema if not exists private;
|
||||
create view private.profiles_view as
|
||||
select * from profiles;
|
||||
```
|
||||
|
||||
@@ -30,6 +30,14 @@ create table profiles (
|
||||
username text,
|
||||
avatar_url text
|
||||
);
|
||||
|
||||
-- Enable RLS on profiles
|
||||
alter table profiles enable row level security;
|
||||
|
||||
create policy "Users can view own profile"
|
||||
on profiles for select
|
||||
to authenticated
|
||||
using ((select auth.uid()) = id);
|
||||
```
|
||||
|
||||
## Alternative: SET NULL for Optional Relationships
|
||||
@@ -72,7 +80,7 @@ create trigger on_auth_user_created
|
||||
```
|
||||
|
||||
**Important:** Use `security definer` and `set search_path = ''` for triggers on
|
||||
auth.users.
|
||||
auth.users. If the trigger fails, it will block signups — test thoroughly.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ create extension if not exists vector with schema extensions;
|
||||
-- Scheduled jobs (pg_cron requires pg_catalog, not extensions)
|
||||
create extension if not exists pg_cron with schema pg_catalog;
|
||||
|
||||
-- HTTP requests from database
|
||||
create extension if not exists pg_net with schema extensions;
|
||||
-- HTTP requests from database (pg_net creates its own 'net' schema)
|
||||
create extension if not exists pg_net;
|
||||
|
||||
-- Full-text search improvements
|
||||
create extension if not exists pg_trgm with schema extensions;
|
||||
@@ -65,14 +65,19 @@ select * from pg_extension;
|
||||
## Using Extensions
|
||||
|
||||
```sql
|
||||
-- pgvector example
|
||||
-- pgvector example (use extensions. prefix for type)
|
||||
create table documents (
|
||||
id bigint primary key generated always as identity,
|
||||
content text,
|
||||
embedding vector(1536) -- OpenAI ada-002 dimensions
|
||||
embedding extensions.vector(1536) -- OpenAI ada-002 dimensions
|
||||
);
|
||||
|
||||
create index on documents using ivfflat (embedding vector_cosine_ops);
|
||||
-- HNSW is recommended over IVFFlat for most use cases
|
||||
create index on documents using hnsw (embedding extensions.vector_cosine_ops);
|
||||
|
||||
-- If using IVFFlat, lists parameter is required
|
||||
create index on documents using ivfflat (embedding extensions.vector_cosine_ops)
|
||||
with (lists = 100);
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
@@ -87,6 +87,18 @@ const { data } = await supabase
|
||||
.from("users")
|
||||
.select("email, preferences->theme")
|
||||
.eq("preferences->>notifications", "true");
|
||||
|
||||
// containment queries
|
||||
const { data: darkUsers } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.contains("preferences", { theme: "dark" });
|
||||
|
||||
// contained-by query
|
||||
const { data: subset } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.containedBy("preferences", { theme: "dark", notifications: true });
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
@@ -43,7 +43,7 @@ alter publication supabase_realtime add table messages;
|
||||
|
||||
**Via Dashboard:**
|
||||
|
||||
Database > Publications > supabase_realtime > Add table
|
||||
Stop and ask the user to navigate to the Supabase Dashboard and add the table under Database > Publications > supabase_realtime.
|
||||
|
||||
## Realtime with RLS
|
||||
|
||||
@@ -73,7 +73,9 @@ const channel = supabase
|
||||
|
||||
- Add indexes on columns used in Realtime filters
|
||||
- Keep RLS policies simple for subscribed tables
|
||||
- Monitor "Realtime Private Channel RLS Execution Time" in Dashboard
|
||||
- Stop and ask the user to monitor "Realtime Private Channel RLS Execution Time" in the Supabase Dashboard
|
||||
- Prefer Broadcast over Postgres Changes for scalability — Postgres Changes
|
||||
has limitations at scale due to single-thread processing
|
||||
|
||||
## Replica Identity
|
||||
|
||||
@@ -85,6 +87,10 @@ all columns:
|
||||
alter table messages replica identity full;
|
||||
```
|
||||
|
||||
**Caveat:** RLS policies are not applied to DELETE events — there is no way for
|
||||
Postgres to verify user access to a deleted record. With `replica identity full`,
|
||||
DELETE events only include primary key columns in the `old` record.
|
||||
|
||||
## Related
|
||||
|
||||
- [rls-mandatory.md](rls-mandatory.md)
|
||||
|
||||
@@ -39,8 +39,15 @@ create table events (
|
||||
- Stores time in UTC internally
|
||||
- Converts to/from session timezone automatically
|
||||
- `now()` returns current time in session timezone, stored as UTC
|
||||
- Supabase databases are set to UTC by default — keep it that way
|
||||
|
||||
```sql
|
||||
-- Check current timezone
|
||||
show timezone;
|
||||
|
||||
-- Change database timezone (not recommended)
|
||||
alter database postgres set timezone to 'America/New_York';
|
||||
|
||||
-- Insert with timezone
|
||||
insert into events (name, starts_at)
|
||||
values ('Launch', '2024-03-15 10:00:00-05'); -- EST
|
||||
|
||||
@@ -67,7 +67,9 @@ begin
|
||||
raise exception 'Unauthorized';
|
||||
end if;
|
||||
|
||||
delete from auth.users where id = target_user_id;
|
||||
-- Use the Supabase Admin API to delete users instead of direct table access
|
||||
-- Direct DML on auth.users is unsupported and may break auth internals
|
||||
delete from public.profiles where id = target_user_id;
|
||||
end;
|
||||
$$;
|
||||
```
|
||||
@@ -87,6 +89,10 @@ as $$
|
||||
where user_id = (select auth.uid());
|
||||
$$;
|
||||
|
||||
-- Revoke default public access, grant only to authenticated
|
||||
revoke execute on function private.user_teams from public;
|
||||
grant execute on function private.user_teams to authenticated;
|
||||
|
||||
-- RLS policy uses cached function result (no per-row join)
|
||||
create policy "Team members see team data" on team_data
|
||||
for select to authenticated
|
||||
@@ -95,10 +101,13 @@ create policy "Team members see team data" on team_data
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Always set search_path = ''** - Prevents search_path injection attacks
|
||||
2. **Validate caller permissions** - Don't assume caller is authorized
|
||||
3. **Keep functions minimal** - Only expose necessary operations
|
||||
4. **Log sensitive operations** - Audit trail for admin actions
|
||||
1. **Always set search_path = ''** - Prevents search_path injection attacks. Qualify all table references (e.g., `public.my_table`)
|
||||
2. **Revoke default execute permissions** - `revoke execute on function my_func from public;` then grant selectively
|
||||
3. **Validate caller permissions** - Don't assume caller is authorized
|
||||
4. **Keep functions minimal** - Only expose necessary operations
|
||||
5. **Log sensitive operations** - Audit trail for admin actions
|
||||
6. **Never directly modify `auth.users`** - Use the Supabase Admin API instead
|
||||
7. **JWT freshness caveat** - `auth.jwt()` values reflect the JWT at issuance time. Changes to `app_metadata` (e.g., removing a role) are not reflected until the JWT is refreshed
|
||||
|
||||
```sql
|
||||
create function private.sensitive_operation()
|
||||
@@ -109,7 +118,7 @@ set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
-- Log the operation
|
||||
insert into audit_log (user_id, action, timestamp)
|
||||
insert into public.audit_log (user_id, action, timestamp)
|
||||
values ((select auth.uid()), 'sensitive_operation', now());
|
||||
|
||||
-- Perform operation
|
||||
|
||||
@@ -71,10 +71,16 @@ The publishable and secret keys are replacing the legacy JWT-based keys. Decode
|
||||
|
||||
## If Service Key is Exposed
|
||||
|
||||
1. Immediately rotate keys in Dashboard > Settings > API Keys
|
||||
2. Review database for unauthorized changes
|
||||
3. Check logs for suspicious activity
|
||||
4. Update all backend services with new key
|
||||
Don't rush. Remediate the root cause first, then:
|
||||
|
||||
1. Stop and ask the user to create a new secret API key in the Supabase Dashboard under Settings > API Keys
|
||||
2. Replace the compromised key across all backend services
|
||||
3. Delete the old key (irreversible)
|
||||
4. Review database for unauthorized changes
|
||||
5. Check logs for suspicious activity
|
||||
|
||||
For legacy JWT `service_role` keys, transition to the new secret key format
|
||||
first, then rotate the JWT secret if it was also compromised.
|
||||
|
||||
## Alternative: Security Definer Functions
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ tags: realtime, broadcast, send, receive, subscribe
|
||||
|
||||
## Send and Receive Broadcast Messages
|
||||
|
||||
Broadcast enables low-latency pub/sub messaging between clients. Prefer Broadcast over Postgres Changes for applications that require more concurrent connections.
|
||||
Broadcast enables low-latency pub/sub messaging between clients. Prefer Broadcast
|
||||
over Postgres Changes for scalability — Postgres Changes triggers per-subscriber
|
||||
RLS checks and processes on a single thread.
|
||||
|
||||
## Subscribe to Broadcast Events
|
||||
|
||||
@@ -42,10 +44,12 @@ channel.send({
|
||||
})
|
||||
```
|
||||
|
||||
**Before subscribing or one-off (HTTP):**
|
||||
**Before subscribing or one-off (HTTP — no subscribe needed):**
|
||||
|
||||
```javascript
|
||||
await channel.httpSend('message_created', { text: 'Hello!' })
|
||||
supabase
|
||||
.channel('room:123')
|
||||
.httpSend('message_created', { text: 'Hello!' })
|
||||
```
|
||||
|
||||
## Receive Own Messages
|
||||
@@ -79,7 +83,7 @@ const channel = supabase.channel('room:123', {
|
||||
},
|
||||
})
|
||||
|
||||
// Returns 'ok' when server confirms receipt
|
||||
// Server confirms receipt when ack is enabled
|
||||
const status = await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'message_created',
|
||||
|
||||
@@ -17,6 +17,7 @@ Broadcasts database changes in a standard format.
|
||||
create or replace function room_messages_broadcast()
|
||||
returns trigger
|
||||
security definer
|
||||
set search_path = ''
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
@@ -29,7 +30,7 @@ begin
|
||||
new, -- new record
|
||||
old -- old record
|
||||
);
|
||||
return coalesce(new, old);
|
||||
return null; -- AFTER trigger return value is ignored
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ function ChatRoom({ roomId }) {
|
||||
|
||||
return () => {
|
||||
if (channelRef.current) {
|
||||
supabase.removeChannel(channelRef.current)
|
||||
channelRef.current.unsubscribe()
|
||||
channelRef.current = null
|
||||
}
|
||||
}
|
||||
@@ -81,10 +81,11 @@ channel.subscribe()
|
||||
|------|-----------------|------------------------|
|
||||
| Free | 200 | 100 |
|
||||
| Pro | 500 | 100 |
|
||||
| Pro (no spend cap) | 10,000 | 100 |
|
||||
| Team | 10,000 | 100 |
|
||||
|
||||
Leaked channels count against quotas even when inactive.
|
||||
For Pay as you go customers you can edit these limits on [Realtime Settings](https://supabase.com/dashboard/project/_/realtime/settings)
|
||||
For Pay as you go customers, stop and ask the user to edit these limits in the Supabase Dashboard under Realtime Settings.
|
||||
## Related
|
||||
|
||||
- [patterns-errors.md](patterns-errors.md)
|
||||
|
||||
@@ -41,14 +41,12 @@ Log message types:
|
||||
|
||||
## Server-Side Log Level
|
||||
|
||||
Configure Realtime server log verbosity via client params:
|
||||
Configure Realtime server log verbosity:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
params: {
|
||||
log_level: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
||||
},
|
||||
logLevel: 'info', // 'info' | 'warn' | 'error'
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -45,9 +45,9 @@ channel.subscribe((status, err) => {
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| `too_many_connections` | Connection limit exceeded | Clean up unused channels, upgrade plan |
|
||||
| `too_many_channels` | Too many channels per connection | Remove unused channels (limit: 100/connection) |
|
||||
| `too_many_joins` | Channel join rate exceeded | Reduce join frequency |
|
||||
| `ConnectionRateLimitReached` | Max connections reached | Upgrade plan |
|
||||
| `DatabaseLackOfConnections` | No available DB connections | Increase compute size |
|
||||
| `tenant_events` | Too many messages/second | Reduce message rate or upgrade plan |
|
||||
| `TenantNotFound` | Invalid project reference | Verify project URL |
|
||||
|
||||
## Automatic Reconnection
|
||||
@@ -72,12 +72,30 @@ Log message types include `push`, `receive`, `transport`, `error`, and `worker`.
|
||||
|
||||
## Silent Disconnections in Background
|
||||
|
||||
WebSocket connections can disconnect when apps are backgrounded (mobile, inactive tabs). Supabase reconnects automatically. Re-track presence after reconnection if needed:
|
||||
WebSocket connections can disconnect when apps are backgrounded (mobile, inactive
|
||||
tabs) due to browser throttling of timers. Two solutions:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
// 1. Use Web Worker to prevent browser throttling of heartbeats
|
||||
worker: true,
|
||||
// 2. Detect disconnections and reconnect
|
||||
heartbeatCallback: (client) => {
|
||||
if (client.connectionState() === 'disconnected') {
|
||||
client.connect()
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Use both together: `worker` prevents throttling, `heartbeatCallback` handles
|
||||
network-level disconnections. Re-track presence after reconnection if needed:
|
||||
|
||||
```javascript
|
||||
channel.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// Re-track presence after reconnection
|
||||
channel.track({ user_id: userId, online_at: new Date().toISOString() })
|
||||
}
|
||||
})
|
||||
@@ -94,4 +112,4 @@ Private channel authorization fails when:
|
||||
|
||||
- [patterns-cleanup.md](patterns-cleanup.md)
|
||||
- [setup-auth.md](setup-auth.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/troubleshooting)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/limits)
|
||||
|
||||
@@ -78,6 +78,9 @@ alter table messages replica identity full;
|
||||
alter publication supabase_realtime add table messages;
|
||||
```
|
||||
|
||||
**Caveat:** RLS policies are not applied to DELETE events. With `replica identity
|
||||
full`, DELETE events still only include primary key columns in the `old` record.
|
||||
|
||||
## Scaling Limitation
|
||||
|
||||
Each change triggers RLS checks for every subscriber:
|
||||
@@ -86,6 +89,10 @@ Each change triggers RLS checks for every subscriber:
|
||||
100 subscribers = 100 database reads per change
|
||||
```
|
||||
|
||||
Database changes are processed on a single thread to maintain order. Compute
|
||||
upgrades do not significantly improve Postgres Changes throughput. If your
|
||||
database cannot authorize changes fast enough, changes are delayed until timeout.
|
||||
|
||||
For high-traffic tables, migrate to [broadcast-database.md](broadcast-database.md).
|
||||
|
||||
## DELETE Events Not Filterable
|
||||
|
||||
@@ -12,9 +12,9 @@ Presence synchronizes shared state between users. Use sparingly due to computati
|
||||
## Track Presence
|
||||
|
||||
```javascript
|
||||
const channel = supabase.channel('room:123', {
|
||||
config: { private: true },
|
||||
})
|
||||
// private: true is a channel setting, not presence-specific
|
||||
// Add it for production use (see setup-auth.md)
|
||||
const channel = supabase.channel('room:123')
|
||||
|
||||
channel
|
||||
.on('presence', { event: 'sync' }, () => {
|
||||
@@ -78,8 +78,9 @@ const channel = supabase.channel('room:123', {
|
||||
|------|-------------------------|
|
||||
| Free | 20 |
|
||||
| Pro | 50 |
|
||||
| Pro (no spend cap) | 1,000 |
|
||||
| Team/Enterprise | 1,000 |
|
||||
For Pay as you go customers you can edit these limits on [Realtime Settings](https://supabase.com/dashboard/project/_/realtime/settings)
|
||||
For Pay as you go customers, stop and ask the user to edit these limits in the Supabase Dashboard under Realtime Settings.
|
||||
## Related
|
||||
|
||||
- [setup-channels.md](setup-channels.md)
|
||||
|
||||
@@ -11,6 +11,10 @@ Always use private channels in production. Public channels allow any client to s
|
||||
|
||||
## Enable Private Channels
|
||||
|
||||
**Prerequisite:** Stop and ask the user to disable "Allow public access" in the
|
||||
Supabase Dashboard under Realtime Settings. Without this, channels are public
|
||||
even with `private: true`.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
@@ -57,11 +61,11 @@ create policy "room_members_can_read"
|
||||
on realtime.messages for select
|
||||
to authenticated
|
||||
using (
|
||||
extension in ('broadcast', 'presence')
|
||||
and exists (
|
||||
exists (
|
||||
select 1 from room_members
|
||||
where user_id = (select auth.uid())
|
||||
and room_id = split_part(realtime.topic(), ':', 2)::uuid
|
||||
and topic = (select realtime.topic())
|
||||
and realtime.messages.extension in ('broadcast', 'presence')
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ Channels are rooms where clients communicate. Use consistent naming and appropri
|
||||
|
||||
## Topic Naming Convention
|
||||
|
||||
Use `scope:entity:id` format for predictable, filterable topics.
|
||||
Use `scope:id:entity` format for predictable, filterable topics.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
@@ -35,7 +35,7 @@ const channel = supabase.channel('game:789:moves')
|
||||
```javascript
|
||||
const channel = supabase.channel('room:123:messages', {
|
||||
config: {
|
||||
private: true, // Require authentication (recommended)
|
||||
private: true, // Require authentication (requires RLS on realtime.messages)
|
||||
broadcast: {
|
||||
self: true, // Receive own messages
|
||||
ack: true, // Get server acknowledgment
|
||||
|
||||
Reference in New Issue
Block a user