A log server
This guide shows how to run a public transparency log over HTTP on any SQLite; read it when anyone should be able to append to your log and verify it.
Example: examples/log-server
Writers use POST /add, and everyone else reads the tlog-tiles API.
The handler is a plain (Request) => Promise<Response>, so one source serves on Node, Bun, Deno and
Workers.
const log = await openServerLog({
key: await importLogKey(env.LOG_SKEY),
storage: { sqlite: fromSqliteSync(db) }, // a file: lease locking, shareable
http: { cors: true }, // a public log, readable from any origin
});
const serve = combineHandlers(async (request) => {
if (new URL(request.url).pathname !== "/add") return undefined;
try {
const entry = await readEntryBody(request); // POST only, capped while it streams
if (entry === undefined) return new Response("too large\n", { status: 413 });
return addResponse((await log.append(entry)).index); // the bare index, as Tessera answers
} catch (err) {
return addErrorResponse(err); // 405 if not a POST, 503 on pushback
}
}, log.handler);
readEntryBody refuses anything but a POST, since crawlers, link previews and prefetchers send GETs.
To give writers their receipts instead of the bare index, answer with receipt.text.
Then Deno.serve(serve), Bun.serve({ fetch: serve }), or createServer(toNodeListener(serve)) on
Node. The example keeps the runtime-specific part (which SQLite binding opens the file, how the
handler is served) in one small file per runtime, chosen by detectRuntime(). On Deno, a route that
reads request.signal prints a harmless warning; see On Deno.
Locking
For a SQLite file, the adapter’s default is lease locking, which lets any number of processes, on any
mix of runtimes, append to one file; the example’s README shows Node, Bun and Deno doing it at once.
Pass locking: "single-writer" only to declare that this process is the file’s only writer; if a
second process makes the same claim, the first stops writing (WRITER_CONFLICT) before the log can
fork. Choosing storage explains both.
Verifying it
A client trusts the log’s vkey and nothing else: it checks the checkpoint’s signature, proves each
entry it cares about with an inclusion proof, and proves that each new checkpoint extends the last
it saw (webtessera/client’s fetchCheckpoint, newProofBuilder, and verifyInclusion /
verifyConsistency from webtessera/merkle/proof). The example’s scripts/verify.ts does all
three, and scripts/verify_go.ts has Tessera’s own Go client verify the same log, which it does,
byte for byte.
Writers are not authenticated in the example, as on Tessera’s own test personalities; put your
authentication in front of POST /add.
This page is generated from docs/guides/log-server.md
. Edit it on GitHub.