From 51bc5efbaf8d977a5c8ac154410afc3d45761805 Mon Sep 17 00:00:00 2001 From: Paul Guilbert Date: Wed, 11 Dec 2024 15:33:52 +0100 Subject: [PATCH 1/3] fix: throw when a store is used outside of a Nuxt-aware context. Prefer the Nuxt Pinia instance over the global active Pinia instance. Since the Nuxt Pinia instance is discarded after each request, it ensures that we can't accidentally use one from another request. Additionally, `usePinia` will throw an error when used outside of a Nuxt-aware context. The error is as follows in dev : > [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. --- .../playground/pages/usage-after-await.vue | 17 ++++++++++++++ packages/nuxt/src/runtime/composables.ts | 23 +++++++++++++++++++ packages/nuxt/test/nuxt.spec.ts | 4 ++++ 3 files changed, 44 insertions(+) create mode 100644 packages/nuxt/playground/pages/usage-after-await.vue diff --git a/packages/nuxt/playground/pages/usage-after-await.vue b/packages/nuxt/playground/pages/usage-after-await.vue new file mode 100644 index 0000000000..adc86268d0 --- /dev/null +++ b/packages/nuxt/playground/pages/usage-after-await.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/nuxt/src/runtime/composables.ts b/packages/nuxt/src/runtime/composables.ts index 07fcf54767..098511a766 100644 --- a/packages/nuxt/src/runtime/composables.ts +++ b/packages/nuxt/src/runtime/composables.ts @@ -1,4 +1,27 @@ import { useNuxtApp } from '#app' +import { + defineStore as _defineStore, + type Pinia, + type StoreGeneric, +} from 'pinia' export * from 'pinia' export const usePinia = () => useNuxtApp().$pinia + +export const defineStore = (...args) => { + if (!import.meta.server) { + return _defineStore(...args) + } + + const store = _defineStore(...args) + + function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric { + if (pinia) { + return store(pinia, hot) + } + + return store(usePinia(), hot) + } + + return useStore +} diff --git a/packages/nuxt/test/nuxt.spec.ts b/packages/nuxt/test/nuxt.spec.ts index 8bcbfccc05..a1bd848258 100644 --- a/packages/nuxt/test/nuxt.spec.ts +++ b/packages/nuxt/test/nuxt.spec.ts @@ -33,4 +33,8 @@ describe('works with nuxt', async () => { expect(html).not.toContain('I should not be serialized or hydrated') expect(html).toContain('skipHydrate-wrapped state is correct') }) + + it('throws an error server-side when the nuxt context is not available', async () => { + await expect($fetch('/usage-after-await')).rejects.toThrow() + }) }) From 68883749ea12e25403605bb9b207bf66f0dc4e6b Mon Sep 17 00:00:00 2001 From: Paul Guilbert Date: Fri, 13 Dec 2024 17:25:50 +0100 Subject: [PATCH 2/3] feat: improve typing --- packages/nuxt/src/runtime/composables.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/nuxt/src/runtime/composables.ts b/packages/nuxt/src/runtime/composables.ts index 098511a766..93fdc60a55 100644 --- a/packages/nuxt/src/runtime/composables.ts +++ b/packages/nuxt/src/runtime/composables.ts @@ -6,22 +6,22 @@ import { } from 'pinia' export * from 'pinia' -export const usePinia = () => useNuxtApp().$pinia +export const usePinia = () => useNuxtApp().$pinia as Pinia | undefined -export const defineStore = (...args) => { +export const defineStore: typeof _defineStore = ( + ...args: [idOrOptions: any, setup?: any, setupOptions?: any] +) => { if (!import.meta.server) { return _defineStore(...args) } - const store = _defineStore(...args) - + const originalUseStore = _defineStore(...args) function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric { - if (pinia) { - return store(pinia, hot) - } - - return store(usePinia(), hot) + return originalUseStore(pinia || usePinia(), hot) } + useStore.$id = originalUseStore.$id + useStore._pinia = originalUseStore._pinia + return useStore } From d357d58153db7fb6e10bef94490c6ab9d0ae9e9a Mon Sep 17 00:00:00 2001 From: Paul Guilbert Date: Fri, 13 Dec 2024 18:02:00 +0100 Subject: [PATCH 3/3] test: improve tests --- packages/nuxt/playground/pages/usage-after-await.vue | 7 +++++++ packages/nuxt/test/nuxt.spec.ts | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/playground/pages/usage-after-await.vue b/packages/nuxt/playground/pages/usage-after-await.vue index adc86268d0..64af35d2ed 100644 --- a/packages/nuxt/playground/pages/usage-after-await.vue +++ b/packages/nuxt/playground/pages/usage-after-await.vue @@ -6,6 +6,13 @@ const useFancyCounter = async () => { return useCounter() } +const event = useRequestEvent() +useNuxtApp().hook('vue:error', (error) => { + if (event) { + setResponseStatus(event, 500, String(error)) + } +}) + const counter = await useFancyCounter() diff --git a/packages/nuxt/test/nuxt.spec.ts b/packages/nuxt/test/nuxt.spec.ts index a1bd848258..1e082e4b8c 100644 --- a/packages/nuxt/test/nuxt.spec.ts +++ b/packages/nuxt/test/nuxt.spec.ts @@ -35,6 +35,8 @@ describe('works with nuxt', async () => { }) it('throws an error server-side when the nuxt context is not available', async () => { - await expect($fetch('/usage-after-await')).rejects.toThrow() + await expect($fetch('/usage-after-await')).rejects.toThrowError( + '[nuxt] instance unavailable' + ) }) })