Skip to content

Commit

Permalink
feat: unique api calls per build (#1705)
Browse files Browse the repository at this point in the history
  • Loading branch information
farnabaz committed Dec 1, 2022
1 parent 4803a82 commit 3e2e6eb
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 34 deletions.
10 changes: 6 additions & 4 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ export default defineNuxtModule<ModuleOptions>({
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))
Expand Down Expand Up @@ -326,7 +329,7 @@ export default defineNuxtModule<ModuleOptions>({
)

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
Expand Down Expand Up @@ -583,10 +586,9 @@ export default defineNuxtModule<ModuleOptions>({
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
Expand Down
28 changes: 14 additions & 14 deletions src/runtime/composables/client-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -49,16 +49,16 @@ export function createDB (storage: Storage) {
}
}

let contentDatabase
let contentDatabaseInitPromise
let contentDatabase: ReturnType<typeof createDB> | null = null
let contentDatabaseInitPromise: ReturnType<typeof initContentDatabase> | null = null
export async function useContentDatabase () {
if (contentDatabaseInitPromise) {
await contentDatabaseInitPromise
} else if (!contentDatabase) {
contentDatabaseInitPromise = initContentDatabase()
contentDatabase = await contentDatabaseInitPromise
}
return contentDatabase
return contentDatabase!
}

/**
Expand All @@ -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
Expand All @@ -91,11 +91,11 @@ async function initContentDatabase () {
return _contentDatabase
}

export async function generateNavigation (query): Promise<Array<NavItem>> {
export async function generateNavigation (query?: QueryBuilderParams): Promise<Array<NavItem>> {
const db = await useContentDatabase()

if (!getPreview() && Object.keys(query || {}).length === 0) {
return db.storage.getItem('navigation')
return db.storage.getItem('navigation') as Promise<Array<NavItem>>
}

const contents = await db.query(query)
Expand All @@ -116,11 +116,11 @@ export async function generateNavigation (query): Promise<Array<NavItem>> {

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)
Expand Down
14 changes: 7 additions & 7 deletions src/runtime/composables/navigation.ts
Original file line number Diff line number Diff line change
@@ -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<Array<NavItem>> => {
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 `<head>` in production
if (!process.dev && process.server) {
Expand All @@ -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<NavItem[]>(apiPath, {
method: 'GET',
responseType: 'json',
params: {
Expand All @@ -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('<!DOCTYPE html>')) {
if (typeof data === 'string' && (data as string).startsWith('<!DOCTYPE html>')) {
throw new Error('Not found')
}

Expand Down
7 changes: 4 additions & 3 deletions src/runtime/composables/query.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -10,6 +10,7 @@ import { addPrerenderPath, shouldUseClientDB, withContentBase } from './utils'
* Query fetcher
*/
export const createQueryFetch = <T = ParsedContent>(path?: string) => async (query: QueryBuilder<T>) => {
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
Expand All @@ -26,7 +27,7 @@ export const createQueryFetch = <T = ParsedContent>(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) {
Expand All @@ -35,7 +36,7 @@ export const createQueryFetch = <T = ParsedContent>(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<ParsedContent>)
}

const data = await $fetch(apiPath as any, {
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/server/api/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ const resolveTheme = (theme: string | Record<string, string>): Record<string, Th
}

return Object.entries(theme).reduce((acc, [key, value]) => {
acc[key] = BUNDLED_THEMES.find(t => t === value)
acc[key] = BUNDLED_THEMES.find(t => t === value)!
return acc
}, {})
}, {} as Record<string, Theme>)
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/runtime/server/api/navigation.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 3e2e6eb

Please sign in to comment.