(ns ird.p2p.db.api-purity-test
"The public-API hard rule, enforced. Every public namespace — every
src/ird/p2p/db/**/*.cljs EXCEPT src/ird/p2p/db/impl/** — must contain
zero JS interop: no `^js` meta, no `js/...`, no `.-` property access, no
`#js` literal, no `js-obj`, no `goog/`, no `aget`/`aset`. All values that
cross a public boundary are pure ClojureScript data plus core.async
channels; bytes travel as base64 strings, with the impl namespaces doing
the conversion. This test scans the source files and fails listing every
offending file and pattern."
(:require [clojure.string :as str]
[cljs.test :refer [deftest is]]))
(def ^:private fs (js/require "node:fs"))
(def ^:private path (js/require "node:path"))
(defn- cljs-files [dir]
(mapcat (fn [entry]
(let [full (.join path dir (.-name entry))]
(cond
(.isDirectory entry) (cljs-files full)
(str/ends-with? (.-name entry) ".cljs") [full]
:else [])))
(.readdirSync fs dir #js {:withFileTypes true})))
(def ^:private forbidden
["^js" "js/" ".-" "#js" "js-obj" "goog/" "(aget" "(aset"])
(defn- public-ns-files []
(->> (cljs-files "src/ird/p2p/db")
(remove #(str/includes? % "/impl/"))
(filter #(.existsSync fs %))
distinct
sort))
(deftest public-namespaces-have-zero-js-interop
(let [files (public-ns-files)]
(is (<= 4 (count files)) "the public surface is present (core, crypto, identity, errors)")
(doseq [f files]
(let [src (.readFileSync fs f "utf8")
hits (filter #(str/includes? src %) forbidden)]
(is (empty? hits) (str f " contains forbidden JS interop: " (pr-str (vec hits))
" — interop belongs under src/ird/p2p/db/impl/"))))))
(deftest impl-is-the-only-interop-boundary
;; a sanity check that the scan actually looks at real files, not an empty set
(let [impl (filter #(str/includes? % "/impl/") (cljs-files "src/ird/p2p/db"))]
(is (seq impl) "there are impl namespaces")
(is (some (fn [f] (str/includes? (.readFileSync fs f "utf8") "js/")) impl)
"impl namespaces do use interop, so the scan distinguishes them")))