searchez 1.0.0

A searchable-model layer for Rust: make a type searchable, keep the index in sync, and search with real relevance ranking — over a pluggable backend, with a batteries-included in-memory engine that needs no external service.
Documentation
// searchez/src/backend.rs
//
// The pluggable engine seam. `SearchEngine` is written entirely against this
// trait, so the in-memory backend and any future server backend (Meilisearch,
// OpenSearch, Tantivy, ...) are interchangeable. This is the split the ecosystem
// was missing: great engines exist, but nothing sat above them to own indexing
// lifecycle and hydration. Implement four methods and you inherit all of that.

use crate::document::Document;
use crate::error::Result;
use crate::query::{Hit, Query};
use async_trait::async_trait;

/// A document ready to index: its id plus the fields.
#[derive(Debug, Clone)]
pub struct IndexDoc {
    pub id: String,
    pub document: Document,
}

/// A storage-and-retrieval engine for search documents. Methods are keyed by
/// `index` (Searchkick's per-model index) so one backend serves many models.
#[async_trait]
pub trait Backend: Send + Sync {
    /// Insert or replace documents by id. Replacing an existing id must fully
    /// supersede the old document (no stale terms left behind).
    async fn upsert(&self, index: &str, docs: Vec<IndexDoc>) -> Result<()>;

    /// Remove documents by id. Absent ids are ignored (idempotent).
    async fn delete(&self, index: &str, ids: &[String]) -> Result<()>;

    /// Run a query and return hits, already ranked and paginated per the query.
    async fn search(&self, index: &str, query: &Query) -> Result<Vec<Hit>>;

    /// Drop every document in an index. Used by a full reindex.
    async fn clear(&self, index: &str) -> Result<()>;

    /// Number of documents in an index (for tests, health, and reindex reports).
    async fn count(&self, index: &str) -> Result<usize>;
}