Fix sync tests (#2194)

* Fix sync tests

* fix build
This commit is contained in:
Brendan Allan
2024-03-13 13:34:14 +08:00
committed by GitHub
parent 7e5f7c0539
commit 95786c1cbb
13 changed files with 327 additions and 245 deletions

BIN
Cargo.lock generated
View File

Binary file not shown.

View File

@@ -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"

View File

@@ -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![],
}
}

View File

@@ -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<SharedState>) {
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(())
// }
}

View File

@@ -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![],
}
}

View File

@@ -119,6 +119,8 @@ impl Manager {
) -> prisma_client_rust::Result<Vec<CRDTOperation>> {
let db = &self.db;
dbg!(&args);
macro_rules! db_args {
($args:ident, $op:ident) => {
vec![prisma_client_rust::operator::or(

View File

@@ -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<Self>, broadcast::Receiver<SyncMessage>) {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}
});
tokio::spawn({
let task_2 = tokio::spawn({
let instance1 = instance1.clone();
let instance2 = instance2.clone();
@@ -142,6 +157,7 @@ async fn bruh() -> Result<(), Box<dyn std::error::Error>> {
.unwrap();
let ingest = &instance2.sync.ingest;
ingest
.event_tx
.send(ingest::Event::Messages(ingest::MessagesEvent {
@@ -202,5 +218,8 @@ async fn bruh() -> Result<(), Box<dyn std::error::Error>> {
instance1.teardown().await;
instance2.teardown().await;
task_1.abort();
task_2.abort();
Ok(())
}

View File

@@ -26,7 +26,7 @@ pub async fn run_actor(sync: Arc<sd_core_sync::Manager>, notify: Arc<Notify>) {
let timestamps = match req {
Request::FinishedIngesting => break,
Request::Messages { timestamps } => timestamps,
Request::Messages { timestamps, .. } => timestamps,
_ => continue,
};

View File

@@ -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![],
}
}

View File

@@ -217,7 +217,7 @@ mod responder {
let timestamps = match req {
Request::FinishedIngesting => break,
Request::Messages { timestamps } => timestamps,
Request::Messages { timestamps, .. } => timestamps,
_ => continue,
};

View File

@@ -12,3 +12,4 @@ serde = { workspace = true }
serde_json = { workspace = true }
rmp-serde = "1.1.2"
rmpv.workspace = true
uuid = { workspace = true }

View File

@@ -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<prisma::PrismaClient> {
std::sync::Arc::new(
prisma::PrismaClient::_builder()
.with_url(format!("file:/tmp/test-db-{}", uuid::Uuid::new_v4()))
.build()
.await
.unwrap(),
)
}

View File

@@ -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<null>, 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<number>, result: { item: Reference<ObjectWithFilePaths2>; nodes: CacheNode[] } | null } |
{ key: "files.getConvertableImageExtensions", input: never, result: string[] } |
{ key: "files.getMediaData", input: LibraryArgs<number>, result: MediaMetadata } |
{ key: "files.getPath", input: LibraryArgs<number>, result: string | null } |
{ key: "invalidation.test-invalidate", input: never, result: number } |
{ key: "jobs.isActive", input: LibraryArgs<null>, result: boolean } |
{ key: "jobs.reports", input: LibraryArgs<null>, result: JobGroup[] } |
{ key: "labels.count", input: LibraryArgs<null>, result: number } |
{ key: "labels.get", input: LibraryArgs<number>, result: { id: number; name: string; date_created: string | null; date_modified: string | null } | null } |
{ key: "labels.getForObject", input: LibraryArgs<number>, result: Label[] } |
{ key: "labels.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: { date_created: string; object: { id: number } }[] } } |
{ key: "labels.list", input: LibraryArgs<null>, result: Label[] } |
{ key: "labels.listWithThumbnails", input: LibraryArgs<string>, result: ExplorerItem[] } |
{ key: "library.kindStatistics", input: LibraryArgs<null>, result: KindStatistics } |
{ key: "library.list", input: never, result: NormalisedResults<LibraryConfigWrapped> } |
{ key: "library.statistics", input: LibraryArgs<null>, result: StatisticsResponse } |
{ key: "locations.get", input: LibraryArgs<number>, result: { item: Reference<Location>; nodes: CacheNode[] } | null } |
{ key: "locations.getWithRules", input: LibraryArgs<number>, result: { item: Reference<LocationWithIndexerRule>; nodes: CacheNode[] } | null } |
{ key: "locations.indexer_rules.get", input: LibraryArgs<number>, result: NormalisedResult<IndexerRule> } |
{ key: "locations.indexer_rules.list", input: LibraryArgs<null>, result: NormalisedResults<IndexerRule> } |
{ key: "locations.indexer_rules.listForLocation", input: LibraryArgs<number>, result: NormalisedResults<IndexerRule> } |
{ key: "locations.list", input: LibraryArgs<null>, result: NormalisedResults<Location> } |
{ 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<string | null>, 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<null>, result: LibraryPreferences } |
{ key: "search.objects", input: LibraryArgs<ObjectSearchArgs>, result: SearchData<ExplorerItem> } |
{ key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
{ key: "search.paths", input: LibraryArgs<FilePathSearchArgs>, result: SearchData<ExplorerItem> } |
{ key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
{ key: "search.saved.get", input: LibraryArgs<number>, 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<null>, result: SavedSearch[] } |
{ key: "sync.enabled", input: LibraryArgs<null>, result: boolean } |
{ key: "sync.messages", input: LibraryArgs<null>, result: CRDTOperation[] } |
{ key: "tags.get", input: LibraryArgs<number>, result: { item: Reference<Tag>; nodes: CacheNode[] } | null } |
{ key: "tags.getForObject", input: LibraryArgs<number>, result: NormalisedResults<Tag> } |
{ key: "tags.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: ({ date_created: string | null; object: { id: number } })[] } } |
{ key: "tags.list", input: LibraryArgs<null>, result: NormalisedResults<Tag> } |
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<null>, 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<number>, result: { item: Reference<ObjectWithFilePaths2>; nodes: CacheNode[] } | null } |
{ key: "files.getConvertableImageExtensions", input: never, result: string[] } |
{ key: "files.getMediaData", input: LibraryArgs<number>, result: MediaMetadata } |
{ key: "files.getPath", input: LibraryArgs<number>, result: string | null } |
{ key: "invalidation.test-invalidate", input: never, result: number } |
{ key: "jobs.isActive", input: LibraryArgs<null>, result: boolean } |
{ key: "jobs.reports", input: LibraryArgs<null>, result: JobGroup[] } |
{ key: "labels.count", input: LibraryArgs<null>, result: number } |
{ key: "labels.get", input: LibraryArgs<number>, result: { id: number; name: string; date_created: string | null; date_modified: string | null } | null } |
{ key: "labels.getForObject", input: LibraryArgs<number>, result: Label[] } |
{ key: "labels.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: { date_created: string; object: { id: number } }[] } } |
{ key: "labels.list", input: LibraryArgs<null>, result: Label[] } |
{ key: "labels.listWithThumbnails", input: LibraryArgs<string>, result: ExplorerItem[] } |
{ key: "library.kindStatistics", input: LibraryArgs<null>, result: KindStatistics } |
{ key: "library.list", input: never, result: NormalisedResults<LibraryConfigWrapped> } |
{ key: "library.statistics", input: LibraryArgs<null>, result: StatisticsResponse } |
{ key: "locations.get", input: LibraryArgs<number>, result: { item: Reference<Location>; nodes: CacheNode[] } | null } |
{ key: "locations.getWithRules", input: LibraryArgs<number>, result: { item: Reference<LocationWithIndexerRule>; nodes: CacheNode[] } | null } |
{ key: "locations.indexer_rules.get", input: LibraryArgs<number>, result: NormalisedResult<IndexerRule> } |
{ key: "locations.indexer_rules.list", input: LibraryArgs<null>, result: NormalisedResults<IndexerRule> } |
{ key: "locations.indexer_rules.listForLocation", input: LibraryArgs<number>, result: NormalisedResults<IndexerRule> } |
{ key: "locations.list", input: LibraryArgs<null>, result: NormalisedResults<Location> } |
{ 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<string | null>, 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<null>, result: LibraryPreferences } |
{ key: "search.objects", input: LibraryArgs<ObjectSearchArgs>, result: SearchData<ExplorerItem> } |
{ key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
{ key: "search.paths", input: LibraryArgs<FilePathSearchArgs>, result: SearchData<ExplorerItem> } |
{ key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
{ key: "search.saved.get", input: LibraryArgs<number>, 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<null>, result: SavedSearch[] } |
{ key: "sync.enabled", input: LibraryArgs<null>, result: boolean } |
{ key: "sync.messages", input: LibraryArgs<null>, result: CRDTOperation[] } |
{ key: "tags.get", input: LibraryArgs<number>, result: { item: Reference<Tag>; nodes: CacheNode[] } | null } |
{ key: "tags.getForObject", input: LibraryArgs<number>, result: NormalisedResults<Tag> } |
{ key: "tags.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: ({ date_created: string | null; object: { id: number } })[] } } |
{ key: "tags.list", input: LibraryArgs<null>, result: NormalisedResults<Tag> } |
{ key: "volumes.list", input: never, result: NormalisedResults<Volume> },
mutations:
{ key: "api.sendFeedback", input: Feedback, result: null } |
{ key: "auth.logout", input: never, result: null } |
{ key: "backups.backup", input: LibraryArgs<null>, result: string } |
{ key: "backups.delete", input: string, result: null } |
{ key: "backups.restore", input: string, result: null } |
{ key: "cloud.library.create", input: LibraryArgs<null>, result: null } |
{ key: "cloud.library.join", input: string, result: LibraryConfigWrapped } |
{ key: "cloud.library.sync", input: LibraryArgs<null>, 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<EphemeralFileSystemOps>, result: null } |
{ key: "ephemeralFiles.createFolder", input: LibraryArgs<CreateEphemeralFolderArgs>, result: string } |
{ key: "ephemeralFiles.cutFiles", input: LibraryArgs<EphemeralFileSystemOps>, result: null } |
{ key: "ephemeralFiles.deleteFiles", input: LibraryArgs<string[]>, result: null } |
{ key: "ephemeralFiles.renameFile", input: LibraryArgs<EphemeralRenameFileArgs>, result: null } |
{ key: "files.convertImage", input: LibraryArgs<ConvertImageArgs>, result: null } |
{ key: "files.copyFiles", input: LibraryArgs<OldFileCopierJobInit>, result: null } |
{ key: "files.createFolder", input: LibraryArgs<CreateFolderArgs>, result: string } |
{ key: "files.cutFiles", input: LibraryArgs<OldFileCutterJobInit>, result: null } |
{ key: "files.deleteFiles", input: LibraryArgs<OldFileDeleterJobInit>, result: null } |
{ key: "files.eraseFiles", input: LibraryArgs<OldFileEraserJobInit>, result: null } |
{ key: "files.removeAccessTime", input: LibraryArgs<number[]>, result: null } |
{ key: "files.renameFile", input: LibraryArgs<RenameFileArgs>, result: null } |
{ key: "files.setFavorite", input: LibraryArgs<SetFavoriteArgs>, result: null } |
{ key: "files.setNote", input: LibraryArgs<SetNoteArgs>, result: null } |
{ key: "files.updateAccessTime", input: LibraryArgs<number[]>, result: null } |
{ key: "invalidation.test-invalidate-mutation", input: LibraryArgs<null>, result: null } |
{ key: "jobs.cancel", input: LibraryArgs<string>, result: null } |
{ key: "jobs.clear", input: LibraryArgs<string>, result: null } |
{ key: "jobs.clearAll", input: LibraryArgs<null>, result: null } |
{ key: "jobs.generateLabelsForLocation", input: LibraryArgs<GenerateLabelsForLocationArgs>, result: null } |
{ key: "jobs.generateThumbsForLocation", input: LibraryArgs<GenerateThumbsForLocationArgs>, result: null } |
{ key: "jobs.identifyUniqueFiles", input: LibraryArgs<IdentifyUniqueFilesArgs>, result: null } |
{ key: "jobs.objectValidator", input: LibraryArgs<ObjectValidatorArgs>, result: null } |
{ key: "jobs.pause", input: LibraryArgs<string>, result: null } |
{ key: "jobs.resume", input: LibraryArgs<string>, result: null } |
{ key: "labels.delete", input: LibraryArgs<number>, result: null } |
{ key: "library.create", input: CreateLibraryArgs, result: NormalisedResult<LibraryConfigWrapped> } |
{ key: "library.delete", input: string, result: null } |
{ key: "library.edit", input: EditLibraryArgs, result: null } |
{ key: "library.startActor", input: LibraryArgs<string>, result: null } |
{ key: "library.stopActor", input: LibraryArgs<string>, result: null } |
{ key: "locations.addLibrary", input: LibraryArgs<LocationCreateArgs>, result: number | null } |
{ key: "locations.create", input: LibraryArgs<LocationCreateArgs>, result: number | null } |
{ key: "locations.delete", input: LibraryArgs<number>, result: null } |
{ key: "locations.fullRescan", input: LibraryArgs<FullRescanArgs>, result: null } |
{ key: "locations.indexer_rules.create", input: LibraryArgs<IndexerRuleCreateArgs>, result: null } |
{ key: "locations.indexer_rules.delete", input: LibraryArgs<number>, result: null } |
{ key: "locations.relink", input: LibraryArgs<string>, result: number } |
{ key: "locations.subPathRescan", input: LibraryArgs<RescanArgs>, result: null } |
{ key: "locations.update", input: LibraryArgs<LocationUpdateArgs>, 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<LibraryPreferences>, 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<number>, result: null } |
{ key: "search.saved.update", input: LibraryArgs<[number, Args]>, result: null } |
{ key: "sync.enable", input: LibraryArgs<null>, result: null } |
{ key: "tags.assign", input: LibraryArgs<{ targets: Target[]; tag_id: number; unassign: boolean }>, result: null } |
{ key: "tags.create", input: LibraryArgs<TagCreateArgs>, result: Tag } |
{ key: "tags.delete", input: LibraryArgs<number>, result: null } |
{ key: "tags.update", input: LibraryArgs<TagUpdateArgs>, result: null } |
mutations:
{ key: "api.sendFeedback", input: Feedback, result: null } |
{ key: "auth.logout", input: never, result: null } |
{ key: "backups.backup", input: LibraryArgs<null>, result: string } |
{ key: "backups.delete", input: string, result: null } |
{ key: "backups.restore", input: string, result: null } |
{ key: "cloud.library.create", input: LibraryArgs<null>, result: null } |
{ key: "cloud.library.join", input: string, result: LibraryConfigWrapped } |
{ key: "cloud.library.sync", input: LibraryArgs<null>, 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<EphemeralFileSystemOps>, result: null } |
{ key: "ephemeralFiles.createFolder", input: LibraryArgs<CreateEphemeralFolderArgs>, result: string } |
{ key: "ephemeralFiles.cutFiles", input: LibraryArgs<EphemeralFileSystemOps>, result: null } |
{ key: "ephemeralFiles.deleteFiles", input: LibraryArgs<string[]>, result: null } |
{ key: "ephemeralFiles.renameFile", input: LibraryArgs<EphemeralRenameFileArgs>, result: null } |
{ key: "files.convertImage", input: LibraryArgs<ConvertImageArgs>, result: null } |
{ key: "files.copyFiles", input: LibraryArgs<OldFileCopierJobInit>, result: null } |
{ key: "files.createFolder", input: LibraryArgs<CreateFolderArgs>, result: string } |
{ key: "files.cutFiles", input: LibraryArgs<OldFileCutterJobInit>, result: null } |
{ key: "files.deleteFiles", input: LibraryArgs<OldFileDeleterJobInit>, result: null } |
{ key: "files.eraseFiles", input: LibraryArgs<OldFileEraserJobInit>, result: null } |
{ key: "files.removeAccessTime", input: LibraryArgs<number[]>, result: null } |
{ key: "files.renameFile", input: LibraryArgs<RenameFileArgs>, result: null } |
{ key: "files.setFavorite", input: LibraryArgs<SetFavoriteArgs>, result: null } |
{ key: "files.setNote", input: LibraryArgs<SetNoteArgs>, result: null } |
{ key: "files.updateAccessTime", input: LibraryArgs<number[]>, result: null } |
{ key: "invalidation.test-invalidate-mutation", input: LibraryArgs<null>, result: null } |
{ key: "jobs.cancel", input: LibraryArgs<string>, result: null } |
{ key: "jobs.clear", input: LibraryArgs<string>, result: null } |
{ key: "jobs.clearAll", input: LibraryArgs<null>, result: null } |
{ key: "jobs.generateLabelsForLocation", input: LibraryArgs<GenerateLabelsForLocationArgs>, result: null } |
{ key: "jobs.generateThumbsForLocation", input: LibraryArgs<GenerateThumbsForLocationArgs>, result: null } |
{ key: "jobs.identifyUniqueFiles", input: LibraryArgs<IdentifyUniqueFilesArgs>, result: null } |
{ key: "jobs.objectValidator", input: LibraryArgs<ObjectValidatorArgs>, result: null } |
{ key: "jobs.pause", input: LibraryArgs<string>, result: null } |
{ key: "jobs.resume", input: LibraryArgs<string>, result: null } |
{ key: "labels.delete", input: LibraryArgs<number>, result: null } |
{ key: "library.create", input: CreateLibraryArgs, result: NormalisedResult<LibraryConfigWrapped> } |
{ key: "library.delete", input: string, result: null } |
{ key: "library.edit", input: EditLibraryArgs, result: null } |
{ key: "library.startActor", input: LibraryArgs<string>, result: null } |
{ key: "library.stopActor", input: LibraryArgs<string>, result: null } |
{ key: "locations.addLibrary", input: LibraryArgs<LocationCreateArgs>, result: number | null } |
{ key: "locations.create", input: LibraryArgs<LocationCreateArgs>, result: number | null } |
{ key: "locations.delete", input: LibraryArgs<number>, result: null } |
{ key: "locations.fullRescan", input: LibraryArgs<FullRescanArgs>, result: null } |
{ key: "locations.indexer_rules.create", input: LibraryArgs<IndexerRuleCreateArgs>, result: null } |
{ key: "locations.indexer_rules.delete", input: LibraryArgs<number>, result: null } |
{ key: "locations.relink", input: LibraryArgs<string>, result: number } |
{ key: "locations.subPathRescan", input: LibraryArgs<RescanArgs>, result: null } |
{ key: "locations.update", input: LibraryArgs<LocationUpdateArgs>, 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<LibraryPreferences>, 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<number>, result: null } |
{ key: "search.saved.update", input: LibraryArgs<[number, Args]>, result: null } |
{ key: "sync.enable", input: LibraryArgs<null>, result: null } |
{ key: "tags.assign", input: LibraryArgs<{ targets: Target[]; tag_id: number; unassign: boolean }>, result: null } |
{ key: "tags.create", input: LibraryArgs<TagCreateArgs>, result: Tag } |
{ key: "tags.delete", input: LibraryArgs<number>, result: null } |
{ key: "tags.update", input: LibraryArgs<TagUpdateArgs>, 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<null>, result: string[] } |
{ key: "jobs.progress", input: LibraryArgs<null>, result: JobProgressEvent } |
{ key: "library.actors", input: LibraryArgs<null>, result: { [key in string]: boolean } } |
{ key: "locations.online", input: never, result: number[][] } |
{ key: "locations.quickRescan", input: LibraryArgs<LightScanArgs>, result: null } |
{ key: "notifications.listen", input: never, result: Notification } |
{ key: "p2p.events", input: never, result: P2PEvent } |
{ key: "search.ephemeralPaths", input: LibraryArgs<EphemeralPathSearchArgs>, result: EphemeralPathsResultItem } |
subscriptions:
{ key: "auth.loginSession", input: never, result: Response } |
{ key: "invalidation.listen", input: never, result: InvalidateOperationEvent[] } |
{ key: "jobs.newThumbnail", input: LibraryArgs<null>, result: string[] } |
{ key: "jobs.progress", input: LibraryArgs<null>, result: JobProgressEvent } |
{ key: "library.actors", input: LibraryArgs<null>, result: { [key in string]: boolean } } |
{ key: "locations.online", input: never, result: number[][] } |
{ key: "locations.quickRescan", input: LibraryArgs<LightScanArgs>, result: null } |
{ key: "notifications.listen", input: never, result: Notification } |
{ key: "p2p.events", input: never, result: P2PEvent } |
{ key: "search.ephemeralPaths", input: LibraryArgs<EphemeralPathSearchArgs>, result: EphemeralPathsResultItem } |
{ key: "sync.newMessage", input: LibraryArgs<null>, 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<T> = { 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<FilePathOrder> }
* `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<T>` 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<T> = { item: Reference<T>; nodes: CacheNode[] }
/**
* A type that can be used to return a group of `Reference<T>` 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<T> = { items: Reference<T>[]; nodes: CacheNode[] }
@@ -526,10 +526,10 @@ export type Range<T> = { 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<T> = { __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.
*/