diff --git a/src/module.ts b/src/module.ts index fae4cb0bd..fe47483ee 100644 --- a/src/module.ts +++ b/src/module.ts @@ -278,6 +278,9 @@ export default defineNuxtModule({ const { resolve } = createResolver(import.meta.url) const resolveRuntimeModule = (path: string) => resolveModule(path, { paths: resolve('./runtime') }) + // Disable cache in dev mode + const buildIntegrity = nuxt.options.dev ? undefined : Date.now() + if (options.base) { logger.warn('content.base is deprecated. Use content.api.baseURL instead.') options.api.baseURL = withLeadingSlash(joinURL('api', options.base)) @@ -326,7 +329,7 @@ export default defineNuxtModule({ ) if (!nuxt.options.dev) { - nitroConfig.prerender.routes.unshift(`${options.api.baseURL}/cache.json`) + nitroConfig.prerender.routes.unshift(`${options.api.baseURL}/cache.${buildIntegrity}.json`) } // Register source storages @@ -583,10 +586,9 @@ export default defineNuxtModule({ contentContext.markdown = processMarkdownOptions(contentContext.markdown) nuxt.options.runtimeConfig.public.content = defu(nuxt.options.runtimeConfig.public.content, { + integrity: buildIntegrity, clientDB: { - isSPA: options.experimental.clientDB && nuxt.options.ssr === false, - // Disable cache in dev mode - integrity: nuxt.options.dev ? undefined : Date.now() + isSPA: options.experimental.clientDB && nuxt.options.ssr === false }, api: { baseURL: options.api.baseURL diff --git a/src/runtime/composables/client-db.ts b/src/runtime/composables/client-db.ts index ef8940ed7..82b727268 100644 --- a/src/runtime/composables/client-db.ts +++ b/src/runtime/composables/client-db.ts @@ -9,7 +9,7 @@ import { createQuery } from '../query/query' import type { NavItem, ParsedContent, ParsedContentMeta, QueryBuilderParams } from '../types' import { createNav } from '../server/navigation' -const withContentBase = url => withBase(url, useRuntimeConfig().public.content.api.baseURL) +const withContentBase = (url: string) => withBase(url, useRuntimeConfig().public.content.api.baseURL) export const contentStorage = prefixStorage(createStorage({ driver: memoryDriver() }), '@content') @@ -49,8 +49,8 @@ export function createDB (storage: Storage) { } } -let contentDatabase -let contentDatabaseInitPromise +let contentDatabase: ReturnType | null = null +let contentDatabaseInitPromise: ReturnType | null = null export async function useContentDatabase () { if (contentDatabaseInitPromise) { await contentDatabaseInitPromise @@ -58,7 +58,7 @@ export async function useContentDatabase () { contentDatabaseInitPromise = initContentDatabase() contentDatabase = await contentDatabaseInitPromise } - return contentDatabase + return contentDatabase! } /** @@ -68,20 +68,20 @@ export async function useContentDatabase () { */ async function initContentDatabase () { const nuxtApp = useNuxtApp() - const { clientDB } = useRuntimeConfig().public.content + const { content } = useRuntimeConfig().public const _contentDatabase = createDB(contentStorage) const integrity = await _contentDatabase.storage.getItem('integrity') - if (clientDB.integrity !== +integrity) { - const { contents, navigation } = await $fetch(withContentBase('cache.json')) as any + if (content.integrity !== +(integrity || 0)) { + const { contents, navigation } = await $fetch(withContentBase(`cache.${content.integrity}.json`)) as any await Promise.all( - contents.map(content => _contentDatabase.storage.setItem(`cache:${content._id}`, content)) + contents.map((content: ParsedContent) => _contentDatabase.storage.setItem(`cache:${content._id}`, content)) ) await _contentDatabase.storage.setItem('navigation', navigation) - await _contentDatabase.storage.setItem('integrity', clientDB.integrity) + await _contentDatabase.storage.setItem('integrity', content.integrity) } // call `content:storage` hook to allow plugins to fill storage @@ -91,11 +91,11 @@ async function initContentDatabase () { return _contentDatabase } -export async function generateNavigation (query): Promise> { +export async function generateNavigation (query?: QueryBuilderParams): Promise> { const db = await useContentDatabase() if (!getPreview() && Object.keys(query || {}).length === 0) { - return db.storage.getItem('navigation') + return db.storage.getItem('navigation') as Promise> } const contents = await db.query(query) @@ -116,11 +116,11 @@ export async function generateNavigation (query): Promise> { const dirConfigs = await db.query().where({ _path: /\/_dir$/i, _partial: true }).find() - const configs = dirConfigs.reduce((configs, conf) => { - if (conf.title.toLowerCase() === 'dir') { + const configs = dirConfigs.reduce((configs, conf: ParsedContent) => { + if (conf.title?.toLowerCase() === 'dir') { conf.title = undefined } - const key = conf._path.split('/').slice(0, -1).join('/') || '/' + const key = conf._path!.split('/').slice(0, -1).join('/') || '/' configs[key] = { ...conf, // Extract meta from body. (non MD files) diff --git a/src/runtime/composables/navigation.ts b/src/runtime/composables/navigation.ts index a8a1e2924..9efc68cd0 100644 --- a/src/runtime/composables/navigation.ts +++ b/src/runtime/composables/navigation.ts @@ -1,16 +1,16 @@ import { hash } from 'ohash' -import { useCookie } from '#app' +import { useCookie, useRuntimeConfig } from '#app' import type { NavItem, QueryBuilder, QueryBuilderParams } from '../types' import { jsonStringify } from '../utils/json' import { addPrerenderPath, shouldUseClientDB, withContentBase } from './utils' export const fetchContentNavigation = async (queryBuilder?: QueryBuilder | QueryBuilderParams): Promise> => { - let params = queryBuilder + const { content } = useRuntimeConfig().public // When params is an instance of QueryBuilder then we need to pick the params explicitly - if (typeof params?.params === 'function') { params = params.params() } + const params: QueryBuilderParams = typeof queryBuilder?.params === 'function' ? queryBuilder.params() : queryBuilder - const apiPath = withContentBase(params ? `/navigation/${hash(params)}.json` : '/navigation') + const apiPath = withContentBase(params ? `/navigation/${hash(params)}.${content.integerity}.json` : '/navigation') // Add `prefetch` to `` in production if (!process.dev && process.server) { @@ -19,10 +19,10 @@ export const fetchContentNavigation = async (queryBuilder?: QueryBuilder | Query if (shouldUseClientDB()) { const generateNavigation = await import('./client-db').then(m => m.generateNavigation) - return generateNavigation(params || {}) + return generateNavigation(params) } - const data = await $fetch(apiPath, { + const data = await $fetch(apiPath, { method: 'GET', responseType: 'json', params: { @@ -33,7 +33,7 @@ export const fetchContentNavigation = async (queryBuilder?: QueryBuilder | Query // On SSG, all url are redirected to `404.html` when not found, so we need to check the content type // to know if the response is a valid JSON or not - if (typeof data === 'string' && data.startsWith('')) { + if (typeof data === 'string' && (data as string).startsWith('')) { throw new Error('Not found') } diff --git a/src/runtime/composables/query.ts b/src/runtime/composables/query.ts index 5b28cdf94..76916194b 100644 --- a/src/runtime/composables/query.ts +++ b/src/runtime/composables/query.ts @@ -1,6 +1,6 @@ import { joinURL, withLeadingSlash, withoutTrailingSlash } from 'ufo' import { hash } from 'ohash' -import { useCookie } from '#app' +import { useCookie, useRuntimeConfig } from '#app' import { createQuery } from '../query/query' import type { ParsedContent, QueryBuilder, QueryBuilderParams } from '../types' import { jsonStringify } from '../utils/json' @@ -10,6 +10,7 @@ import { addPrerenderPath, shouldUseClientDB, withContentBase } from './utils' * Query fetcher */ export const createQueryFetch = (path?: string) => async (query: QueryBuilder) => { + const { content } = useRuntimeConfig().public if (path) { if (query.params().first && (query.params().where || []).length === 0) { // If query contains `path` and does not contain any `where` condition @@ -26,7 +27,7 @@ export const createQueryFetch = (path?: string) => async (que const params = query.params() - const apiPath = withContentBase(process.dev ? '/query' : `/query/${hash(params)}.json`) + const apiPath = withContentBase(process.dev ? '/query' : `/query/${hash(params)}.${content.integerity}.json`) // Prefetch the query if (!process.dev && process.server) { @@ -35,7 +36,7 @@ export const createQueryFetch = (path?: string) => async (que if (shouldUseClientDB()) { const db = await import('./client-db').then(m => m.useContentDatabase()) - return db.fetch(query) + return db.fetch(query as QueryBuilder) } const data = await $fetch(apiPath as any, { diff --git a/src/runtime/server/api/highlight.ts b/src/runtime/server/api/highlight.ts index 39f1d8579..307ec4895 100644 --- a/src/runtime/server/api/highlight.ts +++ b/src/runtime/server/api/highlight.ts @@ -30,9 +30,9 @@ const resolveTheme = (theme: string | Record): Record { - acc[key] = BUNDLED_THEMES.find(t => t === value) + acc[key] = BUNDLED_THEMES.find(t => t === value)! return acc - }, {}) + }, {} as Record) } /** diff --git a/src/runtime/server/api/navigation.ts b/src/runtime/server/api/navigation.ts index dcfd381f2..73e6539b9 100644 --- a/src/runtime/server/api/navigation.ts +++ b/src/runtime/server/api/navigation.ts @@ -1,7 +1,7 @@ import { defineEventHandler } from 'h3' import { cacheStorage, serverQueryContent } from '../storage' import { createNav } from '../navigation' -import { ParsedContentMeta } from '../../types' +import { ParsedContent, ParsedContentMeta } from '../../types' import { getContentQuery } from '../../utils/query' import { isPreview } from '../preview' @@ -34,11 +34,11 @@ export default defineEventHandler(async (event) => { const dirConfigs = await serverQueryContent(event).where({ _path: /\/_dir$/i, _partial: true }).find() - const configs = dirConfigs.reduce((configs, conf) => { - if (conf.title.toLowerCase() === 'dir') { + const configs = dirConfigs.reduce((configs, conf: ParsedContent) => { + if (conf.title?.toLowerCase() === 'dir') { conf.title = undefined } - const key = conf._path.split('/').slice(0, -1).join('/') || '/' + const key = conf._path!.split('/').slice(0, -1).join('/') || '/' configs[key] = { ...conf, // Extract meta from body. (non MD files)