Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(database): add support for BunSQLite and refactor related code #2944

Merged
merged 9 commits into from
Jan 7, 2025
15 changes: 15 additions & 0 deletions docs/content/docs/1.getting-started/3.configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Configure markdown parser.
#### `toc`

::code-group

```ts [Default]
toc: {
depth: 2,
Expand All @@ -37,6 +38,7 @@ type Toc = {
searchDepth: number
}
```

::

Control behavior of Table of Contents generation.
Expand All @@ -63,13 +65,15 @@ export default defineNuxtConfig({
#### `remarkPlugins`

::code-group

```ts [Default]
remarkPlugins: {}
```

```ts [Signature]
type RemarkPlugins = Record<string, false | MarkdownPlugin>
```

::

A list of [remark](https://github.com/remarkjs/remark) plugins to use.
Expand Down Expand Up @@ -101,13 +105,15 @@ export default defineNuxtConfig({
#### `rehypePlugins`

::code-group

```ts [Default]
rehypePlugins: {}
```

```ts [Signature]
type RehypePlugins = object
```

::

A list of [rehype](https://github.com/remarkjs/remark-rehype) plugins to use.
Expand All @@ -132,13 +138,15 @@ export default defineNuxtConfig({
#### `highlight`

::code-group

```ts [Default]
highlight: false
```

```ts [Signature]
type Highlight = false | object
```

::

Nuxt Content uses [Shiki](https://github.com/shikijs/shiki) to provide syntax highlighting for [`ProsePre`](/docs/components/prose#prosepre) and [`ProseCode`](/docs/components/prose#prosecode).
Expand Down Expand Up @@ -257,6 +265,7 @@ Here is the list of supported database adapters:
### `SQLite`

If you want to change the default database location and move it to elsewhere you can use `sqlite` adapter to do so. This is the default value to the `database` option.
Depending on your runtime-environment different sqlite adapters will be used (Node: better-sqlite-3, Bun: bun:sqlite).

```ts [nuxt.config.ts]
export default defineNuxtConfig({
Expand Down Expand Up @@ -343,13 +352,15 @@ Configure content renderer.
### `anchorLinks`

::code-group

```ts [Default]
{ h2: true, h3: true, h4: true }
```

```ts [Signature]
type AnchorLinks = boolean | Record<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6', boolean>
```

::

Control anchor link generation, by default it generates anchor links for `h2`, `h3` and `h4` heading
Expand All @@ -369,13 +380,15 @@ Value:
### `alias`

::code-group

```ts [Default]
alias: {}
```

```ts [Signature]
type Alias = Record<string, string>
```

::

Aliases will be used to replace markdown components and render custom components instead of default ones.
Expand Down Expand Up @@ -414,6 +427,7 @@ The watcher is a development feature and will not be included in production.
::

::code-group

```ts [Enabled]
export default defineNuxtConfig({
content: {
Expand All @@ -436,4 +450,5 @@ export default defineNuxtConfig({
}
})
```

::
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"release": "npm run lint && npm run test && npm run prepack && release-it",
"lint": "eslint .",
"test": "vitest run",
"test:bun": "bun test ./test/bun.test.ts",
"test:watch": "vitest watch",
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
"verify": "npm run dev:prepare && npm run lint && npm run test && npm run typecheck"
Expand Down Expand Up @@ -107,6 +108,7 @@
"@nuxt/test-utils": "^3.15.1",
"@release-it/conventional-changelog": "^9.0.4",
"@types/better-sqlite3": "^7.6.12",
"@types/bun": "^1.1.14",
"@types/micromatch": "^4.0.9",
"@types/minimatch": "^5.1.2",
"@types/node": "^22.10.5",
Expand All @@ -133,7 +135,9 @@
"./src/studio"
],
"externals": [
"untyped"
"untyped",
"bun:sqlite",
"bun:test"
],
"rollup": {
"output": {
Expand Down
25 changes: 24 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 4 additions & 6 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import { generateCollectionInsert, generateCollectionTableDefinition } from './u
import { componentsManifestTemplate, contentTypesTemplate, fullDatabaseRawDumpTemplate, manifestTemplate, moduleTemplates } from './utils/templates'
import type { ResolvedCollection } from './types/collection'
import type { ModuleOptions, SqliteDatabaseConfig } from './types/module'
import { getContentChecksum, localDatabase, logger, watchContents, chunks, watchComponents, startSocketServer } from './utils/dev'
import { getContentChecksum, logger, watchContents, chunks, watchComponents, startSocketServer } from './utils/dev'
import { loadContentConfig } from './utils/config'
import { createParser } from './utils/content'
import { installMDCModule } from './utils/mdc'
import { findPreset } from './presets'
import type { Manifest } from './types/manifest'
import { setupStudio } from './utils/studio/module'
import { parseSourceBase } from './utils/source'
import { getLocalDatabase } from './utils/sqlite'

// Export public utils
export * from './utils'
Expand Down Expand Up @@ -103,7 +104,6 @@ export default defineNuxtModule<ModuleOptions>({
(options.database as SqliteDatabaseConfig).filename = (options.database as SqliteDatabaseConfig).filename
await mkdir(dirname((options.database as SqliteDatabaseConfig).filename), { recursive: true }).catch(() => {})
}

const { collections } = await loadContentConfig(nuxt)
manifest.collections = collections

Expand Down Expand Up @@ -186,7 +186,6 @@ export default defineNuxtModule<ModuleOptions>({
if (nuxt.options._prepare) {
return
}

const dumpGeneratePromise = processCollectionItems(nuxt, manifest.collections, options)
.then((fest) => {
manifest.checksum = fest.checksum
Expand Down Expand Up @@ -234,8 +233,8 @@ export default defineNuxtModule<ModuleOptions>({
async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollection[], options: ModuleOptions) {
const collectionDump: Record<string, string[]> = {}
const collectionChecksum: Record<string, string> = {}
const db = localDatabase(options._localDatabase!.filename)
const databaseContents = db.fetchDevelopmentCache()
const db = await getLocalDatabase(options._localDatabase!.filename)
const databaseContents = await db.fetchDevelopmentCache()

const configHash = hash({
mdcHighlight: (nuxt.options as unknown as { mdc: MDCModuleOptions }).mdc?.highlight,
Expand All @@ -251,7 +250,6 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio

// Remove all existing content collections to start with a clean state
db.dropContentTables()

// Create database dump
for await (const collection of collections) {
if (collection.name === 'info') {
Expand Down
26 changes: 6 additions & 20 deletions src/runtime/adapters/sqlite.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import Database from 'better-sqlite3'
import { isAbsolute } from 'pathe'
import { createDatabaseAdapter } from '../internal/database-adapter'
import { getBetter3DatabaseAdapter } from '../internal/sqlite'
import { getBunSqliteDatabaseAdapter } from '../internal/bunsqlite'

let db: Database.Database
export default createDatabaseAdapter<{ filename: string }>((opts) => {
if (!db) {
const filename = !opts || isAbsolute(opts?.filename || '')
? opts?.filename
: new URL(opts.filename, (globalThis as unknown as { _importMeta_: { url: string } })._importMeta_.url).pathname
db = new Database(process.platform === 'win32' ? filename.slice(1) : filename)
}

return {
async all<T>(sql: string, params?: Array<number | string | boolean>): Promise<T[]> {
return params ? db.prepare<unknown[], T>(sql).all(params) : db.prepare<unknown[], T>(sql).all()
},
async first<T>(sql: string, params?: Array<number | string | boolean>) {
return params ? db.prepare<unknown[], T>(sql).get(params) : db.prepare<unknown[], T>(sql).get()
},
async exec(sql: string): Promise<void> {
await db.exec(sql)
},
// NOTE: Not using the getDefaultSqliteAdapter function here because its not in the runtime directory.
if (process.versions.bun) {
return getBunSqliteDatabaseAdapter(opts)
}
return getBetter3DatabaseAdapter(opts)
})
72 changes: 72 additions & 0 deletions src/runtime/internal/bunsqlite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as zlib from 'node:zlib'
import { isAbsolute } from 'pathe'
import type { Database as BunDatabaseType } from 'bun:sqlite'

felixrydberg marked this conversation as resolved.
Show resolved Hide resolved
/**
* CompressionStream and DecompressionStream polyfill for Bun
*/
if (!globalThis.CompressionStream) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const make = (ctx: unknown, handle: any) => Object.assign(ctx, {
writable: new WritableStream({
write: (chunk: ArrayBufferView) => handle.write(chunk),
close: () => handle.end(),
}),
readable: new ReadableStream({
type: 'bytes',
start(ctrl) {
handle.on('data', (chunk: ArrayBufferView) => ctrl.enqueue(chunk))
handle.once('end', () => ctrl.close())
},
}),
})

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
globalThis.CompressionStream = class CompressionStream {
constructor(format: string) {
make(this, format === 'deflate'
? zlib.createDeflate()
: format === 'gzip' ? zlib.createGzip() : zlib.createDeflateRaw())
}
} as unknown as typeof CompressionStream

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
globalThis.DecompressionStream = class DecompressionStream {
constructor(format: string) {
make(this, format === 'deflate'
? zlib.createInflate()
: format === 'gzip'
? zlib.createGunzip()
: zlib.createInflateRaw())
}
} as unknown as typeof DecompressionStream
}

function getBunDatabaseSync() {
// A top level import will make Nuxt complain about a missing module
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('bun:sqlite').Database
}

let db: BunDatabaseType
export const getBunSqliteDatabaseAdapter = (opts: { filename: string }) => {
const Database = getBunDatabaseSync()
if (!db) {
const filename = !opts || isAbsolute(opts?.filename || '') || opts?.filename === ':memory:'
? opts?.filename
: new URL(opts.filename, (globalThis as unknown as { _importMeta_: { url: string } })._importMeta_.url).pathname
db = new Database(filename, { create: true })
}

return {
async all<T>(sql: string, params?: Array<number | string | boolean>): Promise<T[]> {
return params ? db.prepare(sql).all(...params) as T[] : db.prepare(sql).all() as T[]
},
async first<T>(sql: string, params?: Array<number | string | boolean>): Promise<T | null> {
return params ? db.prepare(sql).get(...params) as T : db.prepare(sql).get() as T
},
async exec(sql: string): Promise<unknown> {
return db.prepare(sql).run()
},
}
}
Loading