// Package browse is the read-only web-browsing data layer over bare Dolt
// chunk stores. It is deliberately the only package in dolt.sr.ht that reaches
// into low-level dolthub/dolt internals (nbs, prolly, durable, diff), so that
// all version-fragile code stays contained here and a future module bump only
// needs to be re-verified against this one package.
//
// # Bare stores have no working set
//
// dolt.sr.ht serves bare NBS chunk-store directories (created via
// doltdb.WriteEmptyRepo and grown by pushes over the remotesapi). They have no
// .dolt/ working set, so the sqle engine and the embedded driver cannot open
// them. Everything here reads from committed roots only.
//
// # Open discipline (why not doltdb.LoadDoltDB)
//
// The obvious entry point, doltdb.LoadDoltDB, routes through the file
// dbfactory, which wraps the store in a GenerationalNBS (newgen + an oldgen
// subdir + a ghost gen). In the pinned dolt/go version
// (v0.40.5-0.20260626152440-45335d44ad79), calling Close() on such a
// generational store over one of our bare stores panics deep in nbs:
//
// panic: Close() called and reduced ref count to < 0.
// store/nbs/table_index.go:532 onHeapTableIndex.Close
// ... GenerationalNBS.Close -> NomsBlockStore.Close -> tableSet.close
//
// It reproduces on a plain open-read-only-then-Close, independent of any
// reads, so the documented open-per-request + Close pattern would crash the
// web process, not just tests. A single (non-generational) nbs.NewLocalStore
// — the exact construction storage.Cache uses to serve pushes — closes
// cleanly. We therefore build the DoltDB by hand from one NewLocalStore via
// doltdb.DoltDBFromCS and never touch the generational path.
//
// # Freshness and concurrency
//
// Open is called once per request and Close releases the handle, so every
// request gets a fresh read of the on-disk manifest and observes commits
// landed by the push writer since the last request — no stale cache, no
// refresh dance. NBS readers are manifest-based and append-only: opening a
// second NewLocalStore over a directory the push writer is also serving is
// safe (no exclusive lock is held for the store's lifetime; the manifest lock
// is taken only briefly during atomic updates), so a reader always sees a
// consistent snapshot.
package browse
import (
"context"
"errors"
"fmt"
"path/filepath"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/store/nbs"
"github.com/dolthub/dolt/go/store/types"
)
// readStoreMemTableSize bounds the in-memory memtable of the read handle. It
// matches the value storage.Cache uses for served stores; browsing never
// writes, so the memtable stays empty in practice.
const readStoreMemTableSize = 128 * 1024 * 1024
// ErrRefNotFound is returned when a ref string matches neither an existing
// branch nor a resolvable commit hash.
var ErrRefNotFound = errors.New("browse: ref not found")
// ErrTableNotFound is returned when a table does not exist in the resolved
// root value.
var ErrTableNotFound = errors.New("browse: table not found")
// DB is a read-only handle to a single bare Dolt chunk store. It is not safe
// for concurrent use; open one per request and Close it when done.
type DB struct {
ddb *doltdb.DoltDB
path string
}
// Open opens the bare Dolt chunk store at diskPath for read-only browsing.
// diskPath is the store directory itself (the dir that holds the NBS manifest
// and table files), not a parent. The caller must Close the returned DB.
//
// See the package doc for why this bypasses doltdb.LoadDoltDB.
func Open(ctx context.Context, diskPath string) (*DB, error) {
cs, err := nbs.NewLocalStore(
ctx,
types.Format_DOLT.VersionString(),
diskPath,
readStoreMemTableSize,
nbs.NewUnlimitedMemQuotaProvider(),
false,
)
if err != nil {
return nil, fmt.Errorf("browse: open store %q: %w", diskPath, err)
}
ddb, err := doltdb.DoltDBFromCS(cs, filepath.Base(diskPath))
if err != nil {
// cs is not yet owned by a DoltDB, close it directly.
_ = cs.Close()
return nil, fmt.Errorf("browse: build doltdb for %q: %w", diskPath, err)
}
return &DB{ddb: ddb, path: diskPath}, nil
}
// Close releases the underlying chunk store. It closes the single
// non-generational NBS store, which is safe in the pinned version (unlike the
// generational store LoadDoltDB would have produced).
func (db *DB) Close() error {
return db.ddb.Close()
}