From aa9a117a224a67976d74ca4784ea517bb6358fb3 Mon Sep 17 00:00:00 2001 From: Ericson Soares Date: Thu, 20 Jun 2024 17:03:39 -0300 Subject: [PATCH] Stronger linter on sync generator --- core/src/api/nodes.rs | 1 + crates/sync-generator/src/attribute/mod.rs | 26 +- crates/sync-generator/src/attribute/parser.rs | 34 +- crates/sync-generator/src/lib.rs | 59 ++- crates/sync-generator/src/model.rs | 195 +++++---- crates/sync-generator/src/sync_data.rs | 397 ++++++++++-------- packages/client/src/core.ts | 2 +- 7 files changed, 393 insertions(+), 321 deletions(-) diff --git a/core/src/api/nodes.rs b/core/src/api/nodes.rs index 50f98fa8a..0f422b593 100644 --- a/core/src/api/nodes.rs +++ b/core/src/api/nodes.rs @@ -28,6 +28,7 @@ pub(crate) fn mount() -> AlphaRouter { pub p2p_discovery: Option, pub p2p_remote_access: Option, pub p2p_manual_peers: Option>, + #[cfg(feature = "ai")] pub image_labeler_version: Option, } R.mutation(|node, args: ChangeNodeNameArgs| async move { diff --git a/crates/sync-generator/src/attribute/mod.rs b/crates/sync-generator/src/attribute/mod.rs index 6dbb32a4f..e836fa795 100644 --- a/crates/sync-generator/src/attribute/mod.rs +++ b/crates/sync-generator/src/attribute/mod.rs @@ -10,17 +10,19 @@ pub enum AttributeFieldValue<'a> { #[allow(unused)] impl AttributeFieldValue<'_> { - pub fn as_single(&self) -> Option<&str> { - match self { - AttributeFieldValue::Single(field) => Some(field), - _ => None, + pub const fn as_single(&self) -> Option<&str> { + if let AttributeFieldValue::Single(field) = self { + Some(field) + } else { + None } } - pub fn as_list(&self) -> Option<&Vec<&str>> { - match self { - AttributeFieldValue::List(fields) => Some(fields), - _ => None, + pub const fn as_list(&self) -> Option<&Vec<&str>> { + if let AttributeFieldValue::List(fields) = self { + Some(fields) + } else { + None } } } @@ -36,12 +38,14 @@ impl<'a> Attribute<'a> { parser::parse(input).map(|(_, a)| a).map_err(|_| ()) } - pub fn field(&self, name: &str) -> Option<&AttributeFieldValue> { - self.fields.iter().find(|(n, _)| *n == name).map(|(_, v)| v) + pub fn field(&self, name: &str) -> Option<&AttributeFieldValue<'_>> { + self.fields + .iter() + .find_map(|(n, v)| (*n == name).then_some(v)) } } -pub fn model_attributes(model: ModelWalker) -> Vec { +pub fn model_attributes(model: ModelWalker<'_>) -> Vec> { model .ast_model() .documentation() diff --git a/crates/sync-generator/src/attribute/parser.rs b/crates/sync-generator/src/attribute/parser.rs index c0e3f635d..384be1ff2 100644 --- a/crates/sync-generator/src/attribute/parser.rs +++ b/crates/sync-generator/src/attribute/parser.rs @@ -1,11 +1,11 @@ use nom::{ branch::alt, - bytes::complete::*, - character::complete::*, - combinator::*, + bytes::complete::{is_not, tag}, + character::complete::{alpha1, char, multispace0}, + combinator::{map, opt}, error::{ErrorKind, ParseError}, - multi::*, - sequence::*, + multi::separated_list1, + sequence::{delimited, separated_pair}, AsChar, IResult, InputTakeAtPosition, }; @@ -24,7 +24,7 @@ fn parens(input: &str) -> IResult<&str, &str> { delimited(char('('), is_not(")"), char(')'))(input) } -fn single_value>(i: T) -> IResult +fn single_value>(i: &T) -> IResult where T: InputTakeAtPosition, ::Item: AsChar, @@ -41,19 +41,19 @@ where fn list_value(input: &str) -> IResult<&str, Vec<&str>> { delimited( char('['), - separated_list1(char(','), remove_ws(single_value)), + separated_list1(char(','), remove_ws(|a| single_value(&a))), char(']'), )(input) } -fn attribute_field_value(input: &str) -> IResult<&str, AttributeFieldValue> { +fn attribute_field_value(input: &str) -> IResult<&str, AttributeFieldValue<'_>> { remove_ws(alt(( - map(list_value, AttributeFieldValue::List), - map(single_value, AttributeFieldValue::Single), + map(|a| list_value(a), AttributeFieldValue::List), + map(|a| single_value(&a), AttributeFieldValue::Single), )))(input) } -fn attribute_field(input: &str) -> IResult<&str, (&str, AttributeFieldValue)> { +fn attribute_field(input: &str) -> IResult<&str, (&str, AttributeFieldValue<'_>)> { remove_ws(separated_pair( remove_ws(is_not(":")), char(':'), @@ -61,11 +61,11 @@ fn attribute_field(input: &str) -> IResult<&str, (&str, AttributeFieldValue)> { ))(input) } -fn attribute_fields(input: &str) -> IResult<&str, Vec<(&str, AttributeFieldValue)>> { +fn attribute_fields(input: &str) -> IResult<&str, Vec<(&str, AttributeFieldValue<'_>)>> { separated_list1(char(','), attribute_field)(input) } -pub fn parse(input: &str) -> IResult<&str, Attribute> { +pub fn parse(input: &str) -> IResult<&str, Attribute<'_>> { let (input, _) = remove_ws(tag("@"))(input)?; let (input, name) = alpha1(input)?; let (input, values_str) = opt(remove_ws(parens))(input)?; @@ -86,7 +86,7 @@ mod test { fn marker() { let s = "@local"; - let (remaining, attribute) = super::parse(s).unwrap(); + let (remaining, attribute) = parse(s).unwrap(); assert_eq!(remaining, ""); assert_eq!(attribute.name, "local"); @@ -97,7 +97,7 @@ mod test { fn single() { let s = "@local(foo: bar)"; - let (remaining, attribute) = super::parse(s).unwrap(); + let (remaining, attribute) = parse(s).unwrap(); assert_eq!(remaining, ""); assert_eq!(attribute.name, "local"); @@ -113,7 +113,7 @@ mod test { fn list() { let s = "@local(foo: [bar, baz])"; - let (remaining, attribute) = match super::parse(s) { + let (remaining, attribute) = match parse(s) { Ok(v) => v, Err(e) => panic!("{}", e), }; @@ -136,7 +136,7 @@ mod test { fn multiple() { let s = "@local(foo: bar, baz: qux)"; - let (remaining, attribute) = super::parse(s).unwrap(); + let (remaining, attribute) = parse(s).unwrap(); assert_eq!(remaining, ""); assert_eq!(attribute.name, "local"); diff --git a/crates/sync-generator/src/lib.rs b/crates/sync-generator/src/lib.rs index 0d546575c..1fb8279b9 100644 --- a/crates/sync-generator/src/lib.rs +++ b/crates/sync-generator/src/lib.rs @@ -1,8 +1,31 @@ -mod attribute; -mod model; -mod sync_data; - -use attribute::*; +#![warn( + clippy::all, + clippy::pedantic, + clippy::correctness, + clippy::perf, + clippy::style, + clippy::suspicious, + clippy::complexity, + clippy::nursery, + clippy::unwrap_used, + unused_qualifications, + rust_2018_idioms, + trivial_casts, + trivial_numeric_casts, + unused_allocation, + clippy::unnecessary_cast, + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::dbg_macro, + clippy::deprecated_cfg_attr, + clippy::separated_literal_suffix, + deprecated +)] +#![forbid(deprecated_in_future)] +#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] use prisma_client_rust_sdk::{ prelude::*, @@ -11,6 +34,12 @@ use prisma_client_rust_sdk::{ }, }; +mod attribute; +mod model; +mod sync_data; + +use attribute::{model_attributes, Attribute, AttributeFieldValue}; + #[derive(Debug, serde::Serialize, thiserror::Error)] enum Error {} @@ -38,7 +67,7 @@ pub enum ModelSyncType<'a> { } impl<'a> ModelSyncType<'a> { - fn from_attribute(attr: Attribute, model: ModelWalker<'a>) -> Option { + fn from_attribute(attr: &Attribute<'_>, model: ModelWalker<'a>) -> Option { Some(match attr.name { "local" | "shared" => { let id = attr @@ -69,14 +98,15 @@ impl<'a> ModelSyncType<'a> { AttributeFieldValue::List(_) => None, }) .and_then(|name| { - match model + if let RefinedFieldWalker::Relation(r) = model .fields() .find(|f| f.name() == name) .unwrap_or_else(|| panic!("'{name}' field not found")) .refine() { - RefinedFieldWalker::Relation(r) => Some(r), - _ => None, + Some(r) + } else { + None } }) .unwrap_or_else(|| panic!("'{name}' must be a relation field")) @@ -96,11 +126,10 @@ impl<'a> ModelSyncType<'a> { }) } - fn sync_id(&self) -> Vec { + fn sync_id(&self) -> Vec> { match self { // Self::Owned { id } => id.clone(), - Self::Local { id, .. } => vec![*id], - Self::Shared { id, .. } => vec![*id], + Self::Local { id, .. } | Self::Shared { id, .. } => vec![*id], Self::Relation { group, item, .. } => vec![(*group).into(), (*item).into()], } } @@ -127,7 +156,7 @@ impl PrismaGenerator for SDSyncGenerator { type Error = Error; - fn generate(self, args: GenerateArgs) -> Result { + fn generate(self, args: GenerateArgs<'_>) -> Result { let db = &args.schema.db; let models_with_sync_types = db @@ -136,13 +165,13 @@ impl PrismaGenerator for SDSyncGenerator { .map(|(model, attributes)| { let sync_type = attributes .into_iter() - .find_map(|a| ModelSyncType::from_attribute(a, model)); + .find_map(|a| ModelSyncType::from_attribute(&a, model)); (model, sync_type) }) .collect::>(); - let model_sync_data = sync_data::r#enum(models_with_sync_types.clone()); + let model_sync_data = sync_data::enumerate(&models_with_sync_types); let mut module = Module::new( "root", diff --git a/crates/sync-generator/src/model.rs b/crates/sync-generator/src/model.rs index f68437446..767c1d820 100644 --- a/crates/sync-generator/src/model.rs +++ b/crates/sync-generator/src/model.rs @@ -1,13 +1,14 @@ use prisma_client_rust_sdk::{prelude::*, prisma::prisma_models::walkers::RefinedFieldWalker}; +use prisma_models::{ast::ModelId, walkers::Walker}; use crate::{ModelSyncType, ModelWithSyncType}; -pub fn module((model, sync_type): ModelWithSyncType) -> Module { +pub fn module((model, sync_type): ModelWithSyncType<'_>) -> Module { let model_name_snake = snake_ident(model.name()); let sync_id = sync_type.as_ref().map(|sync_type| { let fields = sync_type.sync_id(); - let fields = fields.iter().flat_map(|field| { + let fields = fields.iter().map(|field| { let name_snake = snake_ident(field.name()); let typ = match field.refine() { @@ -18,58 +19,10 @@ pub fn module((model, sync_type): ModelWithSyncType) -> Module { } }; - Some(quote!(pub #name_snake: #typ)) + quote!(pub #name_snake: #typ) }); - let model_stuff = match sync_type { - ModelSyncType::Relation { - item, - group, - model_id, - } => { - let item_name_snake = snake_ident(item.name()); - let item_model_name_snake = snake_ident(item.related_model().name()); - - let group_name_snake = snake_ident(group.name()); - let group_model_name_snake = snake_ident(group.related_model().name()); - - Some(quote! { - impl sd_sync::RelationSyncId for SyncId { - type ItemSyncId = super::#item_model_name_snake::SyncId; - type GroupSyncId = super::#group_model_name_snake::SyncId; - - fn split(&self) -> (&Self::ItemSyncId, &Self::GroupSyncId) { - ( - &self.#item_name_snake, - &self.#group_name_snake - ) - } - } - - pub const MODEL_ID: u16 = #model_id; - - impl sd_sync::SyncModel for #model_name_snake::Types { - const MODEL_ID: u16 = MODEL_ID; - } - - impl sd_sync::RelationSyncModel for #model_name_snake::Types { - type SyncId = SyncId; - } - }) - } - ModelSyncType::Shared { model_id, .. } => Some(quote! { - pub const MODEL_ID: u16 = #model_id; - - impl sd_sync::SyncModel for #model_name_snake::Types { - const MODEL_ID: u16 = MODEL_ID; - } - - impl sd_sync::SharedSyncModel for #model_name_snake::Types { - type SyncId = SyncId; - } - }), - _ => None, - }; + let model_stuff = parse_model(sync_type, &model_name_snake); quote! { #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] @@ -101,8 +54,9 @@ pub fn module((model, sync_type): ModelWithSyncType) -> Module { let relation_model_name_snake = snake_ident(relation_field.related_model().name()); - match relation_field.referenced_fields() { - Some(i) => { + relation_field.referenced_fields().map_or_else( + || None, + |i| { if i.count() == 1 { Some(quote! {{ let val: std::collections::HashMap = ::rmpv::ext::from_value(val).unwrap(); @@ -115,17 +69,17 @@ pub fn module((model, sync_type): ModelWithSyncType) -> Module { } else { None } - } - _ => None, - } + }, + ) } } .map(|body| quote!(#model_name_snake::#field_name_snake::NAME => #body)) }); - match field_matches.clone().count() { - 0 => quote!(), - _ => quote! { + if field_matches.clone().count() == 0 { + quote!() + } else { + quote! { impl #model_name_snake::SetParam { pub fn deserialize(field: &str, val: ::rmpv::Value) -> Option { Some(match field { @@ -134,41 +88,11 @@ pub fn module((model, sync_type): ModelWithSyncType) -> Module { }) } } - }, + } } }; - let unique_param_impl = { - let field_matches = model - .unique_criterias() - .flat_map(|criteria| match &criteria.fields().next() { - Some(field) if criteria.fields().len() == 1 => { - let field_name_snake = snake_ident(field.name()); - - Some(quote!(#model_name_snake::#field_name_snake::NAME => - #model_name_snake::#field_name_snake::equals( - ::rmpv::ext::from_value(val).unwrap() - ), - )) - } - _ => None, - }) - .collect::>(); - - match field_matches.len() { - 0 => quote!(), - _ => quote! { - impl #model_name_snake::UniqueWhereParam { - pub fn deserialize(field: &str, val: ::rmpv::Value) -> Option { - Some(match field { - #(#field_matches)* - _ => return None - }) - } - } - }, - } - }; + let unique_param_impl = process_unique_params(model, &model_name_snake); Module::new( model.name(), @@ -184,3 +108,90 @@ pub fn module((model, sync_type): ModelWithSyncType) -> Module { }, ) } + +#[inline] +fn parse_model(sync_type: &ModelSyncType<'_>, model_name_snake: &Ident) -> Option { + match sync_type { + ModelSyncType::Relation { + item, + group, + model_id, + } => { + let item_name_snake = snake_ident(item.name()); + let item_model_name_snake = snake_ident(item.related_model().name()); + + let group_name_snake = snake_ident(group.name()); + let group_model_name_snake = snake_ident(group.related_model().name()); + + Some(quote! { + impl sd_sync::RelationSyncId for SyncId { + type ItemSyncId = super::#item_model_name_snake::SyncId; + type GroupSyncId = super::#group_model_name_snake::SyncId; + + fn split(&self) -> (&Self::ItemSyncId, &Self::GroupSyncId) { + ( + &self.#item_name_snake, + &self.#group_name_snake + ) + } + } + + pub const MODEL_ID: u16 = #model_id; + + impl sd_sync::SyncModel for #model_name_snake::Types { + const MODEL_ID: u16 = MODEL_ID; + } + + impl sd_sync::RelationSyncModel for #model_name_snake::Types { + type SyncId = SyncId; + } + }) + } + ModelSyncType::Shared { model_id, .. } => Some(quote! { + pub const MODEL_ID: u16 = #model_id; + + impl sd_sync::SyncModel for #model_name_snake::Types { + const MODEL_ID: u16 = MODEL_ID; + } + + impl sd_sync::SharedSyncModel for #model_name_snake::Types { + type SyncId = SyncId; + } + }), + ModelSyncType::Local { .. } => None, + } +} + +#[inline] +fn process_unique_params(model: Walker<'_, ModelId>, model_name_snake: &Ident) -> TokenStream { + let field_matches = model + .unique_criterias() + .filter_map(|criteria| match &criteria.fields().next() { + Some(field) if criteria.fields().len() == 1 => { + let field_name_snake = snake_ident(field.name()); + + Some(quote!(#model_name_snake::#field_name_snake::NAME => + #model_name_snake::#field_name_snake::equals( + ::rmpv::ext::from_value(val).unwrap() + ), + )) + } + _ => None, + }) + .collect::>(); + + if field_matches.is_empty() { + quote!() + } else { + quote! { + impl #model_name_snake::UniqueWhereParam { + pub fn deserialize(field: &str, val: ::rmpv::Value) -> Option { + Some(match field { + #(#field_matches)* + _ => return None + }) + } + } + } + } +} diff --git a/crates/sync-generator/src/sync_data.rs b/crates/sync-generator/src/sync_data.rs index f1bfcade7..66556e752 100644 --- a/crates/sync-generator/src/sync_data.rs +++ b/crates/sync-generator/src/sync_data.rs @@ -2,10 +2,11 @@ use prisma_client_rust_sdk::{ prelude::*, prisma::prisma_models::walkers::{RefinedFieldWalker, RelationFieldWalker}, }; +use prisma_models::walkers::{FieldWalker, ScalarFieldWalker}; use crate::{ModelSyncType, ModelWithSyncType}; -pub fn r#enum(models: Vec) -> TokenStream { +pub fn enumerate(models: &[ModelWithSyncType<'_>]) -> TokenStream { let (variants, matches): (Vec<_>, Vec<_>) = models .iter() .filter_map(|(model, sync_type)| { @@ -38,193 +39,12 @@ pub fn r#enum(models: Vec) -> TokenStream { let match_arms = match sync_type.as_ref()? { ModelSyncType::Shared { id, model_id } => { - let (get_id, equals_value, id_name_snake, create_id) = match id.refine() { - RefinedFieldWalker::Relation(rel) => { - let scalar_field = rel.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(data) => { - let data: Vec<_> = data.into_iter().map(|(field, value)| { - prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap() - }).collect(); - - db.#model_name_snake() - .upsert( - prisma::#model_name_snake::#id_name_snake::equals(#equals_value), - prisma::#model_name_snake::create(#create_id, data.clone()), - data - ) - .exec() - .await?; - }, - sd_sync::CRDTOperationData::Update { field, value } => { - let data = vec![ - prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap() - ]; - - db.#model_name_snake() - .upsert( - prisma::#model_name_snake::#id_name_snake::equals(#equals_value), - prisma::#model_name_snake::create(#create_id, data.clone()), - data, - ) - .exec() - .await?; - }, - sd_sync::CRDTOperationData::Delete => { - db.#model_name_snake() - .delete(prisma::#model_name_snake::#id_name_snake::equals(#equals_value)) - .exec() - .await?; - - db.crdt_operation() - .delete_many(vec![ - prisma::crdt_operation::model::equals(#model_id as i32), - prisma::crdt_operation::record_id::equals(rmp_serde::to_vec(&id).unwrap()), - prisma::crdt_operation::kind::equals(sd_sync::OperationKind::Create.to_string()) - ]) - .exec() - .await?; - }, - } - } + handle_crdt_ops_shared(id, *model_id, &model_name_snake) } ModelSyncType::Relation { item, group, .. } => { - let compound_id = format_ident!( - "{}", - group - .fields() - .unwrap() - .chain(item.fields().unwrap()) - .map(|f| f.name()) - .collect::>() - .join("_") - ); - - let db_batch_items = { - let batch_item = |item: &RelationFieldWalker| { - let item_model_sync_id_field_name_snake = models - .iter() - .find(|m| m.0.name() == item.related_model().name()) - .and_then(|(_m, sync)| sync.as_ref()) - .map(|sync| snake_ident(sync.sync_id()[0].name())) - .unwrap(); - let item_model_name_snake = snake_ident(item.related_model().name()); - let item_field_name_snake = snake_ident(item.name()); - - quote! { - db.#item_model_name_snake() - .find_unique( - prisma::#item_model_name_snake::#item_model_sync_id_field_name_snake::equals( - id.#item_field_name_snake.#item_model_sync_id_field_name_snake.clone() - ) - ) - .select(prisma::#item_model_name_snake::select!({ id })) - } - }; - - [batch_item(group), batch_item(item)] - }; - - let create_items = { - let create_item = |item: &RelationFieldWalker, var: TokenStream| { - let item_model_name_snake = snake_ident(item.related_model().name()); - - quote!( - prisma::#item_model_name_snake::id::equals(#var.id) - ) - }; - - [ - create_item(item, quote!(item)), - create_item(group, quote!(group)), - ] - }; - - quote! { - let (Some(group), Some(item)) = - (#(#db_batch_items.exec().await?),*) else { - panic!("item and group not found!"); - }; - - let id = prisma::#model_name_snake::#compound_id(group.id, item.id); - - match data { - sd_sync::CRDTOperationData::Create(_) => { - db.#model_name_snake() - .upsert( - id, - prisma::#model_name_snake::create( - #(#create_items),*, - vec![] - ), - vec![], - ) - .exec() - .await - .ok(); - }, - sd_sync::CRDTOperationData::Update { field, value } => { - let data = vec![prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap()]; - - db.#model_name_snake() - .upsert( - id, - prisma::#model_name_snake::create( - #(#create_items),*, - data.clone(), - ), - data, - ) - .exec() - .await - .ok(); - }, - sd_sync::CRDTOperationData::Delete => { - db.#model_name_snake() - .delete(id) - .exec() - .await - .ok(); - }, - } - } + handle_crdt_ops_relation(models, item, group, &model_name_snake) } - _ => return None, + ModelSyncType::Local { .. } => return None, }; Some(quote! { @@ -257,3 +77,210 @@ pub fn r#enum(models: Vec) -> TokenStream { } } } + +fn handle_crdt_ops_relation( + models: &[ModelWithSyncType<'_>], + item: &RelationFieldWalker<'_>, + group: &RelationFieldWalker<'_>, + model_name_snake: &Ident, +) -> TokenStream { + let compound_id = format_ident!( + "{}", + group + .fields() + .expect("missing group fields") + .chain(item.fields().expect("missing item fields")) + .map(ScalarFieldWalker::name) + .collect::>() + .join("_") + ); + + let db_batch_items = { + let batch_item = |item: &RelationFieldWalker<'_>| { + let item_model_sync_id_field_name_snake = models + .iter() + .find(|m| m.0.name() == item.related_model().name()) + .and_then(|(_m, sync)| sync.as_ref()) + .map(|sync| snake_ident(sync.sync_id()[0].name())) + .expect("missing sync id field name for relation"); + let item_model_name_snake = snake_ident(item.related_model().name()); + let item_field_name_snake = snake_ident(item.name()); + + quote! { + db.#item_model_name_snake() + .find_unique( + prisma::#item_model_name_snake::#item_model_sync_id_field_name_snake::equals( + id.#item_field_name_snake.#item_model_sync_id_field_name_snake.clone() + ) + ) + .select(prisma::#item_model_name_snake::select!({ id })) + } + }; + + [batch_item(group), batch_item(item)] + }; + + let create_items = { + let create_item = |item: &RelationFieldWalker<'_>, var: TokenStream| { + let item_model_name_snake = snake_ident(item.related_model().name()); + + quote!( + prisma::#item_model_name_snake::id::equals(#var.id) + ) + }; + + [ + create_item(item, quote!(item)), + create_item(group, quote!(group)), + ] + }; + + quote! { + let (Some(group), Some(item)) = + (#(#db_batch_items.exec().await?),*) else { + panic!("item and group not found!"); + }; + + let id = prisma::#model_name_snake::#compound_id(group.id, item.id); + + match data { + sd_sync::CRDTOperationData::Create(_) => { + db.#model_name_snake() + .upsert( + id, + prisma::#model_name_snake::create( + #(#create_items),*, + vec![] + ), + vec![], + ) + .exec() + .await + .ok(); + }, + sd_sync::CRDTOperationData::Update { field, value } => { + let data = vec![prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap()]; + + db.#model_name_snake() + .upsert( + id, + prisma::#model_name_snake::create( + #(#create_items),*, + data.clone(), + ), + data, + ) + .exec() + .await + .ok(); + }, + sd_sync::CRDTOperationData::Delete => { + db.#model_name_snake() + .delete(id) + .exec() + .await + .ok(); + }, + } + } +} + +#[inline] +fn handle_crdt_ops_shared( + id: &FieldWalker<'_>, + model_id: u16, + model_name_snake: &Ident, +) -> TokenStream { + let (get_id, equals_value, id_name_snake, create_id) = match id.refine() { + RefinedFieldWalker::Relation(rel) => { + let scalar_field = rel + .fields() + .expect("missing fields") + .next() + .expect("empty fields"); + 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() + .expect("missing opposite relation field") + .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(data) => { + let data: Vec<_> = data.into_iter().map(|(field, value)| { + prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap() + }).collect(); + + db.#model_name_snake() + .upsert( + prisma::#model_name_snake::#id_name_snake::equals(#equals_value), + prisma::#model_name_snake::create(#create_id, data.clone()), + data + ) + .exec() + .await?; + }, + sd_sync::CRDTOperationData::Update { field, value } => { + let data = vec![ + prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap() + ]; + + db.#model_name_snake() + .upsert( + prisma::#model_name_snake::#id_name_snake::equals(#equals_value), + prisma::#model_name_snake::create(#create_id, data.clone()), + data, + ) + .exec() + .await?; + }, + sd_sync::CRDTOperationData::Delete => { + db.#model_name_snake() + .delete(prisma::#model_name_snake::#id_name_snake::equals(#equals_value)) + .exec() + .await?; + + db.crdt_operation() + .delete_many(vec![ + prisma::crdt_operation::model::equals(#model_id as i32), + prisma::crdt_operation::record_id::equals(rmp_serde::to_vec(&id).unwrap()), + prisma::crdt_operation::kind::equals(sd_sync::OperationKind::Create.to_string()) + ]) + .exec() + .await?; + }, + } + } +} diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 667f88974..bf03ae820 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -169,7 +169,7 @@ export type CameraData = { device_make: string | null; device_model: string | nu export type CasId = string -export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_disabled: boolean | null; p2p_ipv6_disabled: boolean | null; p2p_relay_disabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; p2p_remote_access: boolean | null; p2p_manual_peers: string[] | null; image_labeler_version: string | null } +export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_disabled: boolean | null; p2p_ipv6_disabled: boolean | null; p2p_relay_disabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; p2p_remote_access: boolean | null; p2p_manual_peers: string[] | null } export type Chapter = { id: number; start: [number, number]; end: [number, number]; time_base_den: number; time_base_num: number; metadata: Metadata }