Files
AFFiNE-Mirror/packages/backend/native/src/doc.rs
T
DarkSky 8d72e4dc29 feat(core): import progress & perf (#15197)
#### PR Dependency Tree


* **PR #15197** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

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

* **New Features**
* Added a new import pipeline with “plan then commit” batch handling for
Markdown, Notion HTML, Obsidian, and Bear backups.
* Enabled native import sessions with progress, cancellation, and
batch-by-batch committing (including assets, folders, icons, and tags).
  * Added web preflight limits for ZIP and multi-file imports.
* **Bug Fixes**
* Improved import error/warning reporting and continued processing when
some items fail.
* Strengthened snapshot-based file/directory picking to preserve paths.
* **Chores**
  * Updated project packaging/configuration for the new import workflow.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-06 01:29:24 +08:00

293 lines
8.3 KiB
Rust

use affine_common::napi_utils::map_napi_err;
use affine_doc_loader::{
self as doc_loader, BlockInfo, CrawlResult, MarkdownResult, PageDocContent, WorkspaceDocContent,
};
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi(object)]
pub struct NativeMarkdownResult {
pub title: String,
pub markdown: String,
pub known_unsupported_blocks: Vec<String>,
pub unknown_blocks: Vec<String>,
}
impl From<MarkdownResult> for NativeMarkdownResult {
fn from(result: MarkdownResult) -> Self {
Self {
title: result.title,
markdown: result.markdown,
known_unsupported_blocks: result.known_unsupported_blocks,
unknown_blocks: result.unknown_blocks,
}
}
}
#[napi(object)]
pub struct NativePageDocContent {
pub title: String,
pub summary: String,
}
impl From<PageDocContent> for NativePageDocContent {
fn from(result: PageDocContent) -> Self {
Self {
title: result.title,
summary: result.summary,
}
}
}
#[napi(object)]
pub struct NativeWorkspaceDocContent {
pub name: String,
pub avatar_key: String,
}
impl From<WorkspaceDocContent> for NativeWorkspaceDocContent {
fn from(result: WorkspaceDocContent) -> Self {
Self {
name: result.name,
avatar_key: result.avatar_key,
}
}
}
#[napi(object)]
pub struct PublicDocMetaInput {
pub id: String,
pub title: Option<String>,
}
#[napi(object)]
pub struct NativeBlockInfo {
pub block_id: String,
pub flavour: String,
pub content: Option<Vec<String>>,
pub blob: Option<Vec<String>>,
pub ref_doc_id: Option<Vec<String>>,
pub ref_info: Option<Vec<String>>,
pub parent_flavour: Option<String>,
pub parent_block_id: Option<String>,
pub additional: Option<String>,
}
impl From<BlockInfo> for NativeBlockInfo {
fn from(info: BlockInfo) -> Self {
Self {
block_id: info.block_id,
flavour: info.flavour,
content: info.content,
blob: info.blob,
ref_doc_id: info.ref_doc_id,
ref_info: info.ref_info,
parent_flavour: info.parent_flavour,
parent_block_id: info.parent_block_id,
additional: info.additional,
}
}
}
#[napi(object)]
pub struct NativeCrawlResult {
pub blocks: Vec<NativeBlockInfo>,
pub title: String,
pub summary: String,
}
impl From<CrawlResult> for NativeCrawlResult {
fn from(result: CrawlResult) -> Self {
Self {
blocks: result.blocks.into_iter().map(Into::into).collect(),
title: result.title,
summary: result.summary,
}
}
}
#[napi]
pub fn parse_doc_from_binary(doc_bin: Buffer, doc_id: String) -> Result<NativeCrawlResult> {
let result = map_napi_err(
doc_loader::parse_doc_from_binary(doc_bin.into(), doc_id),
Status::GenericFailure,
)?;
Ok(result.into())
}
#[napi]
pub fn parse_page_doc(doc_bin: Buffer, max_summary_length: Option<i32>) -> Result<Option<NativePageDocContent>> {
let result = map_napi_err(
doc_loader::parse_page_doc(doc_bin.into(), max_summary_length.map(|v| v as isize)),
Status::GenericFailure,
)?;
Ok(result.map(Into::into))
}
#[napi]
pub fn parse_workspace_doc(doc_bin: Buffer) -> Result<Option<NativeWorkspaceDocContent>> {
let result = map_napi_err(doc_loader::parse_workspace_doc(doc_bin.into()), Status::GenericFailure)?;
Ok(result.map(Into::into))
}
#[napi]
pub fn parse_doc_to_markdown(
doc_bin: Buffer,
doc_id: String,
ai_editable: Option<bool>,
doc_url_prefix: Option<String>,
) -> Result<NativeMarkdownResult> {
let result = map_napi_err(
doc_loader::parse_doc_to_markdown(doc_bin.into(), doc_id, ai_editable.unwrap_or(false), doc_url_prefix),
Status::GenericFailure,
)?;
Ok(result.into())
}
#[napi]
pub fn read_all_doc_ids_from_root_doc(doc_bin: Buffer, include_trash: Option<bool>) -> Result<Vec<String>> {
let result = map_napi_err(
doc_loader::get_doc_ids_from_binary(doc_bin.into(), include_trash.unwrap_or(false)),
Status::GenericFailure,
)?;
Ok(result)
}
/// Converts markdown content to AFFiNE-compatible y-octo document binary.
///
/// # Arguments
/// * `title` - The document title
/// * `markdown` - The markdown content to convert
/// * `doc_id` - The document ID to use for the y-octo doc
///
/// # Returns
/// A Buffer containing the y-octo document update binary
#[napi]
pub fn create_doc_with_markdown(title: String, markdown: String, doc_id: String) -> Result<Buffer> {
let result = map_napi_err(
doc_loader::build_full_doc(&title, &markdown, &doc_id),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}
/// Updates an existing document with new markdown content.
/// Uses structural diffing to apply block-level replacements for changes.
///
/// # Arguments
/// * `existing_binary` - The current document binary
/// * `new_markdown` - The new markdown content to apply
/// * `doc_id` - The document ID
///
/// # Returns
/// A Buffer containing only the delta (changes) as a y-octo update binary
#[napi]
pub fn update_doc_with_markdown(existing_binary: Buffer, new_markdown: String, doc_id: String) -> Result<Buffer> {
let result = map_napi_err(
doc_loader::update_doc(&existing_binary, &new_markdown, &doc_id),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}
/// Updates a document's title without touching content blocks.
///
/// # Arguments
/// * `existing_binary` - The current document binary
/// * `title` - The new title
/// * `doc_id` - The document ID
///
/// # Returns
/// A Buffer containing only the delta (changes) as a y-octo update binary
#[napi]
pub fn update_doc_title(existing_binary: Buffer, title: String, doc_id: String) -> Result<Buffer> {
let result = map_napi_err(
doc_loader::update_doc_title(&existing_binary, &doc_id, &title),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}
/// Updates or creates the docProperties record for a document.
///
/// # Arguments
/// * `existing_binary` - The current docProperties document binary
/// * `properties_doc_id` - The docProperties document ID
/// (db$${workspaceId}$docProperties)
/// * `target_doc_id` - The document ID to update in docProperties
/// * `created_by` - Optional creator user ID
/// * `updated_by` - Optional updater user ID
///
/// # Returns
/// A Buffer containing only the delta (changes) as a y-octo update binary
#[napi]
pub fn update_doc_properties(
existing_binary: Buffer,
properties_doc_id: String,
target_doc_id: String,
created_by: Option<String>,
updated_by: Option<String>,
) -> Result<Buffer> {
let result = map_napi_err(
doc_loader::update_doc_properties(
&existing_binary,
&properties_doc_id,
&target_doc_id,
created_by.as_deref(),
updated_by.as_deref(),
),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}
/// Adds a document ID to the workspace root doc's meta.pages array.
/// This registers the document in the workspace so it appears in the UI.
///
/// # Arguments
/// * `root_doc_bin` - The current root doc binary (workspaceId doc)
/// * `doc_id` - The document ID to add
/// * `title` - Optional title for the document
///
/// # Returns
/// A Buffer containing the y-octo update binary to apply to the root doc
#[napi]
pub fn add_doc_to_root_doc(root_doc_bin: Buffer, doc_id: String, title: Option<String>) -> Result<Buffer> {
let result = map_napi_err(
doc_loader::add_doc_to_root_doc(root_doc_bin.into(), &doc_id, title.as_deref()),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}
#[napi]
pub fn build_public_root_doc(root_doc_bin: Buffer, doc_metas: Vec<PublicDocMetaInput>) -> Result<Buffer> {
let metas = doc_metas
.iter()
.map(|meta| (meta.id.as_str(), meta.title.as_deref()))
.collect::<Vec<_>>();
let result = map_napi_err(
doc_loader::build_public_root_doc(&root_doc_bin, &metas),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}
/// Updates a document title in the workspace root doc's meta.pages array.
///
/// # Arguments
/// * `root_doc_bin` - The current root doc binary (workspaceId doc)
/// * `doc_id` - The document ID to update
/// * `title` - The new title for the document
///
/// # Returns
/// A Buffer containing the y-octo update binary to apply to the root doc
#[napi]
pub fn update_root_doc_meta_title(root_doc_bin: Buffer, doc_id: String, title: String) -> Result<Buffer> {
let result = map_napi_err(
doc_loader::update_root_doc_meta_title(&root_doc_bin, &doc_id, &title),
Status::GenericFailure,
)?;
Ok(Buffer::from(result))
}