Frameworks & bundlers
granth is plain ESM with no framework coupling. The core works anywhere; only React and Vue get a (tiny, optional) binding, because they have no store contract.
The one universal requirement
Your bundler must be able to resolve a worker URL:
worker: () => new Worker(new URL('./db.worker.js', import.meta.url), { type: 'module' })Vite, webpack 5, Rollup, Parcel 2, esbuild and Next.js all understand this form natively.
React / Next.js
// db.js
import Granth from 'granthdb';
export const db = new Granth('myapp', {
worker: () => new Worker(new URL('./db.worker.js', import.meta.url), { type: 'module' }),
});
db.version(1).stores({ friends: '++id, name, age, *tags' });
// Friends.jsx
import { useLiveQuery, useIsSupported } from 'granth-react';
import { db } from './db';
export function Friends() {
const supported = useIsSupported(); // false during SSR — no hydration mismatch
const friends = useLiveQuery(db, () => db.friends.orderBy('name').toArray(), [], []);
if (!supported) return <p>Loading…</p>;
return <ul>{friends.map((f) => <li key={f.id}>{f.name}</li>)}</ul>;
}Prefer the Dexie call shape? Bind it once:
export const useLive = createLiveQueryHook(db);
// const friends = useLive(() => db.friends.toArray(), [], []);SSR is safe. new Granth(...) at module scope touches no browser API — it only connects when you first query. Server renders return the initialValue.
Angular
No adapter needed — liveQuery implements Symbol.observable:
import { from } from 'rxjs';
import { db } from './db';
@Component({ /* ... */ })
export class FriendsComponent {
friends$ = from(db.liveQuery(() => db.friends.orderBy('name').toArray()));
}<li *ngFor="let f of friends$ | async">{{ f.name }}</li>Svelte / SvelteKit
No adapter needed — subscribe() returns an unsubscribe function, which is the Svelte store contract, so it works with $:
<script>
import { db } from './db';
const friends = db.liveQuery(() => db.friends.orderBy('name').toArray());
</script>
{#each $friends ?? [] as f}<li>{f.name}</li>{/each}Under SvelteKit SSR, guard with browser from $app/environment before querying.
Vue / Nuxt
<script setup>
import { useLiveQuery } from 'granth-vue';
import { db } from './db';
const { data: friends } = useLiveQuery(db, () => db.friends.orderBy('name').toArray(), {
initialValue: [],
});
</script>
<template><li v-for="f in friends" :key="f.id">{{ f.name }}</li></template>Unsubscribes automatically with the component's effect scope.
Solid, Qwik, Lit, Alpine, vanilla
Use subscribe directly:
const stop = db.liveQuery(() => db.friends.toArray()).subscribe(render);
// later: stop();No bundler (<script type="module">)
<script type="module">
import Granth from 'https://esm.sh/granthdb';
const db = new Granth('myapp', {
worker: () => new Worker('/db.worker.js', { type: 'module' }), // a real URL
});
db.version(1).stores({ friends: '++id, name' });
</script>db.worker.js must be served from your origin and import sqlite-wasm from a CDN.
Bundler notes
Vite
// vite.config.js
export default {
optimizeDeps: { exclude: ['@sqlite.org/sqlite-wasm'] }, // esbuild mangles its wasm loading
worker: { format: 'es' },
};webpack 5
Works out of the box. Ensure experiments.asyncWebAssembly if you inline the wasm.
Next.js
Put the worker file under your app directory and use the new URL(...) form. Keep all database access in client components ('use client') or effects.
Angular CLI
Add the worker with ng generate web-worker, or reference it with new URL(...) — Angular 16+ uses esbuild and handles it.
Environments without a Worker
Strict CSP without worker-src, some extension and embedded contexts, SSR and Node can all run granth — on the inline runtime, paired with a non-OPFS backend.
import { Granth } from 'granthdb';
import { inlineRuntime } from 'granth-runtime-inline';
const db = new Granth('myapp', { runtime: inlineRuntime({ createHandlers }) });SQL then runs on the calling thread and OPFS is unavailable. See Runtimes.
Requirements everywhere
- Secure context — HTTPS or
localhost. OPFS and Web Locks both require it. - Chrome 108+, Safari 16.4+, Firefox 111+.
- No COOP/COEP headers needed.