search
Full-text search library providing document indexing, full-text search, and an admin UI. Uses PostgreSQL FTS in production with LIKE fallback for H2/SQLite.
Module layout
libs/search/src/wagoe/search/
├── schema.clj — Malli schemas: SearchDocument, SearchResult, SearchResponse
├── ports.clj — ISearchStore (persistence), ISearchEngine (orchestration),
│ ISearchDefinitions
├── core/
│ ├── index.clj — defsearch, build-document*
│ ├── query.clj — SQL builders (PostgreSQL FTS + H2/SQLite LIKE fallback)
│ ├── ranking.clj, highlighting.clj
│ └── ui.clj — Hiccup: search results, admin index page
└── shell/
├── persistence.clj — SearchStore (next.jdbc, HoneySQL ON CONFLICT upsert)
├── service.clj — SearchService (orchestration, pagination, reindex)
├── http.clj — Routes: API + admin web UI
└── module_wiring.clj — the :wagoe/search component
Defining a search index
(require '[wagoe.search.core.index :as search])
(search/defsearch product-search
{:entity-type :product
:language :english
:fields [{:name :title :weight :a}
{:name :body :weight :b}
{:name :tags :weight :c}]
:filters [:tenant-id :category-id]}) ; optional: filterable dimensions
Weight levels: :a (highest) → :d (lowest).
defsearch only binds the var. Hand definitions, keyed by index id, to
:wagoe/search: as your module’s :search part, or as data in config.
(defn ig-config [_settings _ctx]
{:search {:product-search search/product-search}})
:wagoe/search
{:indexes {:product-search {:entity-type :product
:fields [{:name :title :weight :a}]}}}
Each system holds its own definitions. The older
wagoe.search.shell.registry/defsearch still works, with a deprecation
warning.
The search API
POST /api/v1/search/:index-id and /suggest, and the admin search pages,
serve an index only when its definition opts in:
-
:api? trueserves it to any signed-in user. -
:api {:authorize (fn [request definition] …)}serves it when the hook returns truthy.
Any other index answers 404, the same as an unknown one. An index that backs your own page, with its own visibility rules, should not opt in. The framework ships no indexes.
Indexing documents
(require '[wagoe.search.ports :as ports])
(ports/index-document! search-engine :product-search product-id
{:title "Wagoe Framework"
:body "A Clojure web framework..."
:tags "clojure framework"}
{:filter-values {:tenant-id tenant-id}})
;; Bulk reindex: a seq of [entity-id field-values] pairs
(ports/reindex! search-engine :product-search documents)
;=> {:indexed 120}
Searching
(ports/search search-engine :product-search "clojure framework"
{:filters {:tenant-id tenant-id}
:limit 25
:offset 0})
;=> {:results [{:entity-type :product :entity-id #uuid "..." :rank 0.9 :snippet "..."}]
; :total 42
; :query "clojure framework"
; :took-ms 3}
Testing
clojure -M:test :search