sqlite persistence: the store moves from db.json to dist.db (P5.2)
src/repo/db.sx persists the whole Repo to <store>/dist.db through the vendored SQLite bindings, keeping the load-whole/save-whole call shape. One table per entity; enums as lowercase variant names; list order round-trips via rowid. Enforced uniqueness: apps.slug, channels(app_id, name), tokens.token_hash; lookup indexes on releases(app_id) and artifacts(sha256) (non-unique - identical bytes may ship in several releases). save is DELETE-all + INSERT-all inside BEGIN IMMEDIATE...COMMIT with rollback on failure; every connection sets busy_timeout so the CLI and a running distd interleave safely. A store holding only a pre-SQLite db.json imports once on first load, then the file is renamed db.json.imported; a store with neither starts empty. Consumers gate on db.store_exists instead of probing db.json. The JSON read-back stays for the import path; the entity->json writers stay for distd's /api responses. Tests that parsed db.json directly now assert by querying dist.db through the SQLite bindings; tests/db_import.sx pins the import path; tests/repo_roundtrip.sx pins the SQLite round-trip. make test 22/22.
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
// distd.sx — the distribution server over the local store (subplan 04,
|
||||
// Slices 1-4), run as `dist server run`.
|
||||
//
|
||||
// Serves the state the CLI publishes — db.json metadata and the
|
||||
// content-addressed objects — over HTTP (src/server/http.sx). Reads are
|
||||
// public:
|
||||
// Serves the state the CLI publishes — the store database's metadata and
|
||||
// the content-addressed objects — over HTTP (src/server/http.sx). Reads
|
||||
// are public:
|
||||
//
|
||||
// GET / HTML index: apps, channels, releases, links
|
||||
// GET /healthz {"status":"ok"} — no store access
|
||||
@@ -36,10 +36,10 @@
|
||||
// (`{"status":"error","error":{code,message}}`) with the matching HTTP
|
||||
// status — the API and the CLI report failures identically.
|
||||
//
|
||||
// FRESHNESS: db.json is RELOADED on every /api request, so a `dist ci
|
||||
// publish` / `release promote` between requests is visible immediately —
|
||||
// the store on disk stays the single source of truth (no cache to
|
||||
// invalidate, LAN-scale traffic).
|
||||
// FRESHNESS: the store database is RELOADED on every /api request, so a
|
||||
// `dist ci publish` / `release promote` between requests is visible
|
||||
// immediately — the store on disk stays the single source of truth (no
|
||||
// cache to invalidate, LAN-scale traffic).
|
||||
//
|
||||
// RESPONSE BUFFERS are heap slices from the per-request arena, never big
|
||||
// stack arrays: a stack array of 64K+ in one frame crashes the sx LLVM
|
||||
@@ -138,17 +138,17 @@ respond_error :: (client: i32, code: i64, fail_code: string, fail_message: strin
|
||||
// ── /api renders (builders own the `try`, callers catch) ─────────────
|
||||
|
||||
// Reload the persisted model. Null means the store has no readable
|
||||
// db.json — the 503 error response has already been sent.
|
||||
// database — the 503 error response has already been sent.
|
||||
load_or_503 :: (client: i32, store_dir: string) -> ?Repo {
|
||||
if !exists(path_join(store_dir, "db.json")) {
|
||||
if !db.store_exists(store_dir) {
|
||||
respond_error(client, 503, "store.load",
|
||||
concat("no db.json under the store (nothing published yet): ", store_dir));
|
||||
concat("no store database (nothing published yet): ", store_dir));
|
||||
return null;
|
||||
}
|
||||
loaded, le := db.load(store_dir);
|
||||
if le {
|
||||
respond_error(client, 503, "store.load",
|
||||
concat("db.json under the store could not be loaded: ", store_dir));
|
||||
concat("the store database could not be loaded: ", store_dir));
|
||||
return null;
|
||||
}
|
||||
return loaded;
|
||||
|
||||
Reference in New Issue
Block a user