newsroom.sgit.ai / databases

Databases with no server

This site has no server. The Portugal section is 19 JSON files in a git repository, 1.3 MB in all, each with its hash in a manifest. Two real database engines run over exactly those files in your browser, compiled to WebAssembly: SQLite, and Oxigraph speaking SPARQL 1.1. Nothing is uploaded, nothing is queried remotely, and when the tab closes the database is gone. The files are the database. The engines are readers.

The pattern, in one line. Keep the truth as files; derive every other shape of it at build time; let the reader's own machine do the querying. It is the shape the estate already runs: sgit.ai's RiskMandate vault, where “the browser becomes the database: versioned JSON in the vault is the source of truth, queried live through SQLite compiled to WebAssembly”, and graphs.sgit.ai's Regulation Graph, “SQLite over WebAssembly and rdflib with Turtle export, both client-side and ephemeral”. This section adds the second language: the same graph queried as triples, with a standard graph query language, still with no server.

Two consoles over one set of files

SQL · SQLite via sql.js

The SQL console

15 tables built from the JSON on load, 16 worked queries from a count-by-class to a recursive graph walk, and a box to write your own. Every example ran at build; its row count is printed beside it so your run can disagree.

Open the SQL console →
SPARQL 1.1 · Oxigraph

The graph console

4,001 triples — the same graph, the ontology inside the store, inverses declared with owl:inverseOf and walked with a property path, labels in English and Portuguese so a path reads aloud. 13 worked queries, each shown beside the same question in Cypher.

Open the graph console →

The file system is the database

Every fact on the Portugal section lives in one JSON file that owns it: the people in people.json, the frozen pages in sources.json, the sessions in sessions.json. The build derives the other shapes a reader might want — the graph in graph.json, the same graph as RDF in triples.nt, the manifest with every hash — and nothing is derived twice. There is no import step, no schema migration and no connection string: the SQL console reads a loader spec, tables.json, that says which file and which field each column comes from, so a reader who doubts a cell can open the file it came from.

Three things follow, and they are the argument for doing it this way rather than a cheaper way of doing the usual thing:

What this is not

It is not a graph database, and graphs.sgit.ai's position is inherited on purpose: “not a graph database pitch — the claim is that one grammar is the interface at every boundary, not that things are stored in a graph.” The JSON stays the source of truth. The SPARQL store is built from it on load and thrown away on close; nothing is ever written back. Its shipped page lists “browser SPARQL/Cypher, RDF in code” under what that site does not have; this page is where the estate now has it, as a reader over files rather than a store of record.

It is not a server in disguise. The whole dataset is downloaded, which is fine at 1.3 MB and would not be at a gigabyte; that is the honest scaling limit of the pattern, and the sgit.ai guidance already names the answer — manifests at build time, files on click. And it is not a write path: corrections go through the files, the build and a version, which is the point.

What is vendored, and its cost

EngineBuildLanguageBytesLicence
SQLitesql.js 1.14.2SQL704,945MITconsole
Oxigraphoxigraph 0.5.11 (web build)SPARQL 1.14,099,865MIT OR Apache-2.0console

Both are vendored under assets/vendor/ and attributed in LICENSES.md, for the reason the rest of the site's third-party code is: an evidence chain should not end in a resource that can move. Cypher is shown beside each SPARQL query and not executed. Kùzu compiles to WebAssembly and would run it here; its package is 73 MB unpacked, which is the wrong price for a demonstration, and the choice is recorded rather than hidden.

The files each console loads

FileBytesSHA-256
changes.json2,149ad1d1e37da05
checks.json1,45761c2254b2570
connections.json35,37963af95dc9ded
coverage-notes.json3,4810cef28eec4ad
coverage.json6,43332a72cfba398
event.json3,7451217c9814409
graph.json388,075c2d9281f4bd7
lexicon.json13,193ba1cf8d3a588
manifest.json23,359599f470198d9
notice.json8,189a9b919e0376a
ontology.json26,511432d4fb588dc
orgs.json10,071ef3346010765
people.json18,362bbc579b85be3
sessions.json3,608dff95f982bf4
sources.json43,920a5b0ab6d572a
stories.json2,168bf3f77a71f6e
team.json5,060f3e23c3fe955
topics.json181,0659c38f3505e8d
triples.nt622,00696673dafd45c

From manifest.json, which the file explorer renders and the Portugal gate re-verifies on every build.

For an agent

Two in-browser engines over /portugal/data/. SQL: open /databases/sql.html, wait for the tool:ready event, then window.__tools.sql.run(sql) returns {columns, rows, ms}; the loader spec is /databases/data/tables.json and the worked queries with their build-time row counts are /databases/data/queries-sql.json. SPARQL: /databases/graph.html, window.__tools.sparql.run(query); the triples are /portugal/data/triples.nt (4,001, N-Triples, IRIs under https://newsroom.sgit.ai/portugal/{id,verb,type,prop}/), the worked queries with Cypher equivalents are /databases/data/queries-sparql.json. Both pages fetch only same-origin files and send nothing anywhere.