media data sync (#2102)

* basic sync operation backfill

* media data sync

* sync entry helpers

* fix sync generator

* nicer

* re-add key_id
This commit is contained in:
Brendan Allan 2024-02-21 22:42:10 +11:00 committed by GitHub
parent 393a907b57
commit c533d12df0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 293 additions and 258 deletions

View file

@ -1,10 +1,11 @@
use sd_prisma::{
prisma::{
file_path, label, label_on_object, location, object, tag, tag_on_object, PrismaClient,
file_path, label, label_on_object, location, media_data, object, tag, tag_on_object,
PrismaClient,
},
prisma_sync,
};
use sd_sync::OperationFactory;
use sd_sync::{option_sync_entry, OperationFactory};
use sd_utils::chain_optional_iter;
use serde_json::json;
@ -23,28 +24,26 @@ pub async fn backfill_operations(db: &PrismaClient, sync: &crate::Manager, insta
locations
.into_iter()
.flat_map(|l| {
use location::*;
sync.shared_create(
prisma_sync::location::SyncId { pub_id: l.pub_id },
chain_optional_iter(
[],
[
l.name.map(|v| (location::name::NAME, json!(v))),
l.path.map(|v| (location::path::NAME, json!(v))),
l.total_capacity
.map(|v| (location::total_capacity::NAME, json!(v))),
l.available_capacity
.map(|v| (location::available_capacity::NAME, json!(v))),
l.size_in_bytes
.map(|v| (location::size_in_bytes::NAME, json!(v))),
l.is_archived
.map(|v| (location::is_archived::NAME, json!(v))),
l.generate_preview_media
.map(|v| (location::generate_preview_media::NAME, json!(v))),
l.sync_preview_media
.map(|v| (location::sync_preview_media::NAME, json!(v))),
l.hidden.map(|v| (location::hidden::NAME, json!(v))),
l.date_created
.map(|v| (location::date_created::NAME, json!(v))),
option_sync_entry!(l.name, name),
option_sync_entry!(l.path, path),
option_sync_entry!(l.total_capacity, total_capacity),
option_sync_entry!(l.available_capacity, available_capacity),
option_sync_entry!(l.size_in_bytes, size_in_bytes),
option_sync_entry!(l.is_archived, is_archived),
option_sync_entry!(
l.generate_preview_media,
generate_preview_media
),
option_sync_entry!(l.sync_preview_media, sync_preview_media),
option_sync_entry!(l.hidden, hidden),
option_sync_entry!(l.date_created, date_created),
],
),
)
@ -62,20 +61,65 @@ pub async fn backfill_operations(db: &PrismaClient, sync: &crate::Manager, insta
objects
.into_iter()
.flat_map(|o| {
use object::*;
sync.shared_create(
prisma_sync::object::SyncId { pub_id: o.pub_id },
chain_optional_iter(
[],
[
o.kind.map(|v| (object::kind::NAME, json!(v))),
o.hidden.map(|v| (object::hidden::NAME, json!(v))),
o.favorite.map(|v| (object::favorite::NAME, json!(v))),
o.important.map(|v| (object::important::NAME, json!(v))),
o.note.map(|v| (object::note::NAME, json!(v))),
o.date_created
.map(|v| (object::date_created::NAME, json!(v))),
o.date_accessed
.map(|v| (object::date_accessed::NAME, json!(v))),
option_sync_entry!(o.kind, kind),
option_sync_entry!(o.hidden, hidden),
option_sync_entry!(o.favorite, favorite),
option_sync_entry!(o.important, important),
option_sync_entry!(o.note, note),
option_sync_entry!(o.date_created, date_created),
option_sync_entry!(o.date_accessed, date_accessed),
],
),
)
})
.map(|o| crdt_op_unchecked_db(&o, instance_id))
.collect(),
)
.exec()
.await
.unwrap();
let media_datas = db
.media_data()
.find_many(vec![])
.include(media_data::include!({
object: select { pub_id }
}))
.exec()
.await
.unwrap();
db.crdt_operation()
.create_many(
media_datas
.into_iter()
.flat_map(|md| {
use media_data::*;
sync.shared_create(
prisma_sync::media_data::SyncId {
object: prisma_sync::object::SyncId {
pub_id: md.object.pub_id,
},
},
chain_optional_iter(
[],
[
option_sync_entry!(md.resolution, resolution),
option_sync_entry!(md.media_date, media_date),
option_sync_entry!(md.media_location, media_location),
option_sync_entry!(md.camera_data, camera_data),
option_sync_entry!(md.artist, artist),
option_sync_entry!(md.description, description),
option_sync_entry!(md.copyright, copyright),
option_sync_entry!(md.exif_version, exif_version),
option_sync_entry!(md.epoch_time, epoch_time),
],
),
)
@ -103,35 +147,31 @@ pub async fn backfill_operations(db: &PrismaClient, sync: &crate::Manager, insta
file_paths
.into_iter()
.flat_map(|fp| {
use file_path::*;
sync.shared_create(
prisma_sync::file_path::SyncId { pub_id: fp.pub_id },
chain_optional_iter(
[],
[
fp.is_dir.map(|v| (file_path::is_dir::NAME, json!(v))),
fp.cas_id.map(|v| (file_path::cas_id::NAME, json!(v))),
fp.integrity_checksum
.map(|v| (file_path::integrity_checksum::NAME, json!(v))),
fp.location.map(|l| {
(
file_path::location::NAME,
json!(prisma_sync::location::SyncId { pub_id: l.pub_id }),
)
}),
fp.materialized_path
.map(|v| (file_path::materialized_path::NAME, json!(v))),
fp.name.map(|v| (file_path::name::NAME, json!(v))),
fp.extension.map(|v| (file_path::extension::NAME, json!(v))),
fp.hidden.map(|v| (file_path::hidden::NAME, json!(v))),
fp.size_in_bytes_bytes
.map(|v| (file_path::size_in_bytes_bytes::NAME, json!(v))),
fp.inode.map(|v| (file_path::inode::NAME, json!(v))),
fp.date_created
.map(|v| (file_path::date_created::NAME, json!(v))),
fp.date_modified
.map(|v| (file_path::date_modified::NAME, json!(v))),
fp.date_indexed
.map(|v| (file_path::date_indexed::NAME, json!(v))),
option_sync_entry!(fp.is_dir, is_dir),
option_sync_entry!(fp.cas_id, cas_id),
option_sync_entry!(fp.integrity_checksum, integrity_checksum),
option_sync_entry!(
fp.location.map(|l| prisma_sync::location::SyncId {
pub_id: l.pub_id
}),
location
),
option_sync_entry!(fp.materialized_path, materialized_path),
option_sync_entry!(fp.name, name),
option_sync_entry!(fp.extension, extension),
option_sync_entry!(fp.hidden, hidden),
option_sync_entry!(fp.size_in_bytes_bytes, size_in_bytes_bytes),
option_sync_entry!(fp.inode, inode),
option_sync_entry!(fp.date_created, date_created),
option_sync_entry!(fp.date_modified, date_modified),
option_sync_entry!(fp.date_indexed, date_indexed),
],
),
)
@ -194,8 +234,10 @@ pub async fn backfill_operations(db: &PrismaClient, sync: &crate::Manager, insta
},
chain_optional_iter(
[],
[t_o.date_created
.map(|v| (tag_on_object::date_created::NAME, json!(v)))],
[option_sync_entry!(
t_o.date_created,
tag_on_object::date_created
)],
),
)
})

View file

@ -169,18 +169,9 @@ async fn bruh() -> Result<(), Box<dyn std::error::Error>> {
use prisma::location;
macro_rules! item {
($name:ident, $value:expr) => {
(
(location::$name::NAME, json!($value)),
location::$name::set(Some($value.to_string())),
)
};
}
let (sync_ops, db_ops): (Vec<_>, Vec<_>) = [
item!(name, "Location 0"),
item!(path, "/User/Brendan/Documents"),
sync_db_entry!(location::name, "Location 0"),
sync_db_entry!(location::path, "/User/Brendan/Documents"),
]
.into_iter()
.unzip();

View file

@ -278,6 +278,7 @@ model Object {
// @@map("key")
// }
/// @shared(id: object)
model MediaData {
id Int @id @default(autoincrement())

View file

@ -1,13 +1,12 @@
use crate::{api::utils::library, invalidate_query, library::Library};
use sd_prisma::{prisma::saved_search, prisma_sync};
use sd_sync::OperationFactory;
use sd_sync::{option_sync_db_entry, sync_db_entry, OperationFactory};
use sd_utils::chain_optional_iter;
use chrono::{DateTime, FixedOffset, Utc};
use rspc::alpha::AlphaRouter;
use serde::{de::IgnoredAny, Deserialize, Serialize};
use serde_json::json;
use specta::Type;
use tracing::error;
use uuid::Uuid;
@ -39,18 +38,12 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
let (sync_params, db_params): (Vec<_>, Vec<_>) = chain_optional_iter(
[
(
(saved_search::date_created::NAME, json!(date_created)),
saved_search::date_created::set(Some(date_created)),
),
(
(saved_search::name::NAME, json!(&args.name)),
saved_search::name::set(Some(args.name)),
),
sync_db_entry!(date_created, saved_search::date_created),
sync_db_entry!(args.name, saved_search::name),
],
[
args.filters
.and_then(|s| {
option_sync_db_entry!(
args.filters.and_then(|s| {
// https://github.com/serde-rs/json/issues/579
// https://docs.rs/serde/latest/serde/de/struct.IgnoredAny.html
@ -60,31 +53,12 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
} else {
Some(s)
}
})
.map(|v| {
(
(saved_search::filters::NAME, json!(&v)),
saved_search::filters::set(Some(v)),
)
}),
args.search.map(|v| {
(
(saved_search::search::NAME, json!(&v)),
saved_search::search::set(Some(v)),
)
}),
args.description.map(|v| {
(
(saved_search::description::NAME, json!(&v)),
saved_search::description::set(Some(v)),
)
}),
args.icon.map(|v| {
(
(saved_search::icon::NAME, json!(&v)),
saved_search::icon::set(Some(v)),
)
}),
saved_search::filters
),
option_sync_db_entry!(args.search, saved_search::search),
option_sync_db_entry!(args.description, saved_search::description),
option_sync_db_entry!(args.icon, saved_search::icon),
],
)
.into_iter()
@ -157,41 +131,13 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
})?;
let (sync_params, db_params): (Vec<_>, Vec<_>) = chain_optional_iter(
[(
(saved_search::date_modified::NAME, json!(updated_at)),
saved_search::date_modified::set(Some(updated_at)),
)],
[sync_db_entry!(updated_at, saved_search::date_modified)],
[
args.name.map(|v| {
(
(saved_search::name::NAME, json!(&v)),
saved_search::name::set(v),
)
}),
args.description.map(|v| {
(
(saved_search::name::NAME, json!(&v)),
saved_search::name::set(v),
)
}),
args.icon.map(|v| {
(
(saved_search::icon::NAME, json!(&v)),
saved_search::icon::set(v),
)
}),
args.search.map(|v| {
(
(saved_search::search::NAME, json!(&v)),
saved_search::search::set(v),
)
}),
args.filters.map(|v| {
(
(saved_search::filters::NAME, json!(&v)),
saved_search::filters::set(v),
)
}),
option_sync_db_entry!(args.name.flatten(), saved_search::name),
option_sync_db_entry!(args.description.flatten(), saved_search::name),
option_sync_db_entry!(args.icon.flatten(), saved_search::icon),
option_sync_db_entry!(args.search.flatten(), saved_search::search),
option_sync_db_entry!(args.filters.flatten(), saved_search::filters),
],
)
.into_iter()

View file

@ -116,45 +116,28 @@ async fn execute_indexer_save_step(
),
location_id::set(Some(location.id)),
),
(
(materialized_path::NAME, json!(materialized_path)),
materialized_path::set(Some(materialized_path.to_string())),
),
((name::NAME, json!(name)), name::set(Some(name.to_string()))),
((is_dir::NAME, json!(*is_dir)), is_dir::set(Some(*is_dir))),
(
(extension::NAME, json!(extension)),
extension::set(Some(extension.to_string())),
),
(
(
size_in_bytes_bytes::NAME,
json!(entry.metadata.size_in_bytes.to_be_bytes().to_vec()),
),
size_in_bytes_bytes::set(Some(
entry.metadata.size_in_bytes.to_be_bytes().to_vec(),
)),
),
(
(inode::NAME, json!(entry.metadata.inode.to_le_bytes())),
inode::set(Some(inode_to_db(entry.metadata.inode))),
),
(
(date_created::NAME, json!(entry.metadata.created_at)),
date_created::set(Some(entry.metadata.created_at.into())),
),
(
(date_modified::NAME, json!(entry.metadata.modified_at)),
date_modified::set(Some(entry.metadata.modified_at.into())),
),
(
(date_indexed::NAME, json!(Utc::now())),
date_indexed::set(Some(Utc::now().into())),
),
(
(hidden::NAME, json!(entry.metadata.hidden)),
hidden::set(Some(entry.metadata.hidden)),
sync_db_entry!(materialized_path.to_string(), materialized_path),
sync_db_entry!(name.to_string(), name),
sync_db_entry!(*is_dir, is_dir),
sync_db_entry!(extension.to_string(), extension),
sync_db_entry!(
entry.metadata.size_in_bytes.to_be_bytes().to_vec(),
size_in_bytes_bytes
),
sync_db_entry!(inode_to_db(entry.metadata.inode), inode),
{
let v = entry.metadata.created_at.into();
sync_db_entry!(v, date_created)
},
{
let v = entry.metadata.modified_at.into();
sync_db_entry!(v, date_modified)
},
{
let v = Utc::now().into();
sync_db_entry!(v, date_indexed)
},
sync_db_entry!(entry.metadata.hidden, hidden),
]
.into_iter()
.unzip();
@ -212,48 +195,29 @@ async fn execute_indexer_update_step(
let (sync_params, db_params): (Vec<_>, Vec<_>) = [
// As this file was updated while Spacedrive was offline, we mark the object_id and cas_id as null
// So this file_path will be updated at file identifier job
(
should_unlink_object.then_some((
(object_id::NAME, serde_json::Value::Null),
should_unlink_object.then_some(object::disconnect()),
),
(
(cas_id::NAME, serde_json::Value::Null),
Some(cas_id::set(None)),
),
(
(is_dir::NAME, json!(*is_dir)),
Some(is_dir::set(Some(*is_dir))),
),
(
(
size_in_bytes_bytes::NAME,
json!(entry.metadata.size_in_bytes.to_be_bytes().to_vec()),
),
Some(size_in_bytes_bytes::set(Some(
entry.metadata.size_in_bytes.to_be_bytes().to_vec(),
))),
),
(
(inode::NAME, json!(entry.metadata.inode.to_le_bytes())),
Some(inode::set(Some(inode_to_db(entry.metadata.inode)))),
),
(
(date_created::NAME, json!(entry.metadata.created_at)),
Some(date_created::set(Some(entry.metadata.created_at.into()))),
),
(
(date_modified::NAME, json!(entry.metadata.modified_at)),
Some(date_modified::set(Some(entry.metadata.modified_at.into()))),
),
(
(hidden::NAME, json!(entry.metadata.hidden)),
Some(hidden::set(Some(entry.metadata.hidden))),
),
object::disconnect(),
)),
Some(((cas_id::NAME, serde_json::Value::Null), cas_id::set(None))),
Some(sync_db_entry!(*is_dir, is_dir)),
Some(sync_db_entry!(
entry.metadata.size_in_bytes.to_be_bytes().to_vec(),
size_in_bytes_bytes
)),
Some(sync_db_entry!(inode_to_db(entry.metadata.inode), inode)),
Some({
let v = entry.metadata.created_at.into();
sync_db_entry!(v, date_created)
}),
Some({
let v = entry.metadata.modified_at.into();
sync_db_entry!(v, date_modified)
}),
Some(sync_db_entry!(entry.metadata.hidden, hidden)),
]
.into_iter()
.filter_map(|(sync_param, maybe_db_param)| {
maybe_db_param.map(|db_param| (sync_param, db_param))
})
.flatten()
.unzip();
Ok::<_, IndexerError>((

View file

@ -306,7 +306,7 @@ async fn inner_create_file(
db.file_path().update(
file_path::pub_id::equals(created_file.pub_id.clone()),
vec![file_path::object::connect(object::pub_id::equals(
object_pub_id,
object_pub_id.clone(),
))],
),
)
@ -342,22 +342,30 @@ async fn inner_create_file(
.await
.map_err(|e| error!("Failed to extract media data: {e:#?}"))
{
if let Ok(media_data_params) = media_data_image_to_query_params(media_data)
.map_err(|e| {
error!("Failed to prepare media data create params: {e:#?}")
}) {
db.media_data()
.upsert(
let (sync_params, db_params) = media_data_image_to_query_params(media_data);
sync.write_ops(
db,
(
sync.shared_create(
prisma_sync::media_data::SyncId {
object: prisma_sync::object::SyncId {
pub_id: object_pub_id.clone(),
},
},
sync_params,
),
db.media_data().upsert(
media_data::object_id::equals(object_id),
media_data::create(
object::id::equals(object_id),
media_data_params.clone(),
db_params.clone(),
),
media_data_params,
)
.exec()
.await?;
}
db_params,
),
),
)
.await?;
}
}
}
@ -682,22 +690,31 @@ async fn inner_update_file(
.await
.map_err(|e| error!("Failed to extract media data: {e:#?}"))
{
if let Ok(media_data_params) =
media_data_image_to_query_params(media_data).map_err(|e| {
error!("Failed to prepare media data create params: {e:#?}")
}) {
db.media_data()
.upsert(
let (sync_params, db_params) =
media_data_image_to_query_params(media_data);
sync.write_ops(
db,
(
sync.shared_create(
prisma_sync::media_data::SyncId {
object: prisma_sync::object::SyncId {
pub_id: object.pub_id.clone(),
},
},
sync_params,
),
db.media_data().upsert(
media_data::object_id::equals(object.id),
media_data::create(
object::id::equals(object.id),
media_data_params.clone(),
db_params.clone(),
),
media_data_params,
)
.exec()
.await?;
}
db_params,
),
),
)
.await?;
}
}
}

View file

@ -31,18 +31,24 @@ pub fn media_data_image_to_query(
#[cfg(feature = "location-watcher")]
pub fn media_data_image_to_query_params(
mdi: ImageMetadata,
) -> Result<Vec<SetParam>, MediaDataError> {
Ok(vec![
camera_data::set(serde_json::to_vec(&mdi.camera_data).ok()),
media_date::set(serde_json::to_vec(&mdi.date_taken).ok()),
resolution::set(serde_json::to_vec(&mdi.resolution).ok()),
media_location::set(serde_json::to_vec(&mdi.location).ok()),
artist::set(mdi.artist),
description::set(mdi.description),
copyright::set(mdi.copyright),
exif_version::set(mdi.exif_version),
epoch_time::set(mdi.date_taken.map(|x| x.unix_timestamp())),
])
) -> (Vec<(&'static str, serde_json::Value)>, Vec<SetParam>) {
use sd_sync::option_sync_db_entry;
use sd_utils::chain_optional_iter;
chain_optional_iter(
[],
[
option_sync_db_entry!(serde_json::to_vec(&mdi.camera_data).ok(), camera_data),
option_sync_db_entry!(serde_json::to_vec(&mdi.date_taken).ok(), media_date),
option_sync_db_entry!(serde_json::to_vec(&mdi.location).ok(), media_location),
option_sync_db_entry!(mdi.artist, artist),
option_sync_db_entry!(mdi.description, description),
option_sync_db_entry!(mdi.copyright, copyright),
option_sync_db_entry!(mdi.exif_version, exif_version),
],
)
.into_iter()
.unzip()
}
pub fn media_data_image_from_prisma_data(

View file

@ -1,4 +1,7 @@
use prisma_client_rust_sdk::{prelude::*, prisma::prisma_models::walkers::RelationFieldWalker};
use prisma_client_rust_sdk::{
prelude::*,
prisma::prisma_models::walkers::{RefinedFieldWalker, RelationFieldWalker},
};
use crate::{ModelSyncType, ModelWithSyncType};
@ -35,15 +38,50 @@ pub fn r#enum(models: Vec<ModelWithSyncType>) -> TokenStream {
let match_arms = match sync_type.as_ref()? {
ModelSyncType::Shared { id } => {
let id_name_snake = snake_ident(id.name());
let (get_id, equals_value, id_name_snake, create_id) = match id.refine() {
RefinedFieldWalker::Relation(rel) => {
let scalar_field = rel.referenced_fields().unwrap().next().unwrap();
let id_name_snake = snake_ident(scalar_field.name());
let field_name_snake = snake_ident(rel.name());
let opposite_model_name_snake =
snake_ident(rel.opposite_relation_field().unwrap().model().name());
let relation_equals_condition = quote!(prisma::#opposite_model_name_snake::pub_id::equals(
id.#field_name_snake.pub_id.clone()
));
let rel_fetch = quote! {
let rel = db.#opposite_model_name_snake()
.find_unique(#relation_equals_condition)
.exec()
.await?
.unwrap();
};
(
Some(rel_fetch),
quote!(rel.id),
id_name_snake,
relation_equals_condition,
)
}
RefinedFieldWalker::Scalar(s) => {
let field_name_snake = snake_ident(s.name());
let thing = quote!(id.#field_name_snake.clone());
(None, thing.clone(), field_name_snake, thing)
}
};
quote! {
#get_id
match data {
sd_sync::CRDTOperationData::Create => {
db.#model_name_snake()
.upsert(
prisma::#model_name_snake::#id_name_snake::equals(id.#id_name_snake.clone()),
prisma::#model_name_snake::create(id.#id_name_snake, vec![]),
prisma::#model_name_snake::#id_name_snake::equals(#equals_value),
prisma::#model_name_snake::create(#create_id, vec![]),
vec![]
)
.exec()
@ -56,8 +94,8 @@ pub fn r#enum(models: Vec<ModelWithSyncType>) -> TokenStream {
db.#model_name_snake()
.upsert(
prisma::#model_name_snake::#id_name_snake::equals(id.#id_name_snake.clone()),
prisma::#model_name_snake::create(id.#id_name_snake, data.clone()),
prisma::#model_name_snake::#id_name_snake::equals(#equals_value),
prisma::#model_name_snake::create(#create_id, data.clone()),
data,
)
.exec()
@ -65,7 +103,7 @@ pub fn r#enum(models: Vec<ModelWithSyncType>) -> TokenStream {
},
sd_sync::CRDTOperationData::Delete => {
db.#model_name_snake()
.delete(prisma::#model_name_snake::#id_name_snake::equals(id.#id_name_snake))
.delete(prisma::#model_name_snake::#id_name_snake::equals(#equals_value))
.exec()
.await?;
},

View file

@ -106,3 +106,33 @@ pub trait OperationFactory {
self.new_op(&id, CRDTOperationData::Delete)
}
}
#[macro_export]
macro_rules! sync_entry {
($v:expr, $($m:tt)*) => {{
let v = $v;
($($m)*::NAME, serde_json::json!(&v))
}}
}
#[macro_export]
macro_rules! option_sync_entry {
($v:expr, $($m:tt)*) => {
$v.map(|v| $crate::sync_entry!(v, $($m)*))
}
}
#[macro_export]
macro_rules! sync_db_entry {
($v:expr, $($m:tt)*) => {{
let v = $v;
($crate::sync_entry!(&v, $($m)*), $($m)*::set(Some(v)))
}}
}
#[macro_export]
macro_rules! option_sync_db_entry {
($v:expr, $($m:tt)*) => {
$v.map(|v| $crate::sync_db_entry!(v, $($m)*))
};
}

View file

@ -248,7 +248,7 @@ export type FileDeleterJobInit = { location_id: number; file_path_ids: number[]
export type FileEraserJobInit = { location_id: number; file_path_ids: number[]; passes: string }
export type FilePath = { 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 }
export type FilePath = { 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; date_created: string | null; date_modified: string | null; date_indexed: string | null }
export type FilePathCursor = { isDir: boolean; variant: FilePathCursorVariant }
@ -262,7 +262,7 @@ export type FilePathOrder = { field: "name"; value: SortOrder } | { field: "size
export type FilePathSearchArgs = { take?: number | null; orderAndPagination?: OrderAndPagination<number, FilePathOrder, FilePathCursor> | null; filters?: SearchFilterArgs[]; groupDirectories?: boolean }
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 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; 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 = {
/**