<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SQLRite in a browser tab</title>
<style>
body {
font-family: ui-sans-serif, system-ui, sans-serif;
max-width: 720px;
margin: 2rem auto;
padding: 0 1rem;
line-height: 1.5;
color: #1d1d1f;
}
h1 {
font-size: 1.5rem;
}
h2 {
margin-top: 2.5rem;
padding-top: 1rem;
border-top: 1px solid #e5e5ea;
}
textarea {
width: 100%;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 0.9rem;
padding: 0.5rem;
border: 1px solid #d2d2d7;
border-radius: 6px;
min-height: 8rem;
}
textarea.compact {
min-height: 3rem;
}
button {
margin: 0.5rem 0.3rem 0.5rem 0;
padding: 0.4rem 0.9rem;
background: #0071e3;
color: white;
border: 0;
border-radius: 6px;
cursor: pointer;
}
button:disabled {
background: #b0b0b8;
cursor: not-allowed;
}
button.secondary {
background: #e5e5ea;
color: #1d1d1f;
}
pre {
background: #f5f5f7;
padding: 0.75rem;
border-radius: 6px;
overflow-x: auto;
font-size: 0.85rem;
}
.status {
color: #515154;
font-size: 0.85rem;
}
.ask-banner {
background: #fffaf0;
border: 1px solid #f0d8a8;
padding: 0.75rem 1rem;
border-radius: 6px;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.ask-banner code {
background: #f5e7c4;
padding: 0 0.2rem;
border-radius: 3px;
}
.meta {
color: #6e6e73;
font-size: 0.8rem;
font-family: ui-monospace, Menlo, Consolas, monospace;
}
</style>
</head>
<body>
<h1>SQLRite in a browser tab</h1>
<p>
The full SQLRite engine compiled to WebAssembly. Everything runs in this
tab — no server. State lives in memory; refreshing the page wipes it.
</p>
<h2>SQL console</h2>
<textarea id="sql">
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
INSERT INTO users (name, age) VALUES ('alice', 30);
INSERT INTO users (name, age) VALUES ('bob', 25);
INSERT INTO users (name, age) VALUES ('carol', 40);
SELECT id, name, age FROM users ORDER BY age DESC;</textarea
>
<div>
<button id="run">Run</button>
<button id="reset" class="secondary">Reset DB</button>
<span class="status" id="status"></span>
</div>
<h3>Result</h3>
<pre id="out">(run a query to see output)</pre>
<h2>Ask (natural-language → SQL)</h2>
<p>
The WASM SDK builds the LLM-API request body in this browser tab, but
<strong>doesn't make the HTTP call itself</strong>. CORS + API-key
exposure rule that out (see <code>docs/ask.md</code> for the full
reasoning). Instead, the call goes through a backend proxy you control.
The proxy is ~10 lines — see <code>examples/wasm/server.mjs</code> for a
zero-dependency Node version that this demo expects on
<code>POST /api/llm/complete</code>.
</p>
<div class="ask-banner">
<strong>To use this section:</strong>
<ol>
<li>Set <code>ANTHROPIC_API_KEY</code> in your shell.</li>
<li>
Run <code>node server.mjs</code> from
<code>examples/wasm/</code> (after <code>make build</code>). It serves
this page on <code>http://localhost:8080</code> and proxies
<code>/api/llm/complete</code> to Anthropic with your key.
</li>
<li>Type a question below. The generated SQL drops into the SQL console above.</li>
</ol>
No proxy running? The Ask button will report a clean "couldn't reach
proxy" error and the rest of the console keeps working.
</div>
<textarea id="askQuestion" class="compact" placeholder="e.g. How many users are over 30?">How many users are over 30?</textarea>
<div>
<button id="ask">Ask</button>
<span class="status" id="askStatus"></span>
</div>
<h3>Generated SQL</h3>
<pre id="askSql">(submit a question to see generated SQL here)</pre>
<h3>Explanation</h3>
<pre id="askExplanation">(model rationale shows here)</pre>
<p class="meta" id="askUsage"></p>
<script type="module">
import init, { Database } from "./pkg/sqlrite_wasm.js";
const out = document.getElementById("out");
const status = document.getElementById("status");
const sqlTextarea = document.getElementById("sql");
await init();
let db = new Database();
status.textContent = `sqlrite-wasm ready (in-memory)`;
document.getElementById("run").onclick = () => {
const input = sqlTextarea.value.trim();
if (!input) return;
const statements = input
.split(";")
.map((s) => s.trim())
.filter((s) => s.length > 0);
let lastResult = null;
let lastIsQuery = false;
try {
for (const stmt of statements) {
const isQuery = /^\s*select\b/i.test(stmt);
if (isQuery) {
lastResult = db.query(stmt);
lastIsQuery = true;
} else {
db.exec(stmt);
lastIsQuery = false;
}
}
} catch (err) {
out.textContent = `Error: ${err}`;
return;
}
if (lastIsQuery) {
out.textContent = JSON.stringify(lastResult, null, 2);
} else {
out.textContent = `(${statements.length} statement${statements.length === 1 ? "" : "s"} executed)`;
}
};
document.getElementById("reset").onclick = () => {
db.free();
db = new Database();
out.textContent = "(reset — fresh in-memory DB)";
};
const askButton = document.getElementById("ask");
const askStatus = document.getElementById("askStatus");
const askSqlEl = document.getElementById("askSql");
const askExplanationEl = document.getElementById("askExplanation");
const askUsageEl = document.getElementById("askUsage");
const askQuestionEl = document.getElementById("askQuestion");
askButton.onclick = async () => {
const question = askQuestionEl.value.trim();
if (!question) return;
askButton.disabled = true;
askStatus.textContent = "asking…";
askSqlEl.textContent = "(generating…)";
askExplanationEl.textContent = "";
askUsageEl.textContent = "";
try {
const payload = db.askPrompt(question);
const response = await fetch("/api/llm/complete", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`backend ${response.status}: ${errText}`);
}
const rawApiResponse = await response.text();
const result = db.askParse(rawApiResponse);
if (!result.sql || result.sql.trim().length === 0) {
askSqlEl.textContent = "(model declined to generate SQL)";
askExplanationEl.textContent = result.explanation || "(no explanation)";
} else {
askSqlEl.textContent = result.sql;
askExplanationEl.textContent = result.explanation || "(no explanation)";
sqlTextarea.value = result.sql + ";";
}
const u = result.usage;
askUsageEl.textContent =
`tokens: input=${u.input_tokens}, output=${u.output_tokens}, ` +
`cache_write=${u.cache_creation_input_tokens}, cache_hit=${u.cache_read_input_tokens}`;
askStatus.textContent = "done — SQL pasted into console above; click Run to execute.";
} catch (err) {
const msg = String(err);
if (msg.includes("Failed to fetch") || msg.includes("NetworkError")) {
askStatus.textContent = "couldn't reach proxy — is `node server.mjs` running?";
} else {
askStatus.textContent = msg;
}
askSqlEl.textContent = `Error: ${msg}`;
} finally {
askButton.disabled = false;
}
};
</script>
</body>
</html>