From 3e2ae2ddadad915cc7c4616a37f16d9b322b6477 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Wed, 28 Sep 2022 11:39:04 +0800
Subject: [PATCH 01/11] expand prisma client cache key
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 82c9c8e0e..3e57c9347 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -121,7 +121,7 @@ jobs:
uses: actions/cache@v3
with:
path: ./core/src/prisma.rs
- key: prisma-${{ runner.os }}-${{ hashFiles('./core/prisma/Cargo.toml', './core/prisma/schema.prisma', './core/prisma/src/main.rs') }}
+ key: prisma-${{ runner.os }}-${{ hashFiles('./core/prisma/*', "./Cargo.toml") }}
- name: Generate Prisma client
working-directory: core
From 46e1d47596f96ad043bd810b0fd622b2df4c2330 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Wed, 28 Sep 2022 11:42:05 +0800
Subject: [PATCH 02/11] use proper workflow syntax
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3e57c9347..f6dffcccd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -121,7 +121,7 @@ jobs:
uses: actions/cache@v3
with:
path: ./core/src/prisma.rs
- key: prisma-${{ runner.os }}-${{ hashFiles('./core/prisma/*', "./Cargo.toml") }}
+ key: prisma-${{ runner.os }}-${{ hashFiles('./core/prisma/*', './Cargo.toml') }}
- name: Generate Prisma client
working-directory: core
From 2952d3fc5ef310b3edcdac52b5bd2ec22fd8a195 Mon Sep 17 00:00:00 2001
From: brxken128 <77554505+brxken128@users.noreply.github.com>
Date: Fri, 30 Sep 2022 17:38:31 +0100
Subject: [PATCH 03/11] fix incorrect faq URLs
---
apps/landing/src/components/Footer.tsx | 2 +-
apps/landing/src/pages/team.page.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/landing/src/components/Footer.tsx b/apps/landing/src/components/Footer.tsx
index 48553aa5d..8b400dc83 100644
--- a/apps/landing/src/components/Footer.tsx
+++ b/apps/landing/src/components/Footer.tsx
@@ -56,7 +56,7 @@ export function Footer() {
About
Team
- FAQ
+ FAQ
Careers
Changelog
Blog
diff --git a/apps/landing/src/pages/team.page.tsx b/apps/landing/src/pages/team.page.tsx
index d806bb9e6..1b4de52bc 100644
--- a/apps/landing/src/pages/team.page.tsx
+++ b/apps/landing/src/pages/team.page.tsx
@@ -226,7 +226,7 @@ function Page() {
lives, at unlimited scale.
From 9f0ef3cc53516d80aaf2d4f02f6749a0d1035383 Mon Sep 17 00:00:00 2001
From: Jamie Pine <32987599+jamiepine@users.noreply.github.com>
Date: Fri, 30 Sep 2022 20:51:46 -0700
Subject: [PATCH 04/11] Fix readme licence badge
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 01a60a903..f386104c6 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,9 @@ For independent creatives, hoarders and those that want to own their digital foo
-
+
+
+
From 66dd9aa28b82ef244263daec76cc9b15c56a9d61 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Tue, 4 Oct 2022 17:32:46 +0800
Subject: [PATCH 05/11] move prisma schema and migrations to /core (#392)
* move prisma schema and migrations to /core
* fix prisma updates
---
Cargo.lock | Bin 151042 -> 151039 bytes
Cargo.toml | 18 ++++++++++++++----
core/Cargo.toml | 7 +------
.../20220909073230_init/migration.sql | 0
.../prisma/migrations/migration_lock.toml | 0
{crates => core}/prisma/schema.prisma | 2 +-
core/src/location/indexer/indexer_job.rs | 2 +-
core/src/location/mod.rs | 2 +-
crates/prisma-cli/Cargo.toml | 7 +++++++
crates/{prisma => prisma-cli}/src/main.rs | 0
crates/prisma/Cargo.toml | 12 ------------
11 files changed, 25 insertions(+), 25 deletions(-)
rename {crates => core}/prisma/migrations/20220909073230_init/migration.sql (100%)
rename {crates => core}/prisma/migrations/migration_lock.toml (100%)
rename {crates => core}/prisma/schema.prisma (99%)
create mode 100644 crates/prisma-cli/Cargo.toml
rename crates/{prisma => prisma-cli}/src/main.rs (100%)
delete mode 100644 crates/prisma/Cargo.toml
diff --git a/Cargo.lock b/Cargo.lock
index 76ad097a8cd7f07fe9b3a2dc44b0ce7e399a530c..1cd3a97a32564a1fbc5b434893f6930e0d6e2edd 100644
GIT binary patch
delta 322
zcmZpg!})(UXTui8a);>$S{Oy9_c$<$1f*G}8km|}CL1Q18<-oJCz+)g7#kQ^nx-Tg
znVBS7SePam8mAc~n<*2ia5AH<*z^TQ7=^Y=I5KuJ(%L2iXU03UwL_wPt_$P#xh_np
FoB+vNS)>2}
delta 322
zcmexAo3m*SXTui8a);@GevG2i96Op`6m(~OeM
zk_}VR%ndAzl9LmY&6J5#IK9z@QGEJ&cSf%13mBO=w@WxOb}-V~Iz4B`JG8T-9q6U)
JjV?@eoB-f;URnSE
diff --git a/Cargo.toml b/Cargo.toml
index 96f10f8f7..1755e99b7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,10 +8,20 @@ members = [
]
resolver = "2"
+[workspace.dependencies]
+prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "f9e0579c1b70727b6f030095da264a885b13f0c6", features = [
+ "rspc",
+ "sqlite-create-many",
+ "migrations",
+ "sqlite",
+], default-features = false }
+prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "f9e0579c1b70727b6f030095da264a885b13f0c6", features = [
+ "rspc",
+ "sqlite-create-many",
+ "migrations",
+ "sqlite",
+], default-features = false }
+
[patch.crates-io]
# We use this patch so we can compile for the IOS simulator on M1
openssl-sys = { git = "https://github.com/spacedriveapp/rust-openssl" }
-
-[patch."https://github.com/Brendonovich/prisma-client-rust.git"]
-prisma-client-rust = { git = "https://github.com//Brendonovich/prisma-client-rust.git", rev = "6b59d9f0a07c4c5df1f5c97f2c6c1df7082ccac6" }
-prisma-client-rust-cli = { git = "https://github.com//Brendonovich/prisma-client-rust.git", rev = "6b59d9f0a07c4c5df1f5c97f2c6c1df7082ccac6" }
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 7551f8b1a..8f1e940c7 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -35,12 +35,7 @@ rmp = "^0.8.11"
rmp-serde = "^1.1.0"
# Project dependencies
-prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.0", features = [
- "rspc",
- "sqlite-create-many",
- "migrations",
- "sqlite",
-], default-features = false }
+prisma-client-rust = { workspace = true }
rspc = { version = "0.0.5", features = ["uuid", "chrono", "tracing"] }
uuid = { version = "1.1.2", features = ["v4", "serde"] }
sysinfo = "0.23.9"
diff --git a/crates/prisma/migrations/20220909073230_init/migration.sql b/core/prisma/migrations/20220909073230_init/migration.sql
similarity index 100%
rename from crates/prisma/migrations/20220909073230_init/migration.sql
rename to core/prisma/migrations/20220909073230_init/migration.sql
diff --git a/crates/prisma/migrations/migration_lock.toml b/core/prisma/migrations/migration_lock.toml
similarity index 100%
rename from crates/prisma/migrations/migration_lock.toml
rename to core/prisma/migrations/migration_lock.toml
diff --git a/crates/prisma/schema.prisma b/core/prisma/schema.prisma
similarity index 99%
rename from crates/prisma/schema.prisma
rename to core/prisma/schema.prisma
index 9e4a3ce83..0b8c45a6c 100644
--- a/crates/prisma/schema.prisma
+++ b/core/prisma/schema.prisma
@@ -5,7 +5,7 @@ datasource db {
generator client {
provider = "cargo prisma"
- output = "../../core/src/prisma.rs"
+ output = "../src/prisma.rs"
}
model Migration {
diff --git a/core/src/location/indexer/indexer_job.rs b/core/src/location/indexer/indexer_job.rs
index f958961b5..6133a2ca7 100644
--- a/core/src/location/indexer/indexer_job.rs
+++ b/core/src/location/indexer/indexer_job.rs
@@ -262,7 +262,7 @@ impl StatefulJob for IndexerJob {
.to_string_lossy()
.to_string();
- file_path::create(
+ file_path::create_unchecked(
entry.file_id,
location_id,
materialized_path,
diff --git a/core/src/location/mod.rs b/core/src/location/mod.rs
index bc13355d5..4d5d29551 100644
--- a/core/src/location/mod.rs
+++ b/core/src/location/mod.rs
@@ -224,7 +224,7 @@ async fn link_location_and_indexer_rules(
.create_many(
rules_ids
.iter()
- .map(|id| indexer_rules_in_location::create(location_id, *id, vec![]))
+ .map(|id| indexer_rules_in_location::create_unchecked(location_id, *id, vec![]))
.collect(),
)
.exec()
diff --git a/crates/prisma-cli/Cargo.toml b/crates/prisma-cli/Cargo.toml
new file mode 100644
index 000000000..52cccb65d
--- /dev/null
+++ b/crates/prisma-cli/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "prisma-cli"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+prisma-client-rust-cli = { workspace = true }
diff --git a/crates/prisma/src/main.rs b/crates/prisma-cli/src/main.rs
similarity index 100%
rename from crates/prisma/src/main.rs
rename to crates/prisma-cli/src/main.rs
diff --git a/crates/prisma/Cargo.toml b/crates/prisma/Cargo.toml
deleted file mode 100644
index c37060622..000000000
--- a/crates/prisma/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "prisma-cli"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.0", features = [
- "rspc",
- "sqlite-create-many",
- "migrations",
- "sqlite",
-], default-features = false }
From 786b7c9c1cebae3423d82881c95b568c77c6cd1b Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Tue, 4 Oct 2022 18:42:40 +0800
Subject: [PATCH 06/11] db:gen in core
---
package.json | 78 ++++++++++++++++++++++++++--------------------------
1 file changed, 39 insertions(+), 39 deletions(-)
diff --git a/package.json b/package.json
index ab0932e3d..108b3fa60 100644
--- a/package.json
+++ b/package.json
@@ -1,41 +1,41 @@
{
- "name": "spacedrive",
- "version": "0.0.0",
- "private": true,
- "scripts": {
- "prep": "pnpm db:gen && cargo test",
- "build": "turbo run build",
- "landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web",
- "db:migrate": "cd crates && cargo prisma migrate dev",
- "db:gen": "cd crates && cargo prisma generate",
- "format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
- "desktop": "pnpm --filter @sd/desktop --",
- "web": "pnpm --filter @sd/web -- ",
- "landing": "pnpm --filter @sd/landing -- ",
- "ui": "pnpm --filter @sd/ui -- ",
- "interface": "pnpm --filter @sd/interface -- ",
- "docs": "pnpm --filter @sd/docs -- ",
- "client": "pnpm --filter @sd/client -- ",
- "server": "pnpm --filter @sd/server -- ",
- "typecheck": "pnpm -r exec tsc"
- },
- "devDependencies": {
- "@cspell/dict-rust": "^2.0.1",
- "@cspell/dict-typescript": "^2.0.1",
- "@evilmartians/lefthook": "^1.0.5",
- "@trivago/prettier-plugin-sort-imports": "^3.3.0",
- "cspell": "^6.4.0",
- "markdown-link-check": "^3.10.2",
- "prettier": "^2.7.1",
- "typescript": "^4.7.4"
- },
- "overrides": {
- "vite-plugin-svgr": "https://github.com/spacedriveapp/vite-plugin-svgr#cb4195b69849429cdb18d1f12381676bf9196a84"
- },
- "engines": {
- "pnpm": ">=6.0.0",
- "npm": "pnpm",
- "yarn": "pnpm",
- "node": ">=14.0.0"
- }
+ "name": "spacedrive",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "prep": "pnpm db:gen && cargo test",
+ "build": "turbo run build",
+ "landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web",
+ "db:migrate": "cd crates && cargo prisma migrate dev",
+ "db:gen": "cd core && cargo prisma generate",
+ "format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
+ "desktop": "pnpm --filter @sd/desktop --",
+ "web": "pnpm --filter @sd/web -- ",
+ "landing": "pnpm --filter @sd/landing -- ",
+ "ui": "pnpm --filter @sd/ui -- ",
+ "interface": "pnpm --filter @sd/interface -- ",
+ "docs": "pnpm --filter @sd/docs -- ",
+ "client": "pnpm --filter @sd/client -- ",
+ "server": "pnpm --filter @sd/server -- ",
+ "typecheck": "pnpm -r exec tsc"
+ },
+ "devDependencies": {
+ "@cspell/dict-rust": "^2.0.1",
+ "@cspell/dict-typescript": "^2.0.1",
+ "@evilmartians/lefthook": "^1.0.5",
+ "@trivago/prettier-plugin-sort-imports": "^3.3.0",
+ "cspell": "^6.4.0",
+ "markdown-link-check": "^3.10.2",
+ "prettier": "^2.7.1",
+ "typescript": "^4.7.4"
+ },
+ "overrides": {
+ "vite-plugin-svgr": "https://github.com/spacedriveapp/vite-plugin-svgr#cb4195b69849429cdb18d1f12381676bf9196a84"
+ },
+ "engines": {
+ "pnpm": ">=6.0.0",
+ "npm": "pnpm",
+ "yarn": "pnpm",
+ "node": ">=14.0.0"
+ }
}
From 7d15ce0152ecea4a5b3ead374c53cf29c78a5b0d Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Tue, 4 Oct 2022 20:03:49 +0800
Subject: [PATCH 07/11] run rustfmt in ci
---
.github/workflows/ci.yml | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f6dffcccd..412634903 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -50,6 +50,22 @@ jobs:
- name: Perform typechecks
run: pnpm typecheck
+
+ rustfmt:
+ name: rustfmt
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ components: rustfmt
+
+ - name: Run rustfmt
+ run: cargo fmt --all -- --check
build-core:
name: Build Core (${{ matrix.platform }})
From e3f92ab4ecf8d071959a9ff887fb749722cf2b29 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Tue, 4 Oct 2022 20:07:05 +0800
Subject: [PATCH 08/11] please rustfmt
---
apps/mobile/rust/src/android.rs | 11 +++--------
apps/mobile/rust/src/ios.rs | 4 ++--
apps/server/src/main.rs | 11 +++--------
3 files changed, 8 insertions(+), 18 deletions(-)
diff --git a/apps/mobile/rust/src/android.rs b/apps/mobile/rust/src/android.rs
index 3f25a3dd4..19a185109 100644
--- a/apps/mobile/rust/src/android.rs
+++ b/apps/mobile/rust/src/android.rs
@@ -22,7 +22,7 @@ pub extern "system" fn Java_com_spacedrive_app_SDCore_registerCoreEventListener(
Err(err) => {
println!("Failed to serialize event: {}", err);
continue;
- },
+ }
};
let env = jvm.attach_current_thread().unwrap();
@@ -65,12 +65,7 @@ pub extern "system" fn Java_com_spacedrive_app_SDCore_handleCoreMsg(
let data_dir: String = {
let env = jvm.attach_current_thread().unwrap();
let data_dir = env
- .call_method(
- &class,
- "getDataDirectory",
- "()Ljava/lang/String;",
- &[],
- )
+ .call_method(&class, "getDataDirectory", "()Ljava/lang/String;", &[])
.unwrap()
.l()
.unwrap();
@@ -81,7 +76,7 @@ pub extern "system" fn Java_com_spacedrive_app_SDCore_handleCoreMsg(
let new_node = Node::new(data_dir).await.expect("Unable to create node");
node.replace(new_node.clone());
new_node
- },
+ }
};
let resp = serde_json::to_string(
diff --git a/apps/mobile/rust/src/ios.rs b/apps/mobile/rust/src/ios.rs
index 9abcc7c87..2d6ea5f55 100644
--- a/apps/mobile/rust/src/ios.rs
+++ b/apps/mobile/rust/src/ios.rs
@@ -45,7 +45,7 @@ pub unsafe extern "C" fn register_core_event_listener(id: *mut Object) {
Err(err) => {
println!("Failed to serialize event: {}", err);
continue;
- },
+ }
};
let data = NSString::from_str(&data);
let _: () = msg_send![id, sendCoreEvent: data];
@@ -73,7 +73,7 @@ pub unsafe extern "C" fn sd_core_msg(query: *const c_char, resolve: *const c_voi
let new_node = Node::new(doc_dir).await;
node.replace(new_node.clone());
new_node
- },
+ }
};
resolve.resolve(
diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs
index 85892f76e..26b1ea883 100644
--- a/apps/server/src/main.rs
+++ b/apps/server/src/main.rs
@@ -22,11 +22,9 @@ async fn main() {
}
std::env::current_dir()
- .expect(
- "Unable to get your current directory. Maybe try setting $DATA_DIR?",
- )
+ .expect("Unable to get your current directory. Maybe try setting $DATA_DIR?")
.join("sdserver_data")
- },
+ }
};
let port = env::var("PORT")
@@ -60,10 +58,7 @@ async fn main() {
"/rspcws",
router.axum_ws_handler(move || node.get_request_context()),
)
- .fallback(
- (|| async { "404 Not Found: We're past the event horizon..." })
- .into_service(),
- );
+ .fallback((|| async { "404 Not Found: We're past the event horizon..." }).into_service());
let mut addr = "[::]:8080".parse::().unwrap(); // This listens on IPv6 and IPv4
addr.set_port(port);
From ed06e3051ebd890e364c0c5413507195ae7325d4 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Tue, 4 Oct 2022 20:25:12 +0800
Subject: [PATCH 09/11] [ENG-84] Sync library (#394)
* new sql lib stuff
* add sync stuff + rename all crates
* build sd-core
* add sync/example/dist to source
* fix sync example in monorepop
* appease clippy
* update lockfile
* update commit hooks
* fix typescript
* fix typescript build
* please rustfmt
---
.github/workflows/ci.yml | 2 +-
Cargo.lock | Bin 151039 -> 152641 bytes
Cargo.toml | 1 +
apps/desktop/src-tauri/Cargo.toml | 10 +-
apps/desktop/src-tauri/src/main.rs | 2 +-
apps/mobile/rust/Cargo.toml | 2 +-
apps/mobile/rust/src/android.rs | 2 +-
apps/mobile/rust/src/ios.rs | 2 +-
apps/mobile/rust/src/lib.rs | 2 +-
apps/server/Cargo.toml | 2 +-
apps/server/src/main.rs | 2 +-
apps/server/src/utils.rs | 2 +-
core/Cargo.toml | 6 +-
crates/ffmpeg/Cargo.toml | 2 +-
crates/p2p/Cargo.toml | 2 +-
crates/sync/Cargo.toml | 8 +-
crates/sync/README.md | 4 +
crates/sync/docs/HLC.md | 79 +++++
crates/sync/docs/index.md | 115 +++++++
crates/sync/docs/prisma-schema.md | 52 +++
crates/sync/example/.gitignore | 2 +
crates/sync/example/.prettierrc | 3 +
crates/sync/example/.vscode/extensions.json | 3 +
crates/sync/example/README.md | 7 +
crates/sync/example/dist/.gitignore | 5 +
crates/sync/example/index.html | 17 +
crates/sync/example/package.json | 32 ++
crates/sync/example/pnpm-lock.yaml | Bin 0 -> 50259 bytes
crates/sync/example/postcss.config.js | 6 +
crates/sync/example/public/tauri.svg | 6 +
crates/sync/example/public/vite.svg | 1 +
crates/sync/example/src-tauri/.gitignore | 4 +
crates/sync/example/src-tauri/.taurignore | 0
crates/sync/example/src-tauri/Cargo.lock | Bin 0 -> 80155 bytes
crates/sync/example/src-tauri/Cargo.toml | 31 ++
crates/sync/example/src-tauri/build.rs | 3 +
.../sync/example/src-tauri/icons/128x128.png | Bin 0 -> 3512 bytes
.../example/src-tauri/icons/128x128@2x.png | Bin 0 -> 7012 bytes
crates/sync/example/src-tauri/icons/32x32.png | Bin 0 -> 974 bytes
.../src-tauri/icons/Square107x107Logo.png | Bin 0 -> 2863 bytes
.../src-tauri/icons/Square142x142Logo.png | Bin 0 -> 3858 bytes
.../src-tauri/icons/Square150x150Logo.png | Bin 0 -> 3966 bytes
.../src-tauri/icons/Square284x284Logo.png | Bin 0 -> 7737 bytes
.../src-tauri/icons/Square30x30Logo.png | Bin 0 -> 903 bytes
.../src-tauri/icons/Square310x310Logo.png | Bin 0 -> 8591 bytes
.../src-tauri/icons/Square44x44Logo.png | Bin 0 -> 1299 bytes
.../src-tauri/icons/Square71x71Logo.png | Bin 0 -> 2011 bytes
.../src-tauri/icons/Square89x89Logo.png | Bin 0 -> 2468 bytes
.../example/src-tauri/icons/StoreLogo.png | Bin 0 -> 1523 bytes
crates/sync/example/src-tauri/icons/icon.icns | Bin 0 -> 98451 bytes
crates/sync/example/src-tauri/icons/icon.ico | Bin 0 -> 86642 bytes
crates/sync/example/src-tauri/icons/icon.png | Bin 0 -> 14183 bytes
crates/sync/example/src-tauri/src/main.rs | 77 +++++
crates/sync/example/src-tauri/tauri.conf.json | 66 ++++
crates/sync/example/src/App.tsx | 75 +++++
crates/sync/example/src/bindings.ts | 15 +
crates/sync/example/src/index.css | 3 +
crates/sync/example/src/index.tsx | 17 +
crates/sync/example/src/rspc.ts | 22 ++
crates/sync/example/tailwind.config.js | 11 +
crates/sync/example/tsconfig.json | 15 +
crates/sync/example/vite.config.ts | 25 ++
crates/sync/src/attribute/mod.rs | 41 +++
crates/sync/src/attribute/parser.rs | 155 +++++++++
crates/sync/src/crdt.rs | 72 ++++
crates/sync/src/datamodel/field.rs | 186 +++++++++++
crates/sync/src/datamodel/mod.rs | 39 +++
crates/sync/src/datamodel/model.rs | 314 ++++++++++++++++++
crates/sync/src/db.rs | 182 ++++++++++
crates/sync/src/generator/client.rs | 182 ++++++++++
crates/sync/src/generator/mod.rs | 55 +++
crates/sync/src/generator/model/actions.rs | 90 +++++
crates/sync/src/generator/model/create.rs | 186 +++++++++++
.../sync/src/generator/model/create_params.rs | 203 +++++++++++
crates/sync/src/generator/model/delete.rs | 24 ++
crates/sync/src/generator/model/mod.rs | 74 +++++
crates/sync/src/generator/model/owned/mod.rs | 46 +++
.../sync/src/generator/model/relation/mod.rs | 108 ++++++
crates/sync/src/generator/model/set_param.rs | 181 ++++++++++
crates/sync/src/generator/model/shared/mod.rs | 124 +++++++
crates/sync/src/generator/model/sync_id.rs | 202 +++++++++++
crates/sync/src/generator/model/update.rs | 27 ++
crates/sync/src/lib.rs | 5 +
crates/sync/src/main.rs | 61 +++-
lefthook.yml | 2 +-
package.json | 3 +-
packages/interface/package.json | 186 +++++------
packages/ui/package.json | 16 +-
pnpm-lock.yaml | Bin 672390 -> 695278 bytes
pnpm-workspace.yaml | 1 +
90 files changed, 3382 insertions(+), 126 deletions(-)
create mode 100644 crates/sync/README.md
create mode 100644 crates/sync/docs/HLC.md
create mode 100644 crates/sync/docs/index.md
create mode 100644 crates/sync/docs/prisma-schema.md
create mode 100644 crates/sync/example/.gitignore
create mode 100644 crates/sync/example/.prettierrc
create mode 100644 crates/sync/example/.vscode/extensions.json
create mode 100644 crates/sync/example/README.md
create mode 100644 crates/sync/example/dist/.gitignore
create mode 100644 crates/sync/example/index.html
create mode 100644 crates/sync/example/package.json
create mode 100644 crates/sync/example/pnpm-lock.yaml
create mode 100644 crates/sync/example/postcss.config.js
create mode 100644 crates/sync/example/public/tauri.svg
create mode 100644 crates/sync/example/public/vite.svg
create mode 100644 crates/sync/example/src-tauri/.gitignore
create mode 100644 crates/sync/example/src-tauri/.taurignore
create mode 100644 crates/sync/example/src-tauri/Cargo.lock
create mode 100644 crates/sync/example/src-tauri/Cargo.toml
create mode 100644 crates/sync/example/src-tauri/build.rs
create mode 100644 crates/sync/example/src-tauri/icons/128x128.png
create mode 100644 crates/sync/example/src-tauri/icons/128x128@2x.png
create mode 100644 crates/sync/example/src-tauri/icons/32x32.png
create mode 100644 crates/sync/example/src-tauri/icons/Square107x107Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square142x142Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square150x150Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square284x284Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square30x30Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square310x310Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square44x44Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square71x71Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/Square89x89Logo.png
create mode 100644 crates/sync/example/src-tauri/icons/StoreLogo.png
create mode 100644 crates/sync/example/src-tauri/icons/icon.icns
create mode 100644 crates/sync/example/src-tauri/icons/icon.ico
create mode 100644 crates/sync/example/src-tauri/icons/icon.png
create mode 100644 crates/sync/example/src-tauri/src/main.rs
create mode 100644 crates/sync/example/src-tauri/tauri.conf.json
create mode 100644 crates/sync/example/src/App.tsx
create mode 100644 crates/sync/example/src/bindings.ts
create mode 100644 crates/sync/example/src/index.css
create mode 100644 crates/sync/example/src/index.tsx
create mode 100644 crates/sync/example/src/rspc.ts
create mode 100644 crates/sync/example/tailwind.config.js
create mode 100644 crates/sync/example/tsconfig.json
create mode 100644 crates/sync/example/vite.config.ts
create mode 100644 crates/sync/src/attribute/mod.rs
create mode 100644 crates/sync/src/attribute/parser.rs
create mode 100644 crates/sync/src/crdt.rs
create mode 100644 crates/sync/src/datamodel/field.rs
create mode 100644 crates/sync/src/datamodel/mod.rs
create mode 100644 crates/sync/src/datamodel/model.rs
create mode 100644 crates/sync/src/db.rs
create mode 100644 crates/sync/src/generator/client.rs
create mode 100644 crates/sync/src/generator/mod.rs
create mode 100644 crates/sync/src/generator/model/actions.rs
create mode 100644 crates/sync/src/generator/model/create.rs
create mode 100644 crates/sync/src/generator/model/create_params.rs
create mode 100644 crates/sync/src/generator/model/delete.rs
create mode 100644 crates/sync/src/generator/model/mod.rs
create mode 100644 crates/sync/src/generator/model/owned/mod.rs
create mode 100644 crates/sync/src/generator/model/relation/mod.rs
create mode 100644 crates/sync/src/generator/model/set_param.rs
create mode 100644 crates/sync/src/generator/model/shared/mod.rs
create mode 100644 crates/sync/src/generator/model/sync_id.rs
create mode 100644 crates/sync/src/generator/model/update.rs
create mode 100644 crates/sync/src/lib.rs
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 412634903..eb51550b1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -148,7 +148,7 @@ jobs:
run: cargo fetch
- name: Check Core
- run: cargo check -p sdcore --release
+ run: cargo check -p sd-core --release
- name: Bundle Desktop
run: pnpm desktop tauri build
diff --git a/Cargo.lock b/Cargo.lock
index 1cd3a97a32564a1fbc5b434893f6930e0d6e2edd..93040d70f66277f4492eb18c896f6fb2bc9a4d6c 100644
GIT binary patch
delta 1185
zcmZvcOK4qH6oy$zOw%N7jftnNu}uipf+Uyyeqd`-38F1&Lvfn()44^XXahGbZvjCe*<@<7gk^>{kjVGt-U|<#bnpwoS9!ZH5baIeP&!6
zYPgLSYpy8{!BQ$LG14+G1@fY9>8pR5O9Fq%yy-hUXaVd)8(?JUCx{ItVuo
zbmlZrjaMHZfj4_f(?9)?W`TsLyp4`BX)$t0Go?|KQiW`y!WnHXBZ34aywlowf@PpQ
zcTA@BZ3@ngZH1<3?i@Up7SF<6>7!maIUzBLE^9<5HPJamq@rP5@`!a5T1Xpn
z9Vk$u31h-XOEr;HnP4m@RF@n5gX!ur^mN9>g~diFpSw2PQx9&zY?i`WdI!=UyK@?!
zoP*h&>De1lr12Ff%ITuB%AN;dV5{nXn;+v`Jv^1$Ka|GKL%D5i>qB~V5C+nvW#~^Q
zmf;B)OD_$==(c~Rb<5g)Yn-hSb}Y`!y7coZJdi#=4=?xKuH{ZQlId+Ls`(e;y}ti3
zZu|ncntBU*@7!^lXIgK--POVhT!8fL3iPGtkHPdfS4u>}N#u%}+%wNiAlMO-ku}U%
z>J39i1{=I!B0sj8k;stZh-IeJ_g7#~dVLahw2K-_mmh(F>g!eLD5~}^;mkeha#z;C
z$Zt}XX`@aftgN;sYVWLdEM~MQL7rGHg*6VNC`^b3vDR2o=AtxLinRX+cyue8R>Wh;
znXpXad=eF9&Nonx6caoOn_l#cl>mo`DG
z={U`_Qj!VGO)x|#jHDFY7#zIH1ja%t!V&gZ1x`IumPsrrwZRCd;u3*Ioc|6-*T!W9
zEvVMmvS2Ln3gI9L!;%oigJW28CpZc!IH@gWItWLM3c`DuNtG{l8u`ItnXdeRQv-3N
zRyah6Ag3fpNEY`OLQ3zP(uio4F{@eeDdMg+7jSpNg{wA_cvXzG_s$7lZo(6qHb
diff --git a/Cargo.toml b/Cargo.toml
index 1755e99b7..e94d34133 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
members = [
"core",
"crates/*",
+ "crates/sync/example/src-tauri",
"apps/desktop/src-tauri",
"apps/mobile/rust",
"apps/server",
diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml
index ec1592f58..62f0b2a48 100644
--- a/apps/desktop/src-tauri/Cargo.toml
+++ b/apps/desktop/src-tauri/Cargo.toml
@@ -12,7 +12,7 @@ build = "build.rs"
[dependencies]
tauri = { version = "1.0.4", features = ["api-all", "macos-private-api"] }
rspc = { version = "0.0.5", features = ["tauri"] }
-sdcore = { path = "../../../core", features = ["ffmpeg"] }
+sd-core = { path = "../../../core", features = ["ffmpeg"] }
tokio = { version = "1.17.0", features = ["sync"] }
window-shadows = "0.1.2"
tracing = "0.1.35"
@@ -25,8 +25,10 @@ swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", branch = "aut
tauri-build = { version = "1.0.0", features = [] }
[target.'cfg(target_os = "macos")'.build-dependencies]
-swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", branch = "autorelease", features = ["build"] }
+swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", branch = "autorelease", features = [
+ "build",
+] }
[features]
-default = [ "custom-protocol" ]
-custom-protocol = [ "tauri/custom-protocol" ]
+default = ["custom-protocol"]
+custom-protocol = ["tauri/custom-protocol"]
diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs
index 891a8cbce..ccd97e1ae 100644
--- a/apps/desktop/src-tauri/src/main.rs
+++ b/apps/desktop/src-tauri/src/main.rs
@@ -6,7 +6,7 @@
use std::error::Error;
use std::path::PathBuf;
-use sdcore::Node;
+use sd_core::Node;
use tauri::async_runtime::block_on;
use tauri::{
api::path,
diff --git a/apps/mobile/rust/Cargo.toml b/apps/mobile/rust/Cargo.toml
index 42d673cfe..955690969 100644
--- a/apps/mobile/rust/Cargo.toml
+++ b/apps/mobile/rust/Cargo.toml
@@ -10,7 +10,7 @@ crate-type = ["staticlib", "cdylib"] # staticlib for IOS and cdylib for Android
[dependencies]
once_cell = "1.13.0"
-sdcore = { path = "../../../core", features = [
+sd-core = { path = "../../../core", features = [
"mobile",
"p2p",
], default-features = false }
diff --git a/apps/mobile/rust/src/android.rs b/apps/mobile/rust/src/android.rs
index 19a185109..e7af9be6f 100644
--- a/apps/mobile/rust/src/android.rs
+++ b/apps/mobile/rust/src/android.rs
@@ -2,7 +2,7 @@ use crate::{CLIENT_CONTEXT, EVENT_SENDER, NODE, RUNTIME};
use jni::objects::{JClass, JObject, JString};
use jni::JNIEnv;
use rspc::Request;
-use sdcore::Node;
+use sd_core::Node;
use tokio::sync::mpsc::unbounded_channel;
#[no_mangle]
diff --git a/apps/mobile/rust/src/ios.rs b/apps/mobile/rust/src/ios.rs
index 2d6ea5f55..3f9c0821f 100644
--- a/apps/mobile/rust/src/ios.rs
+++ b/apps/mobile/rust/src/ios.rs
@@ -9,7 +9,7 @@ use objc::{class, msg_send, runtime::Object, sel, sel_impl};
use objc_foundation::{INSString, NSString};
use objc_id::Id;
use rspc::Request;
-use sdcore::Node;
+use sd_core::Node;
extern "C" {
fn get_data_directory() -> *const c_char;
diff --git a/apps/mobile/rust/src/lib.rs b/apps/mobile/rust/src/lib.rs
index 1619101c2..fb837282c 100644
--- a/apps/mobile/rust/src/lib.rs
+++ b/apps/mobile/rust/src/lib.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
use once_cell::sync::{Lazy, OnceCell};
use rspc::{ClientContext, Response};
-use sdcore::{api::Router, Node};
+use sd_core::{api::Router, Node};
use tokio::{
runtime::Runtime,
sync::{mpsc::UnboundedSender, Mutex},
diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml
index 99bd2e887..1344cc92d 100644
--- a/apps/server/Cargo.toml
+++ b/apps/server/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-sdcore = { path = "../../core", features = [] }
+sd-core = { path = "../../core", features = [] }
rspc = { version = "0.0.5", features = ["axum"] }
axum = "0.5.13"
tokio = { version = "1.17.0", features = ["sync", "rt-multi-thread", "signal"] }
diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs
index 26b1ea883..d4063c43d 100644
--- a/apps/server/src/main.rs
+++ b/apps/server/src/main.rs
@@ -6,7 +6,7 @@ use axum::{
http::{header::CONTENT_TYPE, HeaderMap, StatusCode},
routing::get,
};
-use sdcore::Node;
+use sd_core::Node;
use tracing::info;
mod utils;
diff --git a/apps/server/src/utils.rs b/apps/server/src/utils.rs
index a6bdc360a..93fca62b6 100644
--- a/apps/server/src/utils.rs
+++ b/apps/server/src/utils.rs
@@ -1,6 +1,6 @@
use std::sync::Arc;
-use sdcore::Node;
+use sd_core::Node;
use tokio::signal;
/// shutdown_signal will inform axum to gracefully shutdown when the process is asked to shutdown.
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 8f1e940c7..7b7d898a9 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "sdcore"
+name = "sd-core"
version = "0.1.0"
description = "Virtual distributed filesystem engine that powers Spacedrive."
authors = ["Spacedrive Technology Inc."]
@@ -16,7 +16,7 @@ mobile = [
] # This feature allows features to be disabled when the Core is running on mobile.
ffmpeg = [
"dep:ffmpeg-next",
- "dep:sd_ffmpeg",
+ "dep:sd-ffmpeg",
] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg.
[dependencies]
@@ -51,7 +51,7 @@ async-trait = "^0.1.52"
image = "0.24.1"
webp = "0.2.2"
ffmpeg-next = { version = "5.0.3", optional = true, features = [] }
-sd_ffmpeg = { path = "../crates/ffmpeg", optional = true }
+sd-ffmpeg = { path = "../crates/ffmpeg", optional = true }
fs_extra = "1.2.0"
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }
diff --git a/crates/ffmpeg/Cargo.toml b/crates/ffmpeg/Cargo.toml
index 216a7103b..fb04f88f1 100644
--- a/crates/ffmpeg/Cargo.toml
+++ b/crates/ffmpeg/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "sd_ffmpeg"
+name = "sd-ffmpeg"
version = "0.1.0"
authors = ["Ericson Soares "]
edition = "2021"
diff --git a/crates/p2p/Cargo.toml b/crates/p2p/Cargo.toml
index a564b62cf..e59d768f9 100644
--- a/crates/p2p/Cargo.toml
+++ b/crates/p2p/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "p2p"
+name = "sd-p2p"
version = "0.1.0"
edition = "2021"
diff --git a/crates/sync/Cargo.toml b/crates/sync/Cargo.toml
index ac99e6ce2..6c2435139 100644
--- a/crates/sync/Cargo.toml
+++ b/crates/sync/Cargo.toml
@@ -1,8 +1,12 @@
[package]
-name = "sync"
+name = "sd-sync"
version = "0.1.0"
edition = "2021"
-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+rspc = { version = "0.1.2", features = ["uuid"] }
+serde = "1.0.145"
+serde_json = "1.0.85"
+uhlc = "0.5.1"
+uuid = { version = "1.1.2", features = ["serde", "v4"] }
diff --git a/crates/sync/README.md b/crates/sync/README.md
new file mode 100644
index 000000000..211cd3dc6
--- /dev/null
+++ b/crates/sync/README.md
@@ -0,0 +1,4 @@
+# crdt-rs
+
+Just @brendonovich experimenting with CRDT stuff.
+
diff --git a/crates/sync/docs/HLC.md b/crates/sync/docs/HLC.md
new file mode 100644
index 000000000..4c10ef0ca
--- /dev/null
+++ b/crates/sync/docs/HLC.md
@@ -0,0 +1,79 @@
+
+
+```rust
+pub fn update_with_timestamp(&self, timestamp: &Timestamp) -> Result<(), String> {
+ let mut now = (self.clock)();
+ now.0 &= LMASK;
+ let msg_time = timestamp.get_time();
+ if *msg_time > now && *msg_time - now > self.delta {
+ let err_msg = format!(
+ "incoming timestamp from {} exceeding delta {}ms is rejected: {} vs. now: {}",
+ timestamp.get_id(),
+ self.delta.to_duration().as_millis(),
+ msg_time,
+ now
+ );
+ warn!("{}", err_msg);
+ Err(err_msg)
+ } else {
+ let mut last_time = lock!(self.last_time);
+ let max_time = cmp::max(cmp::max(now, *msg_time), *last_time);
+ if max_time == now {
+ *last_time = now;
+ } else if max_time == *msg_time {
+ *last_time = *msg_time + 1;
+ } else {
+ *last_time += 1;
+ }
+ Ok(())
+ }
+}
+```
+
+```javascript
+Timestamp.recv = function(msg) {
+ if (!clock) {
+ return null;
+ }
+
+ var now = Date.now();
+
+ var msg_time = msg.millis();
+ var msg_time = msg.counter();
+
+ if (msg_time - now > config.maxDrift) {
+ throw new Timestamp.ClockDriftError();
+ }
+
+ var last_time = clock.timestamp.millis();
+ var last_time = clock.timestamp.counter();
+
+ var max_time = Math.max(Math.max(last_time, now), msg_time);
+
+ var last_time =
+ max_time === last_time && lNew === msg_time
+ ? Math.max(last_time, msg_time) + 1
+ : max_time === last_time
+ ? last_time + 1
+ : max_time === msg_time
+ ? msg_time + 1
+ : 0;
+
+ // 3.
+ if (max_time - phys > config.maxDrift) {
+ throw new Timestamp.ClockDriftError();
+ }
+ if (last_time > MAX_COUNTER) {
+ throw new Timestamp.OverflowError();
+ }
+
+ clock.timestamp.setMillis(max_time);
+ clock.timestamp.setCounter(last_time);
+
+ return new Timestamp(
+ clock.timestamp.millis(),
+ clock.timestamp.counter(),
+ clock.timestamp.node()
+ );
+};
+```
diff --git a/crates/sync/docs/index.md b/crates/sync/docs/index.md
new file mode 100644
index 000000000..d83395995
--- /dev/null
+++ b/crates/sync/docs/index.md
@@ -0,0 +1,115 @@
+# Owned Records
+
+Node which owns the record is the sole source of truth for that record's state
+
+# Shared Records
+
+This includes Shared Record O-M Relations, since the foreign key is stored on the Many record.
+
+## Create
+
+```json
+{
+ "type": "CREATE",
+ "recordId": "{uuid}",
+ "model": "{model}",
+ "data": {
+ "key": "value"
+ },
+ "node": "{uuid}",
+ "timestamp": {
+ "hybrid": "logical clock"
+ }
+}
+```
+
+## Update
+
+```json
+{
+ "type": "UPDATE",
+ "recordId": "{uuid}",
+ "field": "{field}",
+ "value": "{value}",
+ "node": "{uuid}",
+ "timestamp": {
+ "hybrid": "logical clock"
+ }
+}
+```
+
+## Delete
+
+```json
+{
+ "type": "DELETE",
+ "recordId": "{uuid}",
+ "node": "{uuid}",
+ "timestamp": {
+ "hybrid": "logical clock"
+ }
+}
+```
+
+# Shared Record M-M Relations
+
+x-M relations usually signify an item belonging to a group. 1-M relations are handled normally by Shared Records since the ID of the record is just the ID of the M record. M-M relations require custom handling since they are identified by the two records they join, so 2 create instructions
+
+UNANSWERED: M-M relations that _can_ be duplicated. In this case, a single ID for the relation would suffice, in the same way that 1-M relations do.
+
+NOTE: Ordering is very important when syncing relations. If a target of a relation doesn't exist, what should happen? This presents two options:
+
+1. Don't use a foreign key, just join/fetch separately on a possibly non-existent foreign id. This is pretty cringe since Prisma only affords the niceties of relations if foreign keys are actually used.
+2. Require that all operations are synced in order, independent of which node they were created on. This is nicer since it means that in order for a node to create a relation in the first place it must possess a message indicating creation of the relation target, but it is much more difficult to coordinate deletion of messages this way. Probably still doable though.
+
+Option 2 is probably the best way to go, since having to do annoying joins and losing database ergonomics is not great. Additionally, option 2 would result in the ability to sync shared data between any two nodes, even if the node being synced from didn't create the operation in the first place.
+
+## Create
+
+```json
+{
+ "type": "CREATE",
+ // Record that is being assigned to a group eg. a file
+ "relationItem": "{uuid}",
+ // Group that the record is being assigned to eg. a photo album
+ "relationGroup": "{uuid}",
+ // Name of the model which represents the relation
+ "relation": "model",
+ "node": "{uuid}",
+ "timestamp": {
+ "hybrid": "logical clock"
+ }
+}
+```
+
+## Update
+
+```json
+{
+ "type": "UPDATE",
+ "relationItem": "{uuid}",
+ "relationGroup": "{uuid}",
+ "relation": "model",
+ "field": "field",
+ "value": "{value}",
+ "node": "{uuid}",
+ "timestamp": {
+ "hybrid": "logical clock"
+ }
+}
+```
+
+## Delete
+
+```json
+{
+ "type": "DELETE",
+ "relationItem": "{uuid}",
+ "relationGroup": "{uuid}",
+ "relation": "relation",
+ "node": "{uuid}",
+ "timestamp": {
+ "hybrid": "logical clock"
+ }
+}
+```
diff --git a/crates/sync/docs/prisma-schema.md b/crates/sync/docs/prisma-schema.md
new file mode 100644
index 000000000..cb14b555f
--- /dev/null
+++ b/crates/sync/docs/prisma-schema.md
@@ -0,0 +1,52 @@
+# Prisma Schema
+
+`prisma-crdt` introduces new attributes that must be applied to fields and models via triple slash documentation comments.
+
+_Sync ID_: As well as having a primary key - denoted in Prisma with the `@id` field attribute - `prisma-crdt` introduces another ID - the _Sync ID_.
+A model's Sync ID defaults to its regular ID, and is what identifies a model inside a sync operation.
+Regular IDs are often not suitable for use inside a sync operation, as they may not be unique when sent to other nodes - eg. autoincrementing IDs - so something more unique can used, like a UUID.
+
+## Model Attributes
+
+#### `@local`
+
+Model that is entirely disconnected from sync.
+
+_Arguments_
+
+- `id` (optional): Scalar field to override the default Sync ID.
+
+#### `@owned`
+
+Model that is synced via replicating from its owner to all other nodes, with the other nodes treating the model's owner as its single source of truth.
+
+_Arguments_
+
+- `owner`: Field that identifies the owner of this model. If a scalar, will directly use that value in sync operations. If a relation, the Sync ID of the related model will be resolved for sync operations.
+- `id` (optional): Scalar field to override the default Sync ID.
+
+#### `@shared`
+
+Model that is synced via updates on a per-field basis.
+
+_Arguments_
+
+- `id` (optional): Scalar field to override the default Sync ID.
+- `create` (optional): How the model should be created.
+ - `Unique` (default): Model can be created with many required arguemnts, but ID provided _must_ be unique across all nodes. Useful for Tags since their IDs are non-deterministic.
+ - `Atomic`: Require the model to have no required arguments apart from ID and apply all create arguments as atomic updates. Necessary for models with the same ID that can be created on multiple nodes. Useful for Files since their ID is dependent on their content, and could be the same across nodes.
+
+#### `@relation`
+
+Similar to `@shared`, but identified by the two records that it relates. Sync ID is always the combination of `item` and `group`.
+
+_Arguments_
+
+- `item`: Field that identifies the item that the relation links to. Operates like the `owner` argument of `@owned`.
+- `group`: Field that identifies the group that the item should be related to. Operates like the `owner` argument of `@owned`.
+
+## Field Attributes
+
+#### `@node`
+
+A relation whose value is automatically set to the current node. This could be done manually, but `@node` allows `node_id` fields to not be stored in `OwnedOperationData`, but rather be resolved from the `node_id` field of a `CRDTOperation`, saving on bandwidth.
diff --git a/crates/sync/example/.gitignore b/crates/sync/example/.gitignore
new file mode 100644
index 000000000..76add878f
--- /dev/null
+++ b/crates/sync/example/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist
\ No newline at end of file
diff --git a/crates/sync/example/.prettierrc b/crates/sync/example/.prettierrc
new file mode 100644
index 000000000..1c5e96602
--- /dev/null
+++ b/crates/sync/example/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "printWidth": 80
+}
diff --git a/crates/sync/example/.vscode/extensions.json b/crates/sync/example/.vscode/extensions.json
new file mode 100644
index 000000000..24d7cc6de
--- /dev/null
+++ b/crates/sync/example/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
+}
diff --git a/crates/sync/example/README.md b/crates/sync/example/README.md
new file mode 100644
index 000000000..648e2c163
--- /dev/null
+++ b/crates/sync/example/README.md
@@ -0,0 +1,7 @@
+# Tauri + Solid + Typescript
+
+This template should help get you started developing with Tauri, Solid and Typescript in Vite.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
diff --git a/crates/sync/example/dist/.gitignore b/crates/sync/example/dist/.gitignore
new file mode 100644
index 000000000..c53272268
--- /dev/null
+++ b/crates/sync/example/dist/.gitignore
@@ -0,0 +1,5 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
+# This is done so that Tauri never complains that '../dist does not exist'
diff --git a/crates/sync/example/index.html b/crates/sync/example/index.html
new file mode 100644
index 000000000..7cb13f956
--- /dev/null
+++ b/crates/sync/example/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ Tauri + Solid + Typescript App
+
+
+
+
+
+
+
+
+
diff --git a/crates/sync/example/package.json b/crates/sync/example/package.json
new file mode 100644
index 000000000..1b3589ea6
--- /dev/null
+++ b/crates/sync/example/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@sd/sync-example",
+ "version": "0.0.0",
+ "description": "",
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build": "vite build",
+ "serve": "vite preview",
+ "tauri": "tauri"
+ },
+ "license": "MIT",
+ "devDependencies": {
+ "@tauri-apps/cli": "^1.1.0",
+ "@types/babel__core": "^7.1.19",
+ "@types/node": "^18.7.10",
+ "autoprefixer": "^10.4.12",
+ "postcss": "^8.4.16",
+ "tailwindcss": "^3.1.8",
+ "typescript": "^4.7.4",
+ "vite": "^3.0.0",
+ "vite-plugin-solid": "^2.3.0"
+ },
+ "dependencies": {
+ "@rspc/client": "~0.1.2",
+ "@rspc/solid": "~0.1.2",
+ "@rspc/tauri": "~0.1.2",
+ "@tanstack/solid-query": "4.7.1",
+ "clsx": "^1.2.1",
+ "solid-js": "^1.4.7"
+ }
+}
diff --git a/crates/sync/example/pnpm-lock.yaml b/crates/sync/example/pnpm-lock.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f4d0872a3628c0d3712f2736cf4d99e5670ca989
GIT binary patch
literal 50259
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
+
+
+
+
+
diff --git a/crates/sync/example/public/vite.svg b/crates/sync/example/public/vite.svg
new file mode 100644
index 000000000..e7b8dfb1b
--- /dev/null
+++ b/crates/sync/example/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/crates/sync/example/src-tauri/.gitignore b/crates/sync/example/src-tauri/.gitignore
new file mode 100644
index 000000000..f4dfb82b2
--- /dev/null
+++ b/crates/sync/example/src-tauri/.gitignore
@@ -0,0 +1,4 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
diff --git a/crates/sync/example/src-tauri/.taurignore b/crates/sync/example/src-tauri/.taurignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/crates/sync/example/src-tauri/Cargo.lock b/crates/sync/example/src-tauri/Cargo.lock
new file mode 100644
index 0000000000000000000000000000000000000000..97cb9e3f27bf50f10fcbe6475f320f53e0e4e417
GIT binary patch
literal 80155
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