diff --git a/playground/api/cache.ts b/playground/api/cache.ts index f96c2111c6..10b3f77252 100644 --- a/playground/api/cache.ts +++ b/playground/api/cache.ts @@ -1,8 +1,8 @@ -import { cachifyHandle } from '#nitro/cache' +import { cachedEventHandler } from '#nitro' const waitFor = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) -export default cachifyHandle(async () => { +export default cachedEventHandler(async () => { await waitFor(2000) return 'Response generated after 2 seconds at ' + new Date() }, { swr: true }) diff --git a/src/runtime/cache.ts b/src/runtime/cache.ts index e42bac2a2e..76a849e923 100644 --- a/src/runtime/cache.ts +++ b/src/runtime/cache.ts @@ -1,18 +1,19 @@ import { hash } from 'ohash' -import type { Handler } from 'h3' -import { storage } from '#nitro/virtual/storage' +import { H3Response, toEventHandler } from 'h3' +import type { CompatibilityEventHandler, CompatibilityEvent } from 'h3' +import { storage } from '#nitro' -export interface CacheEntry { - value?: any +export interface CacheEntry { + value?: T expires?: number mtime?: number integrity?: string } -export interface CachifyOptions { +export interface CachifyOptions { name?: string getKey?: (...args: any[]) => string - transform?: (entry: CacheEntry, ...args: any[]) => any + transform?: (entry: CacheEntry, ...args: any[]) => any group?: string integrity?: any ttl?: number @@ -27,20 +28,19 @@ const defaultCacheOptions = { ttl: 1 } -export function cachify (fn: ((...args) => any), opts: CachifyOptions) { +export function defineCachedFunction (fn: ((...args) => T | Promise), opts: CachifyOptions) { opts = { ...defaultCacheOptions, ...opts } - const pending: { [key: string]: Promise } = {} + const pending: { [key: string]: Promise } = {} // Normalize cache params const group = opts.group || '' const name = opts.name || fn.name || '_' - const integrity = hash(opts.integrity || fn) + const integrity = hash([opts.integrity, fn, opts]) - async function get (key: string, resolver: () => any) { + async function get (key: string, resolver: () => T | Promise): Promise> { const cacheKey = [opts.base, group, name, key].filter(Boolean).join(':') - // TODO: improve unstorage types - const entry: CacheEntry = await storage.getItem(cacheKey) as any || {} + const entry: CacheEntry = await storage.getItem(cacheKey) as any || {} const ttl = (opts.ttl ?? opts.ttl ?? 0) * 1000 if (ttl) { @@ -51,14 +51,13 @@ export function cachify (fn: ((...args) => any), opts: CachifyOptions) { const _resolve = async () => { if (!pending[key]) { - pending[key] = resolver() + pending[key] = Promise.resolve(resolver()) } entry.value = await pending[key] entry.mtime = Date.now() entry.integrity = integrity delete pending[key] - // eslint-disable-next-line no-console - storage.setItem(cacheKey, entry).catch(console.error) + storage.setItem(cacheKey, entry).catch(error => console.error('[nitro] [cache]', error)) } const _resolvePromise = expired ? _resolve() : Promise.resolve() @@ -83,16 +82,30 @@ export function cachify (fn: ((...args) => any), opts: CachifyOptions) { } } +export const cachedFunction = defineCachedFunction + function getKey (...args: string[]) { return args.length ? hash(args, {}) : '' } -export function cachifyHandle (handler: Handler, opts: Omit = defaultCacheOptions) { - const _opts: CachifyOptions = { +export function defineCachedEventHandler (handler: CompatibilityEventHandler, opts: Omit = defaultCacheOptions) { + interface ResponseCacheEntry { + body: H3Response + code: number + headers: Record + } + + const _opts: CachifyOptions = { + ...opts, getKey: req => req.originalUrl || req.url, - transform (entry, _req, res) { + group: opts.group || 'handlers', + integrity: [ + opts.integrity, + handler + ], + transform (entry, event: CompatibilityEvent) { for (const header in entry.value.headers) { - res.setHeader(header, entry.value.headers[header]) + event.res.setHeader(header, entry.value.headers[header]) } const cacheControl = [] if (opts.swr) { @@ -104,16 +117,26 @@ export function cachifyHandle (handler: Handler, opts: Omit { - const body = await handler(req, res) - const headers = res.getHeaders() - return { body, headers } + const _handler = toEventHandler(handler) + return cachedFunction(async (event: CompatibilityEvent) => { + const body = await _handler(event) + const headers = event.res.getHeaders() + const cacheEntry: ResponseCacheEntry = { + code: event.res.statusCode, + headers, + body + } + return cacheEntry }, _opts) } + +export const cachedEventHandler = defineCachedEventHandler diff --git a/src/runtime/config.ts b/src/runtime/config.ts index 11f3bc8854..a58a14f5d8 100644 --- a/src/runtime/config.ts +++ b/src/runtime/config.ts @@ -26,6 +26,7 @@ export const privateConfig = deepFreeze(defu(_runtimeConfig.private, _runtimeCon export const publicConfig = deepFreeze(_runtimeConfig.public) // Default export (usable for server) +export const config = privateConfig export default privateConfig // Utils