@plazmodium/odin 0.3.3-beta → 0.3.5-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -10
- package/builtin/ODIN.md +1067 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +713 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +189 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +205 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts +1 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +6 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/adapters/skills/types.d.ts +1 -0
- package/dist/adapters/skills/types.d.ts.map +1 -1
- package/dist/adapters/workflow-state/in-memory.d.ts +10 -2
- package/dist/adapters/workflow-state/in-memory.d.ts.map +1 -1
- package/dist/adapters/workflow-state/in-memory.js +98 -5
- package/dist/adapters/workflow-state/in-memory.js.map +1 -1
- package/dist/adapters/workflow-state/supabase.d.ts +8 -2
- package/dist/adapters/workflow-state/supabase.d.ts.map +1 -1
- package/dist/adapters/workflow-state/supabase.js +204 -0
- package/dist/adapters/workflow-state/supabase.js.map +1 -1
- package/dist/adapters/workflow-state/types.d.ts +15 -1
- package/dist/adapters/workflow-state/types.d.ts.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/domain/skill-draft-validation.d.ts +18 -0
- package/dist/domain/skill-draft-validation.d.ts.map +1 -0
- package/dist/domain/skill-draft-validation.js +100 -0
- package/dist/domain/skill-draft-validation.js.map +1 -0
- package/dist/domain/skill-proposals.d.ts +11 -0
- package/dist/domain/skill-proposals.d.ts.map +1 -0
- package/dist/domain/skill-proposals.js +103 -0
- package/dist/domain/skill-proposals.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +39 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +30 -1
- package/dist/schemas.js.map +1 -1
- package/dist/server.js +38 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/apply-migrations.d.ts +10 -0
- package/dist/tools/apply-migrations.d.ts.map +1 -1
- package/dist/tools/apply-migrations.js +10 -26
- package/dist/tools/apply-migrations.js.map +1 -1
- package/dist/tools/capture-learning.d.ts.map +1 -1
- package/dist/tools/capture-learning.js +14 -1
- package/dist/tools/capture-learning.js.map +1 -1
- package/dist/tools/get-skill-proposal-queue.d.ts +5 -0
- package/dist/tools/get-skill-proposal-queue.d.ts.map +1 -0
- package/dist/tools/get-skill-proposal-queue.js +21 -0
- package/dist/tools/get-skill-proposal-queue.js.map +1 -0
- package/dist/tools/get-skill-proposals.d.ts +4 -0
- package/dist/tools/get-skill-proposals.d.ts.map +1 -0
- package/dist/tools/get-skill-proposals.js +11 -0
- package/dist/tools/get-skill-proposals.js.map +1 -0
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/tools/publish-skill-proposal.d.ts +5 -0
- package/dist/tools/publish-skill-proposal.d.ts.map +1 -0
- package/dist/tools/publish-skill-proposal.js +57 -0
- package/dist/tools/publish-skill-proposal.js.map +1 -0
- package/dist/tools/record-skill-proposal-decision.d.ts +4 -0
- package/dist/tools/record-skill-proposal-decision.d.ts.map +1 -0
- package/dist/tools/record-skill-proposal-decision.js +22 -0
- package/dist/tools/record-skill-proposal-decision.js.map +1 -0
- package/dist/tools/record-skill-proposal-draft.d.ts +5 -0
- package/dist/tools/record-skill-proposal-draft.d.ts.map +1 -0
- package/dist/tools/record-skill-proposal-draft.js +65 -0
- package/dist/tools/record-skill-proposal-draft.js.map +1 -0
- package/dist/tools/sync-skill-proposal-candidates.d.ts +5 -0
- package/dist/tools/sync-skill-proposal-candidates.d.ts.map +1 -0
- package/dist/tools/sync-skill-proposal-candidates.js +20 -0
- package/dist/tools/sync-skill-proposal-candidates.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/migrations/009_skill_proposal_candidates.sql +124 -0
- package/migrations/010_skill_proposals.sql +36 -0
- package/migrations/README.md +6 -0
- package/package.json +5 -3
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase
|
|
3
|
+
description: Supabase expertise for PostgreSQL database, authentication, storage, real-time subscriptions, and edge functions
|
|
4
|
+
category: database
|
|
5
|
+
version: "2.x"
|
|
6
|
+
depends_on:
|
|
7
|
+
- postgresql
|
|
8
|
+
compatible_with:
|
|
9
|
+
- nextjs-dev
|
|
10
|
+
- nodejs-express
|
|
11
|
+
- prisma-orm
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Supabase Development
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
Supabase is an open-source Firebase alternative providing PostgreSQL database, authentication, instant APIs, real-time subscriptions, storage, and edge functions.
|
|
19
|
+
|
|
20
|
+
## Client Setup
|
|
21
|
+
|
|
22
|
+
### JavaScript/TypeScript
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// src/lib/supabase.ts
|
|
26
|
+
import { createClient } from '@supabase/supabase-js';
|
|
27
|
+
import type { Database } from '@/types/supabase';
|
|
28
|
+
|
|
29
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
30
|
+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
31
|
+
|
|
32
|
+
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey);
|
|
33
|
+
|
|
34
|
+
// For server-side with secret key (admin access, bypasses RLS)
|
|
35
|
+
// Find in: Supabase Dashboard → Settings → API → Secret keys
|
|
36
|
+
export const supabaseAdmin = createClient<Database>(
|
|
37
|
+
supabaseUrl,
|
|
38
|
+
process.env.SUPABASE_SECRET_KEY!,
|
|
39
|
+
{
|
|
40
|
+
auth: { persistSession: false },
|
|
41
|
+
global: {
|
|
42
|
+
// CRITICAL for Next.js 14: Bypass fetch cache to prevent stale data.
|
|
43
|
+
// Next.js patches global fetch with cache: 'force-cache' by default.
|
|
44
|
+
fetch: (input, init) =>
|
|
45
|
+
fetch(input, { ...init, cache: 'no-store' }),
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Generate Types
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > types/supabase.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Database Queries
|
|
58
|
+
|
|
59
|
+
### Basic CRUD
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Select
|
|
63
|
+
const { data, error } = await supabase
|
|
64
|
+
.from('users')
|
|
65
|
+
.select('id, email, name')
|
|
66
|
+
.eq('is_archived', false);
|
|
67
|
+
|
|
68
|
+
// Select with relations
|
|
69
|
+
const { data, error } = await supabase
|
|
70
|
+
.from('posts')
|
|
71
|
+
.select(`
|
|
72
|
+
id,
|
|
73
|
+
title,
|
|
74
|
+
content,
|
|
75
|
+
author:users(id, name, email)
|
|
76
|
+
`)
|
|
77
|
+
.eq('published', true);
|
|
78
|
+
|
|
79
|
+
// Insert
|
|
80
|
+
const { data, error } = await supabase
|
|
81
|
+
.from('users')
|
|
82
|
+
.insert({ email: 'user@example.com', name: 'John' })
|
|
83
|
+
.select()
|
|
84
|
+
.single();
|
|
85
|
+
|
|
86
|
+
// Update
|
|
87
|
+
const { data, error } = await supabase
|
|
88
|
+
.from('users')
|
|
89
|
+
.update({ name: 'Jane' })
|
|
90
|
+
.eq('id', userId)
|
|
91
|
+
.select()
|
|
92
|
+
.single();
|
|
93
|
+
|
|
94
|
+
// Archive (not delete!)
|
|
95
|
+
const { error } = await supabase
|
|
96
|
+
.from('users')
|
|
97
|
+
.update({ is_archived: true, archived_at: new Date().toISOString() })
|
|
98
|
+
.eq('id', userId);
|
|
99
|
+
|
|
100
|
+
// Actual delete (use sparingly)
|
|
101
|
+
const { error } = await supabase
|
|
102
|
+
.from('users')
|
|
103
|
+
.delete()
|
|
104
|
+
.eq('id', userId);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Filtering
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Multiple conditions
|
|
111
|
+
const { data } = await supabase
|
|
112
|
+
.from('posts')
|
|
113
|
+
.select('*')
|
|
114
|
+
.eq('published', true)
|
|
115
|
+
.eq('is_archived', false)
|
|
116
|
+
.gte('created_at', '2024-01-01')
|
|
117
|
+
.order('created_at', { ascending: false })
|
|
118
|
+
.limit(10);
|
|
119
|
+
|
|
120
|
+
// OR conditions
|
|
121
|
+
const { data } = await supabase
|
|
122
|
+
.from('users')
|
|
123
|
+
.select('*')
|
|
124
|
+
.or('role.eq.admin,role.eq.moderator');
|
|
125
|
+
|
|
126
|
+
// Text search
|
|
127
|
+
const { data } = await supabase
|
|
128
|
+
.from('posts')
|
|
129
|
+
.select('*')
|
|
130
|
+
.textSearch('title', 'search query');
|
|
131
|
+
|
|
132
|
+
// In array
|
|
133
|
+
const { data } = await supabase
|
|
134
|
+
.from('users')
|
|
135
|
+
.select('*')
|
|
136
|
+
.in('id', [id1, id2, id3]);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Pagination
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const page = 1;
|
|
143
|
+
const pageSize = 20;
|
|
144
|
+
|
|
145
|
+
const { data, count } = await supabase
|
|
146
|
+
.from('posts')
|
|
147
|
+
.select('*', { count: 'exact' })
|
|
148
|
+
.eq('is_archived', false)
|
|
149
|
+
.range((page - 1) * pageSize, page * pageSize - 1)
|
|
150
|
+
.order('created_at', { ascending: false });
|
|
151
|
+
|
|
152
|
+
const totalPages = Math.ceil((count || 0) / pageSize);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Authentication
|
|
156
|
+
|
|
157
|
+
### Sign Up / Sign In
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Sign up with email
|
|
161
|
+
const { data, error } = await supabase.auth.signUp({
|
|
162
|
+
email: 'user@example.com',
|
|
163
|
+
password: 'securepassword',
|
|
164
|
+
options: {
|
|
165
|
+
data: { name: 'John Doe' }, // Custom user metadata
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Sign in with email
|
|
170
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
171
|
+
email: 'user@example.com',
|
|
172
|
+
password: 'securepassword',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Sign in with OAuth
|
|
176
|
+
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
177
|
+
provider: 'google',
|
|
178
|
+
options: { redirectTo: `${origin}/auth/callback` },
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Sign out
|
|
182
|
+
await supabase.auth.signOut();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Session Management
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Get current session
|
|
189
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
190
|
+
|
|
191
|
+
// Get current user
|
|
192
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
193
|
+
|
|
194
|
+
// Listen to auth changes
|
|
195
|
+
supabase.auth.onAuthStateChange((event, session) => {
|
|
196
|
+
if (event === 'SIGNED_IN') {
|
|
197
|
+
// Handle sign in
|
|
198
|
+
} else if (event === 'SIGNED_OUT') {
|
|
199
|
+
// Handle sign out
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Password Reset
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Send reset email
|
|
208
|
+
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
|
209
|
+
redirectTo: `${origin}/auth/reset-password`,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Update password (after redirect)
|
|
213
|
+
const { error } = await supabase.auth.updateUser({
|
|
214
|
+
password: newPassword,
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Row Level Security (RLS)
|
|
219
|
+
|
|
220
|
+
### Enable RLS
|
|
221
|
+
|
|
222
|
+
```sql
|
|
223
|
+
-- Always enable RLS on tables with user data
|
|
224
|
+
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
|
|
225
|
+
|
|
226
|
+
-- Policy: Users can read published posts
|
|
227
|
+
CREATE POLICY "Public posts are viewable by everyone"
|
|
228
|
+
ON posts FOR SELECT
|
|
229
|
+
USING (published = true AND is_archived = false);
|
|
230
|
+
|
|
231
|
+
-- Policy: Users can only edit their own posts
|
|
232
|
+
CREATE POLICY "Users can edit own posts"
|
|
233
|
+
ON posts FOR UPDATE
|
|
234
|
+
USING (auth.uid() = author_id);
|
|
235
|
+
|
|
236
|
+
-- Policy: Users can only insert as themselves
|
|
237
|
+
CREATE POLICY "Users can create own posts"
|
|
238
|
+
ON posts FOR INSERT
|
|
239
|
+
WITH CHECK (auth.uid() = author_id);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Common RLS Patterns
|
|
243
|
+
|
|
244
|
+
```sql
|
|
245
|
+
-- Authenticated users only
|
|
246
|
+
CREATE POLICY "Authenticated access"
|
|
247
|
+
ON table_name FOR ALL
|
|
248
|
+
USING (auth.role() = 'authenticated');
|
|
249
|
+
|
|
250
|
+
-- Owner access
|
|
251
|
+
CREATE POLICY "Owner access"
|
|
252
|
+
ON table_name FOR ALL
|
|
253
|
+
USING (auth.uid() = user_id);
|
|
254
|
+
|
|
255
|
+
-- Role-based access
|
|
256
|
+
CREATE POLICY "Admin access"
|
|
257
|
+
ON table_name FOR ALL
|
|
258
|
+
USING (
|
|
259
|
+
EXISTS (
|
|
260
|
+
SELECT 1 FROM users
|
|
261
|
+
WHERE users.id = auth.uid()
|
|
262
|
+
AND users.role = 'admin'
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Real-time Subscriptions
|
|
268
|
+
|
|
269
|
+
### Prerequisites (CRITICAL)
|
|
270
|
+
|
|
271
|
+
Supabase Realtime requires **three layers** of configuration:
|
|
272
|
+
|
|
273
|
+
1. **Database layer** — Add tables to the `supabase_realtime` publication:
|
|
274
|
+
```sql
|
|
275
|
+
ALTER PUBLICATION supabase_realtime ADD TABLE your_table;
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
2. **RLS layer** — The subscribing role (usually `anon`) needs SELECT policies:
|
|
279
|
+
```sql
|
|
280
|
+
CREATE POLICY "Anon read access" ON your_table
|
|
281
|
+
FOR SELECT TO anon USING (true);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
3. **Infrastructure layer** — The Realtime service must recognize your project as a "tenant". This is configured through the **Supabase Dashboard → Database → Replication**, NOT via SQL.
|
|
285
|
+
|
|
286
|
+
> **GOTCHA: TenantNotFound Error**
|
|
287
|
+
>
|
|
288
|
+
> If you only configure layers 1 and 2 via SQL but skip layer 3 (Dashboard), WebSocket connections will fail with `TenantNotFound: Tenant not found: <project-ref>`. The database side looks correct (publication exists, RLS policies in place, `wal_level = logical`), but the Realtime Elixir service doesn't have your project registered.
|
|
289
|
+
>
|
|
290
|
+
> **Diagnosis**: Check Supabase Realtime logs for `error_code: "TenantNotFound"`. You may also see Cloudflare Error 1101 ("Worker threw exception") when hitting the `/realtime/v1/` endpoint.
|
|
291
|
+
>
|
|
292
|
+
> **As of 2026-02**: Database → Replication is in **alpha** and requires paid onboarding on some plans. If you can't enable it, use **polling** instead (see Next.js skill for the pattern).
|
|
293
|
+
|
|
294
|
+
### Subscription Code (when Realtime IS available)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Subscribe to changes
|
|
298
|
+
const channel = supabase
|
|
299
|
+
.channel('posts-changes')
|
|
300
|
+
.on(
|
|
301
|
+
'postgres_changes',
|
|
302
|
+
{
|
|
303
|
+
event: '*', // INSERT, UPDATE, DELETE, or *
|
|
304
|
+
schema: 'public',
|
|
305
|
+
table: 'posts',
|
|
306
|
+
filter: 'author_id=eq.' + userId,
|
|
307
|
+
},
|
|
308
|
+
(payload) => {
|
|
309
|
+
console.log('Change:', payload);
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
.subscribe();
|
|
313
|
+
|
|
314
|
+
// Cleanup
|
|
315
|
+
channel.unsubscribe();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Verification Checklist
|
|
319
|
+
|
|
320
|
+
Before debugging Realtime issues, verify ALL three layers:
|
|
321
|
+
|
|
322
|
+
```sql
|
|
323
|
+
-- 1. Check publication has your tables
|
|
324
|
+
SELECT tablename FROM pg_publication_tables WHERE pubname = 'supabase_realtime';
|
|
325
|
+
|
|
326
|
+
-- 2. Check anon SELECT policies exist
|
|
327
|
+
SELECT tablename, policyname FROM pg_policies
|
|
328
|
+
WHERE schemaname = 'public' AND roles::text LIKE '%anon%' AND cmd = 'SELECT';
|
|
329
|
+
|
|
330
|
+
-- 3. Check wal_level (must be 'logical')
|
|
331
|
+
SHOW wal_level;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Then check the **Supabase Dashboard → Database → Replication** to confirm the Realtime service is enabled for your project. If all three SQL checks pass but WebSocket still fails, the issue is layer 3.
|
|
335
|
+
|
|
336
|
+
## Storage
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// Upload file
|
|
340
|
+
const { data, error } = await supabase.storage
|
|
341
|
+
.from('avatars')
|
|
342
|
+
.upload(`${userId}/avatar.png`, file, {
|
|
343
|
+
cacheControl: '3600',
|
|
344
|
+
upsert: true,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Get public URL
|
|
348
|
+
const { data } = supabase.storage
|
|
349
|
+
.from('avatars')
|
|
350
|
+
.getPublicUrl(`${userId}/avatar.png`);
|
|
351
|
+
|
|
352
|
+
// Download file
|
|
353
|
+
const { data, error } = await supabase.storage
|
|
354
|
+
.from('documents')
|
|
355
|
+
.download('path/to/file.pdf');
|
|
356
|
+
|
|
357
|
+
// Delete file
|
|
358
|
+
const { error } = await supabase.storage
|
|
359
|
+
.from('avatars')
|
|
360
|
+
.remove([`${userId}/avatar.png`]);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Edge Functions
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Invoke edge function
|
|
367
|
+
const { data, error } = await supabase.functions.invoke('function-name', {
|
|
368
|
+
body: { key: 'value' },
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Best Practices
|
|
373
|
+
|
|
374
|
+
1. **Always enable RLS** - Never expose tables without row-level security
|
|
375
|
+
2. **Use secret key server-side only** - Never expose the Supabase secret key to client
|
|
376
|
+
3. **Generate types** - Use `supabase gen types` for type safety
|
|
377
|
+
4. **Archive, don't delete** - Use `is_archived` pattern for recoverable data
|
|
378
|
+
5. **Filter archived records** - Always add `.eq('is_archived', false)` to queries
|
|
379
|
+
6. **Handle errors** - Always check for `error` in responses
|
|
380
|
+
7. **Use transactions** - Use `rpc` for complex multi-table operations
|
|
381
|
+
|
|
382
|
+
## Gotchas & Pitfalls
|
|
383
|
+
|
|
384
|
+
- **RLS blocks service role by default** - Use `SECURITY DEFINER` functions or check policies
|
|
385
|
+
- **Real-time has 3 config layers** - SQL publication + RLS policies + Dashboard Replication toggle. Missing any one causes silent failures
|
|
386
|
+
- **Real-time TenantNotFound** - If WebSocket fails with this error, the Dashboard Replication feature isn't enabled. SQL-only setup is NOT sufficient
|
|
387
|
+
- **Real-time requires paid onboarding** - As of 2026-02, Database → Replication is in alpha on some plans. Use polling as fallback
|
|
388
|
+
- **Key naming changed** - "Service Role Key" is now "Secret key", "Anon Key" is now "Publishable key" in the Dashboard
|
|
389
|
+
- **Next.js 14 caches Supabase responses** - The Supabase JS client uses `fetch` internally, and Next.js 14 patches `fetch` with `cache: 'force-cache'` by default. This causes stale data even with `force-dynamic` on pages. **Fix**: Pass `global: { fetch: (input, init) => fetch(input, { ...init, cache: 'no-store' }) }` in the client constructor. See frontend/nextjs-dev skill for full details.
|
|
390
|
+
- **Storage policies separate** - Storage has its own policy system
|
|
391
|
+
- **Type generation** - Re-run after schema changes
|
|
392
|
+
- **Connection limits** - Use connection pooling for serverless
|
|
393
|
+
- **Null handling** - PostgreSQL nulls require explicit handling
|
|
394
|
+
|
|
395
|
+
## Integration with Next.js
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// app/api/posts/route.ts
|
|
399
|
+
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
|
|
400
|
+
import { cookies } from 'next/headers';
|
|
401
|
+
|
|
402
|
+
export async function GET() {
|
|
403
|
+
const supabase = createRouteHandlerClient({ cookies });
|
|
404
|
+
|
|
405
|
+
const { data, error } = await supabase
|
|
406
|
+
.from('posts')
|
|
407
|
+
.select('*')
|
|
408
|
+
.eq('is_archived', false);
|
|
409
|
+
|
|
410
|
+
if (error) {
|
|
411
|
+
return Response.json({ error: error.message }, { status: 500 });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return Response.json(data);
|
|
415
|
+
}
|
|
416
|
+
```
|