Skip to content

Commit

Permalink
fix(client-db): race-condition on multiple calls
Browse files Browse the repository at this point in the history
  • Loading branch information
farnabaz committed Oct 10, 2022
1 parent a3376ef commit c11a480
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 37 deletions.
4 changes: 2 additions & 2 deletions src/module.ts
Expand Up @@ -540,11 +540,11 @@ export default defineNuxtModule<ModuleOptions>({
})

// Context will use in server
nuxt.options.runtimeConfig.content = {
nuxt.options.runtimeConfig.content = defu(nuxt.options.runtimeConfig.content, {
cacheVersion: CACHE_VERSION,
cacheIntegrity,
...contentContext as any
}
})

// @nuxtjs/tailwindcss support
// @ts-ignore - Module might not exist
Expand Down
55 changes: 38 additions & 17 deletions src/runtime/composables/client-db.ts
Expand Up @@ -2,7 +2,7 @@ import type { Storage } from 'unstorage'
// @ts-ignore
import memoryDriver from 'unstorage/drivers/memory'
import { createStorage, prefixStorage } from 'unstorage'
import { useRuntimeConfig, useCookie } from '#app'
import { useRuntimeConfig, useCookie, useNuxtApp } from '#app'
import { withBase } from 'ufo'
import { createPipelineFetcher } from '../query/match/pipeline'
import { createQuery } from '../query/query'
Expand Down Expand Up @@ -39,8 +39,8 @@ export function createDB (storage: Storage) {
}
}
}

return Promise.all(Array.from(keys).map(key => storage.getItem(key) as Promise<ParsedContent>))
const items = await Promise.all(Array.from(keys).map(key => storage.getItem(key) as Promise<ParsedContent>))
return items
}
return {
storage,
Expand All @@ -50,24 +50,45 @@ export function createDB (storage: Storage) {
}

let contentDatabase
let contentDatabaseInitPromise
export async function useContentDatabase () {
if (!contentDatabase) {
const { clientDB } = useRuntimeConfig().public.content
contentDatabase = createDB(contentStorage)
const integrity = await contentDatabase.storage.getItem('integrity')
if (clientDB.integrity !== +integrity) {
const { contents, navigation } = await $fetch(withContentBase('cache.json'))

for (const content of contents) {
await contentDatabase.storage.setItem(`cache:${content._id}`, content)
}
if (contentDatabaseInitPromise) {
await contentDatabaseInitPromise
} else if (!contentDatabase) {
contentDatabaseInitPromise = initContentDatabase()
contentDatabase = await contentDatabaseInitPromise
}
return contentDatabase
}

await contentDatabase.storage.setItem('navigation', navigation)
/**
* Initialize content database
* - Fetch content from cache api
* - Call `content:storage` hook to allow plugins to fill storage
*/
async function initContentDatabase () {
const nuxtApp = useNuxtApp()
const { clientDB } = useRuntimeConfig().public.content

await contentDatabase.storage.setItem('integrity', clientDB.integrity)
}
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

await Promise.all(
contents.map(content => _contentDatabase.storage.setItem(`cache:${content._id}`, content))
)

await _contentDatabase.storage.setItem('navigation', navigation)

await _contentDatabase.storage.setItem('integrity', clientDB.integrity)
}
return contentDatabase

// call `content:storage` hook to allow plugins to fill storage
// @ts-ignore
await nuxtApp.callHook('content:storage', _contentDatabase.storage)

return _contentDatabase
}

export async function generateNavigation (query): Promise<Array<NavItem>> {
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/composables/helpers.ts
Expand Up @@ -48,7 +48,7 @@ const navKeyFromPath = (path: string, key: string, tree: NavItem[]) => {

const goDeep = (path: string, tree: NavItem[]) => {
for (const file of tree) {
if (path.startsWith(file._path) && file[key]) { value = file[key] }
if (path?.startsWith(file._path) && file[key]) { value = file[key] }

if (file._path === path) { return }

Expand Down
40 changes: 23 additions & 17 deletions src/runtime/preview/preview-plugin.ts
@@ -1,12 +1,11 @@
import { createApp } from 'vue'
import { contentStorage } from '../composables/client-db'
import { defineNuxtPlugin, useRoute, useCookie, refreshNuxtData, useRuntimeConfig } from '#imports'
import { defineNuxtPlugin, useRoute, useCookie, refreshNuxtData, useRuntimeConfig, useNuxtApp } from '#imports'
import { ContentPreviewMode } from '#components'

export default defineNuxtPlugin((nuxt) => {
const { previewAPI } = useRuntimeConfig().public.content

async function fetchData (token: string) {
async function fetchData (token: string, contentStorage) {
// Fetch preview data from station
const data = await $fetch('api/projects/preview', {
baseURL: previewAPI,
Expand All @@ -16,14 +15,7 @@ export default defineNuxtPlugin((nuxt) => {
})
// Remove previous preview data
const keys = await contentStorage.getKeys(`${token}:`)
keys.forEach(key => contentStorage.removeItem(key))

// Fill store with preview content
const items = [
...(data.files || []),
...data.additions,
...data.deletions.map(d => ({ ...d, parsed: { _id: d.path.replace(/\//g, ':'), __deleted: true } }))
]
await Promise.all(keys.map(key => contentStorage.removeItem(key)))

// Set preview meta
await contentStorage.setItem(
Expand All @@ -33,12 +25,21 @@ export default defineNuxtPlugin((nuxt) => {
})
)

for (const item of items) {
await contentStorage.setItem(`${token}:${item.parsed._id}`, JSON.stringify(item.parsed))
}
// Fill store with preview content
const items = [
...(data.files || []),
...data.additions,
...data.deletions.map(d => ({ ...d, parsed: { _id: d.path.replace(/\//g, ':'), __deleted: true } }))
]

await Promise.all(
items.map(item => contentStorage.setItem(`${token}:${item.parsed._id}`, JSON.stringify(item.parsed)))
)
}

async function initializePreview () {
function initializePreview () {
let contentStorage
const nuxtApp = useNuxtApp()
const query = useRoute().query || {}
const previewToken = useCookie('previewToken', { sameSite: 'none', secure: true })
if (!query.preview && !previewToken.value) {
Expand All @@ -55,10 +56,15 @@ export default defineNuxtPlugin((nuxt) => {
createApp(ContentPreviewMode, {
previewToken,
apiURL: previewAPI,
onRefresh: () => fetchData(previewToken.value).then(() => refreshNuxtData())
onRefresh: () => fetchData(previewToken.value, contentStorage).then(() => refreshNuxtData())
}).mount(el)

await fetchData(previewToken.value)
// @ts-ignore
nuxtApp.hook('content:storage', async (storage) => {
contentStorage = storage
await fetchData(previewToken.value, contentStorage)
refreshNuxtData()
})
}

nuxt.hook('app:mounted', async () => {
Expand Down

0 comments on commit c11a480

Please sign in to comment.