Extracting reusable libraries from Xapiand

June 24, 2026 · View on GitHub

Xapiand is full of small, well-made components that have nothing to do with search and everything to do with "things you need when writing serious C++." Three are already out in their own repos — base-x, uinteger_t, and fantasyname — and this document is the plan to do the same for the rest.

It's a complete inventory of the codebase's own (non-vendored) code, grouped into extraction waves by return-on-effort, with a concrete split-out plan for each piece, a suggested set of repos to create, and the licensing flags to carry. For the deep technical detail behind any entry, see ARCHITECTURE.md; the scheduling stack has its own study in SCHEDULER.md.

How to read this

  • Difficulty = how much Xapiand coupling you must cut: Easy (header-only or a couple of macro stubs), Moderate (swap a few helpers / a thread abstraction), Hard (peel domain glue off a good core).
  • Value = how broadly useful outside a search engine.
  • License = MIT unless flagged. A few files carry other authors' notices that must travel with them; see the provenance map. Anything under src/xapian/ is GPL and out of scope here.

The shared decoupling toolkit

Most of the catalog is unlocked by the same five cuts. Do these once as a pattern and the per-component work mostly evaporates:

  1. Stub the logging macros. L_CALL, L_ERR, L_DEBUG_HOOK, L_EXC, … all compile to L_NOTHING by default already (log.h). Replace with no-ops or a single injectable trace callback. This is the single most common coupling and it's mechanical.
  2. Swap THROW(Type, …) for standard exceptions. Many leaf files only touch Xapiand exceptions through this macro; std::runtime_error/invalid_argument substitute cleanly. (Or extract the exception type itself — it's Wave 1.)
  3. Replace strings::format with fmt::format / std::format. The internal strings helper is just an fmt wrapper.
  4. Delete ThreadPolicyType. It's threaded through the whole thread/scheduler stack as a template parameter but thread.cc ignores it at runtime (only sets the thread name). Replace with a name string.
  5. Drop opts reads. A few components read the global CLI-options struct; replace those with constructor parameters or a small config struct.

Wave 1 — header-only, (near-)zero coupling

Quick wins. Mostly copy the file(s), stub a macro or two, done.

Proposed libSourceWhat it isDiffValueSplit plan
perfect-hashphf.hhFully constexpr minimal perfect hash for integer keysEasyHighCopy as-is; fix the inverted empty() (phf.hh:1410). Zero deps.
lru-cachelru.hIntrusive LRU + optional TTL with policy-callback evictionEasyHighCopy as-is; pure STL.
callable-traitscallable_traits.hhFull C++17 callable introspection (all qualifier combos)EasyHighCopy as-is; already namespaced, zero deps.
errno-nameserror.hh + errnos.herrno → symbolic name and description, cross-platform X-macroEasyHighCopy both verbatim; zero coupling.
located-exceptionexception.h + exception.ccException that captures func/file/line + lazy fmt, with a WITHOUT_FMT fallbackEasyHighDrop the unused config.h include; keep THROW/RETHROW. (Note: captures location, not a stack trace.)
bloom-filterbloom_filter.hhFixed-size double-hashed Bloom filterEasyMedCarries hashes.hh (or template the hash).
splitsplit.hLazy string_view splitter (full-delimiter + find-first-of)EasyHighCopy as-is, or fold into the string toolkit below.
msgpack-xchangexchange/rapidjson.hpp, xchange/string_view.hpp, xchange/chaiscript.hppmsgpack-c adapters for RapidJSON, string_view, and ChaiScriptEasyHigh (json/sv), Low-Med (chai)Ship the three as one pack; fix one THROWthrow in string_view; keep xpol's co-credit on the rapidjson header. ChaiScript adapter is an optional module.

Wave 2 — small decoupling (stub macros / swap exceptions / fmt)

A short, mechanical decoupling each, following the toolkit above.

Proposed libSourceWhat it isDiffValueSplit plan
term-coloransi_color.hh, colors.h, color_tools.hhCompile-time truecolor+256+16 ANSI escapes, graceful downgrade, hsv2rgbEasyHighSplit the L_* logging shortcuts out of colors.h; inline ESC; strings::formatfmt.
constexpr-stringsstatic_string.hh, hashes.hh, chars.hh, strict_stox.hhCompile-time strings, xxHash/FNV/djb2 + string-switch UDLs, table-driven char classification, strict numeric parsingEasy–ModHighBundle as one "compile-time string toolkit"; swap THROW for std exceptions in strict_stox.
string-metricsmetrics/ (+ string_metric.h)CRTP string-distance kit: Levenshtein, Jaro, Jaro–Winkler, Jaccard, Sørensen–Dice, LCS, soundex-metricEasyHighDrop the 3 serialise helpers in basic_string_metric.h.
phoneticphonetic/Multilingual soundex (EN/FR/DE/ES)EasyHighSame serialise-helper cut; fix the Spanish first-letter bug (spanish_soundex.h:113). Could merge into string-metrics.
memory-statsmemory_stats.*Portable RAM/swap/disk/inode + per-proc RSS/VSZEasyHighSwap ~10 L_*/error::* calls for a callback or fprintf. Bundle into sysinfo with system.*.
lz4-streamcompressor_lz4.*LZ4 block-streaming wrappers (file + data, ring-buffered)EasyHighDepends only on vendored lz4 + io; note the unused-digest bug (wire up the XXH32 it already computes).
urlcodecurl_parser.* (the urldecode + QueryParser half)Configurable percent-decoder + zero-copy query-string parserEasy–ModHighInline the ~10-line hexdec; leave PathParser (Xapiand URL dialect).
mime-typesserver/mime_types.* (+ the nginx table)Load mime.types, map extension → content typeEasy–ModLow–MedReplace ct_type_t with std::pair, make the path a parameter, drop logging.
alloc-kitallocators.*STL allocator over pluggable backend + pool allocator + optional malloc trackingEasyMedSplit the global operator new/delete override into an opt-in TU; keep moya-lang's BSD-3 notice.

Wave 3 — coherent subsystems (bundle & lift)

Bigger pieces that stand on their own once a thread abstraction or a few helpers come along. Several are best shipped as a small dependency tree.

Proposed libSourceWhat it isDiffValueSplit plan
stashstash.hLock-free hierarchical timer-wheel slot store (the crown jewel)EasyHighStub trace macros; delete the vestigial _Ring/_CurrentKey params. See SCHEDULER.md.
thread-poolthread.hh, threadpool.hhBlocking-queue thread pool + CRTP Thread (promise-based join)Easy–ModMedDelete ThreadPolicyType; deps blocking_concurrent_queue.h + likely.h; strings::formatfmt.
timer-schedulerscheduler.h (+ stash + thread-pool)24h multi-resolution timer wheel + thread; cancellable tasks; inline or pool dispatchModHighBuild on stash + thread-pool; stub macros. See SCHEDULER.md.
debouncerdebouncer.hPer-key throttle/debounce with randomized force-windowEasy*HighRides on timer-scheduler; vendor callable_traits + 3 random helpers. (*after the scheduler is out.)
posix-ioio.cc, io.hhEINTR-retry / partial-IO / fsync / fallocate wrappers + fault injectionModHighCut log/error/exception; gate or drop the opts-driven fault injection; likely.h[[likely]]. SQLite-derived fallocate fallback is public-domain.
sysinfosystem.* + memory_stats.*Open/max fd counts, OS/arch probes, RAM/disk/swap/inode statsModMed–HighDrop the stray STATE_* defines in system.cc; carry posix-io; swap logging.
timeutiltimes.*, nanosleep.h, epoch.hh, time_point.hhtimespec arithmetic + macOS clock_gettime shim + chrono one-linersEasyMedDe-dup the doubled nanosleep; keep MM Weiss's BSD-3 on the macOS shim (guard it behind legacy-macOS).
tracebacktraceback.*Symbolized backtraces, cross-thread stack dump (signal), __cxa_throw captureModMedThe backtrace/symbolize core lifts cleanly; leave the global allocator interposition out (it's invasive).
libev-reactorworker.*shared_ptr lifetime tree + cross-loop async control + cooperative GC of finished workersModHighStub macros; depends only on libev otherwise.
condensed-uuidcuuid/UUID generation + variable-length condenser for v1 time-UUIDsModMedDecouple the node-salt from Node::get_local_node (inject the node id).

Wave 4 — keystones & hard cases

High value, but each needs a good core peeled away from real domain glue.

Proposed libSourceWhat it isDiffValueSplit plan
htm-geogeospatial/, cartesian.*Geometry → contiguous integer trixel ranges on a sphere (HTM), full shape supportModHighCut THROW/strings::format; drop the ~940 lines of matplotlib debug writers (htm.cc:838+). Fix the Cartesian operator^=/operator* bugs first (see ARCHITECTURE.md).
haystack-storestorage.h (+ lz4-stream)Single-file append-only blob store templated on format structsModHighReplace opts.* with constructor params; stub logging. Wire up the inert footer/header validation before relying on it.
iso8601 / datemathdatetime.* (string/tm_t/clk_t/calendar core)ISO-8601 + Elasticsearch-style date-math parser/formatterHardHighPeel off every const MsgPack& overload + phf/reserved dispatch; vendor strict_stox; reparent exceptions onto std::runtime_error. The pure parser/formatter is excellent.
sortable-serialise ⚠️sortable_serialise.*Order-preserving (memcmp-sortable) float/double encodingEasyHighTwo files, <cmath>/<cstring> only — but GPL (Xapian origin). License-incompatible with the MIT libs; keep separate or reimplement.
msgpack-patchmsgpack_patcher.*RFC 6902 JSON-Patch over a msgpack DOMModMedTravels with a future MsgPack extraction; collapse ClientError/Error into one patch_error; keep RapidJSON only for the RFC 6901 tokenizer (or hand-roll it).

The MsgPack keystone. msgpack.h (the COW wrapper with the custom UNDEFINED ext type) is the hub several Wave-4 pieces hang off (msgpack-patch, and the data-interchange side of cast). It's a Hard extraction in its own right (entangled with config.h, exceptions, RapidJSON, ChaiScript), but if you ever lift it, the msgpack-xchange adapters and msgpack-patch become a natural family around it.


Suggested bundles (the repos to actually create)

Many entries above are tiny and should ship together. A sensible repo set:

  • Foundational singletons: perfect-hash, lru-cache, callable-traits, errno-names, located-exception, bloom-filter, sortable-serialise (GPL, separate).
  • string-kitconstexpr-strings + split + string-metrics + phonetic (or split metrics/phonetic into their own repo if you want them findable by name).
  • term-color — the ANSI/color trio.
  • schedulerstash + thread-pool + timer-scheduler + debouncer as one repo with internal layering (or stash standalone + scheduler depending on it). See SCHEDULER.md.
  • sysutilposix-io + sysinfo + timeutil + alloc-kit + traceback (the "systems-programming odds and ends" most projects re-implement).
  • msgpack-xmsgpack-xchange now; msgpack-patch later if MsgPack is lifted.
  • Standalone heavies: htm-geo, haystack-store, libev-reactor, iso8601, urlcodec, mime-types, condensed-uuid.

Provenance & license map

Carry these notices on extraction; most of the tree is Dubalu LLC MIT.

SourceOrigin / licenseAction
sortable_serialise.*Xapian — GPLKeep in a GPL-licensed repo, or reimplement clean-room. Do not mix into the MIT libs.
allocators.h (pool allocator)moya-lang.org — BSD-3Keep the BSD-3 notice in alloc-kit.
times.cc (macOS clock_gettime)MM Weiss — BSD-3Keep the BSD-3 notice in timeutil.
xchange/rapidjson.hppco-credited to xpolKeep xpol's attribution.
io.cc (fallocate fallback)SQLite — public domainFine; note it.
y2j/Mapzen — MIT (vendored)Not Xapiand's to republish; point users upstream.
http_parser.hJoyent/Node.js (vendored)Not ours; use upstream.
everything elseDubalu LLC — MITStandard.

Not worth extracting

Domain glue or too-coupled / too-thin to package:

  • Schema & query glue: cast.*, script.*, reserved/*, query_dsl.*, aggregations/*, multivalue/*, database/*, serialise* (Xapiand schema model).
  • Cluster/transport: server/discovery.*, server/*_protocol*, manager.*, node.*, endpoint.* (a good reference Raft-over-UDP, but not a library).
  • Vendored or vendored-glue: metrics.* (your metric catalog over upstream prometheus-cpp), chaipp/* + module_* (ChaiScript↔document binding), exception_xapian.h, cmdoutput.h (TCLAP + Package), readable_revents.hh (libev-only, tiny), y2j/, http_parser.h.
  • Superseded / trivial: fs.* (mostly std::filesystem now; only normalize_path + quarantine_files are value-add), concurrent_queue.h / atomic_shared_ptr.h (thin shims; the former has a locking bug), check_size.* (debug-only), PathParser (bespoke URL dialect).

Suggested sequencing

  1. Wave 1 first — they're nearly free and several (errno-names, located-exception, the msgpack adapters, perfect-hash) are immediately useful elsewhere.
  2. scheduler next — it's the highest-interest piece, and stash is the most novel thing in the whole codebase. SCHEDULER.md is already its blueprint.
  3. string-kit and term-color — high value, all Wave-1/2 effort.
  4. sysutil — bundles a lot of Wave-2/3 systems plumbing into one useful repo.
  5. The heavies lasthtm-geo, haystack-store, iso8601 reward the effort but each needs real decoupling and a bug fix or two on the way out.

Before extracting any component flagged with a bug in ARCHITECTURE.md, fix the bug on the way out — a freshly-published library is the worst place to ship a known operator^= corruption or an inverted empty().