feat: integrate native indexer for mobile (#14174)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added full-text search functionality to mobile apps (Android and iOS),
enabling document indexing and search capabilities.
* Enhanced blob upload support with new GraphQL mutations for creating,
completing, and managing file uploads.

* **Improvements**
* iOS and Android now use SQLite storage backend for improved indexing
performance, aligning with desktop experience.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-12-28 21:34:39 +08:00
committed by GitHub
parent 504460438f
commit 20a80015c0
24 changed files with 1993 additions and 39 deletions
+139 -7
View File
@@ -1,5 +1,5 @@
use affine_common::hashcash::Stamp;
use affine_nbstore::pool::SqliteDocStoragePool;
use affine_nbstore::{pool::SqliteDocStoragePool, Data};
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum UniffiError {
@@ -50,9 +50,11 @@ impl TryFrom<DocRecord> for affine_nbstore::DocRecord {
fn try_from(record: DocRecord) -> Result<Self> {
Ok(Self {
doc_id: record.doc_id,
bin: base64_simd::STANDARD
.decode_to_vec(record.bin)
.map_err(|e| UniffiError::Base64DecodingError(e.to_string()))?,
bin: Into::<Data>::into(
base64_simd::STANDARD
.decode_to_vec(record.bin)
.map_err(|e| UniffiError::Base64DecodingError(e.to_string()))?,
),
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp_millis(record.timestamp)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
@@ -156,9 +158,11 @@ impl TryFrom<SetBlob> for affine_nbstore::SetBlob {
fn try_from(blob: SetBlob) -> Result<Self> {
Ok(Self {
key: blob.key,
data: base64_simd::STANDARD
.decode_to_vec(blob.data)
.map_err(|e| UniffiError::Base64DecodingError(e.to_string()))?,
data: Into::<Data>::into(
base64_simd::STANDARD
.decode_to_vec(blob.data)
.map_err(|e| UniffiError::Base64DecodingError(e.to_string()))?,
),
mime: blob.mime,
})
}
@@ -229,6 +233,38 @@ impl From<affine_nbstore::indexer::NativeCrawlResult> for CrawlResult {
}
}
#[derive(uniffi::Record)]
pub struct SearchHit {
pub id: String,
pub score: f64,
pub terms: Vec<String>,
}
impl From<affine_nbstore::indexer::NativeSearchHit> for SearchHit {
fn from(value: affine_nbstore::indexer::NativeSearchHit) -> Self {
Self {
id: value.id,
score: value.score,
terms: value.terms,
}
}
}
#[derive(uniffi::Record)]
pub struct MatchRange {
pub start: u32,
pub end: u32,
}
impl From<affine_nbstore::indexer::NativeMatch> for MatchRange {
fn from(value: affine_nbstore::indexer::NativeMatch) -> Self {
Self {
start: value.start,
end: value.end,
}
}
}
#[derive(uniffi::Object)]
pub struct DocStoragePool {
inner: SqliteDocStoragePool,
@@ -699,4 +735,100 @@ impl DocStoragePool {
.await?;
Ok(result.into())
}
pub async fn fts_add_document(
&self,
universal_id: String,
index_name: String,
doc_id: String,
text: String,
index: bool,
) -> Result<()> {
self
.inner
.get(universal_id)
.await?
.fts_add(&index_name, &doc_id, &text, index)
.await?;
Ok(())
}
pub async fn fts_delete_document(
&self,
universal_id: String,
index_name: String,
doc_id: String,
) -> Result<()> {
self
.inner
.get(universal_id)
.await?
.fts_delete(&index_name, &doc_id)
.await?;
Ok(())
}
pub async fn fts_get_document(
&self,
universal_id: String,
index_name: String,
doc_id: String,
) -> Result<Option<String>> {
Ok(
self
.inner
.get(universal_id)
.await?
.fts_get(&index_name, &doc_id)
.await?,
)
}
pub async fn fts_search(
&self,
universal_id: String,
index_name: String,
query: String,
) -> Result<Vec<SearchHit>> {
Ok(
self
.inner
.get(universal_id)
.await?
.fts_search(&index_name, &query)
.await?
.into_iter()
.map(Into::into)
.collect(),
)
}
pub async fn fts_get_matches(
&self,
universal_id: String,
index_name: String,
doc_id: String,
query: String,
) -> Result<Vec<MatchRange>> {
Ok(
self
.inner
.get(universal_id)
.await?
.fts_get_matches(&index_name, &doc_id, &query)
.await?
.into_iter()
.map(Into::into)
.collect(),
)
}
pub async fn fts_flush_index(&self, universal_id: String) -> Result<()> {
self.inner.get(universal_id).await?.flush_index().await?;
Ok(())
}
pub async fn fts_index_version(&self) -> Result<u32> {
Ok(affine_nbstore::storage::SqliteDocStorage::index_version())
}
}