@plutocms/supabase 0.0.1-alpha.1
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/.agents/skills/nuxt/GENERATION.md +5 -0
- package/.agents/skills/nuxt/SKILL.md +55 -0
- package/.agents/skills/nuxt/references/advanced-hooks.md +289 -0
- package/.agents/skills/nuxt/references/advanced-layers.md +299 -0
- package/.agents/skills/nuxt/references/advanced-module-authoring.md +554 -0
- package/.agents/skills/nuxt/references/best-practices-data-fetching.md +357 -0
- package/.agents/skills/nuxt/references/best-practices-ssr.md +355 -0
- package/.agents/skills/nuxt/references/core-cli.md +263 -0
- package/.agents/skills/nuxt/references/core-config.md +162 -0
- package/.agents/skills/nuxt/references/core-data-fetching.md +236 -0
- package/.agents/skills/nuxt/references/core-deployment.md +224 -0
- package/.agents/skills/nuxt/references/core-directory-structure.md +269 -0
- package/.agents/skills/nuxt/references/core-modules.md +292 -0
- package/.agents/skills/nuxt/references/core-routing.md +226 -0
- package/.agents/skills/nuxt/references/features-components-autoimport.md +328 -0
- package/.agents/skills/nuxt/references/features-components.md +264 -0
- package/.agents/skills/nuxt/references/features-composables.md +276 -0
- package/.agents/skills/nuxt/references/features-server.md +265 -0
- package/.agents/skills/nuxt/references/features-state.md +194 -0
- package/.agents/skills/nuxt/references/rendering-modes.md +237 -0
- package/.agents/skills/nuxt-ui/SKILL.md +334 -0
- package/.agents/skills/nuxt-ui/references/components.md +377 -0
- package/.agents/skills/nuxt-ui/references/composables.md +127 -0
- package/.agents/skills/nuxt-ui/references/layouts/chat.md +195 -0
- package/.agents/skills/nuxt-ui/references/layouts/dashboard.md +220 -0
- package/.agents/skills/nuxt-ui/references/layouts/docs.md +141 -0
- package/.agents/skills/nuxt-ui/references/layouts/editor.md +167 -0
- package/.agents/skills/nuxt-ui/references/layouts/page.md +260 -0
- package/.agents/skills/nuxt-ui/references/theming.md +427 -0
- package/.agents/skills/supabase-postgres-best-practices/AGENTS.md +68 -0
- package/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
- package/.agents/skills/supabase-postgres-best-practices/README.md +116 -0
- package/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/.editorconfig +12 -0
- package/.env.example +2 -0
- package/.eslintignore +1 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +61 -0
- package/README.md +62 -0
- package/app/components/navbar/NavbarAdmin.vue +169 -0
- package/app/composables/auth.ts +81 -0
- package/app/layouts/setup.vue +13 -0
- package/app/middleware/auth.ts +27 -0
- package/app/middleware/setup-check.ts +25 -0
- package/app/pages/admin/(auth)/login.vue +84 -0
- package/app/pages/admin/(auth)/signup.vue +128 -0
- package/app/pages/admin/settings.vue +116 -0
- package/app/pages/admin/setup.vue +300 -0
- package/app/pages/admin.vue +38 -0
- package/bun.lock +2685 -0
- package/eslint.config.mjs +88 -0
- package/nuxt.config.ts +55 -0
- package/package.json +44 -0
- package/public/schema.sql +103 -0
- package/scripts/supabase-typegen.ts +36 -0
- package/server/api/category/list.get.ts +9 -0
- package/server/api/category/new.post.ts +31 -0
- package/server/api/settings/first_setup.get.ts +39 -0
- package/server/api/settings/index.get.ts +27 -0
- package/server/api/settings/update.post.ts +36 -0
- package/server/api/setup/create.post.ts +58 -0
- package/server/api/signup.post.ts +28 -0
- package/server/api/users/[id].get.ts +21 -0
- package/server/api/users/index.get.ts +16 -0
- package/shared/types/supabase.ts +406 -0
- package/skills-lock.json +20 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: composables-auto-imports
|
|
3
|
+
description: Auto-imported Vue APIs, Nuxt composables, and custom utilities
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composables Auto-imports
|
|
7
|
+
|
|
8
|
+
Nuxt automatically imports Vue APIs, Nuxt composables, and your custom composables/utilities.
|
|
9
|
+
|
|
10
|
+
## Built-in Auto-imports
|
|
11
|
+
|
|
12
|
+
### Vue APIs
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
// No imports needed - all auto-imported
|
|
17
|
+
const count = ref(0)
|
|
18
|
+
const doubled = computed(() => count.value * 2)
|
|
19
|
+
|
|
20
|
+
watch(count, (newVal) => {
|
|
21
|
+
console.log('Count changed:', newVal)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
onMounted(() => {
|
|
25
|
+
console.log('Component mounted')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Lifecycle hooks
|
|
29
|
+
onBeforeMount(() => {})
|
|
30
|
+
onUnmounted(() => {})
|
|
31
|
+
onBeforeUnmount(() => {})
|
|
32
|
+
|
|
33
|
+
// Reactivity
|
|
34
|
+
const state = reactive({ name: 'John' })
|
|
35
|
+
const shallow = shallowRef({ deep: 'object' })
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Nuxt Composables
|
|
40
|
+
|
|
41
|
+
```vue
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
// All auto-imported
|
|
44
|
+
const route = useRoute()
|
|
45
|
+
const router = useRouter()
|
|
46
|
+
const config = useRuntimeConfig()
|
|
47
|
+
const appConfig = useAppConfig()
|
|
48
|
+
const nuxtApp = useNuxtApp()
|
|
49
|
+
|
|
50
|
+
// Data fetching
|
|
51
|
+
const { data } = await useFetch('/api/data')
|
|
52
|
+
const { data: asyncData } = await useAsyncData('key', () => fetchData())
|
|
53
|
+
|
|
54
|
+
// State
|
|
55
|
+
const state = useState('key', () => 'initial')
|
|
56
|
+
const cookie = useCookie('token')
|
|
57
|
+
|
|
58
|
+
// Head/SEO
|
|
59
|
+
useHead({ title: 'My Page' })
|
|
60
|
+
useSeoMeta({ description: 'Page description' })
|
|
61
|
+
|
|
62
|
+
// Request helpers (SSR)
|
|
63
|
+
const headers = useRequestHeaders()
|
|
64
|
+
const event = useRequestEvent()
|
|
65
|
+
const url = useRequestURL()
|
|
66
|
+
</script>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Custom Composables (`app/composables/`)
|
|
70
|
+
|
|
71
|
+
### Creating Composables
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// composables/useCounter.ts
|
|
75
|
+
export function useCounter(initial = 0) {
|
|
76
|
+
const count = ref(initial)
|
|
77
|
+
const increment = () => count.value++
|
|
78
|
+
const decrement = () => count.value--
|
|
79
|
+
return { count, increment, decrement }
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// composables/useAuth.ts
|
|
85
|
+
export function useAuth() {
|
|
86
|
+
const user = useState<User | null>('user', () => null)
|
|
87
|
+
const isLoggedIn = computed(() => !!user.value)
|
|
88
|
+
|
|
89
|
+
async function login(credentials: Credentials) {
|
|
90
|
+
user.value = await $fetch('/api/auth/login', {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body: credentials,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function logout() {
|
|
97
|
+
await $fetch('/api/auth/logout', { method: 'POST' })
|
|
98
|
+
user.value = null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { user, isLoggedIn, login, logout }
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Using Composables
|
|
106
|
+
|
|
107
|
+
```vue
|
|
108
|
+
<script setup lang="ts">
|
|
109
|
+
// Auto-imported - no import statement needed
|
|
110
|
+
const { count, increment } = useCounter(10)
|
|
111
|
+
const { user, isLoggedIn, login } = useAuth()
|
|
112
|
+
</script>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### File Scanning Rules
|
|
116
|
+
|
|
117
|
+
Only top-level files are scanned:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
composables/
|
|
121
|
+
├── useAuth.ts → useAuth() ✓
|
|
122
|
+
├── useCounter.ts → useCounter() ✓
|
|
123
|
+
├── index.ts → exports ✓
|
|
124
|
+
└── nested/
|
|
125
|
+
└── helper.ts → NOT auto-imported ✗
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Re-export nested composables:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// composables/index.ts
|
|
132
|
+
export { useHelper } from './nested/helper'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Or configure scanning:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// nuxt.config.ts
|
|
139
|
+
export default defineNuxtConfig({
|
|
140
|
+
imports: {
|
|
141
|
+
dirs: [
|
|
142
|
+
'composables',
|
|
143
|
+
'composables/**', // Scan all nested
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Utilities (`app/utils/`)
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// utils/format.ts
|
|
153
|
+
export function formatDate(date: Date) {
|
|
154
|
+
return date.toLocaleDateString()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function formatCurrency(amount: number) {
|
|
158
|
+
return new Intl.NumberFormat('en-US', {
|
|
159
|
+
style: 'currency',
|
|
160
|
+
currency: 'USD',
|
|
161
|
+
}).format(amount)
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```vue
|
|
166
|
+
<script setup lang="ts">
|
|
167
|
+
// Auto-imported
|
|
168
|
+
const date = formatDate(new Date())
|
|
169
|
+
const price = formatCurrency(99.99)
|
|
170
|
+
</script>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Server Utils (`server/utils/`)
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// server/utils/db.ts
|
|
177
|
+
export function useDb() {
|
|
178
|
+
return createDbConnection()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// server/utils/auth.ts
|
|
182
|
+
export function verifyToken(token: string) {
|
|
183
|
+
return jwt.verify(token, process.env.JWT_SECRET)
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// server/api/users.ts
|
|
189
|
+
export default defineEventHandler(() => {
|
|
190
|
+
const db = useDb() // Auto-imported
|
|
191
|
+
return db.query('SELECT * FROM users')
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Third-party Package Imports
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// nuxt.config.ts
|
|
199
|
+
export default defineNuxtConfig({
|
|
200
|
+
imports: {
|
|
201
|
+
presets: [
|
|
202
|
+
{
|
|
203
|
+
from: 'vue-i18n',
|
|
204
|
+
imports: ['useI18n'],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
from: 'date-fns',
|
|
208
|
+
imports: ['format', 'parseISO', 'differenceInDays'],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
from: '@vueuse/core',
|
|
212
|
+
imports: ['useMouse', 'useWindowSize'],
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Explicit Imports
|
|
220
|
+
|
|
221
|
+
Use `#imports` alias when needed:
|
|
222
|
+
|
|
223
|
+
```vue
|
|
224
|
+
<script setup lang="ts">
|
|
225
|
+
import { ref, computed, useFetch } from '#imports'
|
|
226
|
+
</script>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Composable Context Rules
|
|
230
|
+
|
|
231
|
+
Nuxt composables must be called in valid context:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// ❌ Wrong - module level
|
|
235
|
+
const config = useRuntimeConfig()
|
|
236
|
+
|
|
237
|
+
export function useMyComposable() {}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// ✅ Correct - inside function
|
|
242
|
+
export function useMyComposable() {
|
|
243
|
+
const config = useRuntimeConfig()
|
|
244
|
+
return { apiBase: config.public.apiBase }
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Valid contexts:**
|
|
249
|
+
- `<script setup>` block
|
|
250
|
+
- `setup()` function
|
|
251
|
+
- `defineNuxtPlugin()` callback
|
|
252
|
+
- `defineNuxtRouteMiddleware()` callback
|
|
253
|
+
|
|
254
|
+
## Disabling Auto-imports
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// nuxt.config.ts
|
|
258
|
+
export default defineNuxtConfig({
|
|
259
|
+
// Disable all auto-imports
|
|
260
|
+
imports: {
|
|
261
|
+
autoImport: false,
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// Or disable only directory scanning (keep Vue/Nuxt imports)
|
|
265
|
+
imports: {
|
|
266
|
+
scan: false,
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
<!--
|
|
272
|
+
Source references:
|
|
273
|
+
- https://nuxt.com/docs/guide/concepts/auto-imports
|
|
274
|
+
- https://nuxt.com/docs/directory-structure/app/composables
|
|
275
|
+
- https://nuxt.com/docs/directory-structure/app/utils
|
|
276
|
+
-->
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-routes
|
|
3
|
+
description: API routes, server middleware, and Nitro server engine in Nuxt
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Server Routes
|
|
7
|
+
|
|
8
|
+
Nuxt includes Nitro server engine for building full-stack applications with API routes and server middleware.
|
|
9
|
+
|
|
10
|
+
## API Routes
|
|
11
|
+
|
|
12
|
+
Create files in `server/api/` directory:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// server/api/hello.ts
|
|
16
|
+
export default defineEventHandler((event) => {
|
|
17
|
+
return { message: 'Hello World' }
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Access at `/api/hello`.
|
|
22
|
+
|
|
23
|
+
### HTTP Methods
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// server/api/users.get.ts - GET /api/users
|
|
27
|
+
export default defineEventHandler(() => {
|
|
28
|
+
return getUsers()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// server/api/users.post.ts - POST /api/users
|
|
32
|
+
export default defineEventHandler(async (event) => {
|
|
33
|
+
const body = await readBody(event)
|
|
34
|
+
return createUser(body)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// server/api/users/[id].put.ts - PUT /api/users/:id
|
|
38
|
+
export default defineEventHandler(async (event) => {
|
|
39
|
+
const id = getRouterParam(event, 'id')
|
|
40
|
+
const body = await readBody(event)
|
|
41
|
+
return updateUser(id, body)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// server/api/users/[id].delete.ts - DELETE /api/users/:id
|
|
45
|
+
export default defineEventHandler((event) => {
|
|
46
|
+
const id = getRouterParam(event, 'id')
|
|
47
|
+
return deleteUser(id)
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Route Parameters
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// server/api/posts/[id].ts
|
|
55
|
+
export default defineEventHandler((event) => {
|
|
56
|
+
const id = getRouterParam(event, 'id')
|
|
57
|
+
return getPost(id)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Catch-all: server/api/[...path].ts
|
|
61
|
+
export default defineEventHandler((event) => {
|
|
62
|
+
const path = getRouterParam(event, 'path')
|
|
63
|
+
return { path }
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Query Parameters
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// server/api/search.ts
|
|
71
|
+
// GET /api/search?q=nuxt&page=1
|
|
72
|
+
export default defineEventHandler((event) => {
|
|
73
|
+
const query = getQuery(event)
|
|
74
|
+
// { q: 'nuxt', page: '1' }
|
|
75
|
+
return search(query.q, Number(query.page))
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Request Body
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// server/api/submit.post.ts
|
|
83
|
+
export default defineEventHandler(async (event) => {
|
|
84
|
+
const body = await readBody(event)
|
|
85
|
+
// Validate and process body
|
|
86
|
+
return { success: true, data: body }
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Headers and Cookies
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// server/api/auth.ts
|
|
94
|
+
export default defineEventHandler((event) => {
|
|
95
|
+
// Read headers
|
|
96
|
+
const auth = getHeader(event, 'authorization')
|
|
97
|
+
|
|
98
|
+
// Read cookies
|
|
99
|
+
const cookies = parseCookies(event)
|
|
100
|
+
const token = getCookie(event, 'token')
|
|
101
|
+
|
|
102
|
+
// Set headers
|
|
103
|
+
setHeader(event, 'X-Custom-Header', 'value')
|
|
104
|
+
|
|
105
|
+
// Set cookies
|
|
106
|
+
setCookie(event, 'token', 'new-token', {
|
|
107
|
+
httpOnly: true,
|
|
108
|
+
secure: true,
|
|
109
|
+
maxAge: 60 * 60 * 24, // 1 day
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return { authenticated: !!token }
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Server Middleware
|
|
117
|
+
|
|
118
|
+
Runs on every request before routes:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// server/middleware/auth.ts
|
|
122
|
+
export default defineEventHandler((event) => {
|
|
123
|
+
const token = getCookie(event, 'token')
|
|
124
|
+
|
|
125
|
+
// Attach data to event context
|
|
126
|
+
event.context.user = token ? verifyToken(token) : null
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// server/middleware/log.ts
|
|
130
|
+
export default defineEventHandler((event) => {
|
|
131
|
+
console.log(`${event.method} ${event.path}`)
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Access context in routes:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// server/api/profile.ts
|
|
139
|
+
export default defineEventHandler((event) => {
|
|
140
|
+
const user = event.context.user
|
|
141
|
+
if (!user) {
|
|
142
|
+
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
143
|
+
}
|
|
144
|
+
return user
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Error Handling
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// server/api/users/[id].ts
|
|
152
|
+
export default defineEventHandler((event) => {
|
|
153
|
+
const id = getRouterParam(event, 'id')
|
|
154
|
+
const user = findUser(id)
|
|
155
|
+
|
|
156
|
+
if (!user) {
|
|
157
|
+
throw createError({
|
|
158
|
+
statusCode: 404,
|
|
159
|
+
statusMessage: 'User not found',
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return user
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Server Utils
|
|
168
|
+
|
|
169
|
+
Auto-imported in `server/utils/`:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// server/utils/db.ts
|
|
173
|
+
export function useDb() {
|
|
174
|
+
return createDbConnection()
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// server/api/users.ts
|
|
180
|
+
export default defineEventHandler(() => {
|
|
181
|
+
const db = useDb() // Auto-imported
|
|
182
|
+
return db.query('SELECT * FROM users')
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Server Plugins
|
|
187
|
+
|
|
188
|
+
Run once when server starts:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// server/plugins/db.ts
|
|
192
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
193
|
+
// Initialize database connection
|
|
194
|
+
const db = createDbConnection()
|
|
195
|
+
|
|
196
|
+
// Add to context
|
|
197
|
+
nitroApp.hooks.hook('request', (event) => {
|
|
198
|
+
event.context.db = db
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Streaming Responses
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// server/api/stream.ts
|
|
207
|
+
export default defineEventHandler((event) => {
|
|
208
|
+
setHeader(event, 'Content-Type', 'text/event-stream')
|
|
209
|
+
setHeader(event, 'Cache-Control', 'no-cache')
|
|
210
|
+
setHeader(event, 'Connection', 'keep-alive')
|
|
211
|
+
|
|
212
|
+
const stream = new ReadableStream({
|
|
213
|
+
async start(controller) {
|
|
214
|
+
for (let i = 0; i < 10; i++) {
|
|
215
|
+
controller.enqueue(`data: ${JSON.stringify({ count: i })}\n\n`)
|
|
216
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
217
|
+
}
|
|
218
|
+
controller.close()
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return stream
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Server Storage
|
|
227
|
+
|
|
228
|
+
Key-value storage with multiple drivers:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// server/api/cache.ts
|
|
232
|
+
export default defineEventHandler(async (event) => {
|
|
233
|
+
const storage = useStorage()
|
|
234
|
+
|
|
235
|
+
// Set value
|
|
236
|
+
await storage.setItem('key', { data: 'value' })
|
|
237
|
+
|
|
238
|
+
// Get value
|
|
239
|
+
const data = await storage.getItem('key')
|
|
240
|
+
|
|
241
|
+
return data
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Configure storage drivers in `nuxt.config.ts`:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
export default defineNuxtConfig({
|
|
249
|
+
nitro: {
|
|
250
|
+
storage: {
|
|
251
|
+
redis: {
|
|
252
|
+
driver: 'redis',
|
|
253
|
+
url: 'redis://localhost:6379',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
<!--
|
|
261
|
+
Source references:
|
|
262
|
+
- https://nuxt.com/docs/getting-started/server
|
|
263
|
+
- https://nuxt.com/docs/directory-structure/server
|
|
264
|
+
- https://nitro.build/guide
|
|
265
|
+
-->
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: state-management
|
|
3
|
+
description: useState composable and SSR-friendly state management in Nuxt
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# State Management
|
|
7
|
+
|
|
8
|
+
Nuxt provides `useState` for SSR-friendly reactive state that persists across components.
|
|
9
|
+
|
|
10
|
+
## useState
|
|
11
|
+
|
|
12
|
+
SSR-safe replacement for `ref` that shares state across components:
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
// State is shared by key 'counter' across all components
|
|
17
|
+
const counter = useState('counter', () => 0)
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div>
|
|
22
|
+
Counter: {{ counter }}
|
|
23
|
+
<button @click="counter++">+</button>
|
|
24
|
+
<button @click="counter--">-</button>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Creating Shared State
|
|
30
|
+
|
|
31
|
+
Define reusable state composables:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// composables/useUser.ts
|
|
35
|
+
export function useUser() {
|
|
36
|
+
return useState<User | null>('user', () => null)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useLocale() {
|
|
40
|
+
return useState('locale', () => 'en')
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
// Same state instance everywhere
|
|
47
|
+
const user = useUser()
|
|
48
|
+
const locale = useLocale()
|
|
49
|
+
</script>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Initializing State
|
|
53
|
+
|
|
54
|
+
Use `callOnce` to initialize state with async data:
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
const config = useState('site-config')
|
|
59
|
+
|
|
60
|
+
await callOnce(async () => {
|
|
61
|
+
config.value = await $fetch('/api/config')
|
|
62
|
+
})
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Best Practices
|
|
67
|
+
|
|
68
|
+
### ❌ Don't Define State Outside Setup
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// ❌ Wrong - causes memory leaks and shared state across requests
|
|
72
|
+
export const globalState = ref({ user: null })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### ✅ Use useState Instead
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// ✅ Correct - SSR-safe
|
|
79
|
+
export const useGlobalState = () => useState('global', () => ({ user: null }))
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Clearing State
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// Clear specific state
|
|
86
|
+
clearNuxtState('counter')
|
|
87
|
+
|
|
88
|
+
// Clear multiple states
|
|
89
|
+
clearNuxtState(['counter', 'user'])
|
|
90
|
+
|
|
91
|
+
// Clear all state (use with caution)
|
|
92
|
+
clearNuxtState()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## With Pinia
|
|
96
|
+
|
|
97
|
+
For complex state management, use Pinia:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx nuxi module add pinia
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// stores/counter.ts
|
|
105
|
+
export const useCounterStore = defineStore('counter', {
|
|
106
|
+
state: () => ({
|
|
107
|
+
count: 0,
|
|
108
|
+
}),
|
|
109
|
+
actions: {
|
|
110
|
+
increment() {
|
|
111
|
+
this.count++
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// stores/user.ts (Composition API style)
|
|
119
|
+
export const useUserStore = defineStore('user', () => {
|
|
120
|
+
const user = ref<User | null>(null)
|
|
121
|
+
const isLoggedIn = computed(() => !!user.value)
|
|
122
|
+
|
|
123
|
+
async function login(credentials: Credentials) {
|
|
124
|
+
user.value = await $fetch('/api/login', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
body: credentials,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { user, isLoggedIn, login }
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```vue
|
|
135
|
+
<script setup lang="ts">
|
|
136
|
+
const counterStore = useCounterStore()
|
|
137
|
+
const userStore = useUserStore()
|
|
138
|
+
|
|
139
|
+
// Initialize store data once
|
|
140
|
+
await callOnce(async () => {
|
|
141
|
+
await userStore.fetchUser()
|
|
142
|
+
})
|
|
143
|
+
</script>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Advanced: Locale Example
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// composables/useLocale.ts
|
|
150
|
+
export function useLocale() {
|
|
151
|
+
return useState('locale', () => useDefaultLocale().value)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function useDefaultLocale(fallback = 'en-US') {
|
|
155
|
+
const locale = ref(fallback)
|
|
156
|
+
|
|
157
|
+
if (import.meta.server) {
|
|
158
|
+
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
|
|
159
|
+
if (reqLocale) locale.value = reqLocale
|
|
160
|
+
}
|
|
161
|
+
else if (import.meta.client) {
|
|
162
|
+
const navLang = navigator.language
|
|
163
|
+
if (navLang) locale.value = navLang
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return locale
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## State Serialization
|
|
171
|
+
|
|
172
|
+
`useState` values are serialized to JSON. Avoid:
|
|
173
|
+
|
|
174
|
+
- Functions
|
|
175
|
+
- Classes
|
|
176
|
+
- Symbols
|
|
177
|
+
- Circular references
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// ❌ Won't work
|
|
181
|
+
useState('fn', () => () => console.log('hi'))
|
|
182
|
+
useState('instance', () => new MyClass())
|
|
183
|
+
|
|
184
|
+
// ✅ Works
|
|
185
|
+
useState('data', () => ({ name: 'John', age: 30 }))
|
|
186
|
+
useState('items', () => ['a', 'b', 'c'])
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
<!--
|
|
190
|
+
Source references:
|
|
191
|
+
- https://nuxt.com/docs/getting-started/state-management
|
|
192
|
+
- https://nuxt.com/docs/api/composables/use-state
|
|
193
|
+
- https://nuxt.com/docs/api/utils/clear-nuxt-state
|
|
194
|
+
-->
|