@luanpdd/kit-mcp 1.35.0 → 1.36.0
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/bin/cli.js +2 -2
- package/bin/mcp.js +6 -6
- package/bin/ui.js +74 -74
- package/gates/ai-prompt-stability.md +120 -120
- package/gates/budget-description.md +68 -68
- package/gates/confidence.md +29 -29
- package/gates/dependency-check.md +33 -33
- package/gates/dept-cycle-prevention.md +179 -179
- package/gates/golden-signals-coverage.md +133 -133
- package/gates/legacy-refactor-safety.md +178 -178
- package/gates/multi-tenant-rls-coverage.md +102 -102
- package/gates/no-personal-uuid.md +72 -72
- package/gates/obs-agents-mcp-supabase.md +86 -86
- package/gates/obs-skills-frontmatter.md +76 -76
- package/gates/observability-coverage.md +151 -151
- package/gates/omm-no-regression.md +83 -83
- package/gates/postmortem-template-required.md +127 -127
- package/gates/prr-checklist-coverage.md +128 -128
- package/gates/regression.md +32 -32
- package/gates/release-pipeline-policy.md +132 -132
- package/gates/secrets-scan.md +33 -33
- package/gates/service-role-not-in-user-facing.md +113 -113
- package/gates/skill-must-include.md +71 -71
- package/gates/sync-idempotent.md +62 -62
- package/gates/verify-phase-goal.md +34 -34
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/workflow-generator.md +537 -167
- package/kit/commands/adicionar-backlog.md +1 -1
- package/kit/commands/adicionar-fase.md +1 -1
- package/kit/commands/adicionar-tarefa.md +1 -1
- package/kit/commands/auditar-observabilidade.md +103 -103
- package/kit/commands/auditar-toil.md +129 -129
- package/kit/commands/caracterizar-prompt.md +195 -195
- package/kit/commands/criar-workflow.md +158 -158
- package/kit/commands/definir-perfil.md +1 -1
- package/kit/commands/definir-slo.md +108 -108
- package/kit/commands/fio.md +1 -1
- package/kit/commands/golden-signals.md +142 -142
- package/kit/commands/instrumentar-fase.md +200 -200
- package/kit/commands/investigar-producao.md +162 -162
- package/kit/commands/observabilidade.md +118 -118
- package/kit/commands/postmortem.md +179 -179
- package/kit/commands/prr.md +205 -205
- package/kit/commands/publicar-rapido.md +207 -207
- package/kit/commands/risk-budget.md +220 -220
- package/kit/commands/sre.md +230 -230
- package/kit/file-manifest.json +424 -424
- package/kit/framework/references/output-style.md +22 -22
- package/kit/hooks/post-apply-migration.js +199 -199
- package/kit/hooks/sidecar-tool-publisher.js +210 -210
- package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
- package/kit/skills/_shared-legacy/glossary.md +389 -389
- package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
- package/kit/skills/_shared-observability/glossary.md +396 -396
- package/kit/skills/_shared-sre/glossary.md +712 -712
- package/kit/skills/_shared-supabase/glossary.md +234 -234
- package/kit/skills/blameless-postmortems/SKILL.md +340 -340
- package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
- package/kit/skills/cascading-failures/SKILL.md +311 -311
- package/kit/skills/core-analysis-loop/SKILL.md +352 -352
- package/kit/skills/distributed-tracing/SKILL.md +362 -362
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
- package/kit/skills/eliminating-toil/SKILL.md +243 -243
- package/kit/skills/event-based-slos/SKILL.md +296 -296
- package/kit/skills/four-golden-signals/SKILL.md +314 -314
- package/kit/skills/hermetic-builds/SKILL.md +323 -323
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
- package/kit/skills/llm-as-dependency/SKILL.md +436 -436
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
- package/kit/skills/observability-driven-development/SKILL.md +315 -315
- package/kit/skills/observability-maturity-model/SKILL.md +222 -222
- package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
- package/kit/skills/production-readiness-review/SKILL.md +305 -305
- package/kit/skills/release-engineering/SKILL.md +367 -367
- package/kit/skills/retry-strategies/SKILL.md +372 -372
- package/kit/skills/sre-risk-management/SKILL.md +221 -221
- package/kit/skills/structured-events/SKILL.md +265 -265
- package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
- package/kit/skills/supabase-database-functions/SKILL.md +332 -332
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
- package/kit/skills/supabase-storage/SKILL.md +234 -234
- package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
- package/kit/skills/telemetry-sampling/SKILL.md +256 -256
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/package.json +1 -1
- package/src/cli/index.js +1114 -1114
- package/src/cli/render.js +194 -194
- package/src/cli/upgrade-check.js +135 -135
- package/src/core/error-redaction.js +76 -76
- package/src/core/failures.js +153 -153
- package/src/core/gate-runner.js +205 -205
- package/src/core/gates.js +82 -82
- package/src/core/logger.js +170 -170
- package/src/core/manifest-verify.js +174 -174
- package/src/core/metrics.js +268 -268
- package/src/core/notify.js +60 -60
- package/src/core/path-safety.js +141 -141
- package/src/core/replays.js +120 -120
- package/src/core/ui.js +185 -185
- package/src/mcp-server/install.js +149 -149
- package/src/mcp-server/roots.js +124 -124
- package/src/ui/auto-spawn.js +113 -113
- package/src/ui/browser.js +78 -78
- package/src/ui/client.js +130 -130
- package/src/ui/events.js +65 -65
- package/src/ui/lockfile.js +191 -191
- package/src/ui/port.js +67 -67
- package/src/ui/server.js +547 -547
- package/src/ui/wrapper.js +129 -129
|
@@ -1,234 +1,234 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supabase-storage
|
|
3
|
-
description: Use ao integrar Storage — buckets públicos vs privados, signed URLs com expiration, RLS sobre storage.objects com multi-tenant path, image transforms, TUS uploads.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Supabase — Storage
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill quando trabalhar com upload, download, ou serve de arquivos via Supabase Storage. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "Supabase Storage", "upload de arquivo"
|
|
13
|
-
- "signed URL", "createSignedUrl"
|
|
14
|
-
- "bucket público vs privado"
|
|
15
|
-
- "RLS storage.objects"
|
|
16
|
-
- "multi-tenant arquivos"
|
|
17
|
-
- "image transforms Supabase"
|
|
18
|
-
- "TUS resumable upload"
|
|
19
|
-
|
|
20
|
-
## Regras absolutas
|
|
21
|
-
|
|
22
|
-
- **Bucket privado é default em produção** — apenas dados públicos (avatares públicos, marketing) vão em buckets públicos.
|
|
23
|
-
- **Bucket público:** URL direta `getPublicUrl()` + servida via CDN (cache).
|
|
24
|
-
- **Bucket privado:** apenas `signed URL` (`createSignedUrl()`) com `expiresIn` curto (60s downloads, 3600s imagens).
|
|
25
|
-
- **`storage.objects`** — RLS sempre habilitada. Sem RLS, qualquer authenticated lê qualquer bucket privado.
|
|
26
|
-
- **`multi-tenant path`** isolation — usar `auth.uid()` (ou `org_id`) como path prefix: `<user_id>/<filename>`. Validar em RLS via `(storage.foldername(name))[1] = (select auth.uid())::text`.
|
|
27
|
-
- **Image transformations** apenas em buckets com transformation enabled (Pro plan+). Query params `?width=800&height=600&resize=contain`.
|
|
28
|
-
- **Uploads grandes (> 6 MB):** use TUS resumable protocol (`uploadToSignedUrl` + chunked upload).
|
|
29
|
-
- **Awareness de egress billing** — bucket público sem cache headers customizados pode disparar custo significativo. Use Smart CDN + TTL adequado.
|
|
30
|
-
- **Não overwrite arquivos públicos** com mesmo nome — CDN cache fica stale. Use versionamento (`avatar-v2.jpg`) ou random suffix.
|
|
31
|
-
|
|
32
|
-
## Patterns canônicos
|
|
33
|
-
|
|
34
|
-
### RLS multi-tenant em `storage.objects`
|
|
35
|
-
|
|
36
|
-
```sql
|
|
37
|
-
-- PT-BR: usuário só vê arquivos sob seu próprio prefix de path
|
|
38
|
-
-- path canônico: <user_id>/<filename> (ex: 550e8400-e29b-41d4-a716-446655440000/avatar.jpg)
|
|
39
|
-
|
|
40
|
-
create policy "users_read_own_files"
|
|
41
|
-
on storage.objects for select to authenticated
|
|
42
|
-
using (
|
|
43
|
-
bucket_id = 'private-uploads'
|
|
44
|
-
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
create policy "users_insert_own_files"
|
|
48
|
-
on storage.objects for insert to authenticated
|
|
49
|
-
with check (
|
|
50
|
-
bucket_id = 'private-uploads'
|
|
51
|
-
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
create policy "users_update_own_files"
|
|
55
|
-
on storage.objects for update to authenticated
|
|
56
|
-
using (
|
|
57
|
-
bucket_id = 'private-uploads'
|
|
58
|
-
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
create policy "users_delete_own_files"
|
|
62
|
-
on storage.objects for delete to authenticated
|
|
63
|
-
using (
|
|
64
|
-
bucket_id = 'private-uploads'
|
|
65
|
-
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
66
|
-
);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Upload com path multi-tenant
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
// PT-BR: cliente — path sempre prefixado com user.id
|
|
73
|
-
import { createClient } from '@/utils/supabase/client'
|
|
74
|
-
|
|
75
|
-
async function uploadAvatar(file: File) {
|
|
76
|
-
const supabase = createClient()
|
|
77
|
-
const { data: { user } } = await supabase.auth.getUser()
|
|
78
|
-
if (!user) throw new Error('not authenticated')
|
|
79
|
-
|
|
80
|
-
// PT-BR: path = <user_id>/<filename>
|
|
81
|
-
const path = `${user.id}/avatar.jpg`
|
|
82
|
-
|
|
83
|
-
const { data, error } = await supabase.storage
|
|
84
|
-
.from('private-uploads')
|
|
85
|
-
.upload(path, file, {
|
|
86
|
-
cacheControl: '3600',
|
|
87
|
-
upsert: true, // PT-BR: sobrescreve se mesmo path
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
if (error) throw error
|
|
91
|
-
return data
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Signed URL — download privado
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
// PT-BR: signed URL com expiração 1h
|
|
99
|
-
const { data, error } = await supabase.storage
|
|
100
|
-
.from('private-uploads')
|
|
101
|
-
.createSignedUrl(`${userId}/avatar.jpg`, 3600)
|
|
102
|
-
|
|
103
|
-
// data.signedUrl pode ser usado em <img src={data.signedUrl}> por 1h
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Image transformations (em bucket com transform habilitado)
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
// PT-BR: signed URL com transformação inline
|
|
110
|
-
const { data } = await supabase.storage
|
|
111
|
-
.from('private-uploads')
|
|
112
|
-
.createSignedUrl(`${userId}/avatar.jpg`, 3600, {
|
|
113
|
-
transform: { width: 200, height: 200, resize: 'cover' },
|
|
114
|
-
})
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Public bucket — getPublicUrl + cache headers
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
// PT-BR: para bucket PÚBLICO apenas (não funciona em privado)
|
|
121
|
-
const { data } = supabase.storage
|
|
122
|
-
.from('public-avatars')
|
|
123
|
-
.getPublicUrl('hero.jpg')
|
|
124
|
-
|
|
125
|
-
// PT-BR: ao upload, set cacheControl alto para reduzir egress
|
|
126
|
-
await supabase.storage
|
|
127
|
-
.from('public-avatars')
|
|
128
|
-
.upload('hero.jpg', file, {
|
|
129
|
-
cacheControl: '31536000', // 1 ano — assets imutáveis
|
|
130
|
-
upsert: false, // não sobrescrever (versionar via path)
|
|
131
|
-
})
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### TUS resumable upload (arquivos grandes)
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
// PT-BR: signed upload URL + TUS chunked upload (>6MB)
|
|
138
|
-
import * as tus from 'npm:tus-js-client'
|
|
139
|
-
|
|
140
|
-
async function uploadLarge(file: File, path: string) {
|
|
141
|
-
const supabase = createClient()
|
|
142
|
-
const { data, error } = await supabase.storage
|
|
143
|
-
.from('private-uploads')
|
|
144
|
-
.createSignedUploadUrl(path)
|
|
145
|
-
if (error) throw error
|
|
146
|
-
|
|
147
|
-
return new Promise((resolve, reject) => {
|
|
148
|
-
const upload = new tus.Upload(file, {
|
|
149
|
-
endpoint: data.signedUrl,
|
|
150
|
-
headers: { authorization: `Bearer ${data.token}` },
|
|
151
|
-
chunkSize: 6 * 1024 * 1024, // 6 MB chunks
|
|
152
|
-
onError: reject,
|
|
153
|
-
onSuccess: () => resolve(upload.url),
|
|
154
|
-
})
|
|
155
|
-
upload.start()
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Notas de futuro (alpha — não detalhar em produção)
|
|
161
|
-
|
|
162
|
-
- **Vector Buckets** e **Analytics Buckets** existem em alpha (2026). Mencione apenas como existência se relevante; pattern canônico ainda mudando — não detalhar.
|
|
163
|
-
|
|
164
|
-
## Anti-patterns
|
|
165
|
-
|
|
166
|
-
### Anti-pattern 1: Path sem prefix de tenant
|
|
167
|
-
|
|
168
|
-
**Errado:**
|
|
169
|
-
```ts
|
|
170
|
-
await supabase.storage.from('private-uploads').upload('avatar.jpg', file)
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**Por quê:** path global — qualquer user sobrescreve `avatar.jpg`. RLS multi-tenant não consegue isolar.
|
|
174
|
-
|
|
175
|
-
**Certo:**
|
|
176
|
-
```ts
|
|
177
|
-
const path = `${user.id}/avatar.jpg`
|
|
178
|
-
await supabase.storage.from('private-uploads').upload(path, file)
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Anti-pattern 2: Bucket privado sem RLS em `storage.objects`
|
|
182
|
-
|
|
183
|
-
**Errado:**
|
|
184
|
-
```sql
|
|
185
|
-
create bucket 'private-uploads';
|
|
186
|
-
-- (esqueceu policies em storage.objects)
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Por quê:** sem RLS em `storage.objects`, qualquer `authenticated` lê arquivos do bucket — multi-tenancy quebrado.
|
|
190
|
-
|
|
191
|
-
**Certo:** ver pattern "RLS multi-tenant" acima — 4 policies separadas (SELECT/INSERT/UPDATE/DELETE).
|
|
192
|
-
|
|
193
|
-
### Anti-pattern 3: `getPublicUrl` em bucket privado
|
|
194
|
-
|
|
195
|
-
**Errado:**
|
|
196
|
-
```ts
|
|
197
|
-
// PT-BR: bucket-id é privado
|
|
198
|
-
const { data } = supabase.storage.from('private-uploads').getPublicUrl('x.jpg')
|
|
199
|
-
// data.publicUrl retorna mas o URL não funciona (403)
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Por quê:** `getPublicUrl` só funciona em buckets marcados public. Em privado, retorna URL que sempre dá 403.
|
|
203
|
-
|
|
204
|
-
**Certo:** use `createSignedUrl` com expiration:
|
|
205
|
-
```ts
|
|
206
|
-
const { data } = await supabase.storage
|
|
207
|
-
.from('private-uploads')
|
|
208
|
-
.createSignedUrl('x.jpg', 3600)
|
|
209
|
-
// data.signedUrl funciona por 1h
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Anti-pattern 4: Overwrite de arquivo público com mesmo path
|
|
213
|
-
|
|
214
|
-
**Errado:**
|
|
215
|
-
```ts
|
|
216
|
-
// PT-BR: hero.jpg público
|
|
217
|
-
await supabase.storage.from('public').upload('hero.jpg', newFile, { upsert: true })
|
|
218
|
-
// CDN cache antigo continua servindo old hero.jpg por horas/dias
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Por quê:** CDN cache pelo path. Overwrite não invalida cache; usuários veem versão antiga.
|
|
222
|
-
|
|
223
|
-
**Certo:** versionar ou random suffix:
|
|
224
|
-
```ts
|
|
225
|
-
await supabase.storage.from('public').upload(`hero-${version}.jpg`, newFile)
|
|
226
|
-
// ou: hero-<sha>.jpg, hero-<timestamp>.jpg
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## Ver também
|
|
230
|
-
|
|
231
|
-
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS sobre `storage.objects` + multi-tenant pattern
|
|
232
|
-
- [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — usuário autenticado obtém `auth.uid()` para path prefix
|
|
233
|
-
- [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions podem mediar uploads complexos
|
|
234
|
-
- [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN
|
|
1
|
+
---
|
|
2
|
+
name: supabase-storage
|
|
3
|
+
description: Use ao integrar Storage — buckets públicos vs privados, signed URLs com expiration, RLS sobre storage.objects com multi-tenant path, image transforms, TUS uploads.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — Storage
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando trabalhar com upload, download, ou serve de arquivos via Supabase Storage. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "Supabase Storage", "upload de arquivo"
|
|
13
|
+
- "signed URL", "createSignedUrl"
|
|
14
|
+
- "bucket público vs privado"
|
|
15
|
+
- "RLS storage.objects"
|
|
16
|
+
- "multi-tenant arquivos"
|
|
17
|
+
- "image transforms Supabase"
|
|
18
|
+
- "TUS resumable upload"
|
|
19
|
+
|
|
20
|
+
## Regras absolutas
|
|
21
|
+
|
|
22
|
+
- **Bucket privado é default em produção** — apenas dados públicos (avatares públicos, marketing) vão em buckets públicos.
|
|
23
|
+
- **Bucket público:** URL direta `getPublicUrl()` + servida via CDN (cache).
|
|
24
|
+
- **Bucket privado:** apenas `signed URL` (`createSignedUrl()`) com `expiresIn` curto (60s downloads, 3600s imagens).
|
|
25
|
+
- **`storage.objects`** — RLS sempre habilitada. Sem RLS, qualquer authenticated lê qualquer bucket privado.
|
|
26
|
+
- **`multi-tenant path`** isolation — usar `auth.uid()` (ou `org_id`) como path prefix: `<user_id>/<filename>`. Validar em RLS via `(storage.foldername(name))[1] = (select auth.uid())::text`.
|
|
27
|
+
- **Image transformations** apenas em buckets com transformation enabled (Pro plan+). Query params `?width=800&height=600&resize=contain`.
|
|
28
|
+
- **Uploads grandes (> 6 MB):** use TUS resumable protocol (`uploadToSignedUrl` + chunked upload).
|
|
29
|
+
- **Awareness de egress billing** — bucket público sem cache headers customizados pode disparar custo significativo. Use Smart CDN + TTL adequado.
|
|
30
|
+
- **Não overwrite arquivos públicos** com mesmo nome — CDN cache fica stale. Use versionamento (`avatar-v2.jpg`) ou random suffix.
|
|
31
|
+
|
|
32
|
+
## Patterns canônicos
|
|
33
|
+
|
|
34
|
+
### RLS multi-tenant em `storage.objects`
|
|
35
|
+
|
|
36
|
+
```sql
|
|
37
|
+
-- PT-BR: usuário só vê arquivos sob seu próprio prefix de path
|
|
38
|
+
-- path canônico: <user_id>/<filename> (ex: 550e8400-e29b-41d4-a716-446655440000/avatar.jpg)
|
|
39
|
+
|
|
40
|
+
create policy "users_read_own_files"
|
|
41
|
+
on storage.objects for select to authenticated
|
|
42
|
+
using (
|
|
43
|
+
bucket_id = 'private-uploads'
|
|
44
|
+
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
create policy "users_insert_own_files"
|
|
48
|
+
on storage.objects for insert to authenticated
|
|
49
|
+
with check (
|
|
50
|
+
bucket_id = 'private-uploads'
|
|
51
|
+
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
create policy "users_update_own_files"
|
|
55
|
+
on storage.objects for update to authenticated
|
|
56
|
+
using (
|
|
57
|
+
bucket_id = 'private-uploads'
|
|
58
|
+
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
create policy "users_delete_own_files"
|
|
62
|
+
on storage.objects for delete to authenticated
|
|
63
|
+
using (
|
|
64
|
+
bucket_id = 'private-uploads'
|
|
65
|
+
and (storage.foldername(name))[1] = (select auth.uid())::text
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Upload com path multi-tenant
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// PT-BR: cliente — path sempre prefixado com user.id
|
|
73
|
+
import { createClient } from '@/utils/supabase/client'
|
|
74
|
+
|
|
75
|
+
async function uploadAvatar(file: File) {
|
|
76
|
+
const supabase = createClient()
|
|
77
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
78
|
+
if (!user) throw new Error('not authenticated')
|
|
79
|
+
|
|
80
|
+
// PT-BR: path = <user_id>/<filename>
|
|
81
|
+
const path = `${user.id}/avatar.jpg`
|
|
82
|
+
|
|
83
|
+
const { data, error } = await supabase.storage
|
|
84
|
+
.from('private-uploads')
|
|
85
|
+
.upload(path, file, {
|
|
86
|
+
cacheControl: '3600',
|
|
87
|
+
upsert: true, // PT-BR: sobrescreve se mesmo path
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (error) throw error
|
|
91
|
+
return data
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Signed URL — download privado
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// PT-BR: signed URL com expiração 1h
|
|
99
|
+
const { data, error } = await supabase.storage
|
|
100
|
+
.from('private-uploads')
|
|
101
|
+
.createSignedUrl(`${userId}/avatar.jpg`, 3600)
|
|
102
|
+
|
|
103
|
+
// data.signedUrl pode ser usado em <img src={data.signedUrl}> por 1h
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Image transformations (em bucket com transform habilitado)
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// PT-BR: signed URL com transformação inline
|
|
110
|
+
const { data } = await supabase.storage
|
|
111
|
+
.from('private-uploads')
|
|
112
|
+
.createSignedUrl(`${userId}/avatar.jpg`, 3600, {
|
|
113
|
+
transform: { width: 200, height: 200, resize: 'cover' },
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Public bucket — getPublicUrl + cache headers
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// PT-BR: para bucket PÚBLICO apenas (não funciona em privado)
|
|
121
|
+
const { data } = supabase.storage
|
|
122
|
+
.from('public-avatars')
|
|
123
|
+
.getPublicUrl('hero.jpg')
|
|
124
|
+
|
|
125
|
+
// PT-BR: ao upload, set cacheControl alto para reduzir egress
|
|
126
|
+
await supabase.storage
|
|
127
|
+
.from('public-avatars')
|
|
128
|
+
.upload('hero.jpg', file, {
|
|
129
|
+
cacheControl: '31536000', // 1 ano — assets imutáveis
|
|
130
|
+
upsert: false, // não sobrescrever (versionar via path)
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### TUS resumable upload (arquivos grandes)
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// PT-BR: signed upload URL + TUS chunked upload (>6MB)
|
|
138
|
+
import * as tus from 'npm:tus-js-client'
|
|
139
|
+
|
|
140
|
+
async function uploadLarge(file: File, path: string) {
|
|
141
|
+
const supabase = createClient()
|
|
142
|
+
const { data, error } = await supabase.storage
|
|
143
|
+
.from('private-uploads')
|
|
144
|
+
.createSignedUploadUrl(path)
|
|
145
|
+
if (error) throw error
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const upload = new tus.Upload(file, {
|
|
149
|
+
endpoint: data.signedUrl,
|
|
150
|
+
headers: { authorization: `Bearer ${data.token}` },
|
|
151
|
+
chunkSize: 6 * 1024 * 1024, // 6 MB chunks
|
|
152
|
+
onError: reject,
|
|
153
|
+
onSuccess: () => resolve(upload.url),
|
|
154
|
+
})
|
|
155
|
+
upload.start()
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Notas de futuro (alpha — não detalhar em produção)
|
|
161
|
+
|
|
162
|
+
- **Vector Buckets** e **Analytics Buckets** existem em alpha (2026). Mencione apenas como existência se relevante; pattern canônico ainda mudando — não detalhar.
|
|
163
|
+
|
|
164
|
+
## Anti-patterns
|
|
165
|
+
|
|
166
|
+
### Anti-pattern 1: Path sem prefix de tenant
|
|
167
|
+
|
|
168
|
+
**Errado:**
|
|
169
|
+
```ts
|
|
170
|
+
await supabase.storage.from('private-uploads').upload('avatar.jpg', file)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Por quê:** path global — qualquer user sobrescreve `avatar.jpg`. RLS multi-tenant não consegue isolar.
|
|
174
|
+
|
|
175
|
+
**Certo:**
|
|
176
|
+
```ts
|
|
177
|
+
const path = `${user.id}/avatar.jpg`
|
|
178
|
+
await supabase.storage.from('private-uploads').upload(path, file)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Anti-pattern 2: Bucket privado sem RLS em `storage.objects`
|
|
182
|
+
|
|
183
|
+
**Errado:**
|
|
184
|
+
```sql
|
|
185
|
+
create bucket 'private-uploads';
|
|
186
|
+
-- (esqueceu policies em storage.objects)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Por quê:** sem RLS em `storage.objects`, qualquer `authenticated` lê arquivos do bucket — multi-tenancy quebrado.
|
|
190
|
+
|
|
191
|
+
**Certo:** ver pattern "RLS multi-tenant" acima — 4 policies separadas (SELECT/INSERT/UPDATE/DELETE).
|
|
192
|
+
|
|
193
|
+
### Anti-pattern 3: `getPublicUrl` em bucket privado
|
|
194
|
+
|
|
195
|
+
**Errado:**
|
|
196
|
+
```ts
|
|
197
|
+
// PT-BR: bucket-id é privado
|
|
198
|
+
const { data } = supabase.storage.from('private-uploads').getPublicUrl('x.jpg')
|
|
199
|
+
// data.publicUrl retorna mas o URL não funciona (403)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Por quê:** `getPublicUrl` só funciona em buckets marcados public. Em privado, retorna URL que sempre dá 403.
|
|
203
|
+
|
|
204
|
+
**Certo:** use `createSignedUrl` com expiration:
|
|
205
|
+
```ts
|
|
206
|
+
const { data } = await supabase.storage
|
|
207
|
+
.from('private-uploads')
|
|
208
|
+
.createSignedUrl('x.jpg', 3600)
|
|
209
|
+
// data.signedUrl funciona por 1h
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Anti-pattern 4: Overwrite de arquivo público com mesmo path
|
|
213
|
+
|
|
214
|
+
**Errado:**
|
|
215
|
+
```ts
|
|
216
|
+
// PT-BR: hero.jpg público
|
|
217
|
+
await supabase.storage.from('public').upload('hero.jpg', newFile, { upsert: true })
|
|
218
|
+
// CDN cache antigo continua servindo old hero.jpg por horas/dias
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Por quê:** CDN cache pelo path. Overwrite não invalida cache; usuários veem versão antiga.
|
|
222
|
+
|
|
223
|
+
**Certo:** versionar ou random suffix:
|
|
224
|
+
```ts
|
|
225
|
+
await supabase.storage.from('public').upload(`hero-${version}.jpg`, newFile)
|
|
226
|
+
// ou: hero-<sha>.jpg, hero-<timestamp>.jpg
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Ver também
|
|
230
|
+
|
|
231
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS sobre `storage.objects` + multi-tenant pattern
|
|
232
|
+
- [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — usuário autenticado obtém `auth.uid()` para path prefix
|
|
233
|
+
- [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions podem mediar uploads complexos
|
|
234
|
+
- [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN
|