Use vendored jsqlite

ober

3eb8b705df26e12e1898e0d4b0dea441d6d48a0e

diff --git a/.gitignore b/.gitignore
index aa70df1..1cdf515 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 # Build artifacts (jerbuild build)
 /jerboa-edge
+vendor/
 /*-main.c
 support/ffi-symbols.gen
 *.so
diff --git a/.jerbuild b/.jerbuild
index 6ac6ff8..897032f 100644
--- a/.jerbuild
+++ b/.jerbuild
@@ -2,12 +2,11 @@
 ;;
 ;; jerbuild bundles Chez Scheme + the jerboa stdlib + the jerboa-native Rust
 ;; crate, so this needs only jerbuild + a C compiler + cargo — no jerboa
-;; source checkout. jerboa-native provides rustls TLS, crypto, and
-;; sqlite-native (no DuckDB).
+;; source checkout. jerboa-native provides rustls TLS and crypto.
 
 (entry "edge.ss")
 (output "jerboa-edge")
-(libdirs ".")
+(libdirs "." "vendor/jsqlite/src")
 ;; Static binary: a custom main.c sets JERBOA_STATIC=1 so the bundled std
 ;; modules skip dlopen of libjerboa_native and use the symbols registered from
 ;; the list below (the linked-in archive members would otherwise be
@@ -16,5 +15,5 @@
 (ffi-symbols "support/ffi-symbols.gen")
 (rust-crates
   ("@bundle/jerboa-native-rs/Cargo.toml"
-   features: "tls,crypto,sqlite"
+   features: "tls,crypto"
    no-default-features: #t))
diff --git a/Makefile b/Makefile
index 6c0b3f2..ac69af8 100644
--- a/Makefile
+++ b/Makefile
@@ -10,18 +10,28 @@ endif
 NATIVE_A := $(JH)/jerboa-native-rs/target/release/libjerboa_native.a
 BIN      := jerboa-edge
 BIN_DIR  := $(HOME)/.local/bin
+VENDOR   := $(CURDIR)/vendor
+JSQLITE_REPO ?= $(VENDOR)/jsqlite
+JSQLITE_URL  ?= https://git.sr.ht/~lisp/jsqlite
 
-.PHONY: all build binary install clean
+.PHONY: all build binary install clean ensure-jsqlite
 
 all: binary
 
+ensure-jsqlite:
+	@if [ ! -f "$(JSQLITE_REPO)/src/jsqlite/api.ss" ]; then \
+	  mkdir -p "$(VENDOR)"; \
+	  git clone --depth 1 "$(JSQLITE_URL)" "$(JSQLITE_REPO)"; \
+	fi
+	@test -f "$(JSQLITE_REPO)/src/jsqlite/api.ss"
+
 # Standalone native binary via .jerbuild (entry edge.ss -> jerboa-edge).
 # Two passes: the first cargo-builds jerboa-native into jerbuild's cache; we
 # then regenerate the FFI symbol list from that archive (so it matches this
 # platform — e.g. epoll on Linux) and relink. A static main.c sets
 # JERBOA_STATIC=1 so the std modules use the registered symbols, not dlopen.
-binary:
-	@touch support/ffi-symbols.gen
+binary: ensure-jsqlite
+	@: > support/ffi-symbols.gen
 	$(JERBUILD) build
 	sh support/gen-ffi-symbols.sh
 	$(JERBUILD) build
diff --git a/edge.ss b/edge.ss
index 9a88b9f..5c2c630 100644
--- a/edge.ss
+++ b/edge.ss
@@ -10,8 +10,8 @@
 ;;;   - HMAC-SHA256 signature verification
 ;;;   - Prometheus metrics (/metrics endpoint)
 ;;;   - Structured JSON logging (greppable by Loki/Splunk)
-;;;   - Graceful SIGTERM shutdown (drain, flush SQLite WAL)
-;;;   - Optional SQLite persistence (EDGE_DB_PATH)
+;;;   - Graceful SIGTERM shutdown (drain, flush jsqlite WAL)
+;;;   - Optional jsqlite persistence (EDGE_DB_PATH)
 ;;;   - Optional TLS termination (EDGE_TLS_CERT + EDGE_TLS_KEY)
 ;;;
 ;;; Zero external dependencies. Everything is Jerboa stdlib.
@@ -51,10 +51,38 @@
         (std misc timeout)
         (std metrics)
         (std os signal)
-        (std db sqlite-native)
+        (prefix (jsqlite api) japi:)
+        (prefix (jsqlite constants) jconst:)
         (std net tls-rustls)
         (std net tcp-raw))
 
+(def sqlite-open japi:sqlite-open)
+(def sqlite-close japi:sqlite-close)
+(def sqlite-exec japi:sqlite-exec)
+(def sqlite-execute japi:sqlite-exec)
+
+(def (bind-sqlite-args! stmt vals)
+  (let loop ([i 1] [vs vals])
+    (unless (null? vs)
+      (japi:sqlite-bind! stmt i (car vs))
+      (loop (+ i 1) (cdr vs)))))
+
+(def (sqlite-query db sql . vals)
+  (let ([stmt (japi:sqlite-prepare db sql)])
+    (bind-sqlite-args! stmt vals)
+    (let ([names (japi:sqlite-columns stmt)])
+      (let loop ([acc '()])
+        (let ([rc (japi:sqlite-step stmt)])
+          (cond
+            [(= rc jconst:SQLITE_ROW)
+             (loop (cons (map cons names (japi:sqlite-row stmt)) acc))]
+            [(= rc jconst:SQLITE_DONE)
+             (japi:sqlite-finalize stmt)
+             (reverse acc)]
+            [else
+             (japi:sqlite-finalize stmt)
+             (reverse acc)]))))))
+
 ;; ═══════════════════════════════════════════════════════════════
 ;; Configuration
 ;; ═══════════════════════════════════════════════════════════════
@@ -112,18 +140,18 @@
     (displayln (json-object->string ht))))
 
 ;; ═══════════════════════════════════════════════════════════════
-;; SQLite Persistence (Phase 3.3)
+;; jsqlite Persistence (Phase 3.3)
 ;;
 ;; Optional: set EDGE_DB_PATH to a file path to enable persistence.
 ;; On startup, existing events and dead-letters are loaded into STM.
-;; Every store-result! / store-dead-letter! also writes to SQLite.
+;; Every store-result! / store-dead-letter! also writes to jsqlite.
 ;; WAL mode is used for concurrent reads with no reader blocking.
 ;;
 ;; All DB writes are fire-and-forget: guarded with (guard (e [#t (void)]))
 ;; so a SQLite error never crashes a worker.
 ;; ═══════════════════════════════════════════════════════════════
 
-(def *db* #f)  ;; SQLite handle — #f when EDGE_DB_PATH is not set
+(def *db* #f)  ;; jsqlite handle — #f when EDGE_DB_PATH is not set
 
 (def (db-init!)
   (when *db-path*
diff --git a/support/static-main.c b/support/static-main.c
index 4fca78c..6639152 100644
--- a/support/static-main.c
+++ b/support/static-main.c
@@ -2,8 +2,8 @@
  * .jerbuild as main-c). Identical to jerbuild's stock template with ONE
  * addition: it sets JERBOA_STATIC=1 before building the heap.
  *
- * The bundled std modules — (std db sqlite-native), (std crypto native-rust),
- * etc. — read JERBOA_STATIC at library-load time and, when set, trust the
+ * The bundled std modules that use native FFI read JERBOA_STATIC at library-load
+ * time and, when set, trust the
  * jerboa-native symbols that are linked into this binary (and registered via
  * register_ffi_symbols() below from the .jerbuild ffi-symbols list) instead of
  * trying to dlopen a libjerboa_native.{so,dylib} that does not exist in a