Skip to content

Commit c11a480

Browse files
committed
fix(client-db): race-condition on multiple calls
1 parent a3376ef commit c11a480

File tree

4 files changed

+64
-37
lines changed

4 files changed

+64
-37
lines changed

src/module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,11 @@ export default defineNuxtModule<ModuleOptions>({
540540
})
541541

542542
// Context will use in server
543-
nuxt.options.runtimeConfig.content = {
543+
nuxt.options.runtimeConfig.content = defu(nuxt.options.runtimeConfig.content, {
544544
cacheVersion: CACHE_VERSION,
545545
cacheIntegrity,
546546
...contentContext as any
547-
}
547+
})
548548

549549
// @nuxtjs/tailwindcss support
550550
// @ts-ignore - Module might not exist

src/runtime/composables/client-db.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Storage } from 'unstorage'
22
// @ts-ignore
33
import memoryDriver from 'unstorage/drivers/memory'
44
import { createStorage, prefixStorage } from 'unstorage'
5-
import { useRuntimeConfig, useCookie } from '#app'
5+
import { useRuntimeConfig, useCookie, useNuxtApp } from '#app'
66
import { withBase } from 'ufo'
77
import { createPipelineFetcher } from '../query/match/pipeline'
88
import { createQuery } from '../query/query'
@@ -39,8 +39,8 @@ export function createDB (storage: Storage) {
3939
}
4040
}
4141
}
42-
43-
return Promise.all(Array.from(keys).map(key => storage.getItem(key) as Promise<ParsedContent>))
42+
const items = await Promise.all(Array.from(keys).map(key => storage.getItem(key) as Promise<ParsedContent>))
43+
return items
4444
}
4545
return {
4646
storage,
@@ -50,24 +50,45 @@ export function createDB (storage: Storage) {
5050
}
5151

5252
let contentDatabase
53+
let contentDatabaseInitPromise
5354
export async function useContentDatabase () {
54-
if (!contentDatabase) {
55-
const { clientDB } = useRuntimeConfig().public.content
56-
contentDatabase = createDB(contentStorage)
57-
const integrity = await contentDatabase.storage.getItem('integrity')
58-
if (clientDB.integrity !== +integrity) {
59-
const { contents, navigation } = await $fetch(withContentBase('cache.json'))
60-
61-
for (const content of contents) {
62-
await contentDatabase.storage.setItem(`cache:${content._id}`, content)
63-
}
55+
if (contentDatabaseInitPromise) {
56+
await contentDatabaseInitPromise
57+
} else if (!contentDatabase) {
58+
contentDatabaseInitPromise = initContentDatabase()
59+
contentDatabase = await contentDatabaseInitPromise
60+
}
61+
return contentDatabase
62+
}
6463

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

67-
await contentDatabase.storage.setItem('integrity', clientDB.integrity)
68-
}
73+
const _contentDatabase = createDB(contentStorage)
74+
const integrity = await _contentDatabase.storage.getItem('integrity')
75+
if (clientDB.integrity !== +integrity) {
76+
const { contents, navigation } = await $fetch(withContentBase('cache.json')) as any
77+
78+
await Promise.all(
79+
contents.map(content => _contentDatabase.storage.setItem(`cache:${content._id}`, content))
80+
)
81+
82+
await _contentDatabase.storage.setItem('navigation', navigation)
83+
84+
await _contentDatabase.storage.setItem('integrity', clientDB.integrity)
6985
}
70-
return contentDatabase
86+
87+
// call `content:storage` hook to allow plugins to fill storage
88+
// @ts-ignore
89+
await nuxtApp.callHook('content:storage', _contentDatabase.storage)
90+
91+
return _contentDatabase
7192
}
7293

7394
export async function generateNavigation (query): Promise<Array<NavItem>> {

src/runtime/composables/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const navKeyFromPath = (path: string, key: string, tree: NavItem[]) => {
4848

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

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

src/runtime/preview/preview-plugin.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { createApp } from 'vue'
2-
import { contentStorage } from '../composables/client-db'
3-
import { defineNuxtPlugin, useRoute, useCookie, refreshNuxtData, useRuntimeConfig } from '#imports'
2+
import { defineNuxtPlugin, useRoute, useCookie, refreshNuxtData, useRuntimeConfig, useNuxtApp } from '#imports'
43
import { ContentPreviewMode } from '#components'
54

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

9-
async function fetchData (token: string) {
8+
async function fetchData (token: string, contentStorage) {
109
// Fetch preview data from station
1110
const data = await $fetch('api/projects/preview', {
1211
baseURL: previewAPI,
@@ -16,14 +15,7 @@ export default defineNuxtPlugin((nuxt) => {
1615
})
1716
// Remove previous preview data
1817
const keys = await contentStorage.getKeys(`${token}:`)
19-
keys.forEach(key => contentStorage.removeItem(key))
20-
21-
// Fill store with preview content
22-
const items = [
23-
...(data.files || []),
24-
...data.additions,
25-
...data.deletions.map(d => ({ ...d, parsed: { _id: d.path.replace(/\//g, ':'), __deleted: true } }))
26-
]
18+
await Promise.all(keys.map(key => contentStorage.removeItem(key)))
2719

2820
// Set preview meta
2921
await contentStorage.setItem(
@@ -33,12 +25,21 @@ export default defineNuxtPlugin((nuxt) => {
3325
})
3426
)
3527

36-
for (const item of items) {
37-
await contentStorage.setItem(`${token}:${item.parsed._id}`, JSON.stringify(item.parsed))
38-
}
28+
// Fill store with preview content
29+
const items = [
30+
...(data.files || []),
31+
...data.additions,
32+
...data.deletions.map(d => ({ ...d, parsed: { _id: d.path.replace(/\//g, ':'), __deleted: true } }))
33+
]
34+
35+
await Promise.all(
36+
items.map(item => contentStorage.setItem(`${token}:${item.parsed._id}`, JSON.stringify(item.parsed)))
37+
)
3938
}
4039

41-
async function initializePreview () {
40+
function initializePreview () {
41+
let contentStorage
42+
const nuxtApp = useNuxtApp()
4243
const query = useRoute().query || {}
4344
const previewToken = useCookie('previewToken', { sameSite: 'none', secure: true })
4445
if (!query.preview && !previewToken.value) {
@@ -55,10 +56,15 @@ export default defineNuxtPlugin((nuxt) => {
5556
createApp(ContentPreviewMode, {
5657
previewToken,
5758
apiURL: previewAPI,
58-
onRefresh: () => fetchData(previewToken.value).then(() => refreshNuxtData())
59+
onRefresh: () => fetchData(previewToken.value, contentStorage).then(() => refreshNuxtData())
5960
}).mount(el)
6061

61-
await fetchData(previewToken.value)
62+
// @ts-ignore
63+
nuxtApp.hook('content:storage', async (storage) => {
64+
contentStorage = storage
65+
await fetchData(previewToken.value, contentStorage)
66+
refreshNuxtData()
67+
})
6268
}
6369

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

0 commit comments

Comments
 (0)