Rabosh
A store, its model and its indexes as one object with one lifecycle.
Rabosh.open(Path.of("data")).use { db ->
db.put(Key.of("user:1"), """{"name":"ada","team":"analytics"}""")
db.flush()
db.createIndex(IndexDefinition.inverted("$.team"))
db.query(Query.where(path("$.team") eq "analytics")).use { rows ->
while (rows.next()) println(rows.key)
}
println(db.schema().render())
db.indexCandidates().forEach(::println)
}What this is for. Assembling the engine by hand means knowing four things in the right order, three of which no signature discloses: the observer has to be installed in the store's options before the store opens, because a flush can begin the moment it does; each layer then has to be attached, and two attachments scan every uncovered segment twice; and the index catalog has to be closed or its mappings stay live, which on Windows means files that can never be deleted. open and close are those four things, done once, in an order that is written down.
It is faster than the manual wiring in exactly one place, and that place is attach: the layers are attached without backfilling and then fed by a single DocumentStore.backfill through one CompositeSegmentObserver, so a segment neither layer covers is read once rather than once per layer. Everything else here is ergonomics.
What it is not. Not a server, not a connection pool, not a transaction manager beyond what WriteBatch already is, and not a second copy of the layers' API. The surface is deliberately narrow — writes, reads, queries, index management, the model — and everything else is reached through store, indexCatalog and catalog, which stay public and unwrapped. A facade that re-exported five modules would drift from them, and a query answered here must be the same query answered there: this class holds no planner, no matcher and no definition of what a predicate means. It delegates to QueryEngine, which is the only one.
Concurrency. The same contract the store makes: one writing thread, any number of reading threads. The cached planner statistics behind query are guarded, so concurrent queries are safe; the state of a query lives in its QueryCursor, as it does through the engine directly.
Properties
The schema catalog, or null when RaboshOptions.schema is false.
The index catalog, or null when RaboshOptions.indexes is false.
The options it was opened with.
A snapshot of the store's current sizes.
The store underneath, unwrapped.
Functions
Covers whatever the sidecars do not, on that same thread, returning at once.
Defines an index and builds it over everything already written.
Defines an index and builds it on a thread of the database's own, returning at once.
Removes an index and deletes its posting files.
The paths worth an index, best first.
The defined indexes, ascending by id.
Runs query and returns its rows, in key order.
An ordered walk over the documents in [from, to], both bounds inclusive and both optional.
The model of what is in the database: which paths exist, how often, with what types.
Commits batch as one record, atomically and as one view.