Remove owned sync (#967)

* remove owned sync + cleanup

* no more atomic records

* byebye owned

* remove owned ops from schema
This commit is contained in:
Brendan Allan 2023-06-19 01:45:06 +02:00 committed by GitHub
parent 2d703f2648
commit d683d22c82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 216 additions and 2436 deletions

View file

@ -0,0 +1,63 @@
/*
Warnings:
- You are about to drop the `owned_operation` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the column `date_created` on the `indexer_rule_in_location` table. All the data in the column will be lost.
- Made the column `pub_id` on table `indexer_rule` required. This step will fail if there are existing NULL values in that column.
*/
-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "owned_operation";
PRAGMA foreign_keys=on;
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_indexer_rule" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"name" TEXT,
"default" BOOLEAN,
"rules_per_kind" BLOB,
"date_created" DATETIME,
"date_modified" DATETIME
);
INSERT INTO "new_indexer_rule" ("date_created", "date_modified", "default", "id", "name", "pub_id", "rules_per_kind") SELECT "date_created", "date_modified", "default", "id", "name", "pub_id", "rules_per_kind" FROM "indexer_rule";
DROP TABLE "indexer_rule";
ALTER TABLE "new_indexer_rule" RENAME TO "indexer_rule";
CREATE UNIQUE INDEX "indexer_rule_pub_id_key" ON "indexer_rule"("pub_id");
CREATE TABLE "new_job" (
"id" BLOB NOT NULL PRIMARY KEY,
"name" TEXT,
"node_id" INTEGER,
"action" TEXT,
"status" INTEGER,
"errors_text" TEXT,
"data" BLOB,
"metadata" BLOB,
"parent_id" BLOB,
"task_count" INTEGER,
"completed_task_count" INTEGER,
"date_estimated_completion" DATETIME,
"date_created" DATETIME,
"date_started" DATETIME,
"date_completed" DATETIME,
CONSTRAINT "job_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "job_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "job" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_job" ("action", "completed_task_count", "data", "date_completed", "date_created", "date_estimated_completion", "date_started", "errors_text", "id", "metadata", "name", "node_id", "parent_id", "status", "task_count") SELECT "action", "completed_task_count", "data", "date_completed", "date_created", "date_estimated_completion", "date_started", "errors_text", "id", "metadata", "name", "node_id", "parent_id", "status", "task_count" FROM "job";
DROP TABLE "job";
ALTER TABLE "new_job" RENAME TO "job";
CREATE TABLE "new_indexer_rule_in_location" (
"location_id" INTEGER NOT NULL,
"indexer_rule_id" INTEGER NOT NULL,
PRIMARY KEY ("location_id", "indexer_rule_id"),
CONSTRAINT "indexer_rule_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "indexer_rule_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rule" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
INSERT INTO "new_indexer_rule_in_location" ("indexer_rule_id", "location_id") SELECT "indexer_rule_id", "location_id" FROM "indexer_rule_in_location";
DROP TABLE "indexer_rule_in_location";
ALTER TABLE "new_indexer_rule_in_location" RENAME TO "indexer_rule_in_location";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View file

@ -16,18 +16,6 @@ generator sync {
//// Sync ////
model OwnedOperation {
id Bytes @id
timestamp BigInt
data Bytes
model String
node_id Int
node Node @relation(fields: [node_id], references: [id])
@@map("owned_operation")
}
model SharedOperation {
id Bytes @id
timestamp BigInt
@ -73,7 +61,6 @@ model Node {
jobs Job[]
Location Location[]
OwnedOperation OwnedOperation[]
SharedOperation SharedOperation[]
@@map("node")
@ -101,7 +88,6 @@ model Location {
id Int @id @default(autoincrement())
pub_id Bytes @unique
node_id Int?
name String?
path String?
total_capacity Int?
@ -112,7 +98,9 @@ model Location {
hidden Boolean?
date_created DateTime?
node Node? @relation(fields: [node_id], references: [id])
node_id Int?
node Node? @relation(fields: [node_id], references: [id])
file_paths FilePath[]
indexer_rules IndexerRulesInLocation[]
@ -402,7 +390,6 @@ model Job {
//// Album ////
/// @shared(id: pub_id)
// model Album {
// id Int @id @default(autoincrement())
// pub_id Bytes @unique

View file

@ -248,25 +248,22 @@ async fn identifier_job_step(
let kind = meta.kind as i32;
let object_creation_args = (
[sync.shared_create(sync_id())]
.into_iter()
.chain(
[
(object::date_created::NAME, json!(fp.date_created)),
(object::kind::NAME, json!(kind)),
]
.into_iter()
.map(|(f, v)| sync.shared_update(sync_id(), f, v)),
)
.collect::<Vec<_>>(),
object::create_unchecked(
uuid_to_bytes(object_pub_id),
vec![
object::date_created::set(fp.date_created),
object::kind::set(Some(kind)),
],
let (sync_params, db_params): (Vec<_>, Vec<_>) = [
(
(object::date_created::NAME, json!(fp.date_created)),
object::date_created::set(fp.date_created),
),
(
(object::kind::NAME, json!(kind)),
object::kind::set(Some(kind)),
),
]
.into_iter()
.unzip();
let object_creation_args = (
sync.unique_shared_create(sync_id(), sync_params),
object::create_unchecked(uuid_to_bytes(object_pub_id), db_params),
);
(object_creation_args, {
@ -287,7 +284,7 @@ async fn identifier_job_step(
.write_ops(db, {
let (sync, db_params): (Vec<_>, Vec<_>) = object_create_args.into_iter().unzip();
(sync.concat(), db.object().create_many(db_params))
(sync, db.object().create_many(db_params))
})
.await
.unwrap_or_else(|e| {

View file

@ -53,7 +53,7 @@ pub enum P2PEvent {
}
pub struct P2PManager {
pub events: broadcast::Sender<P2PEvent>,
pub events: (broadcast::Sender<P2PEvent>, broadcast::Receiver<P2PEvent>),
pub manager: Arc<Manager<PeerMetadata>>,
spacedrop_pairing_reqs: Arc<Mutex<HashMap<Uuid, oneshot::Sender<Option<String>>>>>,
pub metadata_manager: Arc<MetadataManager<PeerMetadata>>,
@ -80,11 +80,13 @@ impl P2PManager {
manager.listen_addrs().await
);
let (tx, _) = broadcast::channel(100);
// need to keep 'rx' around so that the channel isn't dropped
let (tx, rx) = broadcast::channel(100);
let (tx2, rx2) = broadcast::channel(100);
let spacedrop_pairing_reqs = Arc::new(Mutex::new(HashMap::new()));
let spacedrop_progress = Arc::new(Mutex::new(HashMap::new()));
tokio::spawn({
let events = tx.clone();
// let sync_events = tx2.clone();
@ -226,7 +228,7 @@ impl P2PManager {
// https://docs.rs/system_shutdown/latest/system_shutdown/
let this = Arc::new(Self {
events: tx,
events: (tx, rx),
manager,
spacedrop_pairing_reqs,
metadata_manager,
@ -275,7 +277,7 @@ impl P2PManager {
}
pub fn subscribe(&self) -> broadcast::Receiver<P2PEvent> {
self.events.subscribe()
self.events.0.subscribe()
}
#[allow(unused)] // TODO: Remove `allow(unused)` once integrated

View file

@ -50,21 +50,6 @@ impl SyncManager {
) -> prisma_client_rust::Result<<I as prisma_client_rust::BatchItemParent>::ReturnValue> {
#[cfg(feature = "sync-messages")]
let res = {
let owned = _ops
.iter()
.filter_map(|op| match &op.typ {
CRDTOperationType::Owned(owned_op) => Some(tx.owned_operation().create(
op.id.as_bytes().to_vec(),
op.timestamp.0 as i64,
to_vec(&owned_op.items).unwrap(),
owned_op.model.clone(),
node::pub_id::equals(op.node.as_bytes().to_vec()),
vec![],
)),
_ => None,
})
.collect::<Vec<_>>();
let shared = _ops
.iter()
.filter_map(|op| match &op.typ {
@ -90,7 +75,7 @@ impl SyncManager {
})
.collect::<Vec<_>>();
let (res, _) = tx._batch((queries, (owned, shared))).await?;
let (res, _) = tx._batch((queries, shared)).await?;
for op in _ops {
self.tx.send(SyncMessage::Created(op)).ok();
@ -111,21 +96,6 @@ impl SyncManager {
query: Q,
) -> prisma_client_rust::Result<<Q as prisma_client_rust::BatchItemParent>::ReturnValue> {
let ret = match &op.typ {
CRDTOperationType::Owned(owned_op) => {
tx._batch((
tx.owned_operation().create(
op.id.as_bytes().to_vec(),
op.timestamp.0 as i64,
to_vec(&owned_op.items).unwrap(),
owned_op.model.clone(),
node::pub_id::equals(op.node.as_bytes().to_vec()),
vec![],
),
query,
))
.await?
.1
}
CRDTOperationType::Shared(shared_op) => {
let kind = match &shared_op.data {
SharedOperationData::Create(_) => "c",
@ -200,23 +170,29 @@ impl SyncManager {
match ModelSyncData::from_op(op.typ.clone()).unwrap() {
ModelSyncData::FilePath(id, shared_op) => match shared_op {
SharedOperationData::Create(SharedOperationCreateData::Unique(data)) => {
SharedOperationData::Create(data) => {
let data: Vec<_> = data
.into_iter()
.flat_map(|(k, v)| file_path::SetParam::deserialize(&k, v))
.collect();
db.file_path()
.create(
id.pub_id,
data.into_iter()
.flat_map(|(k, v)| file_path::SetParam::deserialize(&k, v))
.collect(),
.upsert(
file_path::pub_id::equals(id.pub_id.clone()),
file_path::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
}
SharedOperationData::Update { field, value } => {
self.db
.file_path()
.update(
file_path::pub_id::equals(id.pub_id),
vec![file_path::SetParam::deserialize(&field, value).unwrap()],
let data = vec![file_path::SetParam::deserialize(&field, value).unwrap()];
db.file_path()
.upsert(
file_path::pub_id::equals(id.pub_id.clone()),
file_path::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
@ -224,13 +200,29 @@ impl SyncManager {
_ => todo!(),
},
ModelSyncData::Location(id, shared_op) => match shared_op {
SharedOperationData::Create(SharedOperationCreateData::Unique(data)) => {
SharedOperationData::Create(data) => {
let data: Vec<_> = data
.into_iter()
.flat_map(|(k, v)| location::SetParam::deserialize(&k, v))
.collect();
db.location()
.create(
id.pub_id,
data.into_iter()
.flat_map(|(k, v)| location::SetParam::deserialize(&k, v))
.collect(),
.upsert(
location::pub_id::equals(id.pub_id.clone()),
location::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
}
SharedOperationData::Update { field, value } => {
let data = vec![location::SetParam::deserialize(&field, value).unwrap()];
db.location()
.upsert(
location::pub_id::equals(id.pub_id.clone()),
location::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
@ -238,22 +230,29 @@ impl SyncManager {
_ => todo!(),
},
ModelSyncData::Object(id, shared_op) => match shared_op {
SharedOperationData::Create(_) => {
SharedOperationData::Create(data) => {
let data: Vec<_> = data
.into_iter()
.flat_map(|(k, v)| object::SetParam::deserialize(&k, v))
.collect();
db.object()
.upsert(
object::pub_id::equals(id.pub_id.clone()),
object::create(id.pub_id, vec![]),
vec![],
data,
)
.exec()
.await
.ok();
.await?;
}
SharedOperationData::Update { field, value } => {
let data = vec![object::SetParam::deserialize(&field, value).unwrap()];
db.object()
.update(
object::pub_id::equals(id.pub_id),
vec![object::SetParam::deserialize(&field, value).unwrap()],
.upsert(
object::pub_id::equals(id.pub_id.clone()),
object::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
@ -261,28 +260,29 @@ impl SyncManager {
_ => todo!(),
},
ModelSyncData::Tag(id, shared_op) => match shared_op {
SharedOperationData::Create(create_data) => match create_data {
SharedOperationCreateData::Unique(create_data) => {
db.tag()
.create(
id.pub_id,
create_data
.into_iter()
.flat_map(|(field, value)| {
tag::SetParam::deserialize(&field, value)
})
.collect(),
)
.exec()
.await?;
}
_ => unreachable!(),
},
SharedOperationData::Update { field, value } => {
SharedOperationData::Create(data) => {
let data: Vec<_> = data
.into_iter()
.flat_map(|(field, value)| tag::SetParam::deserialize(&field, value))
.collect();
db.tag()
.update(
tag::pub_id::equals(id.pub_id),
vec![tag::SetParam::deserialize(&field, value).unwrap()],
.upsert(
tag::pub_id::equals(id.pub_id.clone()),
tag::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
}
SharedOperationData::Update { field, value } => {
let data = vec![tag::SetParam::deserialize(&field, value).unwrap()];
db.tag()
.upsert(
tag::pub_id::equals(id.pub_id.clone()),
tag::create(id.pub_id, data.clone()),
data,
)
.exec()
.await?;
@ -335,91 +335,6 @@ impl SyncManager {
}
}
pub fn owned_create<
const SIZE: usize,
TSyncId: SyncId<ModelTypes = TModel>,
TModel: SyncType<Marker = OwnedSyncType>,
>(
&self,
id: TSyncId,
values: [(&'static str, Value); SIZE],
) -> CRDTOperation {
self.new_op(CRDTOperationType::Owned(OwnedOperation {
model: TModel::MODEL.to_string(),
items: [(id, values)]
.into_iter()
.map(|(id, data)| OwnedOperationItem {
id: json!(id),
data: OwnedOperationData::Create(
data.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
),
})
.collect(),
}))
}
pub fn owned_create_many<
const SIZE: usize,
TSyncId: SyncId<ModelTypes = TModel>,
TModel: SyncType<Marker = OwnedSyncType>,
>(
&self,
data: impl IntoIterator<Item = (TSyncId, [(&'static str, Value); SIZE])>,
skip_duplicates: bool,
) -> CRDTOperation {
self.new_op(CRDTOperationType::Owned(OwnedOperation {
model: TModel::MODEL.to_string(),
items: vec![OwnedOperationItem {
id: Value::Null,
data: OwnedOperationData::CreateMany {
values: data
.into_iter()
.map(|(id, data)| {
(
json!(id),
data.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
)
})
.collect(),
skip_duplicates,
},
}],
}))
}
pub fn owned_update<
TSyncId: SyncId<ModelTypes = TModel>,
TModel: SyncType<Marker = OwnedSyncType>,
>(
&self,
id: TSyncId,
values: impl IntoIterator<Item = (&'static str, Value)>,
) -> CRDTOperation {
self.new_op(CRDTOperationType::Owned(OwnedOperation {
model: TModel::MODEL.to_string(),
items: [(id, values)]
.into_iter()
.map(|(id, data)| OwnedOperationItem {
id: json!(id),
data: OwnedOperationData::Update(
data.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
),
})
.collect(),
}))
}
pub fn shared_create<
TSyncId: SyncId<ModelTypes = TModel>,
TModel: SyncType<Marker = SharedSyncType>,
>(
&self,
id: TSyncId,
) -> CRDTOperation {
self.new_op(CRDTOperationType::Shared(SharedOperation {
model: TModel::MODEL.to_string(),
record_id: json!(id),
data: SharedOperationData::Create(SharedOperationCreateData::Atomic),
}))
}
pub fn unique_shared_create<
TSyncId: SyncId<ModelTypes = TModel>,
TModel: SyncType<Marker = SharedSyncType>,
@ -431,12 +346,12 @@ impl SyncManager {
self.new_op(CRDTOperationType::Shared(SharedOperation {
model: TModel::MODEL.to_string(),
record_id: json!(id),
data: SharedOperationData::Create(SharedOperationCreateData::Unique(
data: SharedOperationData::Create(
values
.into_iter()
.map(|(name, value)| (name.to_string(), value))
.collect(),
)),
),
}))
}
pub fn shared_update<

View file

@ -1,38 +0,0 @@
use crate::prelude::*;
pub fn r#struct(datamodel: &dml::Datamodel) -> TokenStream {
let model_action_fns = datamodel.models.iter().map(|model| {
let model_name_snake = snake_ident(&model.name);
let model_actions_struct = quote!(super::#model_name_snake::Actions);
quote! {
pub fn #model_name_snake(&self) -> #model_actions_struct {
#model_actions_struct { client: self }
}
}
});
quote! {
pub struct PrismaCRDTClient {
pub(super) client: #PRISMA::PrismaClient,
pub node_id: Vec<u8>,
operation_sender: #MPSC::Sender<#SYNC::CRDTOperation>
}
impl PrismaCRDTClient {
pub(super) fn _new(
client: #PRISMA::PrismaClient,
node_id: Vec<u8>,
operation_sender: #MPSC::Sender<#SYNC::CRDTOperation>
) -> Self {
Self {
client,
node_id,
operation_sender,
}
}
#(#model_action_fns)*
}
}
}

View file

@ -1,186 +0,0 @@
use std::ops::Deref;
use crate::prelude::*;
#[derive(Debug)]
pub struct Field<'a> {
pub prisma: &'a dml::Field,
pub model: &'a str,
pub typ: FieldType<'a>,
}
impl<'a> Field<'a> {
pub fn new(field: &'a dml::Field, model: &'a str, datamodel: &'a dml::Datamodel) -> Field<'a> {
let typ = FieldType::new(field, model, datamodel);
Self {
prisma: field,
model,
typ,
}
}
/// Returns the token representation of the field's type,
/// accounting for a sync ID reference if it is a field
/// of a relation
pub fn crdt_type_tokens(&self, datamodel: &Datamodel) -> TokenStream {
let relation_field_info = match &self.typ {
FieldType::Scalar {
relation_field_info,
} => relation_field_info,
_ => unreachable!("Cannot get CRDT type for non-scalar field"),
};
match relation_field_info.as_ref() {
Some(relation_field_info) => {
let relation_model = datamodel
.model(relation_field_info.referenced_model)
.unwrap();
let sync_id_field =
relation_model.sync_id_for_pk(relation_field_info.referenced_field);
match sync_id_field {
Some(field) => {
let relation_field_type = field.field_type().to_tokens();
match self.arity() {
dml::FieldArity::Required => relation_field_type,
dml::FieldArity::Optional => quote!(Option<#relation_field_type>),
dml::FieldArity::List => quote!(Vec<#relation_field_type>),
}
}
None => self.type_tokens(),
}
}
None => datamodel
.model(self.model)
.unwrap()
.sync_id_for_pk(self.name())
.unwrap_or(self)
.type_tokens(),
}
}
}
impl<'a> Deref for Field<'a> {
type Target = dml::Field;
fn deref(&self) -> &Self::Target {
self.prisma
}
}
#[derive(Debug)]
pub enum FieldType<'a> {
Scalar {
/// The relation field that this scalar field is a part of.
relation_field_info: Option<RelationFieldInfo<'a>>,
},
Relation {
relation_info: RelationInfo<'a>,
},
}
impl<'a> FieldType<'a> {
fn new(field: &'a dml::Field, model: &str, datamodel: &'a dml::Datamodel) -> Self {
match field.field_type() {
dml::FieldType::Scalar(_, _, _) => FieldType::Scalar {
relation_field_info: {
datamodel
.find_model(model)
.unwrap()
.fields()
.find_map(|relation_field| {
relation_field
.as_relation_field()
.and_then(|relation_field_data| {
relation_field_data
.relation_info
.fields
.iter()
.position(|rf_name| rf_name == field.name())
.map(|pos| (relation_field_data, pos))
})
.and_then(|(relation_field_data, i)| {
datamodel
.models()
.find(|relation_model| {
relation_model.name
== relation_field_data.relation_info.to
})
.and_then(|relation_model| {
relation_model
.fields()
.find(|referenced_field| {
referenced_field.name()
== relation_field_data
.relation_info
.references[i]
})
.map(|f| (relation_model, f))
})
})
.map(|(ref_model, ref_field)| {
(relation_field.name(), &ref_model.name, ref_field.name())
})
})
.map(|(rel, ref_model, ref_field)| {
RelationFieldInfo::new(rel, ref_model, ref_field)
})
},
},
dml::FieldType::Relation(_) => FieldType::Relation {
relation_info: {
field
.as_relation_field()
.filter(|rf| !OPERATION_MODELS.contains(&rf.relation_info.to.as_str()))
.map(|rf| {
RelationInfo::new(
&rf.relation_info.to,
&rf.relation_info.fields,
&rf.relation_info.references,
)
})
.unwrap()
},
},
t => unimplemented!("Unsupported field type: {:?}", t),
}
}
}
#[derive(Debug)]
pub struct RelationFieldInfo<'a> {
/// Field on the same model that represents the relation
pub relation: &'a str,
pub referenced_model: &'a str,
/// Scalar field on the referenced model that matches the scalar on the same model
pub referenced_field: &'a str,
}
impl<'a> RelationFieldInfo<'a> {
pub fn new(relation: &'a str, referenced_model: &'a str, referenced_field: &'a str) -> Self {
Self {
relation,
referenced_model,
referenced_field,
}
}
}
#[derive(Debug)]
pub struct RelationInfo<'a> {
pub to: &'a str,
pub fields: &'a Vec<String>,
pub references: &'a Vec<String>,
}
impl<'a> RelationInfo<'a> {
pub fn new(to: &'a str, fields: &'a Vec<String>, references: &'a Vec<String>) -> Self {
Self {
to,
fields,
references,
}
}
}

View file

@ -1,39 +0,0 @@
mod field;
mod model;
pub use field::*;
pub use model::*;
use crate::prelude::*;
pub const OPERATION_MODELS: &[&str] = &["OwnedOperation", "SharedOperation", "RelationOperation"];
pub struct Datamodel<'a> {
pub prisma: &'a dml::Datamodel,
pub models: Vec<Model<'a>>,
}
impl<'a> Datamodel<'a> {
pub fn model(&self, name: &str) -> Option<&'a Model> {
self.models.iter().find(|m| m.name == name)
}
}
impl<'a> TryFrom<&'a dml::Datamodel> for Datamodel<'a> {
type Error = String;
fn try_from(datamodel: &'a dml::Datamodel) -> Result<Self, Self::Error> {
let models = datamodel
.models
.iter()
.filter(|m| !OPERATION_MODELS.contains(&m.name.as_str()))
.map(|m| Model::new(m, datamodel))
.collect::<Result<Vec<_>, _>>()?;
let datamodel = Self {
prisma: datamodel,
models,
};
Ok(datamodel)
}
}

View file

@ -1,314 +0,0 @@
use std::{ops::Deref, str::FromStr};
use crate::attribute::{Attribute, AttributeFieldValue};
use crate::prelude::*;
#[derive(Debug)]
pub struct Model<'a> {
pub prisma: &'a dml::Model,
pub typ: ModelType,
pub fields: Vec<Field<'a>>,
}
impl<'a> Model<'a> {
pub fn new(model: &'a dml::Model, datamodel: &'a dml::Datamodel) -> Result<Self, String> {
let crdt_attribute = model
.documentation
.as_ref()
.map(Attribute::parse)
.map(Result::unwrap)
.unwrap();
let fields = model
.fields()
.filter(|f| {
f.as_relation_field()
.filter(|rf| OPERATION_MODELS.contains(&rf.relation_info.to.as_str()))
.is_none()
})
.map(|f| Field::new(f, &model.name, datamodel))
.collect::<Vec<_>>();
let typ = ModelType::from_attribute(&crdt_attribute, &fields, model)?;
let model = Self {
prisma: model,
typ,
fields,
};
Ok(model)
}
pub fn field(&self, name: &str) -> Option<&Field<'a>> {
self.fields.iter().find(|f| f.name() == name)
}
pub fn is_sync_id(&self, field: &str) -> bool {
match &self.typ {
ModelType::Local { id } => id.is_sync_id(field),
ModelType::Owned { id, .. } => id.is_sync_id(field),
ModelType::Shared { id, .. } => id.is_sync_id(field),
ModelType::Relation { item, group } => {
item.is_sync_id(field) || group.is_sync_id(field)
}
}
}
pub fn is_pk(&self, field: &str) -> bool {
self.primary_key
.as_ref()
.unwrap()
.fields
.iter()
.any(|f| f.name == field)
}
pub fn sync_id_for_pk(&self, primary_key: &str) -> Option<&Field<'a>> {
let pk_index = self
.primary_key
.as_ref()
.unwrap()
.fields
.iter()
.position(|f| f.name == primary_key);
pk_index
.and_then(|pk_index| match &self.typ {
ModelType::Local { id } => id.at_index(pk_index),
ModelType::Owned { id, .. } => id.at_index(pk_index),
ModelType::Shared { id, .. } => id.at_index(pk_index),
ModelType::Relation { item, group } => {
item.at_index(0).or_else(|| group.at_index(0))
}
})
.and_then(|f| self.field(f))
}
/// Gets the scalar sync id fields for a model, along with the (possibly) foreign field
/// that their types should be resolved from.
///
/// For example, a scalar field will have no difference between the first and second element.
/// A relation, however, will result in the first element being the model's scalar field,
/// and the second element being the foreign scalar field. It is important to note that these foreign
/// fields could be primary keys that map to sync ids, and this should be checked.
pub fn scalar_sync_id_fields(
&'a self,
datamodel: &'a Datamodel,
) -> impl Iterator<Item = (&'a Field, &'a Field)> {
self.fields
.iter()
.filter(|f| self.is_sync_id(f.name()))
.flat_map(|field| match &field.typ {
FieldType::Scalar { .. } => {
vec![(field, field)]
}
FieldType::Relation { relation_info } => relation_info
.fields
.iter()
.enumerate()
.map(|(i, field)| {
let relation_model = datamodel.model(relation_info.to).unwrap();
// Scalar field on the relation model. Could be a local id,
// so crdt type must be used
let referenced_field =
relation_model.field(&relation_info.references[i]).unwrap();
(self.field(field).unwrap(), referenced_field)
})
.collect(),
})
}
}
impl<'a> Deref for Model<'a> {
type Target = dml::Model;
fn deref(&self) -> &Self::Target {
self.prisma
}
}
#[derive(Debug)]
pub enum ModelType {
Local {
id: SyncIDMapping,
},
Owned {
owner: String,
id: SyncIDMapping,
},
Shared {
id: SyncIDMapping,
create: SharedCreateType,
},
Relation {
item: SyncIDMapping,
group: SyncIDMapping,
},
}
impl ModelType {
pub fn from_attribute(
attribute: &Attribute,
fields: &[Field],
model: &dml::Model,
) -> Result<Self, String> {
let ret = match attribute.name {
"local" => {
let id = SyncIDMapping::from_attribute(attribute.field("id"), fields, model)?;
ModelType::Local { id }
}
"owned" => {
let id = SyncIDMapping::from_attribute(attribute.field("id"), fields, model)?;
let owner = attribute
.field("owner")
.ok_or_else(|| "Missing owner field".to_string())
.map(|owner| owner.as_single().expect("Owner field must be a string"))
.and_then(|owner| {
fields
.iter()
.find(|f| f.name() == owner)
.map(|f| f.name().to_string())
.ok_or(format!("Unknown owner field {}", owner))
})?;
ModelType::Owned { id, owner }
}
"shared" => {
let id = SyncIDMapping::from_attribute(attribute.field("id"), fields, model)?;
let create = attribute
.field("create")
.map(|create| create.as_single().expect("create field must be a string"))
.map(SharedCreateType::from_str)
.unwrap_or(Ok(SharedCreateType::Unique))?;
ModelType::Shared { id, create }
}
"relation" => {
let item = SyncIDMapping::from_attribute(
Some(
attribute
.field("item")
.expect("@relation attribute missing `item` field"),
),
fields,
model,
)?;
let group = SyncIDMapping::from_attribute(
Some(
attribute
.field("group")
.expect("@relation attribute missing `group` field"),
),
fields,
model,
)?;
ModelType::Relation { item, group }
}
name => Err(format!("Invalid attribute type {name}"))?,
};
Ok(ret)
}
}
#[derive(Debug)]
pub enum SyncIDMapping {
Single(String),
Compound(Vec<String>),
}
impl SyncIDMapping {
pub fn from_attribute(
attr_value: Option<&AttributeFieldValue>,
fields: &[Field],
model: &dml::Model,
) -> Result<Self, String> {
let primary_key = model
.primary_key
.as_ref()
.ok_or(format!("Model {} has no primary key", model.name))?;
attr_value
.map(|attr_value| match attr_value {
AttributeFieldValue::Single(field) => {
fields
.iter()
.find(|f| f.name() == *field)
.ok_or(format!("Unknown field {}", field))?;
Ok(SyncIDMapping::Single(field.to_string()))
}
AttributeFieldValue::List(field_list) => {
if primary_key.fields.len() != field_list.len() {
return Err(format!(
"Sync ID for model {} has inconsistent number of fields",
model.name,
));
}
field_list
.iter()
.map(|name| {
fields
.iter()
.find(|f| f.name() == *name)
.map(|f| f.name().to_string())
})
.collect::<Option<_>>()
.map(SyncIDMapping::Compound)
.ok_or(format!("Invalid sync ID for model {}", model.name))
}
})
.unwrap_or_else(|| {
Ok(match primary_key.fields.len() {
1 => SyncIDMapping::Single(primary_key.fields[0].name.to_string()),
_ => SyncIDMapping::Compound(
primary_key
.fields
.iter()
.map(|f| f.name.to_string())
.collect(),
),
})
})
}
pub fn is_sync_id(&self, field: &str) -> bool {
match self {
Self::Single(v) => field == v,
Self::Compound(mappings) => mappings.iter().any(|v| field == v),
}
}
pub fn at_index(&self, i: usize) -> Option<&str> {
match self {
Self::Single(v) => Some(v),
Self::Compound(mappings) => mappings.get(i).map(|v| v.as_str()),
}
}
}
#[derive(Debug)]
pub enum SharedCreateType {
Unique,
Atomic,
}
impl FromStr for SharedCreateType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let ret = match s {
"Unique" => SharedCreateType::Unique,
"Atomic" => SharedCreateType::Atomic,
s => Err(format!("Invalid create type {}", s))?,
};
Ok(ret)
}
}

View file

@ -1,182 +0,0 @@
use crate::generator::prelude::*;
/// Generates the `_create_operation` function for the CRDT client
fn create_operation_fn() -> TokenStream {
quote! {
pub async fn _create_operation(&self, typ: ::prisma_crdt::CRDTOperationType) {
let timestamp = ::uhlc::NTP64(0); // TODO: actual timestamps
let timestamp_bytes = vec![0];
match &typ {
::prisma_crdt::CRDTOperationType::Shared(::prisma_crdt::SharedOperation {
record_id,
model,
data,
}) => {
let (kind, data) = match data {
::prisma_crdt::SharedOperationData::Create(typ) => {
("c".to_string(), ::serde_json::to_vec(typ).unwrap())
}
::prisma_crdt::SharedOperationData::Update { field, value } => {
("u".to_string() + field, ::serde_json::to_vec(value).unwrap())
}
::prisma_crdt::SharedOperationData::Delete => ("d".to_string(), vec![]),
};
self.client
.shared_operation()
.create(
timestamp_bytes,
::serde_json::to_vec(&record_id).unwrap(),
kind,
model.to_string(),
data,
crate::prisma::node::local_id::equals(self.node_local_id),
vec![],
)
.exec()
.await;
}
::prisma_crdt::CRDTOperationType::Owned(op) => {
self.client
.owned_operation()
.create(
timestamp_bytes,
::serde_json::to_vec(op).unwrap(),
crate::prisma::node::local_id::equals(self.node_local_id),
vec![],
)
.exec()
.await;
}
::prisma_crdt::CRDTOperationType::Relation(::prisma_crdt::RelationOperation {
relation,
relation_item,
relation_group,
data,
}) => {
let (kind, data) = match data {
::prisma_crdt::RelationOperationData::Create => ("c".to_string(), vec![]),
::prisma_crdt::RelationOperationData::Update { field, value } => {
("u".to_string() + field, ::serde_json::to_vec(value).unwrap())
}
::prisma_crdt::RelationOperationData::Delete => ("d".to_string(), vec![]),
};
self.client
.relation_operation()
.create(
timestamp_bytes,
relation.to_string(),
::serde_json::to_vec(&relation_item).unwrap(),
::serde_json::to_vec(&relation_group).unwrap(),
kind,
data,
crate::prisma::node::local_id::equals(self.node_local_id),
vec![],
)
.exec()
.await;
}
}
let op = ::prisma_crdt::CRDTOperation::new(self.node_id.clone(), timestamp, typ);
self.operation_sender.send(op).await;
}
}
}
/// Generates action getters for each model for the CRDT Client
fn actions_accessors(datamodel: DatamodelRef) -> Vec<TokenStream> {
datamodel
.models
.iter()
.map(|model| {
let name_snake = snake_ident(&model.name);
match &model.typ {
ModelType::Local { .. } => quote! {
pub fn #name_snake(&self) -> crate::prisma::#name_snake::Actions {
self.client.#name_snake()
}
},
_ => quote! {
pub fn #name_snake(&self) -> super::#name_snake::Actions {
super::#name_snake::Actions::new(self)
}
},
}
})
.collect()
}
/// Generates the `_prisma` module and its `PrismaCRDTClient` struct
///
/// ## Example
///
/// ```
/// mod _prisma {
/// pub struct PrismaCRDTClient {
/// pub(super) client: crate::prisma::PrismaClient,
/// pub node_id: Vec<u8>,
/// pub node_local_id: i32,
/// operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>
/// }
///
/// impl PrismaCRDTClient {
/// pub(super) fn _new(
/// client: crate::prisma::PrismaClient,
/// (node_id, node_local_id): Vec<u8, i32>,
/// operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>
/// ) -> Self {
/// Self {
/// client,
/// operation_sender,
/// node_id,
/// node_local_id
/// }
/// }
///
/// pub async fn _create_operation(..) { .. }
///
/// ..
/// }
/// }
/// ```
pub fn generate(datamodel: DatamodelRef) -> TokenStream {
let create_operation_fn = create_operation_fn();
let actions_accessors = actions_accessors(datamodel);
quote! {
mod _prisma {
pub struct PrismaCRDTClient {
pub(super) client: crate::prisma::PrismaClient,
pub node_id: Vec<u8>,
pub node_local_id: i32,
operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>,
}
impl PrismaCRDTClient {
pub(super) fn _new(
client: crate::prisma::PrismaClient,
(node_id, node_local_id): (Vec<u8>, i32),
operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>,
) -> Self {
Self {
client,
operation_sender,
node_id,
node_local_id,
}
}
#create_operation_fn
#(#actions_accessors)*
}
}
}
}

View file

@ -1,55 +0,0 @@
mod client;
mod model;
use super::prelude::*;
use super::*;
#[derive(Deserialize)]
pub struct PrismaCRDTGenerator {}
impl PrismaGenerator for PrismaCRDTGenerator {
const NAME: &'static str = "Prisma CRDT Generator";
const DEFAULT_OUTPUT: &'static str = "./prisma-crdt.rs";
fn generate(self, args: GenerateArgs) -> String {
let datamodel =
datamodel::Datamodel::try_from(&args.dml).expect("Failed to construct datamodel");
let datamodel_ref = prelude::DatamodelRef(&datamodel);
let header = quote! {
#![allow(clippy::all)]
pub async fn new_client(
prisma_client: crate::prisma::PrismaClient,
node_id: Vec<u8>,
node_local_id: i32
) -> (
_prisma::PrismaCRDTClient,
::tokio::sync::mpsc::Receiver<::prisma_crdt::CRDTOperation>,
) {
let (tx, rx) = ::tokio::sync::mpsc::channel(64);
let crdt_client = _prisma::PrismaCRDTClient::_new(prisma_client, (node_id, node_local_id), tx);
(crdt_client, rx)
}
pub use _prisma::*;
};
let client = client::generate(datamodel_ref);
let models = datamodel
.models
.iter()
.map(|model| model::generate(ModelRef::new(model, datamodel_ref)));
let output = quote! {
#header
#(#models)*
#client
};
output.to_string()
}
}

View file

@ -1,90 +0,0 @@
use crate::generator::prelude::*;
use super::create;
/// Generates struct definition for a model's `Actions` struct
///
/// ## Example
///
/// ```
/// pub struct Actions<'a> {
/// client: &'a super::_prisma::PrismaCRDTClient
/// }
///
/// impl<'a> Actions<'a> {
/// pub(super) fn new(client: &'a super::_prisma::PrismaCRDTClient) -> Self {
/// Self { client }
/// }
///
/// pub fn create(..) {
/// ..
/// }
///
/// pub fn find_unique(
/// self,
/// param: crate::prisma::#model::UniqueWhereParam
/// ) -> crate::prisma::#model::FindUnique<'a> {
/// self.client.client.#model().find_unique(param)
/// }
///
/// pub fn find_many(
/// self,
/// params: Vec<crate::prisma::#model::WhereParam>
/// ) -> crate::prisma::#model::FindMany<'a> {
/// self.client.client.#model().find_many(params)
/// }
/// }
/// ```
pub fn definition(model: ModelRef) -> TokenStream {
let name = snake_ident(&model.name);
let create_fn = create::action_method(model);
quote! {
pub struct Actions<'a> {
client: &'a super::_prisma::PrismaCRDTClient,
}
impl<'a> Actions<'a> {
pub(super) fn new(client: &'a super::_prisma::PrismaCRDTClient) -> Self {
Self { client }
}
#create_fn
pub fn find_unique(
self,
param: crate::prisma::#name::UniqueWhereParam,
) -> crate::prisma::#name::FindUnique<'a> {
self.client.client.#name().find_unique(param)
}
pub fn find_many(
self,
params: Vec<crate::prisma::#name::WhereParam>,
) -> crate::prisma::#name::FindMany<'a> {
self.client.client.#name().find_many(params)
}
pub fn update(
self,
_where: crate::prisma::#name::UniqueWhereParam,
set_params: Vec<SetParam>,
) -> Update<'a> {
Update {
client: self.client,
where_param: _where,
set_params,
}
}
// pub fn delete(self, param: crate::prisma::#name::UniqueWhereParam) -> Delete<'a> {
// Delete {
// client: self.client,
// r#where: param,
// with_params: vec![],
// }
// }
}
}
}

View file

@ -1,186 +0,0 @@
use crate::generator::prelude::*;
use super::{create_params, owned, relation, shared};
/// Generates a call to the underlying Prisma client's `create` method for
/// the given model
///
/// ## Example
///
/// ```
/// self
/// .crdt_client
/// .client
/// .user()
/// .create(
/// self.set_params.name.clone(),
/// self.set_params.profile_id.clone),
/// self.set_params._params.clone().into_iter().map(Into::into).collect()
/// )
/// .exec()
/// .await?
/// ```
pub fn prisma_create(model: ModelRef) -> TokenStream {
let model_name = snake_ident(&model.name);
let create_args = model
.fields
.iter()
.filter(|f| {
f.required_on_create()
&& f.as_scalar_field()
.map(|sf| !model.scalar_field_has_relation(sf))
.unwrap_or(true)
})
.map(|field| {
let field_name_snake = snake_ident(field.name());
match &field.typ {
FieldType::Relation {relation_info} => {
let relation_model_snake = snake_ident(relation_info.to);
if relation_info.fields.len() == 1 {
let relation_field_snake = snake_ident(&relation_info.fields[0]);
let referenced_field_snake = snake_ident(&relation_info.references[0]);
quote!(crate::prisma::#relation_model_snake::#referenced_field_snake::equals(self.set_params.#relation_field_snake.clone()))
} else {
todo!()
}
},
_ => quote!(self.set_params.#field_name_snake.clone()),
}
});
quote! {
self
.crdt_client
.client
.#model_name()
.create(
#(#create_args,)*
self.set_params._params.clone().into_iter().map(Into::into).collect(),
)
.exec()
.await?;
}
}
/// Generates the definition for a model's `Create` struct
///
/// ## Example
///
/// ```
/// pub struct Create<'a> {
/// crdt_client: &'a super::_prisma::PrismaCRDTClient,
/// set_params: CreateParams,
/// with_params: Vec<crate::prisma::#model_name_snake::WithParam>
/// }
///
/// impl<'a> Create<'a> {
/// pub(super) fn new(
/// crdt_client: &'a super::_prisma::PrismaCRDTClient,
/// set_params: CreateParams,
/// with_params: Vec<crate::prisma::#model_name_snake::WithParam>
/// )
///
/// ..
///
/// pub async fn exec(
/// self
/// ) -> Result<crate::prisma::#model_name_snake::WithParam, crate::prisma::QueryError> {
/// let res = self
/// .crdt_client
/// .client
/// .#model_name_snake()
/// .create(
/// ..
/// )
/// .exec()
/// .await?;
///
/// ..
///
/// Ok(res)
/// }
/// }
/// ```
pub fn struct_definition(model: ModelRef) -> TokenStream {
let model_name_snake = snake_ident(&model.name);
let create_call = prisma_create(model);
let exec_body = match model.typ {
ModelType::Owned { .. } => owned::create_exec_body(model),
ModelType::Shared { .. } => shared::create_exec_body(model),
ModelType::Relation { .. } => relation::create_exec_body(model),
// SAFETY: Local models don't have method overrides
ModelType::Local { .. } => unreachable!(),
};
quote! {
pub struct Create<'a> {
crdt_client: &'a super::_prisma::PrismaCRDTClient,
set_params: CreateParams,
with_params: Vec<crate::prisma::#model_name_snake::WithParam>,
}
impl<'a> Create<'a> {
pub(super) fn new(
crdt_client: &'a super::_prisma::PrismaCRDTClient,
set_params: CreateParams,
with_params: Vec<crate::prisma::#model_name_snake::WithParam>,
) -> Self {
Self {
crdt_client,
set_params,
with_params,
}
}
pub fn with(mut self, param: impl Into<crate::prisma::#model_name_snake::WithParam>) -> Self {
self.with_params.push(param.into());
self
}
pub async fn exec(
self,
) -> Result<crate::prisma::#model_name_snake::Data, crate::prisma::QueryError> {
let res = #create_call;
#exec_body
Ok(res)
}
}
}
}
/// Generates a model's `Actions::create` method
///
/// ## Example
///
/// ```
/// pub fn create(self, name: String, profile_id: i32, _params: Vec<SetParam>) -> Create<'a> {
/// Create::new(
/// self.client,
/// CreateParams { .. },
/// vec![]
/// )
/// }
/// ```
pub fn action_method(model: ModelRef) -> TokenStream {
let args = create_params::args(model, Some(quote!(super)));
let create_params_constructor = create_params::constructor(model);
quote! {
pub fn create(self, #(#args),*) -> Create<'a> {
Create::new(
self.client,
#create_params_constructor,
vec![]
)
}
}
}

View file

@ -1,203 +0,0 @@
use crate::generator::prelude::*;
use super::sync_id;
/// Generates definitions for a model's `CreateParams` and `CRDTCreateParams` structs
///
/// ## Example
///
/// ```
/// #[derive(Clone)]
/// pub struct CreateParams {
/// pub _params: Vec<SetParam>,
/// pub name: String,
/// pub profile_id: i32
/// }
///
/// #[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
/// pub struct CRDTCreateParams {
/// #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "_")]
/// pub _params: Vec<CRDTSetParam>,
/// #[serde(flatten)]
/// pub _sync_id: SyncID,
/// pub name: String,
/// pub profile_id: Vec<u8>
/// }
/// ```
pub fn definition(model: ModelRef) -> TokenStream {
let required_scalar_fields = model.required_scalar_fields();
let required_create_params = required_scalar_fields.iter().map(|field| {
let field_name_snake = snake_ident(field.name());
let field_type = match field.field_type() {
dml::FieldType::Scalar(_, _, _) => field.type_tokens(),
dml::FieldType::Enum(e) => {
let enum_name_pascal = pascal_ident(&e);
quote!(super::#enum_name_pascal)
}
_ => todo!(),
};
quote!(#field_name_snake: #field_type)
});
let mut scalar_sync_id_fields = model.scalar_sync_id_fields(&model.datamodel);
let required_crdt_create_params = required_scalar_fields
.iter()
.filter(|f| !scalar_sync_id_fields.any(|sf| sf.0.name() == f.name()))
.map(|field| {
let field_type = field.crdt_type_tokens(&model.datamodel);
let field_name_snake = snake_ident(field.name());
quote!(#field_name_snake: #field_type)
});
quote! {
#[derive(Clone)]
pub struct CreateParams {
pub _params: Vec<SetParam>,
#(pub #required_create_params),*
}
#[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct CRDTCreateParams {
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "_")]
pub _params: Vec<CRDTSetParam>,
#[serde(flatten)]
pub _sync_id: SyncID,
#(pub #required_crdt_create_params),*
}
}
}
/// Generates a list of a model's `CreateParams` as function arguments
///
/// ## Example
///
/// ```
/// name: String, profile_id: i32, _params: Vec<SetParam>
/// ```
pub fn args(model: ModelRef, namespace: Option<TokenStream>) -> Vec<TokenStream> {
let mut required_args = model
.required_scalar_fields()
.into_iter()
.map(|field| {
let field_name_snake = snake_ident(field.name());
let typ = match &field.field_type() {
dml::FieldType::Scalar(_, _, _) => field.type_tokens(),
dml::FieldType::Enum(e) => {
let enum_name_pascal = pascal_ident(e);
quote!(#(#namespace::)super::#enum_name_pascal)
}
_ => unreachable!(),
};
quote!(#field_name_snake: #typ)
})
.collect::<Vec<_>>();
required_args.push(quote!(_params: Vec<SetParam>));
required_args
}
/// Generates a constructor for the `CreateParams` struct
/// that assumes all required fields have been declared beforehand.
///
/// ## Example
///
/// ```
/// CreateParams {
/// name,
/// profile_id,
/// _params
/// }
/// ```
pub fn constructor(model: ModelRef) -> TokenStream {
let required_args = model
.required_scalar_fields()
.into_iter()
.map(|field| snake_ident(field.name()));
quote! {
CreateParams {
#(#required_args,)*
_params
}
}
}
/// Generates a constructor for the CRDTCreateParams struct.
/// Assumes all required fields are in scope.
///
/// ## Example
///
/// ```
/// CRDTCreateParams {
/// _param: {
/// let mut params = vec![];
///
/// for _param in self.set_params._params {
/// params.push(_param.into_crdt(&self.crdt_client).await);
/// }
///
/// params
/// },
/// _sync_id: sync_id.clone(),
/// name: self.set_params.name,
/// profile_id: self
/// .crdt_client
/// .client
/// .profile()
/// .find_unique(crate::prisma::profile::local_id::equals(self.set_params.profile_id))
/// .exec()
/// .await
/// .unwrap()
/// .unwrap()
/// .local_id
/// }
/// ```
pub fn crdt_constructor(model: ModelRef) -> TokenStream {
let crdt_create_params = model
.fields()
.into_iter()
.filter(|f| {
f.is_scalar_field()
&& f.required_on_create()
&& model
.scalar_sync_id_fields(&model.datamodel)
.all(|(sf, _)| sf.name() != f.name())
})
.map(|field| {
let field_name_snake = snake_ident(field.name());
let value = sync_id::scalar_field_to_crdt(
field,
quote!(self.crdt_client.client),
quote!(self.set_params.#field_name_snake),
);
quote!(#field_name_snake: #value)
});
quote! {
CRDTCreateParams {
_params: {
let mut params = vec![];
for _param in self.set_params._params {
params.push(_param.into_crdt(&self.crdt_client).await);
}
params
},
_sync_id: sync_id.clone(),
#(#crdt_create_params,)*
};
}
}

View file

@ -1,24 +0,0 @@
// use crate::generator::prelude::*;
// pub fn generate(model: &Model) -> TokenStream {
// let model_name = snake_ident(&model.name);
//
// quote! {
// pub struct Delete<'a> {
// client: &'a super::_prisma::PrismaCRDTClient,
// where_param: crate::prisma::#model_name::UniqueWhereParam,
// with_params: Vec<crate::prisma::#model_name::WithParam>,
// }
//
// impl<'a> Delete<'a> {
// pub fn with(mut self, param: impl Into<crate::prisma::location::WithParam>) -> Self {
// self.with_params.push(param.into());
// self
// }
//
// pub async fn exec(self) -> Result<Option<crate::prisma::#model_name::Data>, crate::prisma::QueryError> {
//
// }
// }
// }
// }

View file

@ -1,74 +0,0 @@
pub mod actions;
pub mod create;
pub mod create_params;
pub mod delete;
pub mod owned;
pub mod relation;
pub mod set_param;
pub mod shared;
pub mod sync_id;
pub mod update;
use crate::generator::prelude::*;
/// Things specific to the type of the model
fn model_type_tokens(model: ModelRef) -> TokenStream {
match &model.typ {
ModelType::Relation { item, group } => {
let item_field = model.field(item.at_index(0).unwrap()).unwrap();
let item_def = relation::relation_key_definition(item_field, quote!(RelationItem));
let group_field = model.field(group.at_index(0).unwrap()).unwrap();
let group_def = relation::relation_key_definition(group_field, quote!(RelationGroup));
quote! {
#item_def
#group_def
}
}
_ => quote!(),
}
}
pub fn generate(model: ModelRef) -> TokenStream {
let model_name_snake = snake_ident(&model.name);
if matches!(&model.typ, ModelType::Local { .. }) {
return quote!(pub use crate::prisma::#model_name_snake;);
}
let model_type_tokens = model_type_tokens(model);
let set_param_enums = set_param::definition(model);
let sync_id_struct = sync_id::definition(model);
let create_params = create_params::definition(model);
let create_struct = create::struct_definition(model);
let update_struct = update::generate(&model);
// let delete_struct = delete::generate(&model);
let actions_struct = actions::definition(model);
quote!(
pub mod #model_name_snake {
pub use crate::prisma::#model_name_snake::*;
#model_type_tokens
#set_param_enums
#sync_id_struct
#create_params
#create_struct
#update_struct
// #delete_struct
#actions_struct
}
)
}

View file

@ -1,46 +0,0 @@
use crate::generator::prelude::*;
use super::{create_params, sync_id};
/// Generates the body of an owned model's `Create::exec` function
///
/// ## Example
///
/// ```
/// let sync_id = SyncId { .. };
///
/// let params = CRDTCreateParams { .. };
///
/// let params_map = ::prisma_crdt::objectify(params);
///
/// self
/// .crdt_client
/// .create_operation(::prisma_crdt::CRDTOperationType::owned(
/// #model_name_str,
/// vec![::prisma_crdt::OwnedOperationData::Create(params_map)]
/// ))
/// .await;
/// ```
pub fn create_exec_body(model: ModelRef) -> TokenStream {
let model_name_str = &model.name;
let sync_id_constructor = sync_id::constructor(model, quote!(self.set_params));
let crdt_params_constructor = create_params::crdt_constructor(model);
quote! {
let sync_id = #sync_id_constructor;
let params = #crdt_params_constructor;
let params_map = ::prisma_crdt::objectify(params);
self
.crdt_client
._create_operation(::prisma_crdt::CRDTOperationType::owned(
#model_name_str,
vec![::prisma_crdt::OwnedOperationData::Create(params_map)]
))
.await;
}
}

View file

@ -1,108 +0,0 @@
use crate::generator::prelude::*;
use super::sync_id::scalar_field_to_crdt;
/// Generates the struct definition for a relation's key
pub fn relation_key_definition(field: FieldRef, struct_name: TokenStream) -> TokenStream {
let fields = match &field.typ {
FieldType::Relation { relation_info } => relation_info.fields.iter().map(|rel_field| {
let field_name_snake = snake_ident(rel_field);
let field = field
.model
.field(rel_field)
.unwrap_or_else(|| panic!("Model {} has no field {}", field.model.name, rel_field));
let field_type = field.crdt_type_tokens(&field.datamodel);
quote!(#field_name_snake: #field_type)
}),
_ => unreachable!(),
};
quote! {
#[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct #struct_name {
#(pub #fields),*
}
}
}
/// Generates a constructor for a relation's key
pub fn relation_key_constructor(field: FieldRef, struct_name: TokenStream) -> TokenStream {
let key_args = match &field.typ {
FieldType::Relation { relation_info } => relation_info.fields.iter().map(|rel_field| {
let field_name_snake = snake_ident(rel_field);
let field = field.model.field(rel_field).unwrap();
let value = scalar_field_to_crdt(
field,
quote!(self.crdt_client.client),
quote!(self.set_params.#field_name_snake),
);
quote!(#field_name_snake: #value)
}),
_ => unreachable!(), // Item & group must be relations
};
quote! {
#struct_name {
#(#key_args),*
}
}
}
/// Generates the body for a relation model's `Create::exec` function
///
/// ## Example
///
/// ```
/// let relation_item = RelationItem { .. };
///
/// let relation_group = RelationGroup { .. };
///
/// self
/// .crdt_client
/// ._create_operation(::prisma_crdt::CRDTOperationType::relation(
/// #model_name_str,
/// ::prisma_crdt::objectify(relation_item),
/// ::prisma_crdt::objectify(relation_group),
/// ::prisma_crdt::RelationOperationData::create()
/// ))
/// .await;
/// ```
pub fn create_exec_body(model: ModelRef) -> TokenStream {
let model_name_str = &model.name;
let (relation_item_block, relation_group_block) = match &model.typ {
ModelType::Relation { item, group } => {
let relation_item_block = relation_key_constructor(
model.field(item.at_index(0).unwrap()).unwrap(),
quote!(RelationItem),
);
let relation_group_block = relation_key_constructor(
model.field(group.at_index(0).unwrap()).unwrap(),
quote!(RelationGroup),
);
(relation_item_block, relation_group_block)
}
_ => unreachable!(),
};
quote! {
let relation_item = #relation_item_block;
let relation_group = #relation_group_block;
self
.crdt_client
._create_operation(::prisma_crdt::CRDTOperationType::relation(
#model_name_str,
::prisma_crdt::objectify(relation_item),
::prisma_crdt::objectify(relation_group),
::prisma_crdt::RelationOperationData::create()
))
.await;
}
}

View file

@ -1,181 +0,0 @@
use crate::generator::prelude::*;
use super::sync_id::scalar_field_to_crdt;
struct SetParam {
pub variant: TokenStream,
pub into_match_arm: TokenStream,
pub into_crdt_match_arm: TokenStream,
pub from_pcr_set_impl: TokenStream,
}
impl SetParam {
pub fn new(field: FieldRef) -> Self {
let model_name_snake = snake_ident(&field.model.name);
let field_name_snake = snake_ident(field.name());
let field_name_pascal = pascal_ident(field.name());
let variant_name = format_ident!("Set{}", field_name_pascal);
let variant = {
let variant_type = field.type_tokens();
quote!(#variant_name(#variant_type))
};
let into_match_arm = quote!(Self::#variant_name(v) => crate::prisma::#model_name_snake::#field_name_snake::set(v));
let into_crdt_match_arm = {
let to_crdt_block = scalar_field_to_crdt(field, quote!(client), quote!(v));
quote!(Self::#variant_name(v) => CRDTSetParam::#variant_name(#to_crdt_block))
};
let from_pcr_set_impl = quote! {
impl From<crate::prisma::#model_name_snake::#field_name_snake::Set> for SetParam {
fn from(v: crate::prisma::#model_name_snake::#field_name_snake::Set) -> Self {
Self::#variant_name(v.0)
}
}
};
SetParam {
variant,
into_match_arm,
into_crdt_match_arm,
from_pcr_set_impl,
}
}
}
struct CRDTSetParam {
pub variant: TokenStream,
pub into_match_arm: TokenStream,
}
impl CRDTSetParam {
pub fn new(field: FieldRef) -> Self {
let model_name_snake = snake_ident(&field.model.name);
let field_name_snake = snake_ident(field.name());
let field_name_pascal = pascal_ident(field.name());
let variant_name = format_ident!("Set{}", field_name_pascal);
let variant = {
let variant_type = field.crdt_type_tokens(&field.datamodel);
let field_name = field.name();
quote! {
#[serde(rename = #field_name)]
#variant_name(#variant_type)
}
};
let into_match_arm = {
let relation_field_info = match &field.typ {
FieldType::Scalar {
relation_field_info,
} => relation_field_info,
_ => unreachable!("Cannot create CRDTSetParam from relation field!"),
};
let ret = match relation_field_info.as_ref() {
Some(relation_field_info)
if
field.model.name != relation_field_info.referenced_model // This probably isn't good enough
=>
{
let relation_name_snake = snake_ident(relation_field_info.relation);
let relation_model = field.datamodel.model(relation_field_info
.referenced_model).unwrap();
let relation_model_name_snake = snake_ident(&relation_model.name);
let referenced_sync_id_field = relation_model
.sync_id_for_pk(relation_field_info.referenced_field)
.expect("referenced_sync_id_field should be present");
let referenced_sync_id_field_name_snake = snake_ident(referenced_sync_id_field.name());
let ret = quote!(crate::prisma::#model_name_snake::#relation_name_snake::link(
crate::prisma::#relation_model_name_snake::#referenced_sync_id_field_name_snake::equals(v)
));
match field.arity() {
dml::FieldArity::Optional => {
quote!(v.map(|v| #ret).unwrap_or(crate::prisma::#model_name_snake::#relation_name_snake::unlink()))
}
_ => ret,
}
}
_ => {
quote!(crate::prisma::#model_name_snake::#field_name_snake::set(v))
}
};
quote!(Self::#variant_name(v) => #ret)
};
Self {
variant,
into_match_arm,
}
}
}
pub fn definition(model: ModelRef) -> TokenStream {
let model_name_snake = snake_ident(&model.name);
let set_param_fields_iter = model.fields().into_iter().filter(|f| {
model
.scalar_sync_id_fields(&model.datamodel)
.any(|(id, _)| id.name() == f.name())
|| f.is_scalar_field() && !(model.is_pk(f.name()) || model.is_sync_id(f.name()))
});
let set_params = set_param_fields_iter.clone().map(SetParam::new);
let set_param_variants = set_params.clone().map(|p| p.variant);
let set_param_into_match_arms = set_params.clone().map(|p| p.into_match_arm);
let set_param_into_crdt_match_arms = set_params.clone().map(|p| p.into_crdt_match_arm);
let set_param_from_pcr_set_impls = set_params.clone().map(|p| p.from_pcr_set_impl);
let crdt_set_params = set_param_fields_iter.map(CRDTSetParam::new);
let crdt_set_param_variants = crdt_set_params.clone().map(|p| p.variant);
let crdt_set_param_into_match_arms = crdt_set_params.clone().map(|p| p.into_match_arm);
quote! {
#[derive(Clone)]
pub enum SetParam {
#(#set_param_variants),*
}
impl SetParam {
pub async fn into_crdt(self, client: &super::_prisma::PrismaCRDTClient) -> CRDTSetParam {
match self {
#(#set_param_into_crdt_match_arms),*
}
}
}
#(#set_param_from_pcr_set_impls)*
impl Into<crate::prisma::#model_name_snake::SetParam> for SetParam {
fn into(self) -> crate::prisma::#model_name_snake::SetParam {
match self {
#(#set_param_into_match_arms),*
}
}
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub enum CRDTSetParam {
#(#crdt_set_param_variants),*
}
impl Into<crate::prisma::#model_name_snake::SetParam> for CRDTSetParam {
fn into(self) -> crate::prisma::#model_name_snake::SetParam {
match self {
#(#crdt_set_param_into_match_arms),*
}
}
}
}
}

View file

@ -1,124 +0,0 @@
use crate::generator::prelude::*;
use super::{create_params, sync_id};
/// Generates the body of a shared relation's `Create::exec` function
///
/// ## Example
///
/// ### Atomic
///
/// ```
/// let sync_id = SyncId { .. };
///
/// self
/// .crdt_client
/// ._create_operation(::prisma_crdt::CRDTOperationType::shared(
/// #model_name_str,
/// ::serde_json::to_value(&sync_id).unwrap(),
/// ::prisma_crdt::SharedOperationData::create_atomic()
/// ))
/// .await;
///
/// for param in self.set_params._params {
/// let crdt_param = param.into_crdt(self.crdt_client).await;
///
/// let param_map = ::prisma_crdt::objectify(crdt_param);
///
/// for (key, value) in param_map {
/// self
/// .crdt_client
/// ._create_operation(::prisma_crdt::CRDTOperation::shared(
/// #model_name_str,
/// ::serde_json::to_value(&sync_id).unwrap(),
/// ::prisma_crdt::SharedOperationData::update(key, value)
/// ))
/// .await;
/// }
/// }
/// ```
///
/// ### Unique
///
/// ```
/// let sync_id = SyncId { .. };
///
/// let params = CreateCRDTParams { .. };
///
/// let params_map = ::prisma_crdt::objectify(params);
///
/// self
/// .crdt_client
/// ._create_operation(::prisma_crdt::CRDTOperationType::shared(
/// #model_name_str,
/// ::serde_json::to_value(&sync_id).unwrap(),
/// ::prisma_crdt::SharedOperationData::create_unique(params)
/// ))
/// .await;
/// ```
pub fn create_exec_body(model: ModelRef) -> TokenStream {
let model_name_str = &model.name;
let sync_id_constructor = sync_id::constructor(model, quote!(self.set_params));
let create_mode = match &model.typ {
ModelType::Shared { create, .. } => create,
_ => unreachable!(),
};
let the_meat = match create_mode {
SharedCreateType::Atomic => {
quote! {
self
.crdt_client
._create_operation(::prisma_crdt::CRDTOperationType::shared(
#model_name_str,
::serde_json::to_value(&sync_id).unwrap(),
::prisma_crdt::SharedOperationData::create_atomic()
))
.await;
for param in self.set_params._params {
let crdt_param = param.into_crdt(self.crdt_client).await;
let param_map = ::prisma_crdt::objectify(crdt_param);
for (key, value) in param_map {
self
.crdt_client
._create_operation(::prisma_crdt::CRDTOperationType::shared(
#model_name_str,
::serde_json::to_value(&sync_id).unwrap(),
::prisma_crdt::SharedOperationData::update(key, value)
))
.await;
}
}
}
}
SharedCreateType::Unique => {
let crdt_params_constructor = create_params::crdt_constructor(model);
quote! {
let params = #crdt_params_constructor;
let params_map = ::prisma_crdt::objectify(params);
self
.crdt_client
._create_operation(::prisma_crdt::CRDTOperationType::shared(
#model_name_str,
::serde_json::to_value(&sync_id).unwrap(),
::prisma_crdt::SharedOperationData::create_unique(params_map)
))
.await;
}
}
};
quote! {
let sync_id = #sync_id_constructor;
#the_meat
}
}

View file

@ -1,202 +0,0 @@
use crate::generator::prelude::*;
/// Constructs the Sync ID for the given model.
///
/// ## Scalar Example
/// ```
/// @shared(id: unique_id)
/// model User {
/// pk Int @id
/// id Bytes @unique
/// }
/// ```
///
/// ```
/// SyncID {
/// id: #data_var.id
/// }
/// ```
///
/// ## Relation Example
///
/// ```
/// @shared(id: [location, pk])
/// model File {
/// pk Int @id
///
/// location_id Int
/// location Location
/// }
/// ```
///
/// ```
/// SyncID {
/// location_id: self
/// .client
/// .client
/// .file()
/// .find_unique(crate::prisma::location::local_id::equals(#data_var.location_id.clone()))
/// .exec()
/// .await?
/// .id,
/// pk: #data_var.pk.clone()
/// }
/// ```
pub fn constructor(model: ModelRef, data_var: TokenStream) -> TokenStream {
let model_name_snake = snake_ident(&model.name);
let args = model
.fields()
.into_iter()
.filter(|f| model.is_sync_id(f.name()))
.flat_map(|f| match &f.typ {
FieldType::Scalar { .. } => vec![f],
FieldType::Relation { relation_info } => relation_info
.fields
.iter()
.map(|f| {
model
.field(f)
.unwrap_or_else(|| panic!("{} has no field {}", model.name, f))
})
.collect(),
})
.map(|f| {
let field_name_snake = snake_ident(f.name());
let val = scalar_field_to_crdt(
f,
quote!(self.crdt_client.client),
quote!(#data_var.#field_name_snake),
);
quote!(#field_name_snake: #val)
});
quote! {
super::#model_name_snake::SyncID {
#(#args,)*
}
}
}
/// Generates tokens to get the CRDT value of a scalar field.
///
/// ## Reguar Field
/// For a field that has no connection to any model's sync ID,
/// the value used will be `set_param_value`.
///
/// ```
/// field String
/// ```
///
/// ```
/// #set_param_value
/// ```
///
/// ## Sync ID Field
/// For a field that is a foreign key, a query fetching the foreign model's
/// corresponding Sync ID will be generated.
///
/// ```
/// relation_id String
/// relation RelationModel @relation(fields: [relation_id], references: [foreign_pk])
/// ````
///
/// ```
/// #client
/// .relation()
/// .find_unique(crate::prisma::relation::id::equals(#set_param_value))
/// .exec()
/// .await
/// .unwrap()
/// .unwrap()
/// .foregin_sync_id
/// ```
pub fn scalar_field_to_crdt(
field: FieldRef,
client: TokenStream,
set_param_value: TokenStream,
) -> TokenStream {
match &field.typ {
FieldType::Scalar {
relation_field_info,
} => relation_field_info
.as_ref()
.and_then(|relation_field_info| {
let referenced_field_snake = snake_ident(relation_field_info.referenced_field);
let relation_model = field
.datamodel
.model(relation_field_info.referenced_model)
.unwrap();
let relation_model_snake = snake_ident(&relation_model.name);
let referenced_sync_id_field = relation_model
.sync_id_for_pk(relation_field_info.referenced_field)
.expect("referenced_sync_id_field should be present");
// If referenced field is a sync ID, it does not need to be converted
(!field.model.is_sync_id(relation_field_info.referenced_field)).then(|| {
let referenced_sync_id_field_name_snake =
snake_ident(referenced_sync_id_field.name());
let query = quote! {
#client
.#relation_model_snake()
.find_unique(
crate::prisma::#relation_model_snake::#referenced_field_snake::equals(#set_param_value)
)
.exec()
.await
.unwrap()
.unwrap()
.#referenced_sync_id_field_name_snake
};
match field.arity() {
dml::FieldArity::Optional => {
quote! {
// can't map with async :sob:
match #set_param_value {
Some(#set_param_value) => Some(#query),
None => None,
}
}
}
_ => query,
}
})
})
.unwrap_or(quote!(#set_param_value)),
_ => unreachable!(),
}
}
/// Generates a definition of a model's `SyncID` struct
///
/// ## Example
///
/// ```
/// #[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
/// pub struct SyncID {
/// pub id: i32,
/// pub location_id: Vec<u8>
/// }
/// ```
pub fn definition(model: ModelRef) -> TokenStream {
let sync_id_fields = model.scalar_sync_id_fields(&model.datamodel).map(|field| {
let field_type = field.1.crdt_type_tokens(&model.datamodel);
let field_name_snake = snake_ident(field.0.name());
quote!(#field_name_snake: #field_type)
});
quote! {
#[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
pub struct SyncID {
#(pub #sync_id_fields),*
}
}
}

View file

@ -1,27 +0,0 @@
use crate::generator::prelude::*;
pub fn generate(model: &Model) -> TokenStream {
let model_name_snake = snake_ident(&model.name);
quote! {
#[derive(serde::Serialize, serde::Deserialize)]
struct CRDTUpdateParams {
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "_")]
pub _params: Vec<CRDTSetParam>,
#[serde(flatten)]
pub _sync_id: SyncID,
}
pub struct Update<'a> {
client: &'a super::_prisma::PrismaCRDTClient,
where_param: crate::prisma::#model_name_snake::UniqueWhereParam,
set_params: Vec<SetParam>,
}
impl <'a> Update<'a> {
pub async fn exec(self) -> Result<Option<crate::prisma::#model_name_snake::Data>, crate::prisma::QueryError> {
}
}
}
}

View file

@ -21,9 +21,9 @@ enum ModelSyncType<'a> {
Local {
id: FieldVec<'a>,
},
Owned {
id: FieldVec<'a>,
},
// Owned {
// id: FieldVec<'a>,
// },
Shared {
id: FieldVec<'a>,
},
@ -56,7 +56,7 @@ impl<'a> ModelSyncType<'a> {
Some(match attr.name {
"local" => Self::Local { id },
"owned" => Self::Owned { id },
// "owned" => Self::Owned { id },
"shared" => Self::Shared { id },
_ => return None,
})
@ -64,7 +64,7 @@ impl<'a> ModelSyncType<'a> {
fn sync_id(&self) -> Vec<FieldWalker> {
match self {
Self::Owned { id } => id.clone(),
// Self::Owned { id } => id.clone(),
Self::Local { id } => id.clone(),
Self::Shared { id } => id.clone(),
_ => vec![],
@ -76,7 +76,7 @@ impl ToTokens for ModelSyncType<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let variant = match self {
Self::Local { .. } => "Local",
Self::Owned { .. } => "Owned",
// Self::Owned { .. } => "Owned",
Self::Shared { .. } => "Shared",
Self::Relation { .. } => "Relation",
};
@ -246,7 +246,7 @@ impl PrismaGenerator for SDSyncGenerator {
sync_type.and_then(|a| {
let data_type = match a {
ModelSyncType::Owned { .. } => quote!(OwnedOperationData),
// ModelSyncType::Owned { .. } => quote!(OwnedOperationData),
ModelSyncType::Shared { .. } => quote!(SharedOperationData),
ModelSyncType::Relation { .. } => {
quote!(RelationOperationData)
@ -263,12 +263,12 @@ impl PrismaGenerator for SDSyncGenerator {
let cond = quote!(if op.model == prisma::#model_name_snake::NAME);
let match_case = match a {
ModelSyncType::Owned { .. } => {
quote! {
#op_type_enum::Owned(op) #cond =>
Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data)
}
}
// ModelSyncType::Owned { .. } => {
// quote! {
// #op_type_enum::Owned(op) #cond =>
// Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data)
// }
// }
ModelSyncType::Shared { .. } => {
quote! {
#op_type_enum::Shared(op) #cond =>

View file

@ -1,96 +0,0 @@
use crate::{attribute::Attribute, prelude::*};
pub enum ModelType {
Owned,
SharedUnique,
SharedAtomic,
Relation,
}
pub fn model_sync_type(model: &dml::Model, dml: &dml::Datamodel) -> ModelType {
let type_attribute = model
.documentation
.as_ref()
.map(Attribute::parse)
.unwrap()
.unwrap();
match type_attribute.name {
"owned" => ModelType::Owned,
"shared" => ModelType::SharedUnique, // TODO: fix
"relation" => ModelType::Relation,
_ => unreachable!(),
}
}
pub fn module(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
let model_name_snake = snake_ident(&model.name);
let set_params_enum = set_params_enum(model, dml);
let actions_struct = actions_struct(model, dml);
quote! {
pub mod #model_name_snake {
#set_params_enum
#actions_struct
}
}
}
pub fn set_params_enum(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
quote! {
pub enum SetParam {}
}
}
pub fn create_fn(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
let required_scalar_fields = model.required_scalar_fields();
let args = required_scalar_fields.iter().map(|field| {
let name = snake_ident(field.name());
let typ = field.type_tokens(quote!(crate::prisma::));
quote!(#name: #typ)
});
match model_sync_type(model, dml) {
ModelType::Owned => {
quote! {
pub fn create(&self, #(#args),*, _params: Vec<SetParam>) {
}
}
}
ModelType::SharedUnique => {
quote! {
pub fn create(&self, #(#args),*, _params: Vec<SetParam>) {}
}
}
ModelType::SharedAtomic => {
quote! {
pub fn create(&self, _params: Vec<SetParam>) {}
}
}
ModelType::Relation => {
quote! {
pub fn create(&self, _params: Vec<SetParam>) {}
}
}
_ => todo!(),
}
}
pub fn actions_struct(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
let create_fn = create_fn(model, dml);
quote! {
pub struct Actions<'a> {
pub(super) client: &'a super::#CRDT_CLIENT
}
impl<'a> Actions<'a> {
#create_fn
}
}
}

View file

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, fmt::Debug};
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
@ -22,18 +22,12 @@ pub struct RelationOperation {
}
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
pub enum SharedOperationCreateData {
#[serde(rename = "u")]
Unique(Map<String, Value>),
#[serde(rename = "a")]
Atomic,
}
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
#[serde(untagged)]
pub enum SharedOperationData {
Create(SharedOperationCreateData),
#[serde(rename = "c")]
Create(Map<String, Value>),
#[serde(rename = "u")]
Update { field: String, value: Value },
#[serde(rename = "d")]
Delete,
}
@ -44,35 +38,35 @@ pub struct SharedOperation {
pub data: SharedOperationData,
}
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
pub enum OwnedOperationData {
Create(BTreeMap<String, Value>),
CreateMany {
values: Vec<(Value, BTreeMap<String, Value>)>,
skip_duplicates: bool,
},
Update(BTreeMap<String, Value>),
Delete,
}
// #[derive(Serialize, Deserialize, Clone, Debug, Type)]
// pub enum OwnedOperationData {
// Create(BTreeMap<String, Value>),
// CreateMany {
// values: Vec<(Value, BTreeMap<String, Value>)>,
// skip_duplicates: bool,
// },
// Update(BTreeMap<String, Value>),
// Delete,
// }
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
pub struct OwnedOperationItem {
pub id: Value,
pub data: OwnedOperationData,
}
// #[derive(Serialize, Deserialize, Clone, Debug, Type)]
// pub struct OwnedOperationItem {
// pub id: Value,
// pub data: OwnedOperationData,
// }
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
pub struct OwnedOperation {
pub model: String,
pub items: Vec<OwnedOperationItem>,
}
// #[derive(Serialize, Deserialize, Clone, Debug, Type)]
// pub struct OwnedOperation {
// pub model: String,
// pub items: Vec<OwnedOperationItem>,
// }
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
#[serde(untagged)]
pub enum CRDTOperationType {
Shared(SharedOperation),
Relation(RelationOperation),
Owned(OwnedOperation),
// Owned(OwnedOperation),
}
#[derive(Serialize, Deserialize, Clone, Type)]

View file

@ -1,5 +1,6 @@
import {
ArchiveBox,
ArrowsClockwise,
Broadcast,
CopySimple,
Crosshair,
@ -40,6 +41,10 @@ export default () => {
<Icon component={ArchiveBox} />
Imports
</SidebarLink>
{/* <SidebarLink to="sync"> */}
{/* <Icon component={ArrowsClockwise} /> */}
{/* Sync */}
{/* </SidebarLink> */}
</div>
{library && <LibrarySection />}
<Section name="Tools" actionArea={<SubtleButton />}>

View file

@ -8,15 +8,15 @@ const Row = tw.p`overflow-hidden text-ellipsis space-x-1`;
const OperationItem = ({ op }: { op: CRDTOperation }) => {
let contents = null;
if ('record_id' in op.typ) {
if ('model' in op.typ) {
let subContents = null;
if (op.typ.data === null) {
if (op.typ.data === 'd') {
subContents = 'Delete';
} else if (op.typ.data === 'a' || 'u' in op.typ.data) {
} else if ('c' in op.typ.data) {
subContents = 'Create';
} else {
subContents = `Update - ${op.typ.data.field}`;
subContents = `Update - ${op.typ.data.u.field}`;
}
contents = (

View file

@ -77,7 +77,7 @@ export type BuildInfo = { version: string; commit: string }
export type CRDTOperation = { node: string; timestamp: number; id: string; typ: CRDTOperationType }
export type CRDTOperationType = SharedOperation | RelationOperation | OwnedOperation
export type CRDTOperationType = SharedOperation | RelationOperation
/**
* Meow
@ -216,12 +216,6 @@ export type OperatingSystem = "Windows" | "Linux" | "MacOS" | "Ios" | "Android"
export type OptionalRange<T> = { from: T | null; to: T | null }
export type OwnedOperation = { model: string; items: OwnedOperationItem[] }
export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMany: { values: ([any, { [key: string]: any }])[]; skip_duplicates: boolean } } | { Update: { [key: string]: any } } | "Delete"
export type OwnedOperationItem = { id: any; data: OwnedOperationData }
/**
* TODO: P2P event for the frontend
*/
@ -255,9 +249,7 @@ export type SetNoteArgs = { id: number; note: string | null }
export type SharedOperation = { record_id: any; model: string; data: SharedOperationData }
export type SharedOperationCreateData = { u: { [key: string]: any } } | "a"
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null
export type SharedOperationData = { c: { [key: string]: any } } | { u: { field: string; value: any } } | "d"
export type SortOrder = "Asc" | "Desc"