Skip to content

Latest commit

 

History

368 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Varve

A graph database and RDF/SPARQL toolkit for .NET, event-sourced from the first commit.

The name is geological: a varve is one annual sediment layer, countable and datable. One commit is one varve. The vocabulary stays in the documentation — public API names are conventional (Commit, Position, Projection).

The storage thesis

The transaction log is the source of truth. Every index is a projection of it.

Most graph stores keep indexes as the truth and bolt history on afterwards, as a changelog, an audit table, or a temporal extension. Varve inverts that. The log records what changed, not what was asked for — the effective delta of each commit — and it is never rewritten. Indexes, checkpoints and projections are derived, and any of them can be dropped and rebuilt without losing anything.

Three properties fall out of the model rather than being features added to it:

  • Snapshot isolation. A read is pinned to a log position, so it sees one consistent state for its whole lifetime without locking anything.
  • Time travel. An as-of read is the nearest checkpoint plus a delta overlay, at any closed position. There is no compaction in the destructive sense, so history does not expire.
  • Change feeds. A projection is a fold over the log. Subscribing to changes is the same mechanism that builds the indexes.

The cost is stated plainly: writes go through one sequencer, and the log grows. Checkpoints bound read cost; nothing bounds the log, by design.

There is one deliberate exception to "never rewritten", for a reason no append-only store can otherwise answer. Erasure mode, off by default per dataset, encrypts personal terms under a per-subject key, and erasure destroys the key — the log is untouched and the data is gone. Keys never live in the dataset directory.

Status

Milestone 5c. Nine packages: reading and writing four syntaxes, the XSD value spaces, RDFC-1.0 canonicalisation, the SPARQL algebra with its parser and serialiser, the SPARQL results formats read and written, a query evaluator with an optimiser, an event-sourced store in memory, and SPARQL Update over that store, one request as one commit.

Package Layer What it is State
Varve.Iri 0 IRI parsing, resolution and normalisation over UTF-8 working
Varve.Xsd 0 the XSD 1.1 value spaces with SPARQL operator semantics: exact decimal, the integer family, IEEE double and float, boolean, string, the date and time family, durations working
Varve.Rdf 1 terms, triples, quads, and the abstract quad source contract — with cardinality estimates and an inline-value accessor working
Varve.Turtle 2 N-Triples, N-Quads, Turtle, TriG — reader and writer working
Varve.Sparql 2 SPARQL 1.1 Query and Update as an immutable algebra, with the 1.2 additions; the parser and the serialiser working
Varve.Sparql.Results 2 the SPARQL results formats — XML, JSON, CSV, TSV — as pull readers and streaming writers working
Varve.Sparql.Evaluation 3 the optimiser and evaluator: every operator, the function library, aggregates, property paths, SERVICE through a handler, over any quad source working
Varve.Analyzers — the layer rules, at build time working, never shipped
Varve.Store 4 the log, the default projection, pinned and as-of reads, checkpoints, subscriptions, dataset validators, the staging view working, in memory; file and browser backends at milestone 6
Varve.Sparql.Store 5 SPARQL Update over the store: one request, one commit, LOAD through a caller's source working
SHACL, server, CLI 3–6 not built

Conformance

The W3C suites are the acceptance gate, from tests/Varve.Conformance.Tests/baseline/passing.txt:

Suite Passing
rdf11/rdf-trig 357
rdf11/rdf-turtle 313
rdf11/rdf-n-quads 87
rdf11/rdf-n-triples 70
rdf12/rdf-n-triples (syntax) 29
rdf12/rdf-n-quads (syntax) 27
rdf12/rdf-n-triples, rdf-n-quads (canonical form, ADR 0061) 82
sparql10/syntax-sparql1 … 5 (parsed under 1.1) 199
sparql11/syntax-query 94
sparql11/syntax-update-1, -2, syntax-fed 58
sparql12/syntax-triple-terms-positive, -negative 178
sparql12/syntax, version, codepoint-escapes, lang-basedir 25
sparql10 query evaluation, 24 directories (under 1.1), × 2 subjects 566
sparql11 query evaluation, 15 directories with service, × 2 subjects 484
sparql12 query evaluation, 6 directories, × 2 subjects 44
sparql11/csv-tsv-res, json-res, written by the results writers 10
sparql11 update evaluation, 11 directories, one commit or none each 94
rdf-canon, RDFC-1.0: 64 canonical forms, 21 issued maps, 1 refusal 86
Total 2,803 of 2,803

Each SPARQL suite is parsed at its own version, so the 1.0 and 1.1 suites never see the 1.2 grammar; the 1.2 default is for API callers only. The six SPARQL 1.2 suites are against Working Drafts of 2026, ratcheted anyway because the algebra carries 1.2 from the start and the ratchet is what absorbs churn (docs/spec/sparql-grammar.md §1).

Each query evaluation case runs over two subjects, InMemoryDataset and the store's pinned view, and is one ratchet line per subject; a guard runs every case again in ADR 0050's two other value-access arms. 41 SPARQL 1.2 evaluation cases are blocked, not exempt: their data is RDF 1.2 Turtle, which turtle.md §9 refuses, and roadmap slice 6b unblocks them. The guard pins their count.

baseline/exemptions.txt is empty, and that is a result rather than a default: no SPARQL 1.0 negative case turned out to be relaxed by 1.1, and no evaluation, update or canonicalisation case needs one — including none for empty graphs, which the store does not record (SPARQL 1.1 Update §3.2 allows it). eng/ratchet.cs fails the build if any of those 2,803 stops passing, and an exemption with no written justification fails the run too.

Also true today, and measured rather than asserted:

  • Zero bytes allocated per quad, on every entry point in every syntax — measured as the difference between a 500-quad and a 4,000-quad parse, because an absolute figure measures the harness as much as the parser.

  • Native AOT and browser WebAssembly both read and write Turtle. CI publishes the AOT binary and runs it; the interesting AOT failures are at run time and silent.

  • Every reader runs the chunk-boundary oracle: parse whole, parse again split at every byte offset, require the same answer. It has found nine defect classes, two of which produced wrong quads rather than errors.

  • The store's specification is tested against a reference model. §10's properties — effective deltas, dictionary closure, monotone time, the header chain, checkpoint and rebuild equivalence, pinned and as-of reads, Diff, crash recovery at every byte offset, byte-identical logs — run as CsCheck properties against a naive fold written from the specification alone. A scan allocates zero bytes per quad; AOT and the browser both run the store.

  • The SPARQL parser allocates the tree and nothing else: parsing 350 more triple patterns allocates exactly what building them by hand allocates. Parse, write, parse gives the identical tree, by record equality, over 3,000 generated queries and 3,000 generated updates per run and over every positive corpus case; and every corpus case parsed as UTF-16 agrees with its UTF-8 parse. AOT and the browser both parse a query and print its algebra.

  • The optimiser changes no answer: 20,000 generated queries over generated data per run give the same multiset optimised and not, and every rewrite must have fired. Its one counterexample was an operator whose answer depended on join order, now fixed. The store answers as a dataset of its own quads: at a generated as-of position of a generated history with retractions, 2,000 times a run. A SELECT over a basic graph pattern allocates its 48-byte row per solution, and nothing per quad scanned and not matched, over the store and over InMemoryDataset. AOT and the browser both evaluate a basic graph pattern, an aggregate and a property path.

  • The store's cardinality estimate is exact, held to a counted scan over generated histories at the head and at every as-of position (ADR 0049).

  • One update request is one commit. Every update case asserts exactly one commit when the request's net effect is non-empty and none otherwise, and a property applies 2,000 generated request sequences a run both through Varve.Sparql.Store and as a term-level model's deltas through the commit API, and requires the same state at every position and the same commits.

  • Results written read back as written: 5,000 generated solution sequences per format a run, with blank nodes, directional literals, triple terms and unbound variables, through the writer and back through the reader — term for term for XML, JSON and TSV, and what CSV carries for CSV.

  • Isomorphic datasets canonicalise alike, and only they do: 20,000 generated pairs a run against the harness's backtracking check, plus idempotence and a read-back property. It found that RDFC-1.0 itself gives some isomorphic datasets with blank graph names different canonical forms — pyoxigraph gives the same two forms — which docs/spec/rdf-canon.md §6 states; with IRI graph names the equivalence holds.

RDF 1.2 Turtle and TriG are not accepted at all — deliberately, rather than half-accepted. docs/spec/turtle.md §9 lists the constructs and the reasoning. SPARQL 1.2 is accepted in full, triple terms, reifiers, annotations and VERSION included, because the algebra was built with 1.2 from the start.

Queries evaluate and updates commit. Varve.Sparql.Evaluation answers SELECT, ASK, CONSTRUCT and DESCRIBE over any quad source, and Varve.Sparql.Store executes an update request against a Dataset as one commit. LOAD reads through a source the caller supplies; HTTP for LOAD and SERVICE is the server's, milestone 7. NOW() and the random functions read a clock and a random source the caller supplies — Clock = TimeProvider.System for the system clock — and fail by the option's name without one (ADR 0056).

Nothing is published yet. The package metadata and the trusted-publishing workflow are in place and await the first v0.1.0-preview.1 tag.

Quick start

Embedded

dotnet add package Varve.Turtle
await foreach (Quad quad in TurtleReader.ReadAsync(stream, baseIri))
{
    // The reader is streaming and allocation-free per quad. The quad is a
    // view; copy anything you intend to keep past the next iteration.
}

Varve.Rdf gives you the term model and the quad source contract if you only want those; Varve.Iri is usable on its own wherever System.Uri is the wrong answer, which for an IRI it is.

Query query = SparqlParser.ParseQuery("SELECT ?s WHERE { ?s a ?type } LIMIT 10"u8);
string algebra = SparqlWriter.ToText(query);   // parses back to the identical tree

Varve.Sparql parses SPARQL 1.1 and 1.2 Query and Update into an immutable algebra with a source span on every node, and Varve.Xsd gives the value spaces an evaluator compares with — XsdDecimal, XsdDateTime and the rest, allocation-free.

Browser (WebAssembly)

The same packages, unchanged. They target net10.0 with no native assets and no reflection, so browser-wasm needs nothing special — tests/Varve.WasmSmoke is a working example, and it is what proved that no symmetric cipher exists in that host, which changed a decision (ADR 0028).

Server

Not built. Milestone 7, with OIDC bearer authentication decided ahead of it in ADR 0037, followed by an operability milestone and a container image.

Building

git clone --recurse-submodules https://github.com/Hafeok/Varve
dotnet run eng/ci.cs        # the whole pipeline, exactly as CI runs it

Open the repository in the devcontainer and everything the gates need is already there. dotnet run eng/ci.cs -- --list says what the pipeline consists of. CONTRIBUTING.md has the rest.

Constraints

  1. 100% managed code. No P/Invoke, no native assets.
  2. Native AOT and trimming compatible.
  3. One core, three hosts: embedded library, server, browser (WASM).
  4. Minimal dependencies. Every third-party package has an ADR.
  5. Current LTS .NET and current C#. Allocation per quad is a defect.
  6. No copied code. The "permissive licence" half of this constraint is superseded — Varve is MPL-2.0, which is copyleft. See ADR 0031.

docs/brief.md is the authority for all of the above and is not summarised accurately anywhere else, including here.

Documentation

Contributing CONTRIBUTING.md
Governance GOVERNANCE.md
Security SECURITY.md
Conduct CODE_OF_CONDUCT.md
Changes CHANGELOG.md
Questions and ideas Discussions

How this project is run

Varve is run to the Mind Over Machine open-source stewardship standard: trunk-based development with non-blocking reviews, containerised development and pipelines, full commit traceability, signed commits, Detroit-style testing, version-controlled test data, and open project management. GOVERNANCE.md is what that means here in practice.

Varve is developed with AI assistance under human review. Every change passes the same gates whoever wrote it — the build at zero warnings, the conformance ratchet, the dependency register, the native-asset gate, the licence-header check — and every commit carries a sign-off naming the person accountable for it. Each AI-assisted session leaves a record in docs/traceability/ with the prompt it was given and the report it returned.

Licence

MPL-2.0. See also NOTICE and ADR 0031, which supersedes the Apache-2.0 decision in ADR 0002.

MPL-2.0 is file-level copyleft, and the distinction matters:

  • Referencing a Varve package from your own code does not make your code copyleft. Build whatever you like on top, under whatever terms you like.
  • Modifying a Varve source file means that file stays MPL-2.0 and its source has to remain available.

Copyright 2026 Emil Okkels Klein.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages