npm package: @n6k.io/db
Description: Lightweight DuckDB WASM driver with synchronous fetch via SharedArrayBuffer workers
Latest version: 0.3.1

# @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

```bash
npm install @n6k.io/db @duckdb/duckdb-wasm
```

## Requirements

- `SharedArrayBuffer` support — 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/node` export)

## Usage

### Browser

```js
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:

1. A **fetch worker** that performs async HTTP requests
2. A **DuckDB wrapper worker** that exposes synchronous `self.n6k.fetch()` / `self.n6k.fetchBinary()` to WASM code via `SharedArrayBuffer` + `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:

```js
import { wasmDir } from '@n6k.io/db/node'
// => absolute path to the `wasm/` directory inside the package
```

The directory structure is:

```
wasm/
  v1.5.1/
    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):

```js
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 (typically `bundle.mainWorker` from `duckdb.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