diff --git a/package.json b/package.json
index a9e568f..7b23b3b 100644
--- a/package.json
+++ b/package.json
@@ -16,48 +16,49 @@
"eslint:fix": "eslint --fix"
},
"dependencies": {
- "@libsql/client": "^0.15.8",
+ "@libsql/client": "^0.15.9",
"@nuxt/eslint": "1.4.1",
- "@nuxt/icon": "^1.13.0",
+ "@nuxt/icon": "^1.14.0",
"@nuxt/image": "1.10.0",
"@nuxtjs/i18n": "^9.5.5",
- "@pinia/nuxt": "^0.11.0",
- "@tailwindcss/vite": "^4.1.8",
+ "@pinia/nuxt": "^0.11.1",
+ "@tailwindcss/vite": "^4.1.10",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "^2.3.0",
"@tauri-apps/plugin-http": "~2.4.4",
- "@tauri-apps/plugin-notification": "~2.2.2",
- "@tauri-apps/plugin-opener": "^2.2.7",
- "@tauri-apps/plugin-os": "^2.2.1",
- "@tauri-apps/plugin-sql": "~2.2.0",
- "@tauri-apps/plugin-store": "^2.2.0",
- "@vueuse/components": "^13.3.0",
- "@vueuse/core": "^13.3.0",
- "@vueuse/nuxt": "^13.3.0",
- "drizzle-orm": "^0.43.1",
- "eslint": "^9.28.0",
+ "@tauri-apps/plugin-notification": "~2.2.3",
+ "@tauri-apps/plugin-opener": "^2.3.0",
+ "@tauri-apps/plugin-os": "^2.2.2",
+ "@tauri-apps/plugin-sql": "~2.2.1",
+ "@tauri-apps/plugin-store": "^2.2.1",
+ "@vlcn.io/crsqlite": "^0.16.3",
+ "@vueuse/components": "^13.4.0",
+ "@vueuse/core": "^13.4.0",
+ "@vueuse/nuxt": "^13.4.0",
+ "drizzle-orm": "^0.44.2",
+ "eslint": "^9.29.0",
"flyonui": "^2.2.0",
"fuse.js": "^7.1.0",
- "nuxt": "^3.17.4",
+ "nuxt": "^3.17.5",
"nuxt-snackbar": "1.3.0",
- "nuxt-zod-i18n": "^1.11.5",
- "tailwindcss": "^4.1.8",
+ "nuxt-zod-i18n": "^1.12.0",
+ "tailwindcss": "^4.1.10",
"tailwindcss-intersect": "^2.2.0",
- "tailwindcss-motion": "^1.1.0",
- "vue": "^3.5.16",
+ "tailwindcss-motion": "^1.1.1",
+ "vue": "^3.5.17",
"vue-router": "^4.5.1",
- "zod": "^3.25.42"
+ "zod": "^3.25.67"
},
"devDependencies": {
- "@iconify/json": "^2.2.343",
+ "@iconify/json": "^2.2.351",
"@iconify/tailwind4": "^1.0.6",
"@tauri-apps/cli": "^2.5.0",
- "drizzle-kit": "^0.31.1",
+ "drizzle-kit": "^0.31.2",
"globals": "^16.2.0",
"typescript": "^5.8.3"
},
- "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
+ "packageManager": "pnpm@10.12.2",
"pnpm": {
"ignoredBuiltDependencies": [
"@parcel/watcher",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 107a1cb..5d41e66 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,6 +56,9 @@ importers:
'@tauri-apps/plugin-store':
specifier: ^2.2.0
version: 2.2.1
+ '@vlcn.io/crsqlite':
+ specifier: ^0.16.3
+ version: 0.16.3
'@vueuse/components':
specifier: ^13.3.0
version: 13.4.0(vue@3.5.17(typescript@5.8.3))
@@ -1938,6 +1941,9 @@ packages:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
+ '@vlcn.io/crsqlite@0.16.3':
+ resolution: {integrity: sha512-1rKylRr2LyW5kwh/a6ZFmvt7kFK+aKLGkz9+O1w9EFVdH11SMUg6MfvxTfoczKCyb3/E5qvdOk2NDW/Zin6OjQ==}
+
'@volar/language-core@2.4.14':
resolution: {integrity: sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==}
@@ -7302,6 +7308,8 @@ snapshots:
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)
vue: 3.5.17(typescript@5.8.3)
+ '@vlcn.io/crsqlite@0.16.3': {}
+
'@volar/language-core@2.4.14':
dependencies:
'@volar/source-map': 2.4.14
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 352206b..0b2bd60 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -1553,6 +1553,7 @@ dependencies = [
"tauri-plugin-os",
"tauri-plugin-store",
"tokio",
+ "uhlc",
]
[[package]]
@@ -1663,6 +1664,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+[[package]]
+name = "humantime"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
+
[[package]]
name = "hyper"
version = "1.6.0"
@@ -3877,13 +3884,31 @@ dependencies = [
]
[[package]]
-name = "sqlparser"
-version = "0.56.0"
+name = "spin"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e68feb51ffa54fc841e086f58da543facfe3d7ae2a60d69b0a8cbbd30d16ae8d"
+checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
+
+[[package]]
+name = "sqlparser"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07c5f081b292a3d19637f0b32a79e28ff14a9fd23ef47bd7fce08ff5de221eca"
dependencies = [
"log",
"recursive",
+ "sqlparser_derive",
+]
+
+[[package]]
+name = "sqlparser_derive"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
]
[[package]]
@@ -4077,7 +4102,7 @@ dependencies = [
"tao-macros",
"unicode-segmentation",
"url",
- "windows 0.61.1",
+ "windows",
"windows-core 0.61.0",
"windows-version",
"x11-dl",
@@ -4149,7 +4174,7 @@ dependencies = [
"webkit2gtk",
"webview2-com",
"window-vibrancy",
- "windows 0.61.1",
+ "windows",
]
[[package]]
@@ -4252,9 +4277,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
-version = "2.2.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a1edf18000f02903a7c2e5997fb89aca455ecbc0acc15c6535afbb883be223"
+checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da"
dependencies = [
"anyhow",
"dunce",
@@ -4270,7 +4295,6 @@ dependencies = [
"thiserror 2.0.12",
"toml",
"url",
- "uuid",
]
[[package]]
@@ -4316,9 +4340,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-opener"
-version = "2.2.6"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fdc6cb608e04b7d2b6d1f21e9444ad49245f6d03465ba53323d692d1ceb1a30"
+checksum = "2c8983f50326d34437142a6d560b5c3426e91324297519b6eeb32ed0a1d1e0f2"
dependencies = [
"dunce",
"glob",
@@ -4332,7 +4356,7 @@ dependencies = [
"tauri-plugin",
"thiserror 2.0.12",
"url",
- "windows 0.60.0",
+ "windows",
"zbus",
]
@@ -4389,7 +4413,7 @@ dependencies = [
"tauri-utils",
"thiserror 2.0.12",
"url",
- "windows 0.61.1",
+ "windows",
]
[[package]]
@@ -4415,7 +4439,7 @@ dependencies = [
"url",
"webkit2gtk",
"webview2-com",
- "windows 0.61.1",
+ "windows",
"wry",
]
@@ -4475,7 +4499,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
dependencies = [
"quick-xml 0.37.5",
"thiserror 2.0.12",
- "windows 0.61.1",
+ "windows",
"windows-version",
]
@@ -4822,6 +4846,20 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "uhlc"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66bbb93b0c2258fe1e81a84d8de5391f2577b039decabf75a6441ea1ebbf4cb5"
+dependencies = [
+ "humantime",
+ "lazy_static",
+ "log",
+ "rand 0.8.5",
+ "serde",
+ "spin",
+]
+
[[package]]
name = "unic-char-property"
version = "0.9.0"
@@ -5182,9 +5220,9 @@ checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601"
dependencies = [
"webview2-com-macros",
"webview2-com-sys",
- "windows 0.61.1",
+ "windows",
"windows-core 0.61.0",
- "windows-implement 0.60.0",
+ "windows-implement",
"windows-interface",
]
@@ -5206,7 +5244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295"
dependencies = [
"thiserror 2.0.12",
- "windows 0.61.1",
+ "windows",
"windows-core 0.61.0",
]
@@ -5256,39 +5294,17 @@ dependencies = [
"windows-version",
]
-[[package]]
-name = "windows"
-version = "0.60.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529"
-dependencies = [
- "windows-collections 0.1.1",
- "windows-core 0.60.1",
- "windows-future 0.1.1",
- "windows-link",
- "windows-numerics 0.1.1",
-]
-
[[package]]
name = "windows"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
- "windows-collections 0.2.0",
+ "windows-collections",
"windows-core 0.61.0",
- "windows-future 0.2.0",
+ "windows-future",
"windows-link",
- "windows-numerics 0.2.0",
-]
-
-[[package]]
-name = "windows-collections"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec"
-dependencies = [
- "windows-core 0.60.1",
+ "windows-numerics",
]
[[package]]
@@ -5309,42 +5325,19 @@ dependencies = [
"windows-targets 0.52.6",
]
-[[package]]
-name = "windows-core"
-version = "0.60.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247"
-dependencies = [
- "windows-implement 0.59.0",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings 0.3.1",
-]
-
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
- "windows-implement 0.60.0",
+ "windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings 0.4.0",
]
-[[package]]
-name = "windows-future"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0"
-dependencies = [
- "windows-core 0.60.1",
- "windows-link",
-]
-
[[package]]
name = "windows-future"
version = "0.2.0"
@@ -5355,17 +5348,6 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "windows-implement"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
-]
-
[[package]]
name = "windows-implement"
version = "0.60.0"
@@ -5394,16 +5376,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
-[[package]]
-name = "windows-numerics"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed"
-dependencies = [
- "windows-core 0.60.1",
- "windows-link",
-]
-
[[package]]
name = "windows-numerics"
version = "0.2.0"
@@ -5826,7 +5798,7 @@ dependencies = [
"webkit2gtk",
"webkit2gtk-sys",
"webview2-com",
- "windows 0.61.1",
+ "windows",
"windows-core 0.61.0",
"windows-version",
"x11-dl",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 18e22eb..5a181a8 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -20,7 +20,8 @@ tauri-build = { version = "2.2", features = [] }
[dependencies]
rusqlite = { version = "0.36.0", features = [
"load_extension",
- "bundled-sqlcipher-vendored-openssl"
+ "bundled-sqlcipher-vendored-openssl",
+ "functions",
] }
#libsqlite3-sys = { version = "0.31", features = ["bundled-sqlcipher"] }
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
@@ -32,11 +33,12 @@ base64 = "0.22"
mime_guess = "2.0"
mime = "0.3"
fs_extra = "1.3.0"
-sqlparser = { version = "0.56.0", features = [] }
+sqlparser = { version = "0.57.0", features = ["visitor"] }
+uhlc = "0.8"
tauri = { version = "2.5", features = ["protocol-asset", "devtools"] }
tauri-plugin-dialog = "2.2"
-tauri-plugin-fs = "2.2.0"
-tauri-plugin-opener = "2.2"
+tauri-plugin-fs = "2.3.0"
+tauri-plugin-opener = "2.3.0"
tauri-plugin-os = "2"
tauri-plugin-store = "2"
tauri-plugin-http = "2.4"
diff --git a/src-tauri/cr-sqlite/crsqlite-aarch64-apple-darwin.dylib b/src-tauri/cr-sqlite/crsqlite-aarch64-apple-darwin.dylib
new file mode 100755
index 0000000..7fa433a
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-aarch64-apple-darwin.dylib differ
diff --git a/src-tauri/cr-sqlite/crsqlite-aarch64-linux-android.so b/src-tauri/cr-sqlite/crsqlite-aarch64-linux-android.so
new file mode 100755
index 0000000..3d296ac
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-aarch64-linux-android.so differ
diff --git a/src-tauri/cr-sqlite/crsqlite-aarch64-unknown-linux-gnu.so b/src-tauri/cr-sqlite/crsqlite-aarch64-unknown-linux-gnu.so
new file mode 100755
index 0000000..28bcefe
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-aarch64-unknown-linux-gnu.so differ
diff --git a/src-tauri/cr-sqlite/crsqlite-i686-pcwindows-msvc.dll b/src-tauri/cr-sqlite/crsqlite-i686-pcwindows-msvc.dll
new file mode 100755
index 0000000..645e8d2
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-i686-pcwindows-msvc.dll differ
diff --git a/src-tauri/cr-sqlite/crsqlite-x86_64-apple-darwin.dylib b/src-tauri/cr-sqlite/crsqlite-x86_64-apple-darwin.dylib
new file mode 100755
index 0000000..c1de177
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-x86_64-apple-darwin.dylib differ
diff --git a/src-tauri/cr-sqlite/crsqlite-x86_64-pcwindows-msvc.dll b/src-tauri/cr-sqlite/crsqlite-x86_64-pcwindows-msvc.dll
new file mode 100755
index 0000000..77f85cd
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-x86_64-pcwindows-msvc.dll differ
diff --git a/src-tauri/cr-sqlite/crsqlite-x86_64-unknown-linux-gnu.so b/src-tauri/cr-sqlite/crsqlite-x86_64-unknown-linux-gnu.so
new file mode 100755
index 0000000..22c3157
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-x86_64-unknown-linux-gnu.so differ
diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/Info.plist b/src-tauri/cr-sqlite/crsqlite.xcframework/Info.plist
new file mode 100644
index 0000000..2c8f326
--- /dev/null
+++ b/src-tauri/cr-sqlite/crsqlite.xcframework/Info.plist
@@ -0,0 +1,40 @@
+
+
+
+
+ AvailableLibraries
+
+
+ LibraryIdentifier
+ ios-arm64
+ LibraryPath
+ crsqlite.framework
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+
+
+ LibraryIdentifier
+ ios-arm64_x86_64-simulator
+ LibraryPath
+ crsqlite.framework
+ SupportedArchitectures
+
+ arm64
+ x86_64
+
+ SupportedPlatform
+ ios
+ SupportedPlatformVariant
+ simulator
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist
new file mode 100644
index 0000000..9479102
--- /dev/null
+++ b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist
@@ -0,0 +1,18 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ crsqlite
+ CFBundleIdentifier
+ io.vlcn.crsqlite
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ FMWK
+ CFBundleSignature
+ ????
+
+
diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite
new file mode 100755
index 0000000..e2ed0a4
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite differ
diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist
new file mode 100644
index 0000000..9479102
--- /dev/null
+++ b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist
@@ -0,0 +1,18 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ crsqlite
+ CFBundleIdentifier
+ io.vlcn.crsqlite
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ FMWK
+ CFBundleSignature
+ ????
+
+
diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite
new file mode 100755
index 0000000..dfe145d
Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite differ
diff --git a/src-tauri/database/schemas/crdt.ts b/src-tauri/database/schemas/crdt.ts
new file mode 100644
index 0000000..c714fce
--- /dev/null
+++ b/src-tauri/database/schemas/crdt.ts
@@ -0,0 +1,21 @@
+import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
+
+export const haexCrdtMessages = sqliteTable('haex_crdt_messages', {
+ hlc_timestamp: text().primaryKey(),
+ table_name: text(),
+ row_pks: text({ mode: 'json' }),
+ op_type: text({ enum: ['INSERT', 'UPDATE', 'DELETE'] }),
+ column_name: text(),
+ new_value: blob(),
+ old_value: blob(),
+})
+export type InsertHaexCrdtMessages = typeof haexCrdtMessages.$inferInsert
+export type SelectHaexCrdtMessages = typeof haexCrdtMessages.$inferSelect
+
+export const haexCrdtSnapshots = sqliteTable('haex_crdt_snapshots', {
+ snapshot_id: text().primaryKey(),
+ created: text(),
+ epoch_hlc: text(),
+ location_url: text(),
+ file_size_bytes: integer(),
+})
diff --git a/src-tauri/resources/crsqlite b/src-tauri/resources/crsqlite
new file mode 100755
index 0000000..22c3157
Binary files /dev/null and b/src-tauri/resources/crsqlite differ
diff --git a/src-tauri/src/database/core.rs b/src-tauri/src/database/core.rs
index f39b7d9..9b84402 100644
--- a/src-tauri/src/database/core.rs
+++ b/src-tauri/src/database/core.rs
@@ -6,8 +6,8 @@ use rusqlite::{
Connection, OpenFlags, ToSql,
};
use serde_json::Value as JsonValue;
-use std::fs;
use std::path::Path;
+use std::{fs, path::PathBuf};
use tauri::State;
// --- Hilfsfunktion: Konvertiert JSON Value zu etwas, das rusqlite versteht ---
// Diese Funktion ist etwas knifflig wegen Ownership und Lifetimes.
@@ -67,32 +67,10 @@ pub async fn execute(
Ok(affected_rows)
}
-/// Führt SQL-Schreiboperationen (INSERT, UPDATE, DELETE, CREATE) ohne Berechtigungsprüfung aus
-/* pub async fn execute(
- sql: &str,
- params: &[String],
- state: &State<'_, DbConnection>,
-) -> Result {
- let db = state.0.lock().map_err(|e| format!("Mutex-Fehler: {}", e))?;
- let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?;
-
- let rows_affected = conn
- .execute(sql, rusqlite::params_from_iter(params.iter()))
- .map_err(|e| format!("SQL-Ausführungsfehler: {}", e))?;
-
- let last_id = conn.last_insert_rowid();
-
- Ok(serde_json::to_string(&json!({
- "rows_affected": rows_affected,
- "last_insert_id": last_id
- }))
- .map_err(|e| format!("JSON-Serialisierungsfehler: {}", e))?)
-} */
-
#[tauri::command]
pub async fn select(
sql: String,
- params: Vec, // Parameter als JSON Values empfangen
+ params: Vec,
state: &State<'_, DbConnection>,
) -> Result>, String> {
// Ergebnis als Vec
@@ -182,45 +160,6 @@ pub async fn select(
Ok(result_vec)
}
-/// Führt SQL-Leseoperationen (SELECT) ohne Berechtigungsprüfung aus
-/* pub async fn select(
- sql: &str,
- params: &[String],
- state: &State<'_, DbConnection>,
-) -> Result>>, String> {
- let db = state.0.lock().map_err(|e| format!("Mutex-Fehler: {}", e))?;
- let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?;
-
- let mut stmt = conn
- .prepare(sql)
- .map_err(|e| format!("SQL-Vorbereitungsfehler: {}", e))?;
- let columns = stmt.column_count();
- let mut rows = stmt
- .query(rusqlite::params_from_iter(params.iter()))
- .map_err(|e| format!("SQL-Abfragefehler: {}", e))?;
-
- let mut result = Vec::new();
- while let Some(row) = rows
- .next()
- .map_err(|e| format!("Zeilenabruffehler: {}", e))?
- {
- let mut row_data = Vec::new();
- for i in 0..columns {
- let value = row
- .get(i)
- .map_err(|e| format!("Datentypfehler in Spalte {}: {}", i, e))?;
- row_data.push(value);
- }
- /* println!(
- "Select Row Data: {}",
- &row_data.clone().join("").to_string()
- ); */
- result.push(row_data);
- }
-
- Ok(result)
-} */
-
/// Öffnet und initialisiert eine Datenbank mit Verschlüsselung
pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result {
let flags = if create {
@@ -236,6 +175,16 @@ pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result>);
+use crate::database::core::open_and_init_db;
+pub struct HlcService(pub Mutex);
+pub struct DbConnection(pub Arc>>);
#[tauri::command]
pub async fn sql_select(
@@ -145,50 +148,21 @@ pub fn create_encrypted_database(
eprintln!("FEHLER: SQLCipher scheint NICHT aktiv zu sein!");
eprintln!("Der Befehl 'PRAGMA cipher_version;' schlug fehl: {}", e);
eprintln!("Die Datenbank wurde wahrscheinlich NICHT verschlüsselt.");
- // Optional: Hier die Verbindung schließen oder weitere Aktionen unterlassen
- // return Err(e); // Beende das Programm mit dem Fehler
}
}
- /* // Kopieren der Ressourcen-Datenbank zum Zielpfad
- core::copy_file(&resource_path, &path)?;
+ println!("resource_path: {}", resource_path.display());
- // Öffnen der kopierten Datenbank ohne Verschlüsselung
- let conn = Connection::open(&path).map_err(|e| {
- format!(
- "Fehler beim Öffnen der kopierten Datenbank: {}",
- e.to_string()
- )
- })?;
+ conn.close().unwrap();
- // Verschlüsseln der Datenbank mit dem angegebenen Schlüssel
- conn.pragma_update(None, "key", &key)
- .map_err(|e| format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string()))?;
+ let new_conn = open_and_init_db(&path, &key, false)?;
- // Schließen der Verbindung, um sicherzustellen, dass Änderungen gespeichert werden
- drop(conn);
-
- // Öffnen der verschlüsselten Datenbank mit dem Schlüssel
- let encrypted_conn = core::open_and_init_db(&path, &key, false)
- .map_err(|e| format!("Fehler beim Öffnen der verschlüsselten Datenbank: {}", e))?;
-
- // Überprüfen, ob die Datenbank korrekt verschlüsselt wurde, indem wir eine einfache Abfrage ausführen
- let validation_result: Result =
- encrypted_conn.query_row("SELECT 1", [], |row| row.get(0));
-
- if let Err(e) = validation_result {
- return Err(format!(
- "Fehler beim Testen der verschlüsselten Datenbank: {}",
- e.to_string()
- ));
- }
- */
// Aktualisieren der Datenbankverbindung im State
let mut db = state
.0
.lock()
.map_err(|e| format!("Mutex-Fehler: {}", e.to_string()))?;
- *db = Some(conn);
+ *db = Some(new_conn);
Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",))
}
@@ -205,6 +179,7 @@ pub fn open_encrypted_database(
let conn =
core::open_and_init_db(&path, &key, false).map_err(|e| format!("Error during open: {}", e));
+
let mut db = state.0.lock().map_err(|e| e.to_string())?;
*db = Some(conn.unwrap());
@@ -516,3 +491,120 @@ pub fn create_encrypted_database_new(
final_encrypted_db_path.display()
))
}
+
+fn get_target_triple() -> Result {
+ let target_triple = if cfg!(target_os = "linux") {
+ if cfg!(target_arch = "x86_64") {
+ "x86_64-unknown-linux-gnu".to_string()
+ } else if cfg!(target_arch = "aarch64") {
+ "aarch64-unknown-linux-gnu".to_string()
+ } else {
+ return Err(format!(
+ "Unbekannte Linux-Architektur: {}",
+ std::env::consts::ARCH
+ ));
+ }
+ } else if cfg!(target_os = "macos") {
+ if cfg!(target_arch = "x86_64") {
+ "x86_64-apple-darwin".to_string()
+ } else if cfg!(target_arch = "aarch64") {
+ "aarch64-apple-darwin".to_string()
+ } else {
+ return Err(format!(
+ "Unbekannte macOS-Architektur: {}",
+ std::env::consts::ARCH
+ ));
+ }
+ } else if cfg!(target_os = "windows") {
+ if cfg!(target_arch = "x86_64") {
+ "x86_64-pc-windows-msvc".to_string()
+ } else if cfg!(target_arch = "x86") {
+ "i686-pc-windows-msvc".to_string()
+ } else {
+ return Err(format!(
+ "Unbekannte Windows-Architektur: {}",
+ std::env::consts::ARCH
+ ));
+ }
+ } else if cfg!(target_os = "android") {
+ if cfg!(target_arch = "aarch64") {
+ "aarch64-linux-android".to_string()
+ } else {
+ return Err(format!(
+ "Unbekannte Android-Architektur: {}",
+ std::env::consts::ARCH
+ ));
+ }
+ } else if cfg!(target_os = "ios") {
+ if cfg!(target_arch = "aarch64") {
+ "aarch64-apple-ios".to_string()
+ } else {
+ return Err(format!(
+ "Unbekannte iOS-Architektur: {}",
+ std::env::consts::ARCH
+ ));
+ }
+ } else {
+ return Err("Unbekanntes Zielsystem".to_string());
+ };
+ Ok(target_triple)
+}
+
+fn get_crsqlite_path(app_handle: AppHandle) -> Result {
+ // Laden der cr-sqlite Erweiterung
+ let target_triple = get_target_triple()?;
+
+ println!("target_triple: {}", target_triple);
+
+ let crsqlite_path = app_handle
+ .path()
+ .resource_dir()
+ .map_err(|e| format!("Fehler beim Ermitteln des Ressourcenverzeichnisses: {}", e))?
+ .join(format!("crsqlite-{}", target_triple));
+
+ println!("crsqlite_path: {}", crsqlite_path.display());
+ Ok(crsqlite_path)
+}
+
+#[tauri::command]
+pub fn get_hlc_timestamp(state: tauri::State) -> String {
+ let hlc = state.0.lock().unwrap();
+ hlc.new_timestamp().to_string()
+}
+
+#[tauri::command]
+pub fn update_hlc_from_remote(
+ remote_timestamp_str: String,
+ state: tauri::State,
+) -> Result<(), String> {
+ let remote_ts =
+ uhlc::Timestamp::from_str(&remote_timestamp_str).map_err(|e| e.cause.to_string())?;
+
+ let hlc = state.0.lock().unwrap();
+ hlc.update_with_timestamp(&remote_ts)
+ .map_err(|e| format!("HLC update failed: {:?}", e))
+}
+
+#[derive(Debug, Clone)]
+struct SqlTableInfo {
+ cid: u32,
+ name: String,
+ r#type: String,
+ notnull: bool,
+ dflt_value: Option,
+ pk: u8,
+}
+
+#[tauri::command]
+pub async fn create_crdt_trigger_for_table(
+ state: &State<'_, DbConnection>,
+ table_name: String,
+) -> Result>, String> {
+ let stmt = format!(
+ "SELECT cid, name, type, notnull, dflt_value, pk from pragma_table_info('{}')",
+ table_name
+ );
+
+ let table_info = core::select(stmt, vec![], state).await;
+ Ok(table_info.unwrap())
+}
diff --git a/src-tauri/src/database/sql_proxy.rs b/src-tauri/src/database/sql_proxy.rs
new file mode 100644
index 0000000..5f7307a
--- /dev/null
+++ b/src-tauri/src/database/sql_proxy.rs
@@ -0,0 +1,63 @@
+// src-tauri/src/sql_proxy.rs
+
+use rusqlite::Connection;
+use sqlparser::ast::Statement;
+use sqlparser::dialect::SQLiteDialect;
+use sqlparser::parser::Parser;
+use std::sync::{Arc, Mutex};
+
+// Der Schema-Cache wird später benötigt, um zu wissen, welche Tabellen eine 'tombstone'-Spalte haben.
+// Für den Anfang lassen wir ihn leer.
+pub struct SchemaCache {
+ // TODO: z.B. HashMap> für Tabellen und ihre Spalten
+}
+
+impl SchemaCache {
+ pub fn new() -> Self {
+ Self {}
+ }
+ // TODO: Methoden zum Befüllen und Abfragen des Caches
+}
+
+// Die Hauptstruktur unseres Proxys
+pub struct SqlProxy {
+ // Wir benötigen eine threadsichere Referenz auf den Schema-Cache
+ schema_cache: Arc>,
+}
+
+impl SqlProxy {
+ pub fn new(schema_cache: Arc>) -> Self {
+ Self { schema_cache }
+ }
+
+ // Die zentrale Ausführungsfunktion
+ pub fn execute(&self, sql: &str, conn: &Connection) -> Result<(), String> {
+ // 1. Parsen des SQL-Strings in einen AST
+ let dialect = SQLiteDialect {};
+ let mut ast =
+ Parser::parse_sql(&dialect, sql).map_err(|e| format!("SQL-Parse-Fehler: {}", e))?;
+
+ // Sicherstellen, dass wir nur eine Anweisung haben
+ if ast.len() != 1 {
+ return Err("Nur einzelne SQL-Anweisungen werden unterstützt.".to_string());
+ }
+ let statement = &mut ast;
+
+ // 2. Umschreiben des AST (Logik folgt in Abschnitt 2)
+ self.transform_statement(statement)?;
+
+ // 3. Ausführen der (möglicherweise modifizierten) Anweisung
+ let final_sql = statement.to_string();
+ conn.execute(&final_sql)
+ .map_err(|e| format!("DB-Ausführungsfehler: {}", e))?;
+
+ Ok(())
+ }
+
+ // Platzhalter für die Transformationslogik
+ fn transform_statement(&self, statement: &mut Statement) -> Result<(), String> {
+ // HIER KOMMT DIE MAGIE HIN
+ // TODO: Implementierung der `CREATE TABLE`, `DELETE` und `SELECT` Transformationen
+ Ok(())
+ }
+}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 55db12e..86e3160 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -5,7 +5,7 @@ mod models;
use database::DbConnection;
use models::ExtensionState;
-use std::sync::Mutex;
+use std::sync::{Arc, Mutex};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
@@ -42,7 +42,7 @@ pub fn run() {
}
}
})
- .manage(DbConnection(Mutex::new(None)))
+ .manage(DbConnection(Arc::new(Mutex::new(None))))
.manage(ExtensionState::default())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_dialog::init())
@@ -58,6 +58,8 @@ pub fn run() {
database::sql_execute,
database::sql_select,
database::test,
+ database::get_hlc_timestamp,
+ database::update_hlc_from_remote,
extension::copy_directory,
extension::database::extension_sql_execute,
extension::database::extension_sql_select,
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index ef982a3..aa12a41 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -55,6 +55,21 @@
"icons/icon.icns",
"icons/icon.ico"
],
- "resources": ["database/vault.db"]
+ "resources": ["database/vault.db", "resources/"],
+
+ "linux": {
+ "appimage": {
+ "bundleMediaFramework": false,
+ "files": {}
+ },
+ "deb": {
+ "files": {}
+ },
+ "rpm": {
+ "epoch": 0,
+ "files": {},
+ "release": "1"
+ }
+ }
}
}
diff --git a/src/stores/passwords/items.ts b/src/stores/passwords/items.ts
index c9d6d78..09345a7 100644
--- a/src/stores/passwords/items.ts
+++ b/src/stores/passwords/items.ts
@@ -343,3 +343,20 @@ const deleteKeyValueAsync = async (id: string) => {
.delete(haexPasswordsItemKeyValues)
.where(eq(haexPasswordsItemKeyValues.id, id))
}
+
+const areItemsEqual = (
+ groupA: unknown | unknown[] | null,
+ groupB: unknown | unknown[] | null,
+) => {
+ if (groupA === null && groupB === null) return true
+
+ if (Array.isArray(groupA) && Array.isArray(groupB)) {
+ console.log('compare object arrays', groupA, groupB)
+ if (groupA.length === groupB.length) return true
+
+ return groupA.some((group, index) => {
+ return areObjectsEqual(group, groupA[index])
+ })
+ }
+ return areObjectsEqual(groupA, groupB)
+}
\ No newline at end of file
diff --git a/src/stores/vault/index.ts b/src/stores/vault/index.ts
index 7a8abe2..86c5f08 100644
--- a/src/stores/vault/index.ts
+++ b/src/stores/vault/index.ts
@@ -3,6 +3,8 @@ import { invoke } from '@tauri-apps/api/core'
import { platform } from '@tauri-apps/plugin-os'
import * as schema from '@/../src-tauri/database/schemas/vault'
import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy'
+import { isTable, sql } from 'drizzle-orm'
+import { getTableConfig } from 'drizzle-orm/sqlite-core'
interface IVault {
name: string
@@ -63,7 +65,7 @@ export const useVaultStore = defineStore('vaultStore', () => {
// If the query is a SELECT, use the select method
if (isSelectQuery(sql)) {
- console.log('sql_select', sql, params)
+ console.log('sql_select', sql, params, method)
rows = await invoke('sql_select', {
sql,
params,
@@ -72,7 +74,7 @@ export const useVaultStore = defineStore('vaultStore', () => {
return []
})
} else {
- console.log('sql_execute', sql, params)
+ console.log('sql_execute', sql, params, method)
// Otherwise, use the execute method
rows = await invoke('sql_execute', {
sql,
@@ -95,7 +97,6 @@ export const useVaultStore = defineStore('vaultStore', () => {
const { addVaultAsync } = useLastVaultStore()
await addVaultAsync({ path })
-
return vaultId
} catch (error) {
console.error('Error openAsync ', error)
@@ -145,6 +146,21 @@ const getVaultIdAsync = async (path: string) => {
}
const isSelectQuery = (sql: string) => {
+ console.log('check isSelectQuery', sql)
const selectRegex = /^\s*SELECT\b/i
return selectRegex.test(sql)
}
+
+/* const trackAllHaexTablesAsync = async (vaultId: string) => {
+ const { openVaults } = useVaultStore()
+ const promises = Object.values(schema)
+ .filter(isTable)
+ .map((table) => {
+ const stmt = `SELECT crsql_as_crr('${getTableConfig(table).name}');`
+ console.log('track table', getTableConfig(table).name)
+
+ return openVaults?.[vaultId]?.drizzle.run(sql`${stmt}`.getSQL())
+ })
+
+ await Promise.allSettled(promises)
+} */