diff --git a/docs/content/docs/1.getting-started/3.configuration.md b/docs/content/docs/1.getting-started/3.configuration.md index b1d64b8db..46bb1de8a 100644 --- a/docs/content/docs/1.getting-started/3.configuration.md +++ b/docs/content/docs/1.getting-started/3.configuration.md @@ -506,11 +506,12 @@ Experimental features that are not yet stable. ### `experimental.sqliteConnector` -SQLite connectors have limitations in different environments. Some work in serverless environments, while others do not. Nuxt Content supports three different SQLite connectors to cover all environments: +SQLite connectors have limitations in different environments. Some work in serverless environments, while others do not. Nuxt Content supports four different SQLite connectors to cover all environments: -- `better-sqlite3`: Works in all Node environments, GitHub CI, Vercel CI and production, Cloudflare CI pipelines, etc. (Does **not** work in WebContainers and StackBlitz) +- `better-sqlite3`: Works in all Node environments, GitHub CI, Vercel CI and production, Cloudflare CI pipelines, etc. (Does **not** work in WebContainers, StackBlitz, or Bun runtime) - `sqlite3`: Works in Node environments, GitHub CI, and StackBlitz. (Does **not** work in Vercel or Cloudflare) - `native`: As of Node.js v22.5.0, the `node:sqlite` module is available natively in Node.js. This connector works in all Node environments with Node.js version 22.5.0 or newer. +- `bun`: Uses Bun's built-in `bun:sqlite` module. Required when deploying to Bun runtime (e.g., Vercel with `bun1.x` runtime). Automatically selected when building with Bun, but must be explicitly set when building with Node.js for a Bun runtime target. ```ts [nuxt.config.ts] export default defineNuxtConfig({ @@ -520,6 +521,34 @@ export default defineNuxtConfig({ }); ``` +When deploying to Vercel with Bun runtime, the connector is auto-detected if you configure the runtime via Nitro: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + nitro: { + vercel: { + functions: { + runtime: 'bun1.x', + }, + }, + }, +}); +``` + +Alternatively, you can explicitly set the connector: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + content: { + experimental: { sqliteConnector: 'bun' }, + }, +}); +``` + +::prose-note +When `sqliteConnector: 'bun'` is set but the build runs on Node.js (common in CI/CD pipelines like Vercel), the build-time content processing will automatically fall back to a Node.js-compatible connector (e.g., `better-sqlite3`). Only the deployed server runtime will use `bun:sqlite`. No additional configuration is needed. +:: + ### `experimental.nativeSqlite` (deprecated, use `sqliteConnector`) As of Node.js v22.5.0, the `node:sqlite` module is available natively in Node.js. diff --git a/src/module.ts b/src/module.ts index 99603aae6..d7bb4c516 100644 --- a/src/module.ts +++ b/src/module.ts @@ -191,10 +191,14 @@ export default defineNuxtModule({ const preset = findPreset(nuxt) await preset.setupNitro(config, { manifest, resolver, moduleOptions: options, nuxt }) - const resolveOptions = { resolver, sqliteConnector: options.experimental?.sqliteConnector || (options.experimental?.nativeSqlite ? 'native' : undefined) } + const sqliteConnector = options.experimental?.sqliteConnector || (options.experimental?.nativeSqlite ? 'native' : undefined) + const resolveOptions = { resolver, sqliteConnector } config.alias ||= {} config.alias['#content/adapter'] = await resolveDatabaseAdapter(config.runtimeConfig!.content!.database?.type || options.database.type, resolveOptions) - config.alias['#content/local-adapter'] = await resolveDatabaseAdapter(options._localDatabase!.type || 'sqlite', resolveOptions) + // Build-time local adapter must use a connector compatible with the build environment. + // When targeting Bun but building on Node.js, fall back to a Node.js-compatible connector. + const localSqliteConnector = (sqliteConnector === 'bun' && !process.versions.bun) ? undefined : sqliteConnector + config.alias['#content/local-adapter'] = await resolveDatabaseAdapter(options._localDatabase!.type || 'sqlite', { resolver, sqliteConnector: localSqliteConnector }) config.handlers ||= [] manifest.collections.forEach((collection) => { @@ -281,8 +285,10 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio const collectionDump: Record = {} const collectionChecksum: Record = {} const collectionChecksumStructure: Record = {} + const requestedConnector = options.experimental?.sqliteConnector || (options.experimental?.nativeSqlite ? 'native' : undefined) + const buildConnector = (requestedConnector === 'bun' && !process.versions.bun) ? undefined : requestedConnector const db = await getLocalDatabase(options._localDatabase, { - sqliteConnector: options.experimental?.sqliteConnector || (options.experimental?.nativeSqlite ? 'native' : undefined), + sqliteConnector: buildConnector, }) const databaseContents = await db.fetchDevelopmentCache() diff --git a/src/presets/vercel.ts b/src/presets/vercel.ts index 06ac71d50..44d3d1ab5 100644 --- a/src/presets/vercel.ts +++ b/src/presets/vercel.ts @@ -5,8 +5,14 @@ import nodePreset from './node' export default definePreset({ name: 'vercel', parent: nodePreset, - async setup(options) { + async setup(options, nuxt) { options.database ||= { type: 'sqlite', filename: '/tmp/contents.sqlite' } + + const vercelRuntime = (nuxt.options.nitro as Record)?.vercel as { functions?: { runtime?: string } } | undefined + if (typeof vercelRuntime?.functions?.runtime === 'string' && vercelRuntime.functions.runtime.startsWith('bun')) { + options.experimental ||= {} + options.experimental.sqliteConnector ||= 'bun' + } }, async setupNitro(nitroConfig) { const database = nitroConfig.runtimeConfig?.content?.database diff --git a/src/runtime/internal/database.server.ts b/src/runtime/internal/database.server.ts index db93c4d7e..f93767692 100644 --- a/src/runtime/internal/database.server.ts +++ b/src/runtime/internal/database.server.ts @@ -1,7 +1,6 @@ import type { H3Event } from 'h3' import { isAbsolute } from 'pathe' import type { Connector } from 'db0' -import type { ConnectorOptions as SqliteConnectorOptions } from 'db0/connectors/better-sqlite3' import { decompressSQLDump } from './dump' import { fetchDatabase } from './api' import { refineContentFields } from './collection' @@ -210,7 +209,7 @@ function refineDatabaseConfig(config: RuntimeConfig['content']['database']) { } if (config.type === 'sqlite') { - const _config = { ...config } as SqliteConnectorOptions + const _config = { ...config } as { path?: string, name?: string } if (config.filename === ':memory:') { return { name: ':memory:' } } diff --git a/src/types/module.ts b/src/types/module.ts index 2c22951c9..502c270d7 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -209,6 +209,11 @@ export interface ModuleOptions { /** * Use given SQLite connector instead of `better-sqlite3` if available * + * - `better-sqlite3`: Default for Node.js environments + * - `native`: Use Node.js built-in `node:sqlite` (requires Node.js >= 22.5.0) + * - `sqlite3`: Use `sqlite3` package (works in StackBlitz/WebContainers) + * - `bun`: Use Bun's built-in `bun:sqlite` (for Bun runtime targets, e.g. Vercel with bun1.x) + * * @default undefined */ sqliteConnector?: SQLiteConnector @@ -232,4 +237,4 @@ export interface PublicRuntimeConfig { } } -export type SQLiteConnector = 'native' | 'sqlite3' | 'better-sqlite3' +export type SQLiteConnector = 'native' | 'sqlite3' | 'better-sqlite3' | 'bun' diff --git a/src/utils/database.ts b/src/utils/database.ts index 325c787e9..893d05ab2 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -47,10 +47,6 @@ export async function resolveDatabaseAdapter(adapter: 'sqlite' | 'bunsqlite' | ' } adapter = adapter || 'sqlite' - if (adapter === 'sqlite' && process.versions.bun) { - return databaseConnectors.bunsqlite - } - if (adapter === 'sqlite') { return await findBestSqliteAdapter({ sqliteConnector: opts.sqliteConnector, resolver: opts.resolver }) } @@ -168,11 +164,13 @@ export async function getLocalDatabase(database: SqliteDatabaseConfig | D1Databa } async function findBestSqliteAdapter(opts: { sqliteConnector?: SQLiteConnector, resolver?: Resolver }) { - if (process.versions.bun) { + if (opts.sqliteConnector === 'bun') { + if (!process.versions.bun) { + console.warn('[nuxt/content] `sqliteConnector: \'bun\'` targets Bun runtime — the build-time database will use a Node.js-compatible fallback.') + } return opts.resolver ? opts.resolver.resolve('./runtime/internal/connectors/bun-sqlite') : 'db0/connectors/bun-sqlite' } - // if node:sqlite is available, use it if (opts.sqliteConnector === 'native' && isNodeSqliteAvailable()) { return opts.resolver ? opts.resolver.resolve('./runtime/internal/connectors/node-sqlite') : 'db0/connectors/node-sqlite' } @@ -187,6 +185,11 @@ async function findBestSqliteAdapter(opts: { sqliteConnector?: SQLiteConnector, return 'db0/connectors/better-sqlite3' } + // Auto-detect Bun runtime when no explicit connector is set + if (process.versions.bun) { + return opts.resolver ? opts.resolver.resolve('./runtime/internal/connectors/bun-sqlite') : 'db0/connectors/bun-sqlite' + } + if (isWebContainer()) { await ensurePackageInstalled('sqlite3')