@lucashw68/nsdb 1.0.0-rc.2
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/CHANGELOG.md +34 -0
- package/GET_STARTED.md +709 -0
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/cli/index.js +83 -0
- package/helpers/args.js +22 -0
- package/helpers/config.js +142 -0
- package/helpers/generated.js +48 -0
- package/helpers/io.js +39 -0
- package/helpers/metadata.js +19 -0
- package/helpers/names.js +16 -0
- package/helpers/relations.js +101 -0
- package/helpers/shell.js +15 -0
- package/helpers/tables.js +79 -0
- package/helpers/ts.js +37 -0
- package/module.ts +151 -0
- package/nsdb.config.example.mjs +39 -0
- package/nsdb.config.example.ts +42 -0
- package/package.json +114 -0
- package/runtime/components/Form/NsdbRelationSelect.vue +258 -0
- package/runtime/components/NsdbForm.vue +865 -0
- package/runtime/components/NsdbList.vue +961 -0
- package/runtime/composables/useNsdbProfile.ts +119 -0
- package/runtime/composables/useNsdbSchemas.ts +176 -0
- package/runtime/composables/useSupabaseApi.ts +177 -0
- package/runtime/composables/useSupabaseApiStorage.ts +337 -0
- package/runtime/composables/useSupabaseModels.ts +412 -0
- package/runtime/query.ts +126 -0
- package/runtime/stores/createDbStore.ts +439 -0
- package/runtime/stores/createSingletonDbStore.ts +67 -0
- package/runtime/utils/dataFreshness.ts +47 -0
- package/runtime/utils/storage.ts +41 -0
- package/scripts/clear.js +64 -0
- package/scripts/generate-composables.js +100 -0
- package/scripts/generate-enums.js +106 -0
- package/scripts/generate-metadata.js +165 -0
- package/scripts/generate-models.js +164 -0
- package/scripts/generate-schemas.js +443 -0
- package/scripts/generate-stores.js +90 -0
- package/scripts/generate-types.js +196 -0
- package/scripts/init.js +225 -0
- package/templates/model.template.ts +48 -0
- package/templates/schema.template.ts +13 -0
- package/templates/useNsdbModel.template.ts +9 -0
- package/types/config.ts +50 -0
- package/types/entities.ts +66 -0
- package/types/index.ts +14 -0
- package/types/list.ts +78 -0
- package/types/model.ts +57 -0
package/GET_STARTED.md
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
# Get Started — Installer NSDB dans un projet Nuxt 4
|
|
2
|
+
|
|
3
|
+
Ce guide décrit l'installation complète de NSDB dans une app Nuxt 4 existante.
|
|
4
|
+
|
|
5
|
+
> **Statut de publication :** `1.0.0-rc.2` est la release candidate actuelle et n'est pas encore disponible sur npm. Ces commandes d'installation s'appliqueront après publication.
|
|
6
|
+
|
|
7
|
+
NSDB est un module Nuxt qui facilite l'utilisation de Supabase dans Nuxt avec :
|
|
8
|
+
|
|
9
|
+
- génération des types Supabase
|
|
10
|
+
- génération de schemas NSDB par table
|
|
11
|
+
- génération de modèles/composables typés
|
|
12
|
+
- composants `NsdbList` et `NsdbForm`
|
|
13
|
+
- stores Pinia optionnels
|
|
14
|
+
- helpers pour Supabase Database et Storage
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Prérequis
|
|
19
|
+
|
|
20
|
+
Vous devez avoir :
|
|
21
|
+
|
|
22
|
+
- une app Nuxt 4
|
|
23
|
+
- un projet Supabase
|
|
24
|
+
- des tables Supabase existantes
|
|
25
|
+
- les RLS Supabase prévues pour sécuriser les accès
|
|
26
|
+
- Node.js et un package manager (`npm`, `pnpm` ou `yarn`)
|
|
27
|
+
|
|
28
|
+
Si vous utilisez Supabase Auth, le module recommandé côté Nuxt est `@nuxtjs/supabase`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. Installer les dépendances
|
|
33
|
+
|
|
34
|
+
Avec npm :
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @lucashw68/nsdb @nuxtjs/supabase @pinia/nuxt pinia
|
|
38
|
+
npm install -D supabase
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Avec pnpm :
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add @lucashw68/nsdb @nuxtjs/supabase @pinia/nuxt pinia
|
|
45
|
+
pnpm add -D supabase
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Avec yarn :
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
yarn add @lucashw68/nsdb @nuxtjs/supabase @pinia/nuxt pinia
|
|
52
|
+
yarn add -D supabase
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pour activer la persistance locale des stores :
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install pinia-plugin-persistedstate
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Configurer Nuxt
|
|
64
|
+
|
|
65
|
+
Ajoutez NSDB, Pinia et Supabase dans `nuxt.config.ts`.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
export default defineNuxtConfig({
|
|
69
|
+
modules: [
|
|
70
|
+
'@lucashw68/nsdb',
|
|
71
|
+
'@pinia/nuxt',
|
|
72
|
+
'@nuxtjs/supabase',
|
|
73
|
+
'pinia-plugin-persistedstate/nuxt',
|
|
74
|
+
],
|
|
75
|
+
|
|
76
|
+
nsdb: {
|
|
77
|
+
withComponents: true,
|
|
78
|
+
componentsPrefix: 'Nsdb',
|
|
79
|
+
withStores: true,
|
|
80
|
+
autoImportModels: true,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
supabase: {
|
|
84
|
+
redirectOptions: {
|
|
85
|
+
login: '/',
|
|
86
|
+
callback: '/confirm',
|
|
87
|
+
exclude: ['/', '/public'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Si vous n'utilisez pas les stores persistés, retirez simplement :
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
'pinia-plugin-persistedstate/nuxt'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 4. Ajouter les variables d'environnement
|
|
102
|
+
|
|
103
|
+
Créez ou complétez `.env`.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
107
|
+
SUPABASE_KEY=your-anon-key
|
|
108
|
+
SUPABASE_PROJECT_ID=your-project-id
|
|
109
|
+
# Self-hosted uniquement, pour générer les types sans project id :
|
|
110
|
+
SUPABASE_DB_URL=postgresql://postgres:password@localhost:5432/postgres
|
|
111
|
+
# Self-hosted avec génération sur VPS :
|
|
112
|
+
SUPABASE_REMOTE_SSH_HOST=vps
|
|
113
|
+
SUPABASE_REMOTE_PROJECT_PATH=/opt/supabase-projects/example
|
|
114
|
+
SUPABASE_REMOTE_DB_URL=postgresql://postgres:$POSTGRES_PASSWORD@db:5432/postgres
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`SUPABASE_URL` et `SUPABASE_KEY` sont utilisés par `@nuxtjs/supabase`.
|
|
118
|
+
|
|
119
|
+
`SUPABASE_PROJECT_ID` est utilisé par NSDB pour générer les types Supabase avec le CLI Supabase.
|
|
120
|
+
|
|
121
|
+
Si votre projet Supabase est déjà lié localement avec le Supabase CLI, vous pouvez utiliser `--linked` et ne pas renseigner `SUPABASE_PROJECT_ID`.
|
|
122
|
+
|
|
123
|
+
Si vous utilisez Supabase en self-hosted, vous pouvez utiliser `SUPABASE_DB_URL` à la place de `SUPABASE_PROJECT_ID`.
|
|
124
|
+
|
|
125
|
+
Si Postgres n'est pas exposé publiquement, utilisez `SUPABASE_REMOTE_*` pour générer les types sur le VPS via SSH puis les copier localement avec `scp`.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 5. Initialiser NSDB
|
|
130
|
+
|
|
131
|
+
Depuis la racine de votre projet Nuxt :
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx @lucashw68/nsdb init
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Avec yarn :
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
yarn nsdb init
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Options disponibles :
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
nsdb init --linked
|
|
147
|
+
nsdb init --schema private
|
|
148
|
+
nsdb init --project-id your-project-id
|
|
149
|
+
nsdb init --self-hosted
|
|
150
|
+
nsdb init --db-url postgresql://postgres:password@localhost:5432/postgres
|
|
151
|
+
nsdb init --remote-types
|
|
152
|
+
nsdb init --remote-ssh-host vps --remote-project-path /opt/supabase-projects/example
|
|
153
|
+
nsdb init --force
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Exemples :
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx @lucashw68/nsdb init --schema public --project-id abcdefghijkl
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npx @lucashw68/nsdb init --linked --schema public
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Instance Supabase self-hosted :
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npx @lucashw68/nsdb init --self-hosted --schema public
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
ou avec une URL Postgres explicite :
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npx @lucashw68/nsdb init --db-url postgresql://postgres:password@localhost:5432/postgres
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Instance Supabase self-hosted sur VPS sans Postgres exposé :
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npx @lucashw68/nsdb init --remote-types
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
ou avec les valeurs explicites :
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npx @lucashw68/nsdb init \
|
|
188
|
+
--remote-ssh-host vps \
|
|
189
|
+
--remote-project-path /opt/supabase-projects/example \
|
|
190
|
+
--remote-db-url 'postgresql://postgres:$POSTGRES_PASSWORD@db:5432/postgres'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`nsdb init` crée :
|
|
194
|
+
|
|
195
|
+
- `nsdb.config.ts`
|
|
196
|
+
- `.env.example` si absent
|
|
197
|
+
- les dossiers `types`, `nsdb/schemas`, `nsdb/models`, `nsdb/composables`, `stores`
|
|
198
|
+
- les scripts `nsdb:*` dans `package.json`
|
|
199
|
+
|
|
200
|
+
Les fichiers existants ne sont pas écrasés sauf si vous utilisez `--force`.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 6. Vérifier `nsdb.config.ts`
|
|
205
|
+
|
|
206
|
+
Exemple standard :
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import type { NsdbConfig } from '@lucashw68/nsdb/types/config'
|
|
210
|
+
|
|
211
|
+
export default {
|
|
212
|
+
supabase: {
|
|
213
|
+
schema: 'public',
|
|
214
|
+
projectId: process.env.SUPABASE_PROJECT_ID,
|
|
215
|
+
linked: false,
|
|
216
|
+
},
|
|
217
|
+
paths: {
|
|
218
|
+
types: 'types/database.types.ts',
|
|
219
|
+
metadata: 'nsdb/database.metadata.json',
|
|
220
|
+
enums: 'nsdb/enums.ts',
|
|
221
|
+
schemas: 'nsdb/schemas',
|
|
222
|
+
models: 'nsdb/models',
|
|
223
|
+
composables: 'nsdb/composables',
|
|
224
|
+
stores: 'stores',
|
|
225
|
+
},
|
|
226
|
+
imports: {
|
|
227
|
+
databaseTypes: '~~/types/database.types',
|
|
228
|
+
},
|
|
229
|
+
tables: {
|
|
230
|
+
include: ['playlists', 'tracks'],
|
|
231
|
+
columns: {
|
|
232
|
+
playlists: {
|
|
233
|
+
internal_note: { serverOnly: true },
|
|
234
|
+
created_at: { editable: false },
|
|
235
|
+
cover_url: { hidden: true },
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
} satisfies NsdbConfig
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Avec `supabase.dbUrl` ou `SUPABASE_DB_URL`, `generate:all` interroge aussi `pg_catalog` afin de détecter exactement clés primaires, contraintes, valeurs par défaut, nullabilité, identités et colonnes générées. Sans accès PostgreSQL direct, cette étape est ignorée avec un avertissement et la génération utilise le fallback TypeScript moins précis.
|
|
243
|
+
|
|
244
|
+
`serverOnly` retire la colonne des artefacts client NSDB, mais ne remplace jamais RLS. En cas de collision entre un composable applicatif et un modèle généré (par exemple deux `usePlaylists`), Nuxt échoue explicitement. Configurez `autoImportModels: false` puis importez le modèle généré explicitement.
|
|
245
|
+
|
|
246
|
+
Si le projet Supabase est lié localement :
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import type { NsdbConfig } from '@lucashw68/nsdb/types/config'
|
|
250
|
+
|
|
251
|
+
export default {
|
|
252
|
+
supabase: {
|
|
253
|
+
schema: 'public',
|
|
254
|
+
linked: true,
|
|
255
|
+
},
|
|
256
|
+
} satisfies NsdbConfig
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Pour une instance Supabase self-hosted sans project id :
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import type { NsdbConfig } from '@lucashw68/nsdb/types/config'
|
|
263
|
+
|
|
264
|
+
export default {
|
|
265
|
+
supabase: {
|
|
266
|
+
schema: 'public',
|
|
267
|
+
dbUrl: process.env.SUPABASE_DB_URL,
|
|
268
|
+
linked: false,
|
|
269
|
+
},
|
|
270
|
+
} satisfies NsdbConfig
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Pour générer les types directement sur le VPS :
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import type { NsdbConfig } from '@lucashw68/nsdb/types/config'
|
|
277
|
+
|
|
278
|
+
export default {
|
|
279
|
+
supabase: {
|
|
280
|
+
schema: 'public',
|
|
281
|
+
remoteTypes: {
|
|
282
|
+
sshHost: process.env.SUPABASE_REMOTE_SSH_HOST,
|
|
283
|
+
projectPath: process.env.SUPABASE_REMOTE_PROJECT_PATH,
|
|
284
|
+
dbUrl: process.env.SUPABASE_REMOTE_DB_URL,
|
|
285
|
+
remoteOutput: '/tmp/database.types.ts',
|
|
286
|
+
beforeCommand: process.env.SUPABASE_REMOTE_BEFORE_COMMAND,
|
|
287
|
+
supabaseCommand: process.env.SUPABASE_REMOTE_SUPABASE_COMMAND,
|
|
288
|
+
},
|
|
289
|
+
linked: false,
|
|
290
|
+
},
|
|
291
|
+
} satisfies NsdbConfig
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Ce mode exécute la génération sur le VPS, puis copie le fichier généré en local :
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
ssh vps 'cd /opt/supabase-projects/example && npx supabase gen types typescript --db-url "postgresql://postgres:$POSTGRES_PASSWORD@db:5432/postgres" > /tmp/database.types.ts'
|
|
298
|
+
scp vps:/tmp/database.types.ts ./types/database.types.ts
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Si le serveur retourne `command not found: npx`, le shell SSH non interactif ne charge probablement pas Node ou `nvm`.
|
|
302
|
+
|
|
303
|
+
Ajoutez dans `.env` :
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
SUPABASE_REMOTE_BEFORE_COMMAND='source ~/.nvm/nvm.sh'
|
|
307
|
+
SUPABASE_REMOTE_SUPABASE_COMMAND='npx supabase'
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Si Supabase CLI est installé dans le projet distant :
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
SUPABASE_REMOTE_SUPABASE_COMMAND='./node_modules/.bin/supabase'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 7. Générer les fichiers NSDB
|
|
319
|
+
|
|
320
|
+
Lancez :
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npm run nsdb:all
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Avec yarn :
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
yarn nsdb:all
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Cette commande génère :
|
|
333
|
+
|
|
334
|
+
| Étape | Sortie | Rôle |
|
|
335
|
+
|-------|--------|------|
|
|
336
|
+
| Types | `types/database.types.ts` | Types Supabase |
|
|
337
|
+
| Enums | `nsdb/enums.ts` | Enums du schema |
|
|
338
|
+
| Schemas | `nsdb/schemas/*` | Schemas UI par table |
|
|
339
|
+
| Models | `nsdb/models/*` | Modèles typés par table |
|
|
340
|
+
| Stores | `stores/*` | Stores Pinia optionnels |
|
|
341
|
+
| Composable | `nsdb/composables/useNsdbModels.ts` | Accès générique aux modèles |
|
|
342
|
+
|
|
343
|
+
Vous pouvez aussi lancer une étape précise :
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
npm run nsdb:types
|
|
347
|
+
npm run nsdb:schemas
|
|
348
|
+
npm run nsdb:models
|
|
349
|
+
npm run nsdb:stores
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Pour regénérer les stores en écrasant les stores existants :
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
npx @lucashw68/nsdb generate:stores --force
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 8. Configurer les RLS Supabase
|
|
361
|
+
|
|
362
|
+
NSDB ne remplace pas les RLS. Les droits doivent rester côté Supabase.
|
|
363
|
+
|
|
364
|
+
Exemple avec :
|
|
365
|
+
|
|
366
|
+
- `profiles.id = auth.users.id`
|
|
367
|
+
- `playlists.profile_id -> profiles.id`
|
|
368
|
+
|
|
369
|
+
```sql
|
|
370
|
+
alter table profiles enable row level security;
|
|
371
|
+
alter table playlists enable row level security;
|
|
372
|
+
|
|
373
|
+
create policy "Users can read own profile"
|
|
374
|
+
on profiles
|
|
375
|
+
for select
|
|
376
|
+
to authenticated
|
|
377
|
+
using (id = auth.uid());
|
|
378
|
+
|
|
379
|
+
create policy "Users can update own profile"
|
|
380
|
+
on profiles
|
|
381
|
+
for update
|
|
382
|
+
to authenticated
|
|
383
|
+
using (id = auth.uid())
|
|
384
|
+
with check (id = auth.uid());
|
|
385
|
+
|
|
386
|
+
create policy "Users can read own playlists"
|
|
387
|
+
on playlists
|
|
388
|
+
for select
|
|
389
|
+
to authenticated
|
|
390
|
+
using (profile_id = auth.uid());
|
|
391
|
+
|
|
392
|
+
create policy "Users can create own playlists"
|
|
393
|
+
on playlists
|
|
394
|
+
for insert
|
|
395
|
+
to authenticated
|
|
396
|
+
with check (profile_id = auth.uid());
|
|
397
|
+
|
|
398
|
+
create policy "Users can update own playlists"
|
|
399
|
+
on playlists
|
|
400
|
+
for update
|
|
401
|
+
to authenticated
|
|
402
|
+
using (profile_id = auth.uid())
|
|
403
|
+
with check (profile_id = auth.uid());
|
|
404
|
+
|
|
405
|
+
create policy "Users can delete own playlists"
|
|
406
|
+
on playlists
|
|
407
|
+
for delete
|
|
408
|
+
to authenticated
|
|
409
|
+
using (profile_id = auth.uid());
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Si votre schema utilise `profiles.user_id` au lieu de `profiles.id = auth.uid()`, adaptez les conditions avec une sous-requête vers `profiles`.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 9. Ajouter un trigger `profile_id`
|
|
417
|
+
|
|
418
|
+
Si vos tables métier ont une colonne `profile_id`, vous pouvez éviter de l'envoyer depuis le client.
|
|
419
|
+
|
|
420
|
+
Fonction générique :
|
|
421
|
+
|
|
422
|
+
```sql
|
|
423
|
+
create or replace function public.set_profile_id()
|
|
424
|
+
returns trigger as $$
|
|
425
|
+
begin
|
|
426
|
+
if new.profile_id is null then
|
|
427
|
+
new.profile_id := (select auth.uid());
|
|
428
|
+
end if;
|
|
429
|
+
|
|
430
|
+
return new;
|
|
431
|
+
end;
|
|
432
|
+
$$
|
|
433
|
+
language plpgsql
|
|
434
|
+
security definer
|
|
435
|
+
set search_path = '';
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Trigger pour `playlists` :
|
|
439
|
+
|
|
440
|
+
```sql
|
|
441
|
+
drop trigger if exists set_profile_id on public.playlists;
|
|
442
|
+
|
|
443
|
+
create trigger set_profile_id
|
|
444
|
+
before insert on public.playlists
|
|
445
|
+
for each row
|
|
446
|
+
execute function public.set_profile_id();
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Vous pouvez réutiliser la même fonction sur chaque table qui possède `profile_id`.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 10. Utiliser un modèle généré
|
|
454
|
+
|
|
455
|
+
Après génération, NSDB expose des modèles par table.
|
|
456
|
+
|
|
457
|
+
Exemple avec `playlists` :
|
|
458
|
+
|
|
459
|
+
```vue
|
|
460
|
+
<script setup lang="ts">
|
|
461
|
+
const playlists = usePlaylists()
|
|
462
|
+
|
|
463
|
+
await playlists.fetch({
|
|
464
|
+
orderBy: 'created_at',
|
|
465
|
+
orderDirection: 'desc',
|
|
466
|
+
limit: 20,
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
const created = await playlists.create({
|
|
470
|
+
title: 'Ma playlist',
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
await playlists.update(created, {
|
|
474
|
+
title: 'Titre modifie',
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
await playlists.remove(created)
|
|
478
|
+
</script>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
API principale d'un modèle :
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
items
|
|
485
|
+
totalCount
|
|
486
|
+
loading
|
|
487
|
+
error
|
|
488
|
+
stale
|
|
489
|
+
createDraft
|
|
490
|
+
fetch
|
|
491
|
+
refresh
|
|
492
|
+
invalidate
|
|
493
|
+
getById
|
|
494
|
+
create
|
|
495
|
+
update
|
|
496
|
+
remove
|
|
497
|
+
subscribe
|
|
498
|
+
unsubscribe
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
`fetch()` réutilise un résultat encore frais uniquement en mode store. `refresh()`
|
|
502
|
+
interroge toujours Supabase. `invalidate()` conserve les lignes sûres à l'écran,
|
|
503
|
+
mais les marque périmées. Les anciens `sync()`, `find()`, `new()` et
|
|
504
|
+
`fetch({ force: true })` ont été retirés dans la release candidate. Utilisez
|
|
505
|
+
respectivement `subscribe()`, `fetch({ where })`, `createDraft()` et `refresh()`.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## 11. Utiliser `NsdbList`
|
|
510
|
+
|
|
511
|
+
```vue
|
|
512
|
+
<template>
|
|
513
|
+
<NsdbList
|
|
514
|
+
model="playlists"
|
|
515
|
+
:columns="columns"
|
|
516
|
+
:page-size="10"
|
|
517
|
+
searchable
|
|
518
|
+
:search-columns="['title', 'provider']"
|
|
519
|
+
:query="{
|
|
520
|
+
orderBy: 'created_at',
|
|
521
|
+
orderDirection: 'desc'
|
|
522
|
+
}"
|
|
523
|
+
/>
|
|
524
|
+
</template>
|
|
525
|
+
|
|
526
|
+
<script setup lang="ts">
|
|
527
|
+
const columns = [
|
|
528
|
+
{ key: 'title', label: 'Titre' },
|
|
529
|
+
{ key: 'provider', label: 'Provider' },
|
|
530
|
+
{ key: 'created_at', label: 'Créée le' },
|
|
531
|
+
]
|
|
532
|
+
</script>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Avec relation Supabase :
|
|
536
|
+
|
|
537
|
+
```vue
|
|
538
|
+
<NsdbList
|
|
539
|
+
model="playlists"
|
|
540
|
+
:columns="[
|
|
541
|
+
{ key: 'title', label: 'Titre' },
|
|
542
|
+
{ key: 'profile.username', label: 'Auteur' }
|
|
543
|
+
]"
|
|
544
|
+
:query="{
|
|
545
|
+
select: '*, profile:profiles!playlists_profile_id_fkey(*)'
|
|
546
|
+
}"
|
|
547
|
+
/>
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## 12. Utiliser `NsdbForm`
|
|
553
|
+
|
|
554
|
+
```vue
|
|
555
|
+
<template>
|
|
556
|
+
<NsdbForm
|
|
557
|
+
model="playlists"
|
|
558
|
+
:hide-fields="['id', 'profile_id', 'created_at', 'updated_at']"
|
|
559
|
+
@created="onCreated"
|
|
560
|
+
@error="onError"
|
|
561
|
+
/>
|
|
562
|
+
</template>
|
|
563
|
+
|
|
564
|
+
<script setup lang="ts">
|
|
565
|
+
function onCreated(row: unknown) {
|
|
566
|
+
console.log('created', row)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function onError(error: unknown) {
|
|
570
|
+
console.error(error)
|
|
571
|
+
}
|
|
572
|
+
</script>
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Mode édition :
|
|
576
|
+
|
|
577
|
+
```vue
|
|
578
|
+
<NsdbForm
|
|
579
|
+
model="playlists"
|
|
580
|
+
:id="playlistId"
|
|
581
|
+
:hide-fields="['id', 'profile_id', 'created_at', 'updated_at']"
|
|
582
|
+
/>
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## 13. Utiliser les stores optionnels
|
|
588
|
+
|
|
589
|
+
Les stores sont utiles si vous voulez :
|
|
590
|
+
|
|
591
|
+
- conserver des données localement
|
|
592
|
+
- limiter certains appels Supabase
|
|
593
|
+
- centraliser l'état d'une table
|
|
594
|
+
- utiliser `pinia-plugin-persistedstate`
|
|
595
|
+
|
|
596
|
+
```vue
|
|
597
|
+
<script setup lang="ts">
|
|
598
|
+
const playlistsStore = usePlaylistsStore()
|
|
599
|
+
|
|
600
|
+
await playlistsStore.fetchFromSupabase({
|
|
601
|
+
limit: 20,
|
|
602
|
+
staleTimeMs: 60_000,
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
const playlist = playlistsStore.getById('playlist-id')
|
|
606
|
+
|
|
607
|
+
await playlistsStore.create({
|
|
608
|
+
title: 'Depuis le store',
|
|
609
|
+
})
|
|
610
|
+
</script>
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
Les stores générés sont vidés automatiquement quand l'utilisateur Supabase change.
|
|
614
|
+
Deux appels `usePlaylists({ store: true })` partagent la même collection : une
|
|
615
|
+
création, modification ou suppression confirmée par Supabase est donc visible par
|
|
616
|
+
tous les consommateurs sans `refresh()` supplémentaire.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
const playlists = usePlaylists({ store: true })
|
|
620
|
+
|
|
621
|
+
await playlists.fetch() // cache exact tant que le TTL est valide
|
|
622
|
+
await playlists.refresh() // réseau immédiatement
|
|
623
|
+
playlists.invalidate() // conserve items, stale devient true
|
|
624
|
+
|
|
625
|
+
playlists.subscribe() // Realtime opt-in, côté client uniquement
|
|
626
|
+
await playlists.unsubscribe()
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Les vues filtrées, décalées ou contenant des relations sont invalidées après un
|
|
630
|
+
événement ambigu au lieu d'être réévaluées approximativement dans le navigateur.
|
|
631
|
+
Pour une dépendance métier entre deux tables, invalidez-la explicitement :
|
|
632
|
+
|
|
633
|
+
```ts
|
|
634
|
+
await playlists.create({ title: 'Nouvelle playlist' })
|
|
635
|
+
tracks.invalidate()
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Pour un store public non lié à l'utilisateur :
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
export const usePublicCategoriesStore = createDbStore<Category>('categories', {
|
|
642
|
+
key: 'id',
|
|
643
|
+
orderBy: 'name',
|
|
644
|
+
defaultSort: 'asc',
|
|
645
|
+
scopeToUser: false,
|
|
646
|
+
})
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## 14. Utiliser Supabase Storage
|
|
652
|
+
|
|
653
|
+
```vue
|
|
654
|
+
<script setup lang="ts">
|
|
655
|
+
const storage = useSupabaseApiStorage()
|
|
656
|
+
|
|
657
|
+
await storage.upload('avatars', 'users/me.png', file, {
|
|
658
|
+
upsert: true,
|
|
659
|
+
contentType: file.type,
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
const publicUrl = storage.getPublicUrl('avatars', 'users/me.png')
|
|
663
|
+
const signedUrl = await storage.createSignedUrl('avatars', 'users/me.png', 300)
|
|
664
|
+
|
|
665
|
+
await storage.remove('avatars', 'users/me.png')
|
|
666
|
+
</script>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## 15. Vérifier l'installation
|
|
672
|
+
|
|
673
|
+
Lancez le serveur Nuxt :
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
npm run dev
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Puis vérifiez :
|
|
680
|
+
|
|
681
|
+
- les fichiers `types/database.types.ts` et `nsdb/models/*` existent
|
|
682
|
+
- les composables générés sont auto-importés
|
|
683
|
+
- `NsdbList` affiche bien des données
|
|
684
|
+
- les créations passent sans envoyer `profile_id` si le trigger est actif
|
|
685
|
+
- les RLS bloquent les accès non autorisés
|
|
686
|
+
|
|
687
|
+
Pour vérifier le typage :
|
|
688
|
+
|
|
689
|
+
```bash
|
|
690
|
+
npx nuxi typecheck
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## 16. Checklist rapide
|
|
696
|
+
|
|
697
|
+
- [ ] Installer `@lucashw68/nsdb`
|
|
698
|
+
- [ ] Installer `@nuxtjs/supabase`
|
|
699
|
+
- [ ] Installer Pinia
|
|
700
|
+
- [ ] Ajouter les modules dans `nuxt.config.ts`
|
|
701
|
+
- [ ] Ajouter `.env`
|
|
702
|
+
- [ ] Lancer `npx @lucashw68/nsdb init`
|
|
703
|
+
- [ ] Vérifier `nsdb.config.ts`
|
|
704
|
+
- [ ] Lancer `npm run nsdb:all`
|
|
705
|
+
- [ ] Ajouter RLS Supabase
|
|
706
|
+
- [ ] Ajouter triggers `profile_id` si nécessaire
|
|
707
|
+
- [ ] Tester `NsdbList`
|
|
708
|
+
- [ ] Tester `NsdbForm`
|
|
709
|
+
- [ ] Tester création/update/delete avec un utilisateur connecté
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Heitzmann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|