-
-
Notifications
You must be signed in to change notification settings - Fork 605
/
client-db.ts
112 lines (95 loc) · 3.66 KB
/
client-db.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import type { Storage } from 'unstorage'
// @ts-ignore
import memoryDriver from 'unstorage/drivers/memory'
import { createStorage, prefixStorage } from 'unstorage'
import { useRuntimeConfig, useCookie } from '#app'
import { withBase } from 'ufo'
import { createPipelineFetcher } from '../query/match/pipeline'
import { createQuery } from '../query/query'
import type { NavItem, ParsedContent, ParsedContentMeta, QueryBuilderParams } from '../types'
import { createNav } from '../server/navigation'
const withContentBase = url => withBase(url, '/api/' + useRuntimeConfig().public.content.base)
export const contentStorage = prefixStorage(createStorage({ driver: memoryDriver() }), '@content')
export const getPreview = () => {
return useCookie('previewToken').value
}
export function createDB (storage: Storage) {
async function getItems () {
const keys = new Set(await storage.getKeys('cache:'))
// Merge preview items
const previewToken = getPreview()
if (previewToken) {
// Ignore cache content if preview requires it
const previewMeta: any = await storage.getItem(`${previewToken}$`).then(data => data || {})
if (previewMeta.ignoreBuiltContents) {
keys.clear()
}
const previewKeys = await storage.getKeys(`${previewToken}:`)
const previewContents = await Promise.all(previewKeys.map(key => storage.getItem(key) as Promise<ParsedContent>))
for (const pItem of previewContents) {
keys.delete(`cache:${pItem._id}`)
if (!pItem.__deleted) {
keys.add(`${previewToken}:${pItem._id}`)
}
}
}
return Promise.all(Array.from(keys).map(key => storage.getItem(key) as Promise<ParsedContent>))
}
return {
storage,
fetch: createPipelineFetcher(getItems),
query: (query?: QueryBuilderParams) => createQuery(createPipelineFetcher(getItems), query)
}
}
let contentDatabase
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)
}
await contentDatabase.storage.setItem('navigation', navigation)
await contentDatabase.storage.setItem('integrity', clientDB.integrity)
}
}
return contentDatabase
}
export async function generateNavigation (query): Promise<Array<NavItem>> {
const db = await useContentDatabase()
if (!getPreview() && Object.keys(query || {}).length === 0) {
return db.storage.getItem('navigation')
}
const contents = await db.query(query)
.where({
/**
* Partial contents are not included in the navigation
* A partial content is a content that has `_` prefix in its path
*/
_partial: false,
/**
* Exclude any pages which have opted out of navigation via frontmatter.
*/
navigation: {
$ne: false
}
})
.find()
const dirConfigs = await db.query().where({ _path: /\/_dir$/i, _partial: true }).find()
const configs = dirConfigs.reduce((configs, conf) => {
if (conf.title.toLowerCase() === 'dir') {
conf.title = undefined
}
const key = conf._path.split('/').slice(0, -1).join('/') || '/'
configs[key] = {
...conf,
// Extract meta from body. (non MD files)
...conf.body
}
return configs
}, {} as Record<string, ParsedContentMeta>)
return createNav(contents as ParsedContentMeta[], configs)
}