diff --git a/docs/advanced/storage-instance.md b/docs/advanced/storage-instance.md index 3be615a..b5c818a 100644 --- a/docs/advanced/storage-instance.md +++ b/docs/advanced/storage-instance.md @@ -1,12 +1,12 @@ # Using the Storage Instance -The cache storage singleton is not exported directly, but you can access it -from the current request event. The singleton is only attached if the caches -are enabled. +The cache storage singleton is not exported directly, but you can access it from +the current request event. The singleton is only attached if the caches are +enabled. ```typescript export default defineEventHandler(async (event) => { - const multiCache = event.context.__MULTI_CACHE + const multiCache = event.__MULTI_CACHE await multiCache.component.clear() await data = multiCache.data.getItem('foobar') @@ -16,4 +16,3 @@ export default defineEventHandler(async (event) => { } }) ``` - diff --git a/package-lock.json b/package-lock.json index be1be8a..3397337 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "nuxt-multi-cache", - "version": "3.2.0", + "version": "3.3.0", "license": "MIT", "dependencies": { "@nuxt/kit": "^3.12.2", diff --git a/playground/pages/cachedPageWithUncacheableApi.vue b/playground/pages/cachedPageWithUncacheableApi.vue new file mode 100644 index 0000000..190ba9e --- /dev/null +++ b/playground/pages/cachedPageWithUncacheableApi.vue @@ -0,0 +1,16 @@ + + + diff --git a/playground/server/api/uncacheableApi.ts b/playground/server/api/uncacheableApi.ts new file mode 100644 index 0000000..29e0f82 --- /dev/null +++ b/playground/server/api/uncacheableApi.ts @@ -0,0 +1,11 @@ +import { defineEventHandler } from 'h3' +import { useRouteCache } from '#nuxt-multi-cache/composables' + +export default defineEventHandler<{ data: number }>((event) => { + useRouteCache((v) => { + v.setUncacheable() + }, event) + return { + data: Date.now(), + } +}) diff --git a/src/runtime/helpers/server.ts b/src/runtime/helpers/server.ts index f8f4cdd..2fda365 100644 --- a/src/runtime/helpers/server.ts +++ b/src/runtime/helpers/server.ts @@ -11,19 +11,19 @@ export const MULTI_CACHE_PREFIX_KEY = '__MULTI_CACHE_PREFIX' export function getMultiCacheContext( event: H3Event, ): NuxtMultiCacheSSRContext | undefined { - return event?.context?.[MULTI_CACHE_CONTEXT_KEY] + return event?.[MULTI_CACHE_CONTEXT_KEY] } export function getMultiCacheRouteHelper( event: H3Event, ): NuxtMultiCacheRouteCacheHelper | undefined { - return event?.context?.[MULTI_CACHE_ROUTE_CONTEXT_KEY] + return event?.[MULTI_CACHE_ROUTE_CONTEXT_KEY] } export function getMultiCacheCDNHelper( event: H3Event, ): NuxtMultiCacheCDNHelper | undefined { - return event?.context?.[MULTI_CACHE_CDN_CONTEXT_KEY] + return event?.[MULTI_CACHE_CDN_CONTEXT_KEY] } export function getExpiresValue(maxAge: number) { @@ -38,7 +38,7 @@ export function getCacheKeyWithPrefix( cacheKey: string, event: H3Event, ): string { - const prefix = event.context[MULTI_CACHE_PREFIX_KEY] + const prefix = event[MULTI_CACHE_PREFIX_KEY] return prefix ? `${prefix}--${cacheKey}` : cacheKey } diff --git a/src/runtime/server/api/purgeTags.ts b/src/runtime/server/api/purgeTags.ts index fc7d389..c2786eb 100644 --- a/src/runtime/server/api/purgeTags.ts +++ b/src/runtime/server/api/purgeTags.ts @@ -5,10 +5,10 @@ import { decodeRouteCacheItem, } from '../../helpers/cacheItem' import { useMultiCacheApp } from '../utils/useMultiCacheApp' +import { onlyUnique } from '../../helpers/server' import { DEFAULT_CACHE_TAG_INVALIDATION_DELAY } from './../../settings' import type { NuxtMultiCacheSSRContext } from './../../types' import { checkAuth } from './helpers' -import { onlyUnique } from '../../helpers/server' /** * Get the tags to be purged from the request. diff --git a/src/runtime/server/hooks/afterResponse.ts b/src/runtime/server/hooks/afterResponse.ts index 57c5ecd..339323a 100644 --- a/src/runtime/server/hooks/afterResponse.ts +++ b/src/runtime/server/hooks/afterResponse.ts @@ -111,9 +111,7 @@ export async function onAfterResponse( ttl: routeHelper.maxAge, }) - if (event.context.__MULTI_CACHE_REVALIDATION_KEY) { - state.removeKeyBeingRevalidated( - event.context.__MULTI_CACHE_REVALIDATION_KEY, - ) + if (event.__MULTI_CACHE_REVALIDATION_KEY) { + state.removeKeyBeingRevalidated(event.__MULTI_CACHE_REVALIDATION_KEY) } } diff --git a/src/runtime/server/hooks/error.ts b/src/runtime/server/hooks/error.ts index ead8e0b..4cccba4 100644 --- a/src/runtime/server/hooks/error.ts +++ b/src/runtime/server/hooks/error.ts @@ -12,7 +12,7 @@ export function onError(_error: Error, ctx: CapturedErrorContext) { return } // Get the decoded route cache item. The "request" handler may have already fetched this, so we can reuse it. - const decoded = ctx.event.context.__MULTI_CACHE_DECODED_CACHED_ROUTE + const decoded = ctx.event.__MULTI_CACHE_DECODED_CACHED_ROUTE if (!decoded) { return diff --git a/src/runtime/server/hooks/request.ts b/src/runtime/server/hooks/request.ts index fbd5282..4fe4f5e 100644 --- a/src/runtime/server/hooks/request.ts +++ b/src/runtime/server/hooks/request.ts @@ -17,8 +17,8 @@ import { logger } from '../../helpers/logger' import { useMultiCacheApp } from '../utils/useMultiCacheApp' import { NuxtMultiCacheCDNHelper } from '../../helpers/CDNHelper' import { serveCachedRoute } from '../../helpers/routeCache' -import { useRuntimeConfig } from '#imports' import type { MultiCacheState } from '../../helpers/MultiCacheState' +import { useRuntimeConfig } from '#imports' /** * Add the cache context singleton to the current request. @@ -33,27 +33,25 @@ async function addCacheContext( // for example based on cookie or request headers. if (serverOptions.cacheKeyPrefix) { if (typeof serverOptions.cacheKeyPrefix === 'string') { - event.context[MULTI_CACHE_PREFIX_KEY] = serverOptions.cacheKeyPrefix + event[MULTI_CACHE_PREFIX_KEY] = serverOptions.cacheKeyPrefix } else { - event.context[MULTI_CACHE_PREFIX_KEY] = - await serverOptions.cacheKeyPrefix(event) + event[MULTI_CACHE_PREFIX_KEY] = await serverOptions.cacheKeyPrefix(event) } } // Add the cache context object to the SSR context object. - event.context[MULTI_CACHE_CONTEXT_KEY] = cache + event[MULTI_CACHE_CONTEXT_KEY] = cache if (cache.route) { // Add the route cache helper. - event.context[MULTI_CACHE_ROUTE_CONTEXT_KEY] = - new NuxtMultiCacheRouteCacheHelper() + event[MULTI_CACHE_ROUTE_CONTEXT_KEY] = new NuxtMultiCacheRouteCacheHelper() } if (config.cdn.enabled) { const helper = new NuxtMultiCacheCDNHelper() // Add the instances to the H3 event context. - event.context[MULTI_CACHE_CDN_CONTEXT_KEY] = helper + event[MULTI_CACHE_CDN_CONTEXT_KEY] = helper } return cache @@ -180,13 +178,13 @@ export async function onRequest(event: H3Event) { // Mark the key as being revalidated. if (decoded.staleWhileRevalidate) { state.addKeyBeingRevalidated(fullKey) - event.context.__MULTI_CACHE_REVALIDATION_KEY = fullKey + event.__MULTI_CACHE_REVALIDATION_KEY = fullKey } if (decoded.staleIfErrorExpires) { // Store the decoded cache item in the event context. // May be used by the error hook handler to serve a stale route on error. - event.context.__MULTI_CACHE_DECODED_CACHED_ROUTE = decoded + event.__MULTI_CACHE_DECODED_CACHED_ROUTE = decoded } // Returning, so the route is revalidated. diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 852c13c..0bc5a65 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -307,7 +307,7 @@ declare module 'nitropack' { } declare module 'h3' { - export interface H3EventContext { + export interface H3Event { /** * The nuxt-multi-cache cache context. */ diff --git a/test/components/RenderCacheable/__helpers__/index.ts b/test/components/RenderCacheable/__helpers__/index.ts index d13671a..0affd26 100644 --- a/test/components/RenderCacheable/__helpers__/index.ts +++ b/test/components/RenderCacheable/__helpers__/index.ts @@ -33,21 +33,19 @@ export function createTestApp( const ssrContext = { event: { - context: { - __MULTI_CACHE: { - component: { - setItemRaw: (key: string, data: any) => { - if (key === 'InnerComponent::set_error') { - throw new Error('Failed to set item.') - } - storage[key] = data - }, - getItemRaw(key: string) { - if (key === 'InnerComponent::get_error') { - throw new Error('Failed to get item.') - } - return storage[key] - }, + __MULTI_CACHE: { + component: { + setItemRaw: (key: string, data: any) => { + if (key === 'InnerComponent::set_error') { + throw new Error('Failed to set item.') + } + storage[key] = data + }, + getItemRaw(key: string) { + if (key === 'InnerComponent::get_error') { + throw new Error('Failed to get item.') + } + return storage[key] }, }, }, diff --git a/test/composables/useCDNHeaders.nuxt.spec.ts b/test/composables/useCDNHeaders.nuxt.spec.ts index 38cc41c..d72fa8f 100644 --- a/test/composables/useCDNHeaders.nuxt.spec.ts +++ b/test/composables/useCDNHeaders.nuxt.spec.ts @@ -11,9 +11,7 @@ vi.mock('vue', async (importOriginal) => { useSSRContext: () => { return { event: { - context: { - __MULTI_CACHE_CDN: new NuxtMultiCacheCDNHelper(), - }, + __MULTI_CACHE_CDN: new NuxtMultiCacheCDNHelper(), }, } }, @@ -64,9 +62,7 @@ describe('useCDNHeaders composable', () => { expect(helper).toEqual(dummyHelper) }, { - context: { - __MULTI_CACHE_CDN: dummyHelper, - }, + __MULTI_CACHE_CDN: dummyHelper, } as any, ) }) diff --git a/test/composables/useDataCache.nuxt.spec.ts b/test/composables/useDataCache.nuxt.spec.ts index 73ade0f..d0e656b 100644 --- a/test/composables/useDataCache.nuxt.spec.ts +++ b/test/composables/useDataCache.nuxt.spec.ts @@ -28,16 +28,14 @@ vi.mock('vue', async (importOriginal) => { useSSRContext: () => { return { event: { - context: { - __MULTI_CACHE: { - data: { - getItem: (key: string) => { - return Promise.resolve(storage[key]) - }, - setItem: (key: string, data: any) => { - storage[key] = data - return Promise.resolve() - }, + __MULTI_CACHE: { + data: { + getItem: (key: string) => { + return Promise.resolve(storage[key]) + }, + setItem: (key: string, data: any) => { + storage[key] = data + return Promise.resolve() }, }, }, @@ -156,16 +154,14 @@ describe('useDataCache composable', () => { foobar: { data: 'More cached data.' }, } const event = { - context: { - __MULTI_CACHE: { - data: { - getItem: (key: string) => { - return Promise.resolve(storage[key]) - }, - setItem: (key: string, data: any) => { - storage[key] = data - return Promise.resolve() - }, + __MULTI_CACHE: { + data: { + getItem: (key: string) => { + return Promise.resolve(storage[key]) + }, + setItem: (key: string, data: any) => { + storage[key] = data + return Promise.resolve() }, }, }, @@ -180,12 +176,10 @@ describe('useDataCache composable', () => { const consoleSpy = vi.spyOn(global.console, 'debug') const event = { - context: { - __MULTI_CACHE: { - data: { - getItem: () => { - throw new Error('Failed to get item from cache.') - }, + __MULTI_CACHE: { + data: { + getItem: () => { + throw new Error('Failed to get item from cache.') }, }, }, diff --git a/test/composables/useRouteCache.nuxt.spec.ts b/test/composables/useRouteCache.nuxt.spec.ts index a72fe61..6f94d1d 100644 --- a/test/composables/useRouteCache.nuxt.spec.ts +++ b/test/composables/useRouteCache.nuxt.spec.ts @@ -24,20 +24,18 @@ vi.mock('vue', async (importOriginal) => { useSSRContext: () => { return { event: { - context: { - __MULTI_CACHE: { - data: { - getItem: (key: string) => { - return Promise.resolve(storage[key]) - }, - setItem: (key: string, data: any) => { - storage[key] = data - return Promise.resolve() - }, + __MULTI_CACHE: { + data: { + getItem: (key: string) => { + return Promise.resolve(storage[key]) + }, + setItem: (key: string, data: any) => { + storage[key] = data + return Promise.resolve() }, }, - __MULTI_CACHE_ROUTE: new NuxtMultiCacheRouteCacheHelper(), }, + __MULTI_CACHE_ROUTE: new NuxtMultiCacheRouteCacheHelper(), }, } }, @@ -78,9 +76,7 @@ describe('useRouteCache composable', () => { expect(helper).toEqual(dummyHelper) }, { - context: { - __MULTI_CACHE_ROUTE: dummyHelper, - }, + __MULTI_CACHE_ROUTE: dummyHelper, } as any, ) }) diff --git a/test/helpers/server.nuxt.spec.ts b/test/helpers/server.nuxt.spec.ts index 2a46e2b..bda56ed 100644 --- a/test/helpers/server.nuxt.spec.ts +++ b/test/helpers/server.nuxt.spec.ts @@ -6,26 +6,22 @@ import { } from './../../src/runtime/helpers/server' const EVENT: any = { - context: { - __MULTI_CACHE: { - component: { - getItem: () => {}, - }, + __MULTI_CACHE: { + component: { + getItem: () => {}, }, - __MULTI_CACHE_ROUTE: new NuxtMultiCacheRouteCacheHelper(), }, + __MULTI_CACHE_ROUTE: new NuxtMultiCacheRouteCacheHelper(), } describe('Server helpers', () => { test('getMultiCacheContext', () => { expect(getMultiCacheContext({} as any)).toBeUndefined() - expect(getMultiCacheContext(EVENT)).toEqual(EVENT.context.__MULTI_CACHE) + expect(getMultiCacheContext(EVENT)).toEqual(EVENT.__MULTI_CACHE) }) test('getMultiCacheRouteContext', () => { expect(getMultiCacheRouteHelper({} as any)).toBeUndefined() - expect(getMultiCacheRouteHelper(EVENT)).toEqual( - EVENT.context.__MULTI_CACHE_ROUTE, - ) + expect(getMultiCacheRouteHelper(EVENT)).toEqual(EVENT.__MULTI_CACHE_ROUTE) }) }) diff --git a/test/noContextSharing.e2e.spec.ts b/test/noContextSharing.e2e.spec.ts new file mode 100644 index 0000000..0439dff --- /dev/null +++ b/test/noContextSharing.e2e.spec.ts @@ -0,0 +1,56 @@ +import path from 'node:path' +import { setup, createPage } from '@nuxt/test-utils/e2e' +import { describe, expect, test } from 'vitest' +import type { NuxtMultiCacheOptions } from '../src/runtime/types' +import purgeAll from './__helpers__/purgeAll' + +const multiCache: NuxtMultiCacheOptions = { + component: { + enabled: true, + }, + data: { + enabled: true, + }, + route: { + enabled: true, + }, + cdn: { + enabled: true, + }, + api: { + enabled: true, + authorization: false, + cacheTagInvalidationDelay: 5000, + }, +} +const nuxtConfig: any = { + multiCache, +} +await setup({ + server: true, + logLevel: 0, + runner: 'vitest', + build: true, + // browser: true, + rootDir: path.resolve(__dirname, './../playground'), + nuxtConfig, +}) + +describe('The multi cache context', () => { + test('is not shared in the same event context', async () => { + await purgeAll() + + // Should put it in cache, because the page itself is cacheable. + const page = await createPage('/cachedPageWithUncacheableApi') + + // The value. + const dataA = await page.locator('#api-data').innerText() + + // Reload the page. It should be served from cache, even if the API call + // it's doing is uncacheable. + await page.reload() + const dataB = await page.locator('#api-data').innerText() + + expect(dataB).toEqual(dataA) + }) +}) diff --git a/test/server/api/inspectItem.nuxt.spec.ts b/test/server/api/inspectItem.nuxt.spec.ts index 7a308e2..c0e3363 100644 --- a/test/server/api/inspectItem.nuxt.spec.ts +++ b/test/server/api/inspectItem.nuxt.spec.ts @@ -31,7 +31,7 @@ vi.mock('./../../../src/runtime/serverHandler/api/helpers', () => { }, getCacheInstance: (event: any) => { const cache = event.__CACHE_NAME - return event.context.__MULTI_CACHE[cache] + return event.__MULTI_CACHE[cache] }, } }) diff --git a/test/server/api/purgeItem.nuxt.spec.ts b/test/server/api/purgeItem.nuxt.spec.ts index ef0c3f7..0b49e36 100644 --- a/test/server/api/purgeItem.nuxt.spec.ts +++ b/test/server/api/purgeItem.nuxt.spec.ts @@ -30,7 +30,7 @@ vi.mock('./../../../src/runtime/serverHandler/api/helpers', () => { return Promise.resolve() }, getCacheInstance: (event: any) => { - return event.context.__MULTI_CACHE.data + return event.__MULTI_CACHE.data }, } }) @@ -84,10 +84,8 @@ describe('purgeItem API handler', () => { const storageData = createStorage() const event: any = { - context: { - __MULTI_CACHE: { - data: storageData, - }, + __MULTI_CACHE: { + data: storageData, }, } expect(purgeItem(event)).rejects.toThrowErrorMatchingInlineSnapshot( diff --git a/test/server/api/purgeTags.nuxt.spec.ts b/test/server/api/purgeTags.nuxt.spec.ts index 44c18f2..b476ae2 100644 --- a/test/server/api/purgeTags.nuxt.spec.ts +++ b/test/server/api/purgeTags.nuxt.spec.ts @@ -128,10 +128,8 @@ describe('purgeTags API handler', () => { expect( purgeTags({ - context: { - __MULTI_CACHE: { - data: storageData, - }, + __MULTI_CACHE: { + data: storageData, }, } as any), ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -140,10 +138,8 @@ describe('purgeTags API handler', () => { expect( purgeTags({ - context: { - __MULTI_CACHE: { - data: storageData, - }, + __MULTI_CACHE: { + data: storageData, }, body: 'Invalid body', } as any), diff --git a/test/server/api/stats.nuxt.spec.ts b/test/server/api/stats.nuxt.spec.ts index 1484750..ab8758e 100644 --- a/test/server/api/stats.nuxt.spec.ts +++ b/test/server/api/stats.nuxt.spec.ts @@ -27,7 +27,7 @@ vi.mock('./../../../src/runtime/serverHandler/api/helpers', () => { return Promise.resolve() }, getCacheInstance: (event: any) => { - return event.context.__MULTI_CACHE.data + return event.__MULTI_CACHE.data }, } }) diff --git a/test/server/plugin/hooks/afterResponse.nuxt.spec.ts b/test/server/plugin/hooks/afterResponse.nuxt.spec.ts index 596f3b1..1d4ddb4 100644 --- a/test/server/plugin/hooks/afterResponse.nuxt.spec.ts +++ b/test/server/plugin/hooks/afterResponse.nuxt.spec.ts @@ -59,13 +59,11 @@ describe('afterResponse nitro hook handler', () => { expect( await onAfterResponse( { - context: { - [MULTI_CACHE_CONTEXT_KEY]: { - route: {}, - }, - [MULTI_CACHE_ROUTE_CONTEXT_KEY]: - new NuxtMultiCacheRouteCacheHelper().setUncacheable(), + [MULTI_CACHE_CONTEXT_KEY]: { + route: {}, }, + [MULTI_CACHE_ROUTE_CONTEXT_KEY]: + new NuxtMultiCacheRouteCacheHelper().setUncacheable(), } as any, { body: '' } as any, ), @@ -76,13 +74,11 @@ describe('afterResponse nitro hook handler', () => { expect( await onAfterResponse( { - context: { - [MULTI_CACHE_CONTEXT_KEY]: { - route: {}, - }, - [MULTI_CACHE_ROUTE_CONTEXT_KEY]: - new NuxtMultiCacheRouteCacheHelper().setCacheable(), + [MULTI_CACHE_CONTEXT_KEY]: { + route: {}, }, + [MULTI_CACHE_ROUTE_CONTEXT_KEY]: + new NuxtMultiCacheRouteCacheHelper().setCacheable(), node: { res: { @@ -121,19 +117,17 @@ describe('afterResponse nitro hook handler', () => { const event = { path: '/foobar', - context: { - [MULTI_CACHE_CONTEXT_KEY]: { - route: { - setItemRaw(key, item, options) { - storedItems.push({ key, item, options }) - return Promise.resolve() - }, + [MULTI_CACHE_CONTEXT_KEY]: { + route: { + setItemRaw(key, item, options) { + storedItems.push({ key, item, options }) + return Promise.resolve() }, }, - [MULTI_CACHE_ROUTE_CONTEXT_KEY]: new NuxtMultiCacheRouteCacheHelper() - .setCacheable() - .setMaxAge(1200), }, + [MULTI_CACHE_ROUTE_CONTEXT_KEY]: new NuxtMultiCacheRouteCacheHelper() + .setCacheable() + .setMaxAge(1200), node: { res: { diff --git a/test/server/plugin/hooks/beforeResponse.nuxt.spec.ts b/test/server/plugin/hooks/beforeResponse.nuxt.spec.ts index 835e0a2..97fecde 100644 --- a/test/server/plugin/hooks/beforeResponse.nuxt.spec.ts +++ b/test/server/plugin/hooks/beforeResponse.nuxt.spec.ts @@ -1,12 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import { mockNuxtImport } from '@nuxt/test-utils/runtime' import { onBeforeResponse } from '../../../../dist/runtime/server/hooks/beforeResponse' -import { - MULTI_CACHE_CDN_CONTEXT_KEY, - MULTI_CACHE_CONTEXT_KEY, - MULTI_CACHE_ROUTE_CONTEXT_KEY, -} from '../../../../dist/runtime/helpers/server' -import { NuxtMultiCacheRouteCacheHelper } from '../../../../dist/runtime/helpers/RouteCacheHelper' +import { MULTI_CACHE_CDN_CONTEXT_KEY } from '../../../../dist/runtime/helpers/server' import { NuxtMultiCacheCDNHelper } from '../../../../dist/runtime/helpers/CDNHelper' mockNuxtImport('useRuntimeConfig', () => { @@ -70,9 +65,7 @@ describe('beforeResponse nitro hook handler', () => { const event = { path: '/foobar', - context: { - [MULTI_CACHE_CDN_CONTEXT_KEY]: cdnHelper, - }, + [MULTI_CACHE_CDN_CONTEXT_KEY]: cdnHelper, node: { res: {