@dblm/react
v0.1.0
Published
React chat UI + client for querying dblm in natural language (NLQ) through the dblm broker/middleware. A drop-in, fully themeable ChatGPT-style widget with multi-turn sessions.
Maintainers
Readme
@dblm/react
A drop-in, fully themeable NLQ chat widget for React — a ChatGPT-style UI for querying your databases in plain English through dblm. Users chat, start new sessions, and go back and forth in a conversation; you handle auth and theme. We handle the business logic.
React app ──► @dblm/react ──► (your backend | @dblm/middleware) ──► dblm broker ──► database- 💬 Multi-turn chat with a session sidebar — new / rename / delete, persisted across reloads.
- 🧠 Server-side memory kept in sync automatically (each turn replays the session id).
- 🎨 Fully themeable via CSS variables, a
themeprop, per-slotclassNames, or whole-component overrides. Light + dark, zero styling deps. - 📊 Answers render the natural-language summary with collapsible generated SQL and a scrollable data table.
- 🔌 Pluggable transport — talk to
@dblm/middlewaredirectly, proxy through your own backend, or mock it. - 🪝 Headless hook (
useNlqChat) if you'd rather build your own layout.
Install
npm install @dblm/reactreact and react-dom (v18+) are peer dependencies.
Quick start
Import the widget and the default skin, wrap it in a DblmProvider, and give it a
transport. Here's a zero-backend demo using a mock transport:
import { DblmProvider, DblmChat, customTransport } from '@dblm/react';
import '@dblm/react/styles.css';
const transport = customTransport(async (req) => ({
question: req.question,
summary: `You asked: ${req.question}`,
results: [{ source: 'pg', query: 'SELECT 1', result: { columns: ['n'], rows: [[1]], source: 'pg' } }],
}));
export default function App() {
return (
<DblmProvider config={{ transport }}>
<div style={{ height: 600 }}>
<DblmChat />
</div>
</DblmProvider>
);
}The widget fills its parent, so give the parent a height.
Connecting to dblm
@dblm/react never talks to a database directly — it sends questions to a
dblm broker via
@dblm/middleware. The /qd endpoint
requires a service API key plus the end user's client certificate + key (from
/register). There are two supported ways to wire this up — pick based on where you want
those secrets to live.
Option A — proxyTransport (recommended for public apps)
Your browser posts to your own backend, which holds the API key + user cert and forwards to the middleware. No secrets ever reach client-side JS.
import { proxyTransport } from '@dblm/react';
const transport = proxyTransport({
endpoint: '/api/dblm/qd', // your route
credentials: 'include', // send your app's auth cookie
// headers: () => ({ Authorization: `Bearer ${token}` }), // or a bearer token
});A minimal backend route (Express) that injects the secrets and forwards to the middleware:
app.post('/api/dblm/qd', requireAuth, async (req, res) => {
const upstream = await fetch(`${MIDDLEWARE_URL}/qd`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, 'X-User-Id': req.user.id },
body: JSON.stringify({
...req.body, // question, sessionId, module/connections
clientCert: req.user.dblmCert, // fetched from your store
clientKey: req.user.dblmKey,
}),
});
res.status(upstream.status).send(await upstream.text());
});Your endpoint must return the broker's NLQ result JSON unchanged.
Option B — directTransport (internal / trusted apps)
The browser talks straight to @dblm/middleware, sending the API key + the user's own
cert/key on every call. Simplest to wire, but the secrets live in client-side JS — use it
for admin consoles, internal tools, and prototypes, not public sites.
import { directTransport } from '@dblm/react';
// bundle comes from a prior POST /register (client.register(...) or your onboarding flow)
const transport = directTransport({
baseUrl: 'http://localhost:3000',
apiKey: 'your-service-api-key',
userId: bundle.user,
clientCert: bundle.client_cert,
clientKey: bundle.client_key,
});Sessions
The broker keeps conversation memory internally, keyed by session id, but doesn't expose
it for reading back. So @dblm/react owns the transcript and the session list on the
client (persisted to localStorage by default) and keeps the server in sync by sending
the active session's id as the broker sessionId on every turn:
- New chat → a fresh session id + empty transcript.
- Continue → same id replayed, so the broker threads prior turns into the LLM context.
- Switching threads in the sidebar swaps both the local transcript and the server session.
Swap persistence with the storage config (memoryStorage() for none / SSR, or your own
DblmStorage).
Theming
Everything is driven by --dblm-* CSS variables with a polished default skin. Configure
it four ways, from simplest to most powerful:
1. Theme tokens (typed, per-provider or per-widget):
<DblmChat theme={{ colorPrimary: '#0ea5e9', radius: '16px', fontFamily: 'Inter, sans-serif' }} />2. Color scheme — colorScheme="light" | "dark" | "auto" (auto follows the OS).
3. Override CSS variables yourself:
.dblm-chat { --dblm-color-user-bubble: #16a34a; --dblm-radius: 4px; }4. Per-slot class names / whole-component overrides for full control:
<DblmChat
classNames={{ bubble: 'my-bubble', composer: 'my-composer' }}
components={{ MessageBubble: MyCustomBubble }} // replace a slot entirely
/>Common tokens: colorPrimary, colorBg, colorSurface, colorSurfaceAlt, colorText,
colorTextMuted, colorBorder, colorUserBubble, colorAssistantBubble, colorError,
fontFamily, fontMono, radius, radiusSm, spacing, shadow.
Headless usage
Prefer to build your own layout? useNlqChat holds all the state and logic; the widget is
just a default view over it.
import { useNlqChat, useDblm } from '@dblm/react';
function MyChat() {
const { client, storage } = useDblm();
const chat = useNlqChat({ client, storage, target: { module: 'sales' } });
return (
<div>
{chat.messages.map((m) => <div key={m.id}>{m.role}: {m.text}</div>)}
<button onClick={() => chat.send('how many orders today?')} disabled={chat.sending}>Ask</button>
<button onClick={() => chat.newSession()}>New chat</button>
</div>
);
}Returns: sessions, activeSession, messages, sending, error, and actions
send, newSession, selectSession, deleteSession, renameSession, clearSession,
retry, stop.
<DblmChat> props
| Prop | Type | Description |
| --- | --- | --- |
| target | { module } \| { connections } | Database(s) to query for new chats. |
| showSidebar | boolean | Show the session sidebar (default true). |
| theme | DblmTheme | Theme tokens, merged over the provider's. |
| colorScheme | 'light' \| 'dark' \| 'auto' | Overrides the provider scheme. |
| persist | boolean | Persist sessions (default true). |
| placeholder | string | Composer placeholder. |
| welcome | ReactNode | Empty-thread content. |
| classNames | DblmClassNames | Per-slot class overrides. |
| components | DblmComponents | Whole-subcomponent overrides. |
| onSend / onResult / onError / onSessionChange | callbacks | Lifecycle hooks. |
Testing
Three layers, from no-backend to the full live path:
1. React logic (no backend). Unit + integration tests over useNlqChat and
<DblmChat> using a mock transport:
npm test2. The widget by hand (no backend). Run the demo in "Mock" transport mode:
cd example && npm install && npm run dev # transport = Mock3. Full live path — React → middleware → broker → database. The harness at
scripts/e2e.sh boots a dblm broker + @dblm/middleware,
provisions a user (issuing a client cert via /register), then asks a real
question through /qd and asserts the whole stack executed:
# prereqs: dblm binary built (go build -o dblm .), a broker master initialized
# (dblm server init), an indexed connection, a configured LLM provider, jq.
bash scripts/e2e.sh
# env overrides: E2E_CONN=chinook E2E_QUESTION="..." DBLM_BIN=./dblmIt validates API-key auth, the X-User-Id identity, /register cert issuance,
mTLS to the broker, the ACL grant, schema loading, and engine execution — i.e.
everything the mock tests can't reach. Point the demo's "Direct → middleware"
transport at the same middleware to drive that path from the actual UI.
Example
A runnable demo (mock + direct transports, theme switcher, sidebar) lives in
example/:
cd example
npm install
npm run devLicense
MIT
