Database
A database is needed to run the sqlQuery and sqliteFullTextSearch plugins. It's where they build their index and the store they query against.
SQLite Node
Comark ships sqlite(), built on Node's node:sqlite. Create one and pass it to the query and search plugins:
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'
import sqlite from 'comark-content/database/sqlite-node'
import sqlQuery from 'comark-content/plugins/sql-query'
import sqliteFullTextSearch from 'comark-content/plugins/sqlite-full-text-search'
const database = sqlite() // one database, shared by both plugins
comarkContent({
source: fs('./content'),
plugins: [sqlQuery({ database }), sqliteFullTextSearch({ database })],
})They add content.query() for SQL queries over frontmatter and content.search() for BM25 full-text search.
node:sqlite ships with Node 22.5+. To run the plugins in the browser, use sqliteWasm() instead: same API, same plugins. Being WebAssembly, it also runs in any other host without node:sqlite.Options
sqlite(options?: SqliteOptions)| Option | Type | Default | Description |
|---|---|---|---|
filename | string | ':memory:' | Path to the SQLite file. Omit for an in-memory database. |
Persist the database
sqlite() is in-memory by default: it builds its index in RAM and drops it when the process exits, so every start rebuilds it. That's fine in development, but a serverless cold start or a fresh deploy pays the indexing cost each time.
Give it a filename and the database lives on disk instead, so a fresh instance can reuse the index rather than rebuild it:
import sqlite from 'comark-content/database/sqlite-node'
// In-memory (default): the index is rebuilt on every start.
sqlite()
// On disk: written once, then restored by the next instance.
sqlite({ filename: '.comark/content.db' })To also avoid re-parsing content across restarts, cache parsed bodies with a persistent cache driver, or ship a static snapshot.
SQLite WASM
sqliteWasm() is a drop-in replacement for sqlite() that runs in any environment supporting WebAssembly: the browser, Cloudflare Workers, Vercel Edge, or Node when you don't have node:sqlite. Same interface, same filename option, same plugins:
import sqliteWasm from 'comark-content/database/sqlite-wasm'
import sqlQuery from 'comark-content/plugins/sql-query'
const database = sqliteWasm()It needs one extra dependency:
pnpm add @sqlite.org/sqlite-wasmnpm install @sqlite.org/sqlite-wasmyarn add @sqlite.org/sqlite-wasmbun add @sqlite.org/sqlite-wasm@sqlite.org/sqlite-wasm from Vite's dependency optimiser: the package ships a worker plus .wasm asset the optimiser doesn't handle. The official Vite plugin (comark-content/vite) does this for you.export default defineConfig({
optimizeDeps: {
exclude: ['@sqlite.org/sqlite-wasm'],
},
})sqliteWasm() from server-built snapshots instead of parsing on the client. See Artifacts & hydration.Cache vs database
The cache and the database persist two different things. A persistent cache (or shipped artifacts) keeps parsed bodies and the manifest index, so get(), list(), and navigation() skip re-reading the source. A persistent database keeps the query and search index, so the plugins can reuse it instead of rebuilding from scratch. Persist both and a cold start does far less work.