@n6k.io/db
v0.2.0
Published
Lightweight DuckDB WASM driver with synchronous fetch via SharedArrayBuffer workers
Maintainers
Readme
@n6k.io/db
Lightweight DuckDB WASM driver with synchronous fetch via SharedArrayBuffer workers.
Wraps @duckdb/duckdb-wasm workers so that DuckDB extensions can make synchronous HTTP requests from within WASM — bridging the gap between WASM's synchronous execution model and the browser's async fetch() API.
Install
npm install @n6k.io/db @duckdb/duckdb-wasmRequirements
SharedArrayBuffersupport — your server must set these headers:Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp- Node >= 18 (for the
@n6k.io/db/nodeexport)
Usage
Browser
import * as duckdb from '@duckdb/duckdb-wasm'
import { createN6kWorker } from '@n6k.io/db'
const BUNDLES = duckdb.getJsDelivrBundles()
const bundle = await duckdb.selectBundle(BUNDLES)
// Create an n6k-wrapped worker instead of a plain Worker
const worker = createN6kWorker(bundle.mainWorker)
const logger = new duckdb.ConsoleLogger()
const db = new duckdb.AsyncDuckDB(logger, worker)
await db.instantiate(bundle.mainModule, bundle.pthreadWorker)
await db.open({ allowUnsignedExtensions: true })
const conn = await db.connect()
// Point DuckDB at your extension server
await conn.query(`SET custom_extension_repository = '${window.location.origin}';`)
// Load and use extensions
await conn.query('LOAD n6k;')
const result = await conn.query("SELECT * FROM n6k('https://example.com/tables/demo');")
const rows = result.toArray().map(r => r.toJSON())createN6kWorker(mainWorkerUrl) is a drop-in replacement for the worker you'd normally pass to AsyncDuckDB. It spawns two internal workers:
- A fetch worker that performs async HTTP requests
- A DuckDB wrapper worker that exposes synchronous
self.n6k.fetch()/self.n6k.fetchBinary()to WASM code viaSharedArrayBuffer+Atomics
Serving WASM Extensions
The package ships pre-built WASM extensions under wasm/. Your dev server needs to serve these files so DuckDB can load them at runtime.
Use the @n6k.io/db/node export to get the path to the WASM directory:
import { wasmDir } from '@n6k.io/db/node'
// => absolute path to the `wasm/` directory inside the packageThe directory structure is:
wasm/
v1.4.4/
wasm_mvp/
n6k.duckdb_extension.wasm
...
wasm_eh/
n6k.duckdb_extension.wasm
...
wasm_threads/
n6k.duckdb_extension.wasm
...DuckDB selects the best variant for the browser automatically (wasm_threads > wasm_eh > wasm_mvp).
Example middleware (framework-agnostic, works with Express/Connect-style servers):
import path from 'path'
import fs from 'fs'
import { wasmDir } from '@n6k.io/db/node'
function serveExtensions(req, res, next) {
const filePath = path.join(wasmDir, req.url)
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
const mimeTypes = {
'.wasm': 'application/wasm',
'.json': 'application/json',
'.js': 'text/javascript',
}
const ext = path.extname(filePath)
res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream')
res.setHeader('Access-Control-Allow-Origin', '*')
fs.createReadStream(filePath).pipe(res)
return
}
next()
}API
createN6kWorker(mainWorkerUrl: string): Worker
Creates a DuckDB Web Worker with synchronous fetch capabilities. Pass the returned worker to duckdb.AsyncDuckDB in place of a regular Worker.
mainWorkerUrl— URL to the DuckDB WASM main worker script (typicallybundle.mainWorkerfromduckdb.selectBundle())
wasmDir: string (from @n6k.io/db/node)
Absolute path to the wasm/ directory shipped with the package. Use this to configure your dev server to serve extension files.
License
MIT
