From 95786c1cbb04ec64142867eafc19ac212a561750 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Wed, 13 Mar 2024 13:34:14 +0800 Subject: [PATCH] Fix sync tests (#2194) * Fix sync tests * fix build --- Cargo.lock | Bin 279369 -> 279378 bytes Cargo.toml | 39 ++-- core/crates/sync/src/db_operation.rs | 31 ++- core/crates/sync/src/ingest.rs | 84 +++++-- core/crates/sync/src/lib.rs | 10 +- core/crates/sync/src/manager.rs | 2 + core/crates/sync/tests/lib.rs | 29 ++- core/src/cloud/sync/ingest.rs | 2 +- core/src/cloud/sync/receive.rs | 2 +- core/src/p2p/sync/mod.rs | 2 +- crates/prisma/Cargo.toml | 1 + crates/prisma/src/lib.rs | 46 ++-- packages/client/src/core.ts | 324 +++++++++++++-------------- 13 files changed, 327 insertions(+), 245 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 048b92f10d6e90477c5c12fa5ec986793a39b0bc..ecdb005a24e9f05196e4619625e849c910d3a7b4 100644 GIT binary patch delta 30 mcmX@vCU~h$u%U&qg=q`3G1v4hpO}=}eYu#o`*N}Tu>b(G#|qK_ delta 30 mcmccACU~+8YhjFp|u>b(K#|t3< diff --git a/Cargo.toml b/Cargo.toml index b107e48ae..c335d2aa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [workspace] resolver = "2" members = [ - "core", - "core/crates/*", - "crates/*", - "apps/cli", - "apps/p2p-relay", - "apps/desktop/src-tauri", - "apps/desktop/crates/*", - "apps/mobile/modules/sd-core/core", - "apps/mobile/modules/sd-core/android/crate", - "apps/mobile/modules/sd-core/ios/crate", - "apps/server", + "core", + "core/crates/*", + "crates/*", + "apps/cli", + "apps/p2p-relay", + "apps/desktop/src-tauri", + "apps/desktop/crates/*", + "apps/mobile/modules/sd-core/core", + "apps/mobile/modules/sd-core/android/crate", + "apps/mobile/modules/sd-core/ios/crate", + "apps/server", ] [workspace.package] @@ -22,18 +22,19 @@ repository = "https://github.com/spacedriveapp/spacedrive" [workspace.dependencies] # First party dependencies prisma-client-rust = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "f99d6f5566570f3ab1edecb7a172ad25b03d95af", features = [ - "sqlite-create-many", - "migrations", - "sqlite", + "specta", + "sqlite-create-many", + "migrations", + "sqlite", ], default-features = false } prisma-client-rust-cli = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "f99d6f5566570f3ab1edecb7a172ad25b03d95af", features = [ - "specta", - "sqlite-create-many", - "migrations", - "sqlite", + "specta", + "sqlite-create-many", + "migrations", + "sqlite", ], default-features = false } prisma-client-rust-sdk = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "f99d6f5566570f3ab1edecb7a172ad25b03d95af", features = [ - "sqlite", + "sqlite", ], default-features = false } tracing = "0.1.40" diff --git a/core/crates/sync/src/db_operation.rs b/core/crates/sync/src/db_operation.rs index ad74f6101..b167717d4 100644 --- a/core/crates/sync/src/db_operation.rs +++ b/core/crates/sync/src/db_operation.rs @@ -1,4 +1,5 @@ -use sd_prisma::prisma::{cloud_crdt_operation, crdt_operation}; +use rmp_serde::to_vec; +use sd_prisma::prisma::{cloud_crdt_operation, crdt_operation, instance, PrismaClient}; use sd_sync::CRDTOperation; use uhlc::NTP64; use uuid::Uuid; @@ -29,9 +30,9 @@ impl crdt_include::Data { id: self.id(), instance: self.instance(), timestamp: self.timestamp(), - record_id: serde_json::from_slice(&self.record_id).unwrap(), + record_id: rmp_serde::from_slice(&self.record_id).unwrap(), model: self.model, - data: serde_json::from_slice(&self.data).unwrap(), + data: rmp_serde::from_slice(&self.data).unwrap(), // match self { // Self::Shared(op) => CRDTOperationType::Shared(SharedOperation { // record_id: serde_json::from_slice(&op.record_id).unwrap(), @@ -67,7 +68,7 @@ impl cloud_crdt_include::Data { id: self.id(), instance: self.instance(), timestamp: self.timestamp(), - record_id: serde_json::from_slice(&self.record_id).unwrap(), + record_id: rmp_serde::from_slice(&self.record_id).unwrap(), model: self.model, data: serde_json::from_slice(&self.data).unwrap(), // match self { @@ -82,3 +83,25 @@ impl cloud_crdt_include::Data { } } } + +pub async fn write_crdt_op_to_db( + op: &CRDTOperation, + db: &PrismaClient, +) -> Result<(), prisma_client_rust::QueryError> { + crdt_op_db(op).to_query(db).exec().await?; + + Ok(()) +} + +fn crdt_op_db(op: &CRDTOperation) -> crdt_operation::Create { + crdt_operation::Create { + id: op.id.as_bytes().to_vec(), + timestamp: op.timestamp.0 as i64, + instance: instance::pub_id::equals(op.instance.as_bytes().to_vec()), + kind: op.kind().to_string(), + data: to_vec(&op.data).unwrap(), + model: op.model.to_string(), + record_id: rmp_serde::to_vec(&op.record_id).unwrap(), + _params: vec![], + } +} diff --git a/core/crates/sync/src/ingest.rs b/core/crates/sync/src/ingest.rs index de1dbcde6..7b84dfb4c 100644 --- a/core/crates/sync/src/ingest.rs +++ b/core/crates/sync/src/ingest.rs @@ -1,17 +1,17 @@ use std::{ops::Deref, sync::Arc}; use sd_prisma::{ - prisma::{crdt_operation, instance, PrismaClient, SortOrder}, + prisma::{crdt_operation, SortOrder}, prisma_sync::ModelSyncData, }; use sd_sync::CRDTOperation; -use serde_json::to_vec; -use tokio::sync::{mpsc, Mutex}; +use tokio::sync::{mpsc, oneshot, Mutex}; use uhlc::{Timestamp, NTP64}; use uuid::Uuid; use crate::{ actor::{create_actor_io, ActorIO, ActorTypes}, + db_operation::write_crdt_op_to_db, wait, SharedState, }; @@ -19,7 +19,10 @@ use crate::{ #[must_use] /// Stuff that can be handled outside the actor pub enum Request { - Messages { timestamps: Vec<(Uuid, NTP64)> }, + Messages { + timestamps: Vec<(Uuid, NTP64)>, + tx: oneshot::Sender<()>, + }, Ingested, FinishedIngesting, } @@ -62,6 +65,8 @@ impl Actor { State::RetrievingMessages } State::RetrievingMessages => { + let (tx, mut rx) = oneshot::channel::<()>(); + self.io .send(Request::Messages { timestamps: self @@ -71,11 +76,27 @@ impl Actor { .iter() .map(|(&k, &v)| (k, v)) .collect(), + tx, }) .await .ok(); - State::Ingesting(wait!(self.io.event_rx, Event::Messages(event) => event)) + loop { + tokio::select! { + res = &mut rx => { + match res { + Err(_) => break State::WaitingForNotification, + Ok(_) => {} + } + }, + res = self.io.event_rx.recv() => { + match res { + Some(Event::Messages(event)) => break State::Ingesting(event), + _ => {} + } + } + } + } } State::Ingesting(event) => { for op in event.messages { @@ -226,24 +247,43 @@ impl ActorTypes for Actor { type Handler = Handler; } -async fn write_crdt_op_to_db( - op: &CRDTOperation, - db: &PrismaClient, -) -> Result<(), prisma_client_rust::QueryError> { - crdt_op_db(op).to_query(db).exec().await?; +#[cfg(test)] +mod test { + use std::sync::atomic::AtomicBool; - Ok(()) -} + use uhlc::HLCBuilder; -fn crdt_op_db(op: &CRDTOperation) -> crdt_operation::Create { - crdt_operation::Create { - id: op.id.as_bytes().to_vec(), - timestamp: op.timestamp.0 as i64, - instance: instance::pub_id::equals(op.instance.as_bytes().to_vec()), - kind: op.kind().to_string(), - data: to_vec(&op.data).unwrap(), - model: op.model.to_string(), - record_id: to_vec(&op.record_id).unwrap(), - _params: vec![], + use super::*; + + async fn new_actor() -> (Handler, Arc) { + let instance = uuid::Uuid::new_v4(); + let shared = Arc::new(SharedState { + db: sd_prisma::test_db().await, + instance, + clock: HLCBuilder::new().with_id(instance.into()).build(), + timestamps: Default::default(), + emit_messages_flag: Arc::new(AtomicBool::new(true)), + }); + + (Actor::spawn(shared.clone()), shared) } + + // /// If messages tx is dropped, actor should reset and assume no further messages + // /// will be sent + // #[tokio::test] + // async fn retrieve_wait() -> Result<(), ()> { + // let (ingest, _) = new_actor().await; + + // for _ in [(), ()] { + // let mut rx = ingest.req_rx.lock().await; + + // ingest.event_tx.send(Event::Notification).await.unwrap(); + + // let Some(Request::Messages { .. }) = rx.recv().await else { + // panic!("bruh") + // }; + // } + + // Ok(()) + // } } diff --git a/core/crates/sync/src/lib.rs b/core/crates/sync/src/lib.rs index 32674473d..bd329a0ac 100644 --- a/core/crates/sync/src/lib.rs +++ b/core/crates/sync/src/lib.rs @@ -18,7 +18,7 @@ pub use ingest::*; pub use manager::*; pub use uhlc::NTP64; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum SyncMessage { Ingested, Created, @@ -41,9 +41,9 @@ pub fn crdt_op_db(op: &CRDTOperation) -> crdt_operation::Create { timestamp: op.timestamp.0 as i64, instance: instance::pub_id::equals(op.instance.as_bytes().to_vec()), kind: op.kind().to_string(), - data: serde_json::to_vec(&op.data).unwrap(), + data: rmp_serde::to_vec(&op.data).unwrap(), model: op.model.to_string(), - record_id: serde_json::to_vec(&op.record_id).unwrap(), + record_id: rmp_serde::to_vec(&op.record_id).unwrap(), _params: vec![], } } @@ -58,9 +58,9 @@ pub fn crdt_op_unchecked_db( timestamp: op.timestamp.0 as i64, instance_id, kind: op.kind().to_string(), - data: serde_json::to_vec(&op.data).unwrap(), + data: rmp_serde::to_vec(&op.data).unwrap(), model: op.model.to_string(), - record_id: serde_json::to_vec(&op.record_id).unwrap(), + record_id: rmp_serde::to_vec(&op.record_id).unwrap(), _params: vec![], } } diff --git a/core/crates/sync/src/manager.rs b/core/crates/sync/src/manager.rs index 667ffa313..618f98c04 100644 --- a/core/crates/sync/src/manager.rs +++ b/core/crates/sync/src/manager.rs @@ -119,6 +119,8 @@ impl Manager { ) -> prisma_client_rust::Result> { let db = &self.db; + dbg!(&args); + macro_rules! db_args { ($args:ident, $op:ident) => { vec![prisma_client_rust::operator::or( diff --git a/core/crates/sync/tests/lib.rs b/core/crates/sync/tests/lib.rs index 38b0f0e3f..a84a9814b 100644 --- a/core/crates/sync/tests/lib.rs +++ b/core/crates/sync/tests/lib.rs @@ -4,7 +4,7 @@ use sd_sync::*; use sd_utils::uuid_to_bytes; use prisma_client_rust::chrono::Utc; -use std::sync::Arc; +use std::sync::{atomic::AtomicBool, Arc}; use tokio::sync::broadcast; use uuid::Uuid; @@ -21,16 +21,24 @@ struct Instance { impl Instance { async fn new(id: Uuid) -> (Arc, broadcast::Receiver) { + let url = format!("file:{}", db_path(id)); + + println!("new -1: {url}"); + let db = Arc::new( prisma::PrismaClient::_builder() - .with_url(format!("file:{}", db_path(id))) + .with_url(url.to_string()) .build() .await .unwrap(), ); + println!("new 0: {url}"); + db._db_push().await.unwrap(); + println!("new 1"); + db.instance() .create( uuid_to_bytes(id), @@ -46,7 +54,14 @@ impl Instance { .await .unwrap(); - let sync = sd_core_sync::Manager::new(&db, id, &Default::default(), Default::default()); + println!("new 2"); + + let sync = sd_core_sync::Manager::new( + &db, + id, + &Arc::new(AtomicBool::new(true)), + Default::default(), + ); ( Arc::new(Self { @@ -105,7 +120,7 @@ async fn bruh() -> Result<(), Box> { Instance::pair(&instance1, &instance2).await; - tokio::spawn({ + let task_1 = tokio::spawn({ let _instance1 = instance1.clone(); let instance2 = instance2.clone(); @@ -124,7 +139,7 @@ async fn bruh() -> Result<(), Box> { } }); - tokio::spawn({ + let task_2 = tokio::spawn({ let instance1 = instance1.clone(); let instance2 = instance2.clone(); @@ -142,6 +157,7 @@ async fn bruh() -> Result<(), Box> { .unwrap(); let ingest = &instance2.sync.ingest; + ingest .event_tx .send(ingest::Event::Messages(ingest::MessagesEvent { @@ -202,5 +218,8 @@ async fn bruh() -> Result<(), Box> { instance1.teardown().await; instance2.teardown().await; + task_1.abort(); + task_2.abort(); + Ok(()) } diff --git a/core/src/cloud/sync/ingest.rs b/core/src/cloud/sync/ingest.rs index ef585a231..ae9225f4b 100644 --- a/core/src/cloud/sync/ingest.rs +++ b/core/src/cloud/sync/ingest.rs @@ -26,7 +26,7 @@ pub async fn run_actor(sync: Arc, notify: Arc) { let timestamps = match req { Request::FinishedIngesting => break, - Request::Messages { timestamps } => timestamps, + Request::Messages { timestamps, .. } => timestamps, _ => continue, }; diff --git a/core/src/cloud/sync/receive.rs b/core/src/cloud/sync/receive.rs index 73492252b..5f4495669 100644 --- a/core/src/cloud/sync/receive.rs +++ b/core/src/cloud/sync/receive.rs @@ -201,7 +201,7 @@ fn crdt_op_db(op: &CRDTOperation) -> cloud_crdt_operation::Create { kind: op.data.as_kind().to_string(), data: to_vec(&op.data).expect("unable to serialize data"), model: op.model.to_string(), - record_id: to_vec(&op.record_id).expect("unable to serialize record id"), + record_id: rmp_serde::to_vec(&op.record_id).expect("unable to serialize record id"), _params: vec![], } } diff --git a/core/src/p2p/sync/mod.rs b/core/src/p2p/sync/mod.rs index 9d8791589..dcdf70a28 100644 --- a/core/src/p2p/sync/mod.rs +++ b/core/src/p2p/sync/mod.rs @@ -217,7 +217,7 @@ mod responder { let timestamps = match req { Request::FinishedIngesting => break, - Request::Messages { timestamps } => timestamps, + Request::Messages { timestamps, .. } => timestamps, _ => continue, }; diff --git a/crates/prisma/Cargo.toml b/crates/prisma/Cargo.toml index b458e268e..79c0f51c3 100644 --- a/crates/prisma/Cargo.toml +++ b/crates/prisma/Cargo.toml @@ -12,3 +12,4 @@ serde = { workspace = true } serde_json = { workspace = true } rmp-serde = "1.1.2" rmpv.workspace = true +uuid = { workspace = true } diff --git a/crates/prisma/src/lib.rs b/crates/prisma/src/lib.rs index a2ecc0253..5a5a5f3b9 100644 --- a/crates/prisma/src/lib.rs +++ b/crates/prisma/src/lib.rs @@ -3,32 +3,28 @@ pub mod prisma; #[allow(warnings, unused)] pub mod prisma_sync; -impl sd_cache::Model for prisma::tag::Data { - fn name() -> &'static str { - "Tag" - } +macro_rules! impl_model { + ($module:ident) => { + impl sd_cache::Model for prisma::$module::Data { + fn name() -> &'static str { + prisma::$module::NAME + } + } + }; } -impl sd_cache::Model for prisma::object::Data { - fn name() -> &'static str { - "Object" - } -} +impl_model!(tag); +impl_model!(object); +impl_model!(location); +impl_model!(indexer_rule); +impl_model!(file_path); -impl sd_cache::Model for prisma::location::Data { - fn name() -> &'static str { - "Location" - } -} - -impl sd_cache::Model for prisma::indexer_rule::Data { - fn name() -> &'static str { - "IndexerRule" - } -} - -impl sd_cache::Model for prisma::file_path::Data { - fn name() -> &'static str { - "FilePath" - } +pub async fn test_db() -> std::sync::Arc { + std::sync::Arc::new( + prisma::PrismaClient::_builder() + .with_url(format!("file:/tmp/test-db-{}", uuid::Uuid::new_v4())) + .build() + .await + .unwrap(), + ) } diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 896b0ae79..ef1e185b1 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -2,140 +2,140 @@ // This file was generated by [rspc](https://github.com/oscartbeaumont/rspc). Do not edit this file manually. export type Procedures = { - queries: - { key: "auth.me", input: never, result: { id: string; email: string } } | - { key: "backups.getAll", input: never, result: GetAll } | - { key: "buildInfo", input: never, result: BuildInfo } | - { key: "cloud.getApiOrigin", input: never, result: string } | - { key: "cloud.library.get", input: LibraryArgs, result: { id: string; uuid: string; name: string; instances: CloudInstance[]; ownerId: string } | null } | - { key: "cloud.library.list", input: never, result: CloudLibrary[] } | - { key: "cloud.locations.list", input: never, result: CloudLocation[] } | - { key: "ephemeralFiles.getMediaData", input: string, result: ({ type: "Image" } & ImageMetadata) | ({ type: "Video" } & VideoMetadata) | ({ type: "Audio" } & AudioMetadata) | null } | - { key: "files.get", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | - { key: "files.getConvertableImageExtensions", input: never, result: string[] } | - { key: "files.getMediaData", input: LibraryArgs, result: MediaMetadata } | - { key: "files.getPath", input: LibraryArgs, result: string | null } | - { key: "invalidation.test-invalidate", input: never, result: number } | - { key: "jobs.isActive", input: LibraryArgs, result: boolean } | - { key: "jobs.reports", input: LibraryArgs, result: JobGroup[] } | - { key: "labels.count", input: LibraryArgs, result: number } | - { key: "labels.get", input: LibraryArgs, result: { id: number; name: string; date_created: string | null; date_modified: string | null } | null } | - { key: "labels.getForObject", input: LibraryArgs, result: Label[] } | - { key: "labels.getWithObjects", input: LibraryArgs, result: { [key in number]: { date_created: string; object: { id: number } }[] } } | - { key: "labels.list", input: LibraryArgs, result: Label[] } | - { key: "labels.listWithThumbnails", input: LibraryArgs, result: ExplorerItem[] } | - { key: "library.kindStatistics", input: LibraryArgs, result: KindStatistics } | - { key: "library.list", input: never, result: NormalisedResults } | - { key: "library.statistics", input: LibraryArgs, result: StatisticsResponse } | - { key: "locations.get", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | - { key: "locations.getWithRules", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | - { key: "locations.indexer_rules.get", input: LibraryArgs, result: NormalisedResult } | - { key: "locations.indexer_rules.list", input: LibraryArgs, result: NormalisedResults } | - { key: "locations.indexer_rules.listForLocation", input: LibraryArgs, result: NormalisedResults } | - { key: "locations.list", input: LibraryArgs, result: NormalisedResults } | - { key: "locations.systemLocations", input: never, result: SystemLocations } | - { key: "models.image_detection.list", input: never, result: string[] } | - { key: "nodeState", input: never, result: NodeState } | - { key: "nodes.listLocations", input: LibraryArgs, result: ExplorerItem[] } | - { key: "notifications.dismiss", input: NotificationId, result: null } | - { key: "notifications.dismissAll", input: never, result: null } | - { key: "notifications.get", input: never, result: Notification[] } | - { key: "p2p.state", input: never, result: JsonValue } | - { key: "preferences.get", input: LibraryArgs, result: LibraryPreferences } | - { key: "search.objects", input: LibraryArgs, result: SearchData } | - { key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } | - { key: "search.paths", input: LibraryArgs, result: SearchData } | - { key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } | - { key: "search.saved.get", input: LibraryArgs, result: { id: number; pub_id: number[]; search: string | null; filters: string | null; name: string | null; icon: string | null; description: string | null; date_created: string | null; date_modified: string | null } | null } | - { key: "search.saved.list", input: LibraryArgs, result: SavedSearch[] } | - { key: "sync.enabled", input: LibraryArgs, result: boolean } | - { key: "sync.messages", input: LibraryArgs, result: CRDTOperation[] } | - { key: "tags.get", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | - { key: "tags.getForObject", input: LibraryArgs, result: NormalisedResults } | - { key: "tags.getWithObjects", input: LibraryArgs, result: { [key in number]: ({ date_created: string | null; object: { id: number } })[] } } | - { key: "tags.list", input: LibraryArgs, result: NormalisedResults } | + queries: + { key: "auth.me", input: never, result: { id: string; email: string } } | + { key: "backups.getAll", input: never, result: GetAll } | + { key: "buildInfo", input: never, result: BuildInfo } | + { key: "cloud.getApiOrigin", input: never, result: string } | + { key: "cloud.library.get", input: LibraryArgs, result: { id: string; uuid: string; name: string; instances: CloudInstance[]; ownerId: string } | null } | + { key: "cloud.library.list", input: never, result: CloudLibrary[] } | + { key: "cloud.locations.list", input: never, result: CloudLocation[] } | + { key: "ephemeralFiles.getMediaData", input: string, result: ({ type: "Image" } & ImageMetadata) | ({ type: "Video" } & VideoMetadata) | ({ type: "Audio" } & AudioMetadata) | null } | + { key: "files.get", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | + { key: "files.getConvertableImageExtensions", input: never, result: string[] } | + { key: "files.getMediaData", input: LibraryArgs, result: MediaMetadata } | + { key: "files.getPath", input: LibraryArgs, result: string | null } | + { key: "invalidation.test-invalidate", input: never, result: number } | + { key: "jobs.isActive", input: LibraryArgs, result: boolean } | + { key: "jobs.reports", input: LibraryArgs, result: JobGroup[] } | + { key: "labels.count", input: LibraryArgs, result: number } | + { key: "labels.get", input: LibraryArgs, result: { id: number; name: string; date_created: string | null; date_modified: string | null } | null } | + { key: "labels.getForObject", input: LibraryArgs, result: Label[] } | + { key: "labels.getWithObjects", input: LibraryArgs, result: { [key in number]: { date_created: string; object: { id: number } }[] } } | + { key: "labels.list", input: LibraryArgs, result: Label[] } | + { key: "labels.listWithThumbnails", input: LibraryArgs, result: ExplorerItem[] } | + { key: "library.kindStatistics", input: LibraryArgs, result: KindStatistics } | + { key: "library.list", input: never, result: NormalisedResults } | + { key: "library.statistics", input: LibraryArgs, result: StatisticsResponse } | + { key: "locations.get", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | + { key: "locations.getWithRules", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | + { key: "locations.indexer_rules.get", input: LibraryArgs, result: NormalisedResult } | + { key: "locations.indexer_rules.list", input: LibraryArgs, result: NormalisedResults } | + { key: "locations.indexer_rules.listForLocation", input: LibraryArgs, result: NormalisedResults } | + { key: "locations.list", input: LibraryArgs, result: NormalisedResults } | + { key: "locations.systemLocations", input: never, result: SystemLocations } | + { key: "models.image_detection.list", input: never, result: string[] } | + { key: "nodeState", input: never, result: NodeState } | + { key: "nodes.listLocations", input: LibraryArgs, result: ExplorerItem[] } | + { key: "notifications.dismiss", input: NotificationId, result: null } | + { key: "notifications.dismissAll", input: never, result: null } | + { key: "notifications.get", input: never, result: Notification[] } | + { key: "p2p.state", input: never, result: JsonValue } | + { key: "preferences.get", input: LibraryArgs, result: LibraryPreferences } | + { key: "search.objects", input: LibraryArgs, result: SearchData } | + { key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } | + { key: "search.paths", input: LibraryArgs, result: SearchData } | + { key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } | + { key: "search.saved.get", input: LibraryArgs, result: { id: number; pub_id: number[]; search: string | null; filters: string | null; name: string | null; icon: string | null; description: string | null; date_created: string | null; date_modified: string | null } | null } | + { key: "search.saved.list", input: LibraryArgs, result: SavedSearch[] } | + { key: "sync.enabled", input: LibraryArgs, result: boolean } | + { key: "sync.messages", input: LibraryArgs, result: CRDTOperation[] } | + { key: "tags.get", input: LibraryArgs, result: { item: Reference; nodes: CacheNode[] } | null } | + { key: "tags.getForObject", input: LibraryArgs, result: NormalisedResults } | + { key: "tags.getWithObjects", input: LibraryArgs, result: { [key in number]: ({ date_created: string | null; object: { id: number } })[] } } | + { key: "tags.list", input: LibraryArgs, result: NormalisedResults } | { key: "volumes.list", input: never, result: NormalisedResults }, - mutations: - { key: "api.sendFeedback", input: Feedback, result: null } | - { key: "auth.logout", input: never, result: null } | - { key: "backups.backup", input: LibraryArgs, result: string } | - { key: "backups.delete", input: string, result: null } | - { key: "backups.restore", input: string, result: null } | - { key: "cloud.library.create", input: LibraryArgs, result: null } | - { key: "cloud.library.join", input: string, result: LibraryConfigWrapped } | - { key: "cloud.library.sync", input: LibraryArgs, result: null } | - { key: "cloud.locations.create", input: string, result: CloudLocation } | - { key: "cloud.locations.remove", input: string, result: CloudLocation } | - { key: "cloud.locations.testing", input: TestingParams, result: null } | - { key: "cloud.setApiOrigin", input: string, result: null } | - { key: "ephemeralFiles.copyFiles", input: LibraryArgs, result: null } | - { key: "ephemeralFiles.createFolder", input: LibraryArgs, result: string } | - { key: "ephemeralFiles.cutFiles", input: LibraryArgs, result: null } | - { key: "ephemeralFiles.deleteFiles", input: LibraryArgs, result: null } | - { key: "ephemeralFiles.renameFile", input: LibraryArgs, result: null } | - { key: "files.convertImage", input: LibraryArgs, result: null } | - { key: "files.copyFiles", input: LibraryArgs, result: null } | - { key: "files.createFolder", input: LibraryArgs, result: string } | - { key: "files.cutFiles", input: LibraryArgs, result: null } | - { key: "files.deleteFiles", input: LibraryArgs, result: null } | - { key: "files.eraseFiles", input: LibraryArgs, result: null } | - { key: "files.removeAccessTime", input: LibraryArgs, result: null } | - { key: "files.renameFile", input: LibraryArgs, result: null } | - { key: "files.setFavorite", input: LibraryArgs, result: null } | - { key: "files.setNote", input: LibraryArgs, result: null } | - { key: "files.updateAccessTime", input: LibraryArgs, result: null } | - { key: "invalidation.test-invalidate-mutation", input: LibraryArgs, result: null } | - { key: "jobs.cancel", input: LibraryArgs, result: null } | - { key: "jobs.clear", input: LibraryArgs, result: null } | - { key: "jobs.clearAll", input: LibraryArgs, result: null } | - { key: "jobs.generateLabelsForLocation", input: LibraryArgs, result: null } | - { key: "jobs.generateThumbsForLocation", input: LibraryArgs, result: null } | - { key: "jobs.identifyUniqueFiles", input: LibraryArgs, result: null } | - { key: "jobs.objectValidator", input: LibraryArgs, result: null } | - { key: "jobs.pause", input: LibraryArgs, result: null } | - { key: "jobs.resume", input: LibraryArgs, result: null } | - { key: "labels.delete", input: LibraryArgs, result: null } | - { key: "library.create", input: CreateLibraryArgs, result: NormalisedResult } | - { key: "library.delete", input: string, result: null } | - { key: "library.edit", input: EditLibraryArgs, result: null } | - { key: "library.startActor", input: LibraryArgs, result: null } | - { key: "library.stopActor", input: LibraryArgs, result: null } | - { key: "locations.addLibrary", input: LibraryArgs, result: number | null } | - { key: "locations.create", input: LibraryArgs, result: number | null } | - { key: "locations.delete", input: LibraryArgs, result: null } | - { key: "locations.fullRescan", input: LibraryArgs, result: null } | - { key: "locations.indexer_rules.create", input: LibraryArgs, result: null } | - { key: "locations.indexer_rules.delete", input: LibraryArgs, result: null } | - { key: "locations.relink", input: LibraryArgs, result: number } | - { key: "locations.subPathRescan", input: LibraryArgs, result: null } | - { key: "locations.update", input: LibraryArgs, result: null } | - { key: "nodes.edit", input: ChangeNodeNameArgs, result: null } | - { key: "nodes.updateThumbnailerPreferences", input: UpdateThumbnailerPreferences, result: null } | - { key: "p2p.acceptSpacedrop", input: [string, string | null], result: null } | - { key: "p2p.cancelSpacedrop", input: string, result: null } | - { key: "p2p.debugConnect", input: RemoteIdentity, result: string } | - { key: "p2p.spacedrop", input: SpacedropArgs, result: string } | - { key: "preferences.update", input: LibraryArgs, result: null } | - { key: "search.saved.create", input: LibraryArgs<{ name: string; search?: string | null; filters?: string | null; description?: string | null; icon?: string | null }>, result: null } | - { key: "search.saved.delete", input: LibraryArgs, result: null } | - { key: "search.saved.update", input: LibraryArgs<[number, Args]>, result: null } | - { key: "sync.enable", input: LibraryArgs, result: null } | - { key: "tags.assign", input: LibraryArgs<{ targets: Target[]; tag_id: number; unassign: boolean }>, result: null } | - { key: "tags.create", input: LibraryArgs, result: Tag } | - { key: "tags.delete", input: LibraryArgs, result: null } | - { key: "tags.update", input: LibraryArgs, result: null } | + mutations: + { key: "api.sendFeedback", input: Feedback, result: null } | + { key: "auth.logout", input: never, result: null } | + { key: "backups.backup", input: LibraryArgs, result: string } | + { key: "backups.delete", input: string, result: null } | + { key: "backups.restore", input: string, result: null } | + { key: "cloud.library.create", input: LibraryArgs, result: null } | + { key: "cloud.library.join", input: string, result: LibraryConfigWrapped } | + { key: "cloud.library.sync", input: LibraryArgs, result: null } | + { key: "cloud.locations.create", input: string, result: CloudLocation } | + { key: "cloud.locations.remove", input: string, result: CloudLocation } | + { key: "cloud.locations.testing", input: TestingParams, result: null } | + { key: "cloud.setApiOrigin", input: string, result: null } | + { key: "ephemeralFiles.copyFiles", input: LibraryArgs, result: null } | + { key: "ephemeralFiles.createFolder", input: LibraryArgs, result: string } | + { key: "ephemeralFiles.cutFiles", input: LibraryArgs, result: null } | + { key: "ephemeralFiles.deleteFiles", input: LibraryArgs, result: null } | + { key: "ephemeralFiles.renameFile", input: LibraryArgs, result: null } | + { key: "files.convertImage", input: LibraryArgs, result: null } | + { key: "files.copyFiles", input: LibraryArgs, result: null } | + { key: "files.createFolder", input: LibraryArgs, result: string } | + { key: "files.cutFiles", input: LibraryArgs, result: null } | + { key: "files.deleteFiles", input: LibraryArgs, result: null } | + { key: "files.eraseFiles", input: LibraryArgs, result: null } | + { key: "files.removeAccessTime", input: LibraryArgs, result: null } | + { key: "files.renameFile", input: LibraryArgs, result: null } | + { key: "files.setFavorite", input: LibraryArgs, result: null } | + { key: "files.setNote", input: LibraryArgs, result: null } | + { key: "files.updateAccessTime", input: LibraryArgs, result: null } | + { key: "invalidation.test-invalidate-mutation", input: LibraryArgs, result: null } | + { key: "jobs.cancel", input: LibraryArgs, result: null } | + { key: "jobs.clear", input: LibraryArgs, result: null } | + { key: "jobs.clearAll", input: LibraryArgs, result: null } | + { key: "jobs.generateLabelsForLocation", input: LibraryArgs, result: null } | + { key: "jobs.generateThumbsForLocation", input: LibraryArgs, result: null } | + { key: "jobs.identifyUniqueFiles", input: LibraryArgs, result: null } | + { key: "jobs.objectValidator", input: LibraryArgs, result: null } | + { key: "jobs.pause", input: LibraryArgs, result: null } | + { key: "jobs.resume", input: LibraryArgs, result: null } | + { key: "labels.delete", input: LibraryArgs, result: null } | + { key: "library.create", input: CreateLibraryArgs, result: NormalisedResult } | + { key: "library.delete", input: string, result: null } | + { key: "library.edit", input: EditLibraryArgs, result: null } | + { key: "library.startActor", input: LibraryArgs, result: null } | + { key: "library.stopActor", input: LibraryArgs, result: null } | + { key: "locations.addLibrary", input: LibraryArgs, result: number | null } | + { key: "locations.create", input: LibraryArgs, result: number | null } | + { key: "locations.delete", input: LibraryArgs, result: null } | + { key: "locations.fullRescan", input: LibraryArgs, result: null } | + { key: "locations.indexer_rules.create", input: LibraryArgs, result: null } | + { key: "locations.indexer_rules.delete", input: LibraryArgs, result: null } | + { key: "locations.relink", input: LibraryArgs, result: number } | + { key: "locations.subPathRescan", input: LibraryArgs, result: null } | + { key: "locations.update", input: LibraryArgs, result: null } | + { key: "nodes.edit", input: ChangeNodeNameArgs, result: null } | + { key: "nodes.updateThumbnailerPreferences", input: UpdateThumbnailerPreferences, result: null } | + { key: "p2p.acceptSpacedrop", input: [string, string | null], result: null } | + { key: "p2p.cancelSpacedrop", input: string, result: null } | + { key: "p2p.debugConnect", input: RemoteIdentity, result: string } | + { key: "p2p.spacedrop", input: SpacedropArgs, result: string } | + { key: "preferences.update", input: LibraryArgs, result: null } | + { key: "search.saved.create", input: LibraryArgs<{ name: string; search?: string | null; filters?: string | null; description?: string | null; icon?: string | null }>, result: null } | + { key: "search.saved.delete", input: LibraryArgs, result: null } | + { key: "search.saved.update", input: LibraryArgs<[number, Args]>, result: null } | + { key: "sync.enable", input: LibraryArgs, result: null } | + { key: "tags.assign", input: LibraryArgs<{ targets: Target[]; tag_id: number; unassign: boolean }>, result: null } | + { key: "tags.create", input: LibraryArgs, result: Tag } | + { key: "tags.delete", input: LibraryArgs, result: null } | + { key: "tags.update", input: LibraryArgs, result: null } | { key: "toggleFeatureFlag", input: BackendFeature, result: null }, - subscriptions: - { key: "auth.loginSession", input: never, result: Response } | - { key: "invalidation.listen", input: never, result: InvalidateOperationEvent[] } | - { key: "jobs.newThumbnail", input: LibraryArgs, result: string[] } | - { key: "jobs.progress", input: LibraryArgs, result: JobProgressEvent } | - { key: "library.actors", input: LibraryArgs, result: { [key in string]: boolean } } | - { key: "locations.online", input: never, result: number[][] } | - { key: "locations.quickRescan", input: LibraryArgs, result: null } | - { key: "notifications.listen", input: never, result: Notification } | - { key: "p2p.events", input: never, result: P2PEvent } | - { key: "search.ephemeralPaths", input: LibraryArgs, result: EphemeralPathsResultItem } | + subscriptions: + { key: "auth.loginSession", input: never, result: Response } | + { key: "invalidation.listen", input: never, result: InvalidateOperationEvent[] } | + { key: "jobs.newThumbnail", input: LibraryArgs, result: string[] } | + { key: "jobs.progress", input: LibraryArgs, result: JobProgressEvent } | + { key: "library.actors", input: LibraryArgs, result: { [key in string]: boolean } } | + { key: "locations.online", input: never, result: number[][] } | + { key: "locations.quickRescan", input: LibraryArgs, result: null } | + { key: "notifications.listen", input: never, result: Notification } | + { key: "p2p.events", input: never, result: P2PEvent } | + { key: "search.ephemeralPaths", input: LibraryArgs, result: EphemeralPathsResultItem } | { key: "sync.newMessage", input: LibraryArgs, result: null } }; @@ -145,7 +145,7 @@ export type AudioMetadata = { duration: number | null; audio_codec: string | nul /** * All of the feature flags provided by the core itself. The frontend has it's own set of feature flags! - * + * * If you want a variant of this to show up on the frontend it must be added to `backendFeatures` in `useFeatureFlag.tsx` */ export type BackendFeature = "filesOverP2P" | "cloudSync" @@ -172,19 +172,19 @@ export type CloudLocation = { id: string; name: string } export type ColorProfile = "Normal" | "Custom" | "HDRNoOriginal" | "HDRWithOriginal" | "OriginalForHDR" | "Panorama" | "PortraitHDR" | "Portrait" -export type Composite = +export type Composite = /** * The data is present, but we're unable to determine what they mean */ -"Unknown" | +"Unknown" | /** * Not a composite image */ -"False" | +"False" | /** * A general composite image */ -"General" | +"General" | /** * The composite image was captured while shooting */ @@ -257,46 +257,46 @@ export type FilePathSearchArgs = { take?: number | null; orderAndPagination?: Or export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; hidden: boolean | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null } | null } -export type Flash = { +export type Flash = { /** * Specifies how flash was used (on, auto, off, forced, onvalid) - * + * * [`FlashMode::Unknown`] isn't a valid EXIF state, but it's included as the default, * just in case we're unable to correctly match it to a known (valid) state. - * + * * This type should only ever be evaluated if flash EXIF data is present, so having this as a non-option shouldn't be an issue. */ -mode: FlashMode; +mode: FlashMode; /** * Did the flash actually fire? */ -fired: boolean | null; +fired: boolean | null; /** * Did flash return to the camera? (Unsure of the meaning) */ -returned: boolean | null; +returned: boolean | null; /** * Was red eye reduction used? */ red_eye_reduction: boolean | null } -export type FlashMode = +export type FlashMode = /** * The data is present, but we're unable to determine what they mean */ -"Unknown" | +"Unknown" | /** * FLash was on */ -"On" | +"On" | /** * Flash was off */ -"Off" | +"Off" | /** * Flash was set to automatically fire in certain conditions */ -"Auto" | +"Auto" | /** * Flash was forcefully fired */ @@ -325,10 +325,10 @@ export type IndexerRule = { id: number; pub_id: number[]; name: string | null; d /** * `IndexerRuleCreateArgs` is the argument received from the client using rspc to create a new indexer rule. * Note that `rules` field is a vector of tuples of `RuleKind` and `parameters`. - * + * * In case of `RuleKind::AcceptFilesByGlob` or `RuleKind::RejectFilesByGlob`, it will be a * vector of strings containing a glob patterns. - * + * * In case of `RuleKind::AcceptIfChildrenDirectoriesArePresent` or `RuleKind::RejectIfChildrenDirectoriesArePresent` the * `parameters` field must be a vector of strings containing the names of the directories. */ @@ -362,19 +362,19 @@ export type LibraryArgs = { library_id: string; arg: T } /** * LibraryConfig holds the configuration for a specific library. This is stored as a '{uuid}.sdlibrary' file. */ -export type LibraryConfig = { +export type LibraryConfig = { /** * name is the display name of the library. This is used in the UI and is set by the user. */ -name: LibraryName; +name: LibraryName; /** * description is a user set description of the library. This is used in the UI and is set by the user. */ -description: string | null; +description: string | null; /** * id of the current instance so we know who this `.db` is. This can be looked up within the `Instance` table. */ -instance_id: number; +instance_id: number; /** * cloud_id is the ID of the cloud library this library is linked to. * If this is set we can assume the library is synced with the Cloud. @@ -408,7 +408,7 @@ export type LocationSettings = { explorer: ExplorerSettings } * `LocationUpdateArgs` is the argument received from the client using `rspc` to update a location. * It contains the id of the location to be updated, possible a name to change the current location's name * and a vector of indexer rules ids to add or remove from the location. - * + * * It is important to note that only the indexer rule ids in this vector will be used from now on. * Old rules that aren't in this vector will be purged. */ @@ -432,11 +432,11 @@ export type MediaMetadata = ({ type: "Image" } & ImageMetadata) | ({ type: "Vide export type NodePreferences = { thumbnailer: ThumbnailerPreferences } -export type NodeState = ({ +export type NodeState = ({ /** * id is a unique identifier for the current node. Each node has a public identifier (this one) and is given a local id for each library (done within the library code). */ -id: string; +id: string; /** * name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record */ @@ -446,14 +446,14 @@ export type NonIndexedPathItem = { path: string; name: string; extension: string /** * A type that can be used to return a group of `Reference` and `CacheNode`'s - * + * * You don't need to use this, it's just a shortcut to avoid having to write out the full type every time. */ export type NormalisedResult = { item: Reference; nodes: CacheNode[] } /** * A type that can be used to return a group of `Reference` and `CacheNode`'s - * + * * You don't need to use this, it's just a shortcut to avoid having to write out the full type every time. */ export type NormalisedResults = { items: Reference[]; nodes: CacheNode[] } @@ -526,10 +526,10 @@ export type Range = { from: T } | { to: T } /** * A reference to a `CacheNode`. - * + * * This does not contain the actual data, but instead a reference to it. * This allows the CacheNode's to be switched out and the query recomputed without any backend communication. - * + * * If you use a `Reference` in a query, you *must* ensure the corresponding `CacheNode` is also in the query. */ export type Reference = { __type: string; __id: string; "#type": T } @@ -562,7 +562,7 @@ export type SetFavoriteArgs = { id: number; favorite: boolean } export type SetNoteArgs = { id: number; note: string | null } -export type SingleInvalidateOperationEvent = { +export type SingleInvalidateOperationEvent = { /** * This fields are intentionally private. */