diff --git a/package.json b/package.json index a1a572e..f91ac76 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "devDependencies": { "@iconify/json": "^2.2.351", "@iconify/tailwind4": "^1.0.6", + "@libsql/client": "^0.15.15", "@tauri-apps/cli": "^2.5.0", "@vitejs/plugin-vue": "6.0.1", "@vue/compiler-sfc": "^3.5.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 445deed..d69bc3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,25 +13,25 @@ importers: dependencies: '@nuxt/eslint': specifier: 1.9.0 - version: 1.9.0(@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.21)(eslint@9.35.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + version: 1.9.0(@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.21)(eslint@9.35.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/fonts': specifier: 0.11.4 - version: 0.11.4(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0)(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + version: 0.11.4(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0)(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/icon': specifier: 2.0.0 - version: 2.0.0(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + version: 2.0.0(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) '@nuxt/ui': specifier: ^3.3.2 - version: 3.3.3(@babel/parser@7.28.4)(change-case@5.4.4)(db0@0.3.2(drizzle-orm@0.44.5))(embla-carousel@8.6.0)(ioredis@5.7.0)(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))(zod@3.25.76) + version: 3.3.3(@babel/parser@7.28.4)(change-case@5.4.4)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(embla-carousel@8.6.0)(ioredis@5.7.0)(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))(zod@3.25.76) '@nuxtjs/i18n': specifier: 10.0.6 - version: 10.0.6(@vue/compiler-dom@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(rollup@4.50.1)(vue@3.5.21(typescript@5.9.2)) + version: 10.0.6(@vue/compiler-dom@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(rollup@4.50.1)(vue@3.5.21(typescript@5.9.2)) '@pinia/nuxt': specifier: ^0.11.1 version: 0.11.2(magicast@0.3.5)(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))) '@tailwindcss/vite': specifier: ^4.1.10 - version: 4.1.13(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + version: 4.1.13(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@tauri-apps/api': specifier: ^2.5.0 version: 2.8.0 @@ -67,10 +67,10 @@ importers: version: 13.9.0(vue@3.5.21(typescript@5.9.2)) '@vueuse/nuxt': specifier: ^13.4.0 - version: 13.9.0(magicast@0.3.5)(nuxt@4.1.1(@parcel/watcher@2.5.1)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(drizzle-orm@0.44.5)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + version: 13.9.0(magicast@0.3.5)(nuxt@4.1.1(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@24.5.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(drizzle-orm@0.44.5(@libsql/client@0.15.15))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) drizzle-orm: specifier: ^0.44.2 - version: 0.44.5 + version: 0.44.5(@libsql/client@0.15.15) eslint: specifier: ^9.34.0 version: 9.35.0(jiti@2.5.1) @@ -79,7 +79,7 @@ importers: version: 7.1.0 nuxt: specifier: ^4.0.3 - version: 4.1.1(@parcel/watcher@2.5.1)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(drizzle-orm@0.44.5)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1) + version: 4.1.1(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@24.5.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(drizzle-orm@0.44.5(@libsql/client@0.15.15))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1) nuxt-zod-i18n: specifier: ^1.12.0 version: 1.12.1(magicast@0.3.5) @@ -102,12 +102,15 @@ importers: '@iconify/tailwind4': specifier: ^1.0.6 version: 1.0.6(tailwindcss@4.1.13) + '@libsql/client': + specifier: ^0.15.15 + version: 0.15.15 '@tauri-apps/cli': specifier: ^2.5.0 version: 2.8.4 '@vitejs/plugin-vue': specifier: 6.0.1 - version: 6.0.1(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + version: 6.0.1(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) '@vue/compiler-sfc': specifier: ^3.5.17 version: 3.5.21 @@ -128,7 +131,7 @@ importers: version: 5.9.2 vite: specifier: 7.1.3 - version: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + version: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) vue-tsc: specifier: 3.0.6 version: 3.0.6(typescript@5.9.2) @@ -822,6 +825,67 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@libsql/client@0.15.15': + resolution: {integrity: sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==} + + '@libsql/core@0.15.15': + resolution: {integrity: sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==} + + '@libsql/darwin-arm64@0.5.22': + resolution: {integrity: sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==} + cpu: [arm64] + os: [darwin] + + '@libsql/darwin-x64@0.5.22': + resolution: {integrity: sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==} + cpu: [x64] + os: [darwin] + + '@libsql/hrana-client@0.7.0': + resolution: {integrity: sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==} + + '@libsql/isomorphic-fetch@0.3.1': + resolution: {integrity: sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==} + engines: {node: '>=18.0.0'} + + '@libsql/isomorphic-ws@0.1.5': + resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} + + '@libsql/linux-arm-gnueabihf@0.5.22': + resolution: {integrity: sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==} + cpu: [arm] + os: [linux] + + '@libsql/linux-arm-musleabihf@0.5.22': + resolution: {integrity: sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==} + cpu: [arm] + os: [linux] + + '@libsql/linux-arm64-gnu@0.5.22': + resolution: {integrity: sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-arm64-musl@0.5.22': + resolution: {integrity: sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-x64-gnu@0.5.22': + resolution: {integrity: sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==} + cpu: [x64] + os: [linux] + + '@libsql/linux-x64-musl@0.5.22': + resolution: {integrity: sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==} + cpu: [x64] + os: [linux] + + '@libsql/win32-x64-msvc@0.5.22': + resolution: {integrity: sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==} + cpu: [x64] + os: [win32] + '@mapbox/node-pre-gyp@2.0.0': resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} engines: {node: '>=18'} @@ -838,6 +902,9 @@ packages: '@napi-rs/wasm-runtime@1.0.3': resolution: {integrity: sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==} + '@neon-rs/load@0.0.4': + resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1971,6 +2038,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@24.5.0': + resolution: {integrity: sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==} + '@types/parse-path@7.1.0': resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. @@ -1984,6 +2054,9 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.43.0': resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2725,6 +2798,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + db0@0.3.2: resolution: {integrity: sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw==} peerDependencies: @@ -2802,6 +2879,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -3251,6 +3332,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -3291,6 +3376,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -3561,6 +3650,9 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3642,6 +3734,11 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libsql@0.5.22: + resolution: {integrity: sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==} + cpu: [x64, arm64, wasm32, arm] + os: [darwin, linux, win32] + lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} @@ -3891,6 +3988,11 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} @@ -3903,6 +4005,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} @@ -4335,6 +4441,9 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -4773,6 +4882,9 @@ packages: unctx@2.4.1: resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==} + undici-types@7.12.0: + resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} + unenv@2.0.0-rc.20: resolution: {integrity: sha512-8tn4tAl9vD5nWoggAAPz28vf0FY8+pQAayhU94qD+ZkIbVKCBAH/E1MWEEmhb9Whn5EgouYVfBJB20RsTLRDdg==} @@ -5162,6 +5274,10 @@ packages: typescript: optional: true + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5947,6 +6063,68 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@libsql/client@0.15.15': + dependencies: + '@libsql/core': 0.15.15 + '@libsql/hrana-client': 0.7.0 + js-base64: 3.7.8 + libsql: 0.5.22 + promise-limit: 2.7.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/core@0.15.15': + dependencies: + js-base64: 3.7.8 + + '@libsql/darwin-arm64@0.5.22': + optional: true + + '@libsql/darwin-x64@0.5.22': + optional: true + + '@libsql/hrana-client@0.7.0': + dependencies: + '@libsql/isomorphic-fetch': 0.3.1 + '@libsql/isomorphic-ws': 0.1.5 + js-base64: 3.7.8 + node-fetch: 3.3.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/isomorphic-fetch@0.3.1': {} + + '@libsql/isomorphic-ws@0.1.5': + dependencies: + '@types/ws': 8.18.1 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/linux-arm-gnueabihf@0.5.22': + optional: true + + '@libsql/linux-arm-musleabihf@0.5.22': + optional: true + + '@libsql/linux-arm64-gnu@0.5.22': + optional: true + + '@libsql/linux-arm64-musl@0.5.22': + optional: true + + '@libsql/linux-x64-gnu@0.5.22': + optional: true + + '@libsql/linux-x64-musl@0.5.22': + optional: true + + '@libsql/win32-x64-msvc@0.5.22': + optional: true + '@mapbox/node-pre-gyp@2.0.0': dependencies: consola: 3.4.2 @@ -5980,6 +6158,8 @@ snapshots: '@tybys/wasm-util': 0.10.0 optional: true + '@neon-rs/load@0.0.4': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6037,11 +6217,11 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@2.6.3(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': + '@nuxt/devtools-kit@2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@nuxt/kit': 3.19.1(magicast@0.3.5) execa: 8.0.1 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - magicast @@ -6056,12 +6236,12 @@ snapshots: prompts: 2.4.2 semver: 7.7.2 - '@nuxt/devtools@2.6.3(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@nuxt/devtools@2.6.3(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: - '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/devtools-wizard': 2.6.3 '@nuxt/kit': 3.19.1(magicast@0.3.5) - '@vue/devtools-core': 7.7.7(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + '@vue/devtools-core': 7.7.7(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) '@vue/devtools-kit': 7.7.7 birpc: 2.5.0 consola: 3.4.2 @@ -6086,9 +6266,9 @@ snapshots: sirv: 3.0.2 structured-clone-es: 1.0.0 tinyglobby: 0.2.14 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) - vite-plugin-inspect: 11.3.3(@nuxt/kit@3.19.1(magicast@0.3.5))(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) - vite-plugin-vue-tracer: 1.0.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite-plugin-inspect: 11.3.3(@nuxt/kit@3.19.1(magicast@0.3.5))(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + vite-plugin-vue-tracer: 1.0.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) which: 5.0.0 ws: 8.18.3 transitivePeerDependencies: @@ -6137,10 +6317,10 @@ snapshots: - supports-color - typescript - '@nuxt/eslint@1.9.0(@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.21)(eslint@9.35.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': + '@nuxt/eslint@1.9.0(@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.21)(eslint@9.35.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@eslint/config-inspector': 1.2.0(eslint@9.35.0(jiti@2.5.1)) - '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/eslint-config': 1.9.0(@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.21)(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) '@nuxt/eslint-plugin': 1.9.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) '@nuxt/kit': 4.1.1(magicast@0.3.5) @@ -6165,9 +6345,9 @@ snapshots: - utf-8-validate - vite - '@nuxt/fonts@0.11.4(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0)(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': + '@nuxt/fonts@0.11.4(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0)(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/kit': 3.19.1(magicast@0.3.5) consola: 3.4.2 css-tree: 3.1.0 @@ -6186,7 +6366,7 @@ snapshots: ufo: 1.6.1 unifont: 0.4.1 unplugin: 2.3.10 - unstorage: 1.17.1(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0) + unstorage: 1.17.1(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -6211,13 +6391,13 @@ snapshots: - uploadthing - vite - '@nuxt/icon@1.15.0(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@nuxt/icon@1.15.0(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@iconify/collections': 1.0.592 '@iconify/types': 2.0.0 '@iconify/utils': 2.3.0 '@iconify/vue': 5.0.0(vue@3.5.21(typescript@5.9.2)) - '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/kit': 3.19.1(magicast@0.3.5) consola: 3.4.2 local-pkg: 1.1.2 @@ -6233,13 +6413,13 @@ snapshots: - vite - vue - '@nuxt/icon@2.0.0(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@nuxt/icon@2.0.0(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@iconify/collections': 1.0.592 '@iconify/types': 2.0.0 '@iconify/utils': 3.0.1 '@iconify/vue': 5.0.0(vue@3.5.21(typescript@5.9.2)) - '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@nuxt/kit': 4.1.1(magicast@0.3.5) consola: 3.4.2 local-pkg: 1.1.2 @@ -6336,19 +6516,19 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/ui@3.3.3(@babel/parser@7.28.4)(change-case@5.4.4)(db0@0.3.2(drizzle-orm@0.44.5))(embla-carousel@8.6.0)(ioredis@5.7.0)(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))(zod@3.25.76)': + '@nuxt/ui@3.3.3(@babel/parser@7.28.4)(change-case@5.4.4)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(embla-carousel@8.6.0)(ioredis@5.7.0)(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))(zod@3.25.76)': dependencies: '@iconify/vue': 5.0.0(vue@3.5.21(typescript@5.9.2)) '@internationalized/date': 3.9.0 '@internationalized/number': 3.6.5 - '@nuxt/fonts': 0.11.4(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0)(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) - '@nuxt/icon': 1.15.0(magicast@0.3.5)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + '@nuxt/fonts': 0.11.4(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0)(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@nuxt/icon': 1.15.0(magicast@0.3.5)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) '@nuxt/kit': 4.1.1(magicast@0.3.5) '@nuxt/schema': 4.1.1 '@nuxtjs/color-mode': 3.5.2(magicast@0.3.5) '@standard-schema/spec': 1.0.0 '@tailwindcss/postcss': 4.1.13 - '@tailwindcss/vite': 4.1.13(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + '@tailwindcss/vite': 4.1.13(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) '@tanstack/vue-table': 8.21.3(vue@3.5.21(typescript@5.9.2)) '@unhead/vue': 2.0.14(vue@3.5.21(typescript@5.9.2)) '@vueuse/core': 13.9.0(vue@3.5.21(typescript@5.9.2)) @@ -6424,12 +6604,12 @@ snapshots: - vite - vue - '@nuxt/vite-builder@4.1.1(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vue-tsc@3.0.6(typescript@5.9.2))(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1)': + '@nuxt/vite-builder@4.1.1(@types/node@24.5.0)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vue-tsc@3.0.6(typescript@5.9.2))(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1)': dependencies: '@nuxt/kit': 4.1.1(magicast@0.3.5) '@rollup/plugin-replace': 6.0.2(rollup@4.50.1) - '@vitejs/plugin-vue': 6.0.1(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) - '@vitejs/plugin-vue-jsx': 5.1.1(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + '@vitejs/plugin-vue': 6.0.1(vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + '@vitejs/plugin-vue-jsx': 5.1.1(vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) autoprefixer: 10.4.21(postcss@8.5.6) consola: 3.4.2 cssnano: 7.1.1(postcss@8.5.6) @@ -6451,9 +6631,9 @@ snapshots: std-env: 3.9.0 ufo: 1.6.1 unenv: 2.0.0-rc.21 - vite: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) - vite-node: 3.2.4(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) - vite-plugin-checker: 0.10.3(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2)) + vite: 7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite-plugin-checker: 0.10.3(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2)) vue: 3.5.21(typescript@5.9.2) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: @@ -6490,7 +6670,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxtjs/i18n@10.0.6(@vue/compiler-dom@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(rollup@4.50.1)(vue@3.5.21(typescript@5.9.2))': + '@nuxtjs/i18n@10.0.6(@vue/compiler-dom@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(rollup@4.50.1)(vue@3.5.21(typescript@5.9.2))': dependencies: '@intlify/core': 11.1.12 '@intlify/h3': 0.7.1 @@ -6517,7 +6697,7 @@ snapshots: ufo: 1.6.1 unplugin: 2.3.10 unplugin-vue-router: 0.14.0(@vue/compiler-sfc@3.5.21)(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) - unstorage: 1.17.1(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0) + unstorage: 1.17.1(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0) vue-i18n: 11.1.12(vue@3.5.21(typescript@5.9.2)) vue-router: 4.5.1(vue@3.5.21(typescript@5.9.2)) transitivePeerDependencies: @@ -7108,12 +7288,12 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.13 - '@tailwindcss/vite@4.1.13(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': + '@tailwindcss/vite@4.1.13(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.1.13 '@tailwindcss/oxide': 4.1.13 tailwindcss: 4.1.13 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) '@tanstack/table-core@8.21.3': {} @@ -7219,6 +7399,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@24.5.0': + dependencies: + undici-types: 7.12.0 + '@types/parse-path@7.1.0': dependencies: parse-path: 7.1.0 @@ -7229,6 +7413,10 @@ snapshots: '@types/web-bluetooth@0.0.21': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 24.5.0 + '@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -7406,28 +7594,28 @@ snapshots: - rollup - supports-color - '@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.4) '@rolldown/pluginutils': 1.0.0-beta.36 '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.4) - vite: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) vue: 3.5.21(typescript@5.9.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.1(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) vue: 3.5.21(typescript@5.9.2) - '@vitejs/plugin-vue@6.0.1(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) vue: 3.5.21(typescript@5.9.2) '@volar/language-core@2.4.23': @@ -7532,14 +7720,14 @@ snapshots: dependencies: '@vue/devtools-kit': 7.7.7 - '@vue/devtools-core@7.7.7(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@vue/devtools-core@7.7.7(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@vue/devtools-kit': 7.7.7 '@vue/devtools-shared': 7.7.7 mitt: 3.0.1 nanoid: 5.1.5 pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + vite-hot-client: 2.1.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) vue: 3.5.21(typescript@5.9.2) transitivePeerDependencies: - vite @@ -7642,13 +7830,13 @@ snapshots: '@vueuse/metadata@13.9.0': {} - '@vueuse/nuxt@13.9.0(magicast@0.3.5)(nuxt@4.1.1(@parcel/watcher@2.5.1)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(drizzle-orm@0.44.5)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': + '@vueuse/nuxt@13.9.0(magicast@0.3.5)(nuxt@4.1.1(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@24.5.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(drizzle-orm@0.44.5(@libsql/client@0.15.15))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@nuxt/kit': 3.19.1(magicast@0.3.5) '@vueuse/core': 13.9.0(vue@3.5.21(typescript@5.9.2)) '@vueuse/metadata': 13.9.0 local-pkg: 1.1.2 - nuxt: 4.1.1(@parcel/watcher@2.5.1)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(drizzle-orm@0.44.5)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1) + nuxt: 4.1.1(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@24.5.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(drizzle-orm@0.44.5(@libsql/client@0.15.15))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1) vue: 3.5.21(typescript@5.9.2) transitivePeerDependencies: - magicast @@ -8067,9 +8255,12 @@ snapshots: csstype@3.1.3: {} - db0@0.3.2(drizzle-orm@0.44.5): + data-uri-to-buffer@4.0.1: {} + + db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)): optionalDependencies: - drizzle-orm: 0.44.5 + '@libsql/client': 0.15.15 + drizzle-orm: 0.44.5(@libsql/client@0.15.15) de-indent@1.0.2: {} @@ -8102,6 +8293,8 @@ snapshots: detect-libc@1.0.3: {} + detect-libc@2.0.2: {} + detect-libc@2.0.4: {} devalue@5.3.2: {} @@ -8145,7 +8338,9 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.44.5: {} + drizzle-orm@0.44.5(@libsql/client@0.15.15): + optionalDependencies: + '@libsql/client': 0.15.15 duplexer@0.1.2: {} @@ -8537,6 +8732,11 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -8597,6 +8797,10 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fraction.js@4.3.7: {} fresh@2.0.0: {} @@ -8849,6 +9053,8 @@ snapshots: jiti@2.5.1: {} + js-base64@3.7.8: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -8913,6 +9119,21 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libsql@0.5.22: + dependencies: + '@neon-rs/load': 0.0.4 + detect-libc: 2.0.2 + optionalDependencies: + '@libsql/darwin-arm64': 0.5.22 + '@libsql/darwin-x64': 0.5.22 + '@libsql/linux-arm-gnueabihf': 0.5.22 + '@libsql/linux-arm-musleabihf': 0.5.22 + '@libsql/linux-arm64-gnu': 0.5.22 + '@libsql/linux-arm64-musl': 0.5.22 + '@libsql/linux-x64-gnu': 0.5.22 + '@libsql/linux-x64-musl': 0.5.22 + '@libsql/win32-x64-msvc': 0.5.22 + lightningcss-darwin-arm64@1.30.1: optional: true @@ -9117,7 +9338,7 @@ snapshots: natural-compare@1.4.0: {} - nitropack@2.12.5(drizzle-orm@0.44.5): + nitropack@2.12.5(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)): dependencies: '@cloudflare/kv-asset-handler': 0.4.0 '@rollup/plugin-alias': 5.1.1(rollup@4.50.1) @@ -9138,7 +9359,7 @@ snapshots: cookie-es: 2.0.0 croner: 9.1.0 crossws: 0.3.5 - db0: 0.3.2(drizzle-orm@0.44.5) + db0: 0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)) defu: 6.1.4 destr: 2.0.5 dot-prop: 9.0.0 @@ -9184,7 +9405,7 @@ snapshots: unenv: 2.0.0-rc.20 unimport: 5.2.0 unplugin-utils: 0.3.0 - unstorage: 1.17.1(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0) + unstorage: 1.17.1(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0) untyped: 2.0.0 unwasm: 0.3.11 youch: 4.1.0-beta.8 @@ -9219,12 +9440,20 @@ snapshots: node-addon-api@7.1.1: {} + node-domexception@1.0.0: {} + node-fetch-native@1.6.7: {} node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-forge@1.3.1: {} node-gyp-build@4.8.4: {} @@ -9264,15 +9493,15 @@ snapshots: transitivePeerDependencies: - magicast - nuxt@4.1.1(@parcel/watcher@2.5.1)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(drizzle-orm@0.44.5))(drizzle-orm@0.44.5)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1): + nuxt@4.1.1(@libsql/client@0.15.15)(@parcel/watcher@2.5.1)(@types/node@24.5.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(drizzle-orm@0.44.5(@libsql/client@0.15.15))(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(yaml@2.8.1): dependencies: '@nuxt/cli': 3.28.0(magicast@0.3.5) '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 2.6.3(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + '@nuxt/devtools': 2.6.3(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) '@nuxt/kit': 4.1.1(magicast@0.3.5) '@nuxt/schema': 4.1.1 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 4.1.1(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vue-tsc@3.0.6(typescript@5.9.2))(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1) + '@nuxt/vite-builder': 4.1.1(@types/node@24.5.0)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.1)(terser@5.44.0)(typescript@5.9.2)(vue-tsc@3.0.6(typescript@5.9.2))(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1) '@unhead/vue': 2.0.14(vue@3.5.21(typescript@5.9.2)) '@vue/shared': 3.5.21 c12: 3.2.0(magicast@0.3.5) @@ -9299,7 +9528,7 @@ snapshots: mlly: 1.8.0 mocked-exports: 0.1.1 nanotar: 0.2.0 - nitropack: 2.12.5(drizzle-orm@0.44.5) + nitropack: 2.12.5(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)) nypm: 0.6.1 ofetch: 1.4.1 ohash: 2.0.11 @@ -9323,7 +9552,7 @@ snapshots: unimport: 5.2.0 unplugin: 2.3.10 unplugin-vue-router: 0.15.0(@vue/compiler-sfc@3.5.21)(typescript@5.9.2)(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) - unstorage: 1.17.1(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0) + unstorage: 1.17.1(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0) untyped: 2.0.0 vue: 3.5.21(typescript@5.9.2) vue-bundle-renderer: 2.1.2 @@ -9331,6 +9560,7 @@ snapshots: vue-router: 4.5.1(vue@3.5.21(typescript@5.9.2)) optionalDependencies: '@parcel/watcher': 2.5.1 + '@types/node': 24.5.0 transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -9814,6 +10044,8 @@ snapshots: process@0.11.10: {} + promise-limit@2.7.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -10259,6 +10491,8 @@ snapshots: magic-string: 0.30.19 unplugin: 2.3.10 + undici-types@7.12.0: {} + unenv@2.0.0-rc.20: dependencies: defu: 6.1.4 @@ -10454,7 +10688,7 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - unstorage@1.17.1(db0@0.3.2(drizzle-orm@0.44.5))(ioredis@5.7.0): + unstorage@1.17.1(db0@0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)))(ioredis@5.7.0): dependencies: anymatch: 3.1.3 chokidar: 4.0.3 @@ -10465,7 +10699,7 @@ snapshots: ofetch: 1.4.1 ufo: 1.6.1 optionalDependencies: - db0: 0.3.2(drizzle-orm@0.44.5) + db0: 0.3.2(@libsql/client@0.15.15)(drizzle-orm@0.44.5(@libsql/client@0.15.15)) ioredis: 5.7.0 untun@0.1.3: @@ -10513,23 +10747,23 @@ snapshots: transitivePeerDependencies: - '@vue/composition-api' - vite-dev-rpc@1.1.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)): + vite-dev-rpc@1.1.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)): dependencies: birpc: 2.5.0 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) - vite-hot-client: 2.1.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite-hot-client: 2.1.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) - vite-hot-client@2.1.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)): + vite-hot-client@2.1.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)): dependencies: - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) - vite-node@3.2.4(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1): + vite-node@3.2.4(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -10544,7 +10778,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.10.3(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2)): + vite-plugin-checker@0.10.3(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -10554,7 +10788,7 @@ snapshots: strip-ansi: 7.1.2 tiny-invariant: 1.3.3 tinyglobby: 0.2.14 - vite: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) vscode-uri: 3.1.0 optionalDependencies: eslint: 9.35.0(jiti@2.5.1) @@ -10562,7 +10796,7 @@ snapshots: typescript: 5.9.2 vue-tsc: 3.0.6(typescript@5.9.2) - vite-plugin-inspect@11.3.3(@nuxt/kit@3.19.1(magicast@0.3.5))(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)): + vite-plugin-inspect@11.3.3(@nuxt/kit@3.19.1(magicast@0.3.5))(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)): dependencies: ansis: 4.1.0 debug: 4.4.1 @@ -10572,24 +10806,24 @@ snapshots: perfect-debounce: 2.0.0 sirv: 3.0.2 unplugin-utils: 0.3.0 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) - vite-dev-rpc: 1.1.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite-dev-rpc: 1.1.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)) optionalDependencies: '@nuxt/kit': 3.19.1(magicast@0.3.5) transitivePeerDependencies: - supports-color - vite-plugin-vue-tracer@1.0.0(vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)): + vite-plugin-vue-tracer@1.0.0(vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)): dependencies: estree-walker: 3.0.3 exsolve: 1.0.7 magic-string: 0.30.19 pathe: 2.0.3 source-map-js: 1.2.1 - vite: 7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1) vue: 3.5.21(typescript@5.9.2) - vite@7.1.3(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1): + vite@7.1.3(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -10598,13 +10832,14 @@ snapshots: rollup: 4.50.1 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 24.5.0 fsevents: 2.3.3 jiti: 2.5.1 lightningcss: 1.30.1 terser: 5.44.0 yaml: 2.8.1 - vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1): + vite@7.1.5(@types/node@24.5.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -10613,6 +10848,7 @@ snapshots: rollup: 4.50.1 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 24.5.0 fsevents: 2.3.3 jiti: 2.5.1 lightningcss: 1.30.1 @@ -10673,6 +10909,8 @@ snapshots: optionalDependencies: typescript: 5.9.2 + web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} webpack-virtual-modules@0.6.2: {} diff --git a/src-tauri-db/build.rs b/src-tauri-db/build.rs deleted file mode 100644 index 51fb828..0000000 --- a/src-tauri-db/build.rs +++ /dev/null @@ -1,106 +0,0 @@ -use serde::Deserialize; -use std::env; -use std::fs::File; -use std::io::{BufReader, Write}; -use std::path::Path; - -#[derive(Debug, Deserialize)] -struct Schema { - haex: Haex, -} - -#[derive(Debug, Deserialize)] -struct Haex { - settings: String, - extensions: String, - extension_permissions: String, - notifications: String, - passwords: Passwords, - crdt: Crdt, -} - -#[derive(Debug, Deserialize)] -struct Passwords { - groups: String, - group_items: String, - item_details: String, - item_key_values: String, - item_histories: String, -} - -#[derive(Debug, Deserialize)] -struct Crdt { - logs: String, - snapshots: String, - configs: String, -} - -fn main() { - // Pfad zur Eingabe-JSON und zur Ausgabe-Rust-Datei festlegen. - // `OUT_DIR` ist ein spezielles Verzeichnis, das Cargo für generierte Dateien bereitstellt. - let schema_path = Path::new("database/tableNames.json"); - let out_dir = - env::var("OUT_DIR").expect("OUT_DIR ist nicht gesetzt. Führen Sie dies mit Cargo aus."); - let dest_path = Path::new(&out_dir).join("tableNames.rs"); - - // --- 2. JSON-Datei lesen und mit serde parsen --- - let file = File::open(&schema_path).expect("Konnte tableNames.json nicht öffnen"); - let reader = BufReader::new(file); - let schema: Schema = - serde_json::from_reader(reader).expect("Konnte tableNames.json nicht parsen"); - let haex = schema.haex; - - // --- 3. Den zu generierenden Rust-Code als String erstellen --- - // Wir verwenden das `format!`-Makro, um die Werte aus den geparsten Structs - // in einen vordefinierten Code-Template-String einzufügen. - // Das `r#""#`-Format erlaubt uns, mehrzeilige Strings mit Anführungszeichen zu verwenden. - let code = format!( - r#" - // HINWEIS: Diese Datei wurde automatisch von build.rs generiert. - // Manuelle Änderungen werden bei der nächsten Kompilierung überschrieben! - - pub const TABLE_SETTINGS: &str = "{settings}"; - pub const TABLE_EXTENSIONS: &str = "{extensions}"; - pub const TABLE_EXTENSION_PERMISSIONS: &str = "{extension_permissions}"; - pub const TABLE_NOTIFICATIONS: &str = "{notifications}"; - - // Passwords - pub const TABLE_PASSWORDS_GROUPS: &str = "{pw_groups}"; - pub const TABLE_PASSWORDS_GROUP_ITEMS: &str = "{pw_group_items}"; - pub const TABLE_PASSWORDS_ITEM_DETAILS: &str = "{pw_item_details}"; - pub const TABLE_PASSWORDS_ITEM_KEY_VALUES: &str = "{pw_item_key_values}"; - pub const TABLE_PASSWORDS_ITEM_HISTORIES: &str = "{pw_item_histories}"; - - // CRDT - pub const TABLE_CRDT_LOGS: &str = "{crdt_logs}"; - pub const TABLE_CRDT_SNAPSHOTS: &str = "{crdt_snapshots}"; - pub const TABLE_CRDT_CONFIGS: &str = "{crdt_configs}"; - - "#, - // Hier werden die Werte aus dem `haex`-Struct in die Platzhalter oben eingesetzt. - settings = haex.settings, - extensions = haex.extensions, - extension_permissions = haex.extension_permissions, - notifications = haex.notifications, - pw_groups = haex.passwords.groups, - pw_group_items = haex.passwords.group_items, - pw_item_details = haex.passwords.item_details, - pw_item_key_values = haex.passwords.item_key_values, - pw_item_histories = haex.passwords.item_histories, - crdt_logs = haex.crdt.logs, - crdt_snapshots = haex.crdt.snapshots, - crdt_configs = haex.crdt.configs - ); - - // --- 4. Den generierten Code in die Zieldatei schreiben --- - let mut f = File::create(&dest_path).expect("Konnte die Zieldatei nicht erstellen"); - f.write_all(code.as_bytes()) - .expect("Konnte nicht in die Zieldatei schreiben"); - - // --- 5. Cargo anweisen, das Skript erneut auszuführen, wenn sich die JSON-Datei ändert --- - // Diese Zeile ist extrem wichtig für eine reibungslose Entwicklung! Ohne sie - // würde Cargo Änderungen an der JSON-Datei nicht bemerken. - println!("cargo:rerun-if-changed=database/tableNames.json"); - - tauri_build::build() -} diff --git a/src-tauri/database/migrations/0011_illegal_thor_girl.sql b/src-tauri/database/migrations/0011_illegal_thor_girl.sql new file mode 100644 index 0000000..e10f34e --- /dev/null +++ b/src-tauri/database/migrations/0011_illegal_thor_girl.sql @@ -0,0 +1,21 @@ +ALTER TABLE `haex_crdt_settings` RENAME TO `haex_crdt_configs`;--> statement-breakpoint +ALTER TABLE `haex_extensions_permissions` RENAME TO `haex_extension_permissions`;--> statement-breakpoint +ALTER TABLE `haex_crdt_configs` RENAME COLUMN "type" TO "key";--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_haex_extension_permissions` ( + `id` text PRIMARY KEY NOT NULL, + `extension_id` text, + `resource` text, + `operation` text, + `path` text, + `created_at` text DEFAULT (CURRENT_TIMESTAMP), + `updated_at` integer, + `haex_tombstone` integer, + FOREIGN KEY (`extension_id`) REFERENCES `haex_extensions`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_haex_extension_permissions`("id", "extension_id", "resource", "operation", "path", "created_at", "updated_at", "haex_tombstone") SELECT "id", "extension_id", "resource", "operation", "path", "created_at", "updated_at", "haex_tombstone" FROM `haex_extension_permissions`;--> statement-breakpoint +DROP TABLE `haex_extension_permissions`;--> statement-breakpoint +ALTER TABLE `__new_haex_extension_permissions` RENAME TO `haex_extension_permissions`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `haex_extension_permissions_extension_id_resource_operation_path_unique` ON `haex_extension_permissions` (`extension_id`,`resource`,`operation`,`path`); \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/0011_snapshot.json b/src-tauri/database/migrations/meta/0011_snapshot.json new file mode 100644 index 0000000..e2be297 --- /dev/null +++ b/src-tauri/database/migrations/meta/0011_snapshot.json @@ -0,0 +1,830 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "c8c0825d-c435-4a42-986a-a4f70e7f9e8b", + "prevId": "288d577f-f9c8-44e8-964e-da1fa062aff9", + "tables": { + "haex_crdt_configs": { + "name": "haex_crdt_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_logs": { + "name": "haex_crdt_logs", + "columns": { + "hlc_timestamp": { + "name": "hlc_timestamp", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "row_pks": { + "name": "row_pks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "op_type": { + "name": "op_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_snapshots": { + "name": "haex_crdt_snapshots", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created": { + "name": "created", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epoch_hlc": { + "name": "epoch_hlc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_url": { + "name": "location_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extension_permissions": { + "name": "haex_extension_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "extension_id": { + "name": "extension_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extension_permissions_extension_id_resource_operation_path_unique": { + "name": "haex_extension_permissions_extension_id_resource_operation_path_unique", + "columns": [ + "extension_id", + "resource", + "operation", + "path" + ], + "isUnique": true + } + }, + "foreignKeys": { + "haex_extension_permissions_extension_id_haex_extensions_id_fk": { + "name": "haex_extension_permissions_extension_id_haex_extensions_id_fk", + "tableFrom": "haex_extension_permissions", + "tableTo": "haex_extensions", + "columnsFrom": [ + "extension_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extensions": { + "name": "haex_extensions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_notifications": { + "name": "haex_notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_group_items": { + "name": "haex_passwords_group_items", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "haex_passwords_group_items_item_id_group_id_pk": { + "columns": [ + "item_id", + "group_id" + ], + "name": "haex_passwords_group_items_item_id_group_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_groups": { + "name": "haex_passwords_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_groups", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_details": { + "name": "haex_passwords_item_details", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_history": { + "name": "haex_passwords_item_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_property": { + "name": "changed_property", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_history", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_key_values": { + "name": "haex_passwords_item_key_values", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_key_values", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_settings": { + "name": "haex_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": { + "\"haex_crdt_settings\"": "\"haex_crdt_configs\"", + "\"haex_extensions_permissions\"": "\"haex_extension_permissions\"" + }, + "columns": { + "\"haex_crdt_configs\".\"type\"": "\"haex_crdt_configs\".\"key\"" + } + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/_journal.json b/src-tauri/database/migrations/meta/_journal.json index f20df69..a77ec49 100644 --- a/src-tauri/database/migrations/meta/_journal.json +++ b/src-tauri/database/migrations/meta/_journal.json @@ -78,6 +78,13 @@ "when": 1756377828646, "tag": "0010_deep_war_machine", "breakpoints": true + }, + { + "idx": 11, + "version": "6", + "when": 1757968140525, + "tag": "0011_illegal_thor_girl", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src-tauri/database/vault.db b/src-tauri/database/vault.db index 286505d..3c09876 100644 Binary files a/src-tauri/database/vault.db and b/src-tauri/database/vault.db differ diff --git a/src-tauri/src/crdt/hlc.rs b/src-tauri/src/crdt/hlc.rs index 069f165..d026150 100644 --- a/src-tauri/src/crdt/hlc.rs +++ b/src-tauri/src/crdt/hlc.rs @@ -1,5 +1,5 @@ // src/hlc_service.rs - +use crate::table_names::TABLE_CRDT_CONFIGS; use rusqlite::{params, Connection, Result as RusqliteResult, Transaction}; use std::{ fmt::Debug, @@ -14,7 +14,7 @@ use uuid::Uuid; const HLC_NODE_ID_TYPE: &str = "hlc_node_id"; const HLC_TIMESTAMP_TYPE: &str = "hlc_timestamp"; -pub const CRDT_SETTINGS_TABLE: &str = "haex_crdt_settings"; +//pub const TABLE_CRDT_CONFIGS: &str = "haex_crdt_settings"; #[derive(Error, Debug)] pub enum HlcError { @@ -49,7 +49,7 @@ impl HlcService { // 3. Load the last persisted timestamp and update the clock. let last_state_str: RusqliteResult = conn.query_row( - &format!("SELECT value FROM {} WHERE type = ?1", CRDT_SETTINGS_TABLE), + &format!("SELECT value FROM {} WHERE key = ?1", TABLE_CRDT_CONFIGS), params![HLC_TIMESTAMP_TYPE], |row| row.get(0), ); @@ -83,9 +83,9 @@ impl HlcService { tx.execute( &format!( - "INSERT INTO {} (type, value) VALUES (?1,?2) - ON CONFLICT(type) DO UPDATE SET value = excluded.value", - CRDT_SETTINGS_TABLE + "INSERT INTO {} (key, value) VALUES (?1,?2) + ON CONFLICT(key) DO UPDATE SET value = excluded.value", + TABLE_CRDT_CONFIGS ), params![HLC_TIMESTAMP_TYPE, timestamp_str], )?; @@ -97,7 +97,7 @@ impl HlcService { fn get_or_create_node_id(conn: &mut Connection) -> Result { let tx = conn.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?; - let query = format!("SELECT value FROM {} WHERE type =?1", CRDT_SETTINGS_TABLE); + let query = format!("SELECT value FROM {} WHERE key =?1", TABLE_CRDT_CONFIGS); match tx.query_row(&query, params![HLC_NODE_ID_TYPE], |row| { row.get::<_, String>(0) @@ -117,8 +117,8 @@ impl HlcService { tx.execute( &format!( - "INSERT INTO {} (type, value) VALUES (?1, ?2)", - CRDT_SETTINGS_TABLE + "INSERT INTO {} (key, value) VALUES (?1, ?2)", + TABLE_CRDT_CONFIGS ), params![HLC_NODE_ID_TYPE, new_id_str], )?; diff --git a/src-tauri/src/crdt/proxy.rs b/src-tauri/src/crdt/proxy.rs index ae98c4b..8f3be26 100644 --- a/src-tauri/src/crdt/proxy.rs +++ b/src-tauri/src/crdt/proxy.rs @@ -1,8 +1,10 @@ // In src-tauri/src/crdt/proxy.rs - use crate::crdt::hlc::HlcService; use crate::crdt::trigger::{HLC_TIMESTAMP_COLUMN, TOMBSTONE_COLUMN}; +use crate::table_names::{TABLE_CRDT_CONFIGS, TABLE_CRDT_LOGS}; +use rusqlite::Connection; use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; use sqlparser::ast::{ Assignment, AssignmentTarget, BinaryOperator, ColumnDef, DataType, Expr, Ident, Insert, ObjectName, ObjectNamePart, SelectItem, SetExpr, Statement, TableFactor, TableObject, @@ -11,8 +13,11 @@ use sqlparser::ast::{ use sqlparser::dialect::SQLiteDialect; use sqlparser::parser::Parser; use std::collections::HashSet; +use std::sync::{Arc, Mutex}; +use tauri::{path::BaseDirectory, AppHandle, Manager, State}; use ts_rs::TS; use uhlc::Timestamp; +pub struct DbConnection(pub Arc>>); #[derive(Serialize, Deserialize, TS)] #[ts(export)] @@ -41,7 +46,7 @@ pub enum ProxyError { } // Tabellen, die von der Proxy-Logik ausgeschlossen sind. -const EXCLUDED_TABLES: &[&str] = &["haex_crdt_settings", "haex_crdt_logs"]; +const EXCLUDED_TABLES: &[&str] = &[TABLE_CRDT_CONFIGS, TABLE_CRDT_LOGS]; pub struct SqlProxy; @@ -54,7 +59,8 @@ impl SqlProxy { pub fn execute( &self, sql: &str, - conn: &mut rusqlite::Connection, + params: Vec, + state: State<'_, DbConnection>, hlc_service: &HlcService, ) -> Result, ProxyError> { let dialect = SQLiteDialect {}; @@ -64,21 +70,27 @@ impl SqlProxy { let mut modified_schema_tables = HashSet::new(); + let db_lock = state + .0 + .lock() + .map_err(|e| format!("Mutex Lock Fehler: {}", e))?; + let conn = db_lock.as_ref().ok_or("Keine Datenbankverbindung")?; + let tx = conn .transaction() .map_err(|e| ProxyError::TransactionError { reason: e.to_string(), })?; - let hlc_timestamp = - hlc_service - .new_timestamp_and_persist(&tx) - .map_err(|e| ProxyError::HlcError { - reason: e.to_string(), - })?; + /* let hlc_timestamp = + hlc_service + .new_timestamp_and_persist(&tx) + .map_err(|e| ProxyError::HlcError { + reason: e.to_string(), + })?; */ for statement in &mut ast_vec { - if let Some(table_name) = self.transform_statement(statement, Some(&hlc_timestamp))? { + if let Some(table_name) = self.transform_statement(statement)? { modified_schema_tables.insert(table_name); } } @@ -99,15 +111,12 @@ impl SqlProxy { } /// Wendet die Transformation auf ein einzelnes Statement an. - fn transform_statement( - &self, - stmt: &mut Statement, - hlc_timestamp: Option<&Timestamp>, - ) -> Result, ProxyError> { + fn transform_statement(&self, stmt: &mut Statement) -> Result, ProxyError> { match stmt { Statement::Query(query) => { if let SetExpr::Select(select) = &mut *query.body { let mut tombstone_filters = Vec::new(); + for twj in &select.from { if let TableFactor::Table { name, alias, .. } = &twj.relation { if self.is_audited_table(name) { @@ -160,7 +169,7 @@ impl SqlProxy { } } - // Hinweis: UNION, EXCEPT etc. werden hier nicht behandelt, was dem bisherigen Code entspricht. + // TODO: UNION, EXCEPT etc. werden hier nicht behandelt } Statement::CreateTable(create_table) => { @@ -180,9 +189,7 @@ impl SqlProxy { Statement::Insert(insert_stmt) => { if let TableObject::TableName(name) = &insert_stmt.table { if self.is_audited_table(name) { - if let Some(ts) = hlc_timestamp { - self.add_hlc_to_insert(insert_stmt, ts); - } + self.add_hlc_to_insert(insert_stmt); } } } @@ -217,9 +224,8 @@ impl SqlProxy { if let Some(name) = table_name { if self.is_audited_table(&name) { // GEÄNDERT: Übergibt den Zeitstempel an die Transformationsfunktion - if let Some(ts) = hlc_timestamp { - self.transform_delete_to_update(stmt, ts); - } + + self.transform_delete_to_update(stmt); } } else { return Err(ProxyError::UnsupportedStatement { @@ -336,24 +342,20 @@ impl SqlProxy { if !columns.iter().any(|c| c.name.value == HLC_TIMESTAMP_COLUMN) { columns.push(ColumnDef { name: Ident::new(HLC_TIMESTAMP_COLUMN), - data_type: DataType::Text, // HLC wird als String gespeichert + data_type: DataType::String(None), options: vec![], }); } } - fn transform_delete_to_update(&self, stmt: &mut Statement, hlc_timestamp: &Timestamp) { + fn transform_delete_to_update(&self, stmt: &mut Statement) { if let Statement::Delete(del_stmt) = stmt { let table_to_update = match &del_stmt.from { sqlparser::ast::FromTable::WithFromKeyword(from) | sqlparser::ast::FromTable::WithoutKeyword(from) => from[0].clone(), }; - // Erstellt beide Zuweisungen - let assignments = vec![ - self.create_tombstone_assignment(), - self.create_hlc_assignment(hlc_timestamp), - ]; + let assignments = vec![self.create_tombstone_assignment()]; *stmt = Statement::Update { table: table_to_update, diff --git a/src-tauri/src/crdt/trigger.rs b/src-tauri/src/crdt/trigger.rs index b6c893d..e722c8e 100644 --- a/src-tauri/src/crdt/trigger.rs +++ b/src-tauri/src/crdt/trigger.rs @@ -1,16 +1,15 @@ -use crate::table_names::{TABLE_CRDT_CONFIGS, TABLE_CRDT_LOGS}; +use crate::table_names::TABLE_CRDT_LOGS; use rusqlite::{Connection, Result as RusqliteResult, Row, Transaction}; use serde::Serialize; use std::error::Error; use std::fmt::{self, Display, Formatter, Write}; -use std::panic::{self, AssertUnwindSafe}; use ts_rs::TS; -// Die "z_"-Präfix soll sicherstellen, dass diese Trigger als Letzte ausgeführt werden +// Der "z_"-Präfix soll sicherstellen, dass diese Trigger als Letzte ausgeführt werden const INSERT_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_insert"; const UPDATE_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_update"; -const SYNC_ACTIVE_KEY: &str = "sync_active"; +//const SYNC_ACTIVE_KEY: &str = "sync_active"; pub const TOMBSTONE_COLUMN: &str = "haex_tombstone"; pub const HLC_TIMESTAMP_COLUMN: &str = "haex_hlc_timestamp"; @@ -23,6 +22,10 @@ pub enum CrdtSetupError { table_name: String, column_name: String, }, + HlcColumnMissing { + table_name: String, + column_name: String, + }, /// Die Tabelle hat keinen Primärschlüssel, was eine CRDT-Voraussetzung ist. PrimaryKeyMissing { table_name: String }, } @@ -40,6 +43,14 @@ impl Display for CrdtSetupError { "Table '{}' is missing the required tombstone column '{}'", table_name, column_name ), + CrdtSetupError::HlcColumnMissing { + table_name, + column_name, + } => write!( + f, + "Table '{}' is missing the required hlc column '{}'", + table_name, column_name + ), CrdtSetupError::PrimaryKeyMissing { table_name } => { write!(f, "Table '{}' has no primary key", table_name) } @@ -66,55 +77,52 @@ pub enum TriggerSetupResult { TableNotFound, } -fn set_sync_active(conn: &mut Connection) -> RusqliteResult<()> { +/* fn set_sync_active(conn: &mut Connection) -> RusqliteResult<()> { let sql = format!( "INSERT OR REPLACE INTO \"{meta_table}\" (key, value) VALUES (?, '1');", meta_table = TABLE_CRDT_CONFIGS ); conn.execute(&sql, [SYNC_ACTIVE_KEY])?; Ok(()) -} +} */ -fn clear_sync_active(conn: &mut Connection) -> RusqliteResult<()> { +/* fn clear_sync_active(conn: &mut Connection) -> RusqliteResult<()> { let sql = format!( "DELETE FROM \"{meta_table}\" WHERE key = ?;", meta_table = TABLE_CRDT_CONFIGS ); conn.execute(&sql, [SYNC_ACTIVE_KEY])?; Ok(()) -} +} */ /// Führt eine Aktion aus, während die Trigger temporär deaktiviert sind. /// Diese Funktion stellt sicher, dass die Trigger auch bei einem Absturz (Panic) /// wieder aktiviert werden. -pub fn with_triggers_paused(conn: &mut Connection, action: F) -> RusqliteResult +/* pub fn with_triggers_paused(conn: &mut Connection, action: F) -> RusqliteResult where F: FnOnce(&mut Connection) -> RusqliteResult, { - set_sync_active(conn)?; - // AssertUnwindSafe wird benötigt, um den Mutex über eine Panic-Grenze hinweg zu verwenden. // Wir fangen einen möglichen Panic in `action` ab. let result = panic::catch_unwind(AssertUnwindSafe(|| action(conn))); // Diese Aktion MUSS immer ausgeführt werden, egal ob `action` erfolgreich war oder nicht. - clear_sync_active(conn)?; match result { Ok(res) => res, // Alles gut, gib das Ergebnis von `action` zurück. Err(e) => panic::resume_unwind(e), // Ein Panic ist aufgetreten, wir geben ihn weiter, nachdem wir aufgeräumt haben. } -} +} */ /// Erstellt die benötigte Meta-Tabelle, falls sie nicht existiert. -pub fn setup_meta_table(conn: &mut Connection) -> RusqliteResult<()> { +/* pub fn setup_meta_table(conn: &mut Connection) -> RusqliteResult<()> { let sql = format!( "CREATE TABLE IF NOT EXISTS \"{meta_table}\" (key TEXT PRIMARY KEY, value TEXT) WITHOUT ROWID;", meta_table = TABLE_CRDT_CONFIGS ); conn.execute(&sql, [])?; Ok(()) -} +} */ #[derive(Debug)] struct ColumnInfo { @@ -139,6 +147,7 @@ fn is_safe_identifier(name: &str) -> bool { pub fn setup_triggers_for_table( conn: &mut Connection, table_name: &str, + recreate: &bool, ) -> Result { if !is_safe_identifier(table_name) { return Err(rusqlite::Error::InvalidParameterName(format!( @@ -161,6 +170,13 @@ pub fn setup_triggers_for_table( }); } + if !columns.iter().any(|c| c.name == HLC_TIMESTAMP_COLUMN) { + return Err(CrdtSetupError::HlcColumnMissing { + table_name: table_name.to_string(), + column_name: HLC_TIMESTAMP_COLUMN.to_string(), + }); + } + let pks: Vec = columns .iter() .filter(|c| c.is_pk) @@ -175,7 +191,7 @@ pub fn setup_triggers_for_table( let cols_to_track: Vec = columns .iter() - .filter(|c| !c.is_pk && c.name != TOMBSTONE_COLUMN && c.name != HLC_TIMESTAMP_COLUMN) + .filter(|c| !c.is_pk) //&& c.name != TOMBSTONE_COLUMN && c.name != HLC_TIMESTAMP_COLUMN .map(|c| c.name.clone()) .collect(); @@ -186,6 +202,10 @@ pub fn setup_triggers_for_table( // Führe die Erstellung innerhalb einer Transaktion aus let tx = conn.transaction()?; + + if *recreate { + drop_triggers_for_table(&tx, table_name)?; + } tx.execute_batch(&sql_batch)?; tx.commit()?; @@ -224,7 +244,7 @@ pub fn drop_triggers_for_table( Ok(()) } -pub fn recreate_triggers_for_table( +/* pub fn recreate_triggers_for_table( conn: &mut Connection, table_name: &str, ) -> Result { @@ -278,7 +298,7 @@ pub fn recreate_triggers_for_table( Ok(TriggerSetupResult::Success) } - + */ /// Generiert das SQL für den INSERT-Trigger. fn generate_insert_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String { let pk_json_payload = pks @@ -287,29 +307,39 @@ fn generate_insert_trigger_sql(table_name: &str, pks: &[String], cols: &[String] .collect::>() .join(", "); - let column_inserts = cols.iter().fold(String::new(), |mut acc, col| { - writeln!(&mut acc, " INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk, column_name, value) VALUES (NEW.\"{hlc_col}\", 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));", + let column_inserts = if cols.is_empty() { + // Nur PKs -> einfacher Insert ins Log + format!( + "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks) + VALUES (hlc_new_timestamp(), 'INSERT', '{table}', json_object({pk_payload}));", log_table = TABLE_CRDT_LOGS, - hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, - pk_payload = pk_json_payload, - column = col - ).unwrap(); - acc - }); + pk_payload = pk_json_payload + ) + } else { + cols.iter().fold(String::new(), |mut acc, col| { + writeln!( + &mut acc, + "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks, column_name, new_value) + VALUES (hlc_new_timestamp(), 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));", + log_table = TABLE_CRDT_LOGS, + table = table_name, + pk_payload = pk_json_payload, + column = col + ).unwrap(); + acc + }) + }; let trigger_name = INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name); format!( "CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\" AFTER INSERT ON \"{table_name}\" - WHEN (SELECT value FROM \"{config_table}\" WHERE key = '{sync_key}') IS NOT '1' FOR EACH ROW BEGIN - {column_inserts} - END;", - config_table = TABLE_CRDT_CONFIGS, - sync_key = SYNC_ACTIVE_KEY + {column_inserts} + END;" ) } @@ -326,6 +356,57 @@ fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String] .collect::>() .join(", "); + let mut body = String::new(); + + // Spaltenänderungen loggen + if !cols.is_empty() { + for col in cols { + writeln!( + &mut body, + "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks, column_name, new_value, old_value) + SELECT hlc_new_timestamp(), 'UPDATE', '{table}', json_object({pk_payload}), '{column}', + json_object('value', NEW.\"{column}\"), json_object('value', OLD.\"{column}\") + WHERE NEW.\"{column}\" IS NOT OLD.\"{column}\";", + log_table = TABLE_CRDT_LOGS, + table = table_name, + pk_payload = pk_json_payload, + column = col + ).unwrap(); + } + } + + // Soft-delete loggen + writeln!( + &mut body, + "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks) + SELECT hlc_new_timestamp(), 'DELETE', '{table}', json_object({pk_payload}) + WHERE NEW.\"{tombstone_col}\" = 1 AND OLD.\"{tombstone_col}\" = 0;", + log_table = TABLE_CRDT_LOGS, + table = table_name, + pk_payload = pk_json_payload, + tombstone_col = TOMBSTONE_COLUMN + ) + .unwrap(); + + let trigger_name = UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name); + + format!( + "CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\" + AFTER UPDATE ON \"{table_name}\" + FOR EACH ROW + BEGIN + {body} + END;" + ) +} + +/* fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String { + let pk_json_payload = pks + .iter() + .map(|pk| format!("'{}', NEW.\"{}\"", pk, pk)) + .collect::>() + .join(", "); + let column_updates = cols.iter().fold(String::new(), |mut acc, col| { writeln!(&mut acc, " IF NEW.\"{column}\" IS NOT OLD.\"{column}\" THEN INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk, column_name, value, old_value) VALUES (NEW.\"{hlc_col}\", 'UPDATE', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"), json_object('value', OLD.\"{column}\")); END IF;", log_table = TABLE_CRDT_LOGS, @@ -361,7 +442,8 @@ fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String] sync_key = SYNC_ACTIVE_KEY ) } - + */ +/* /// Durchläuft alle `haex_`-Tabellen und richtet die CRDT-Trigger ein. pub fn generate_haex_triggers(conn: &mut Connection) -> Result<(), rusqlite::Error> { println!("🔄 Setup CRDT triggers..."); @@ -387,4 +469,4 @@ pub fn generate_haex_triggers(conn: &mut Connection) -> Result<(), rusqlite::Err } println!("✨ Done setting up CRDT triggers."); Ok(()) -} +} */ diff --git a/src-tauri/src/crdt/trigger_alter.rs b/src-tauri/src/crdt/trigger_alter.rs new file mode 100644 index 0000000..e0d3d8d --- /dev/null +++ b/src-tauri/src/crdt/trigger_alter.rs @@ -0,0 +1,276 @@ +// Wir binden die Konstanten aus unserem generierten Modul ein. +// `crate` bezieht sich auf das Wurzelverzeichnis unseres Crates (src-tauri/src). +use crate::tableNames::*; + +use rusqlite::{Connection, Result as RusqliteResult, Row}; +use serde::Serialize; +use std::error::Error; +use std::fmt::{self, Display, Formatter, Write}; +use std::panic::{self, AssertUnwindSafe}; +use ts_rs::TS; + +// Harte Konstanten, die nicht aus der JSON-Datei kommen, da sie Teil der internen Logik sind. +const SYNC_ACTIVE_KEY: &str = "sync_active"; +const TOMBSTONE_COLUMN: &str = "haex_tombstone"; +const HLC_TIMESTAMP_COLUMN: &str = "haex_hlc_timestamp"; +const INSERT_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_insert"; +const UPDATE_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_update"; + +// --- Eigener Error-Typ für klares Fehler-Handling --- +#[derive(Debug)] +pub enum CrdtSetupError { + DatabaseError(rusqlite::Error), + TombstoneColumnMissing { + table_name: String, + column_name: String, + }, + PrimaryKeyMissing { + table_name: String, + }, +} + +impl Display for CrdtSetupError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + CrdtSetupError::DatabaseError(e) => write!(f, "Database error: {}", e), + CrdtSetupError::TombstoneColumnMissing { + table_name, + column_name, + } => write!( + f, + "Table '{}' is missing the required tombstone column '{}'", + table_name, column_name + ), + CrdtSetupError::PrimaryKeyMissing { table_name } => { + write!(f, "Table '{}' has no primary key", table_name) + } + } + } +} +impl Error for CrdtSetupError {} +impl From for CrdtSetupError { + fn from(err: rusqlite::Error) -> Self { + CrdtSetupError::DatabaseError(err) + } +} + +// --- Öffentliche Structs und Enums --- +#[derive(Debug, Serialize, TS)] +#[ts(export)] +pub enum TriggerSetupResult { + Success, + TableNotFound, +} + +#[derive(Debug)] +struct ColumnInfo { + name: String, + is_pk: bool, +} +impl ColumnInfo { + fn from_row(row: &Row) -> RusqliteResult { + Ok(ColumnInfo { + name: row.get("name")?, + is_pk: row.get::<_, i64>("pk")? > 0, + }) + } +} + +// --- Öffentliche Funktionen für die Anwendungslogik --- + +/// Erstellt die benötigten CRDT-Systemtabellen (z.B. die Config-Tabelle), falls sie nicht existieren. +/// Sollte beim Anwendungsstart einmalig aufgerufen werden. +pub fn setup_crdt_tables(conn: &mut Connection) -> RusqliteResult<()> { + let config_sql = format!( + "CREATE TABLE IF NOT EXISTS \"{config_table}\" (key TEXT PRIMARY KEY, value TEXT) WITHOUT ROWID;", + config_table = TABLE_CRDT_CONFIGS + ); + conn.execute(&config_sql, [])?; + Ok(()) +} + +/// Führt eine Aktion aus, während die Trigger temporär deaktiviert sind. +/// Stellt sicher, dass die Trigger auch bei einem Absturz (Panic) wieder aktiviert werden. +pub fn with_triggers_paused(conn: &mut Connection, action: F) -> RusqliteResult +where + F: FnOnce(&mut Connection) -> RusqliteResult, +{ + set_sync_active(conn)?; + // `catch_unwind` fängt einen möglichen Panic in `action` ab. + let result = panic::catch_unwind(AssertUnwindSafe(|| action(conn))); + // Diese Aufräumaktion wird immer ausgeführt. + clear_sync_active(conn)?; + match result { + Ok(res) => res, // Alles gut, gib das Ergebnis von `action` zurück. + Err(e) => panic::resume_unwind(e), // Ein Panic ist aufgetreten, wir geben ihn weiter, nachdem wir aufgeräumt haben. + } +} + +/// Analysiert alle `haex_`-Tabellen in der Datenbank und erstellt die notwendigen CRDT-Trigger. +pub fn generate_haex_triggers(conn: &mut Connection) -> RusqliteResult<()> { + println!("🔄 Setup CRDT triggers..."); + let table_names: Vec = { + let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'haex_%' AND name NOT LIKE 'haex_crdt_%';")?; + let rows = stmt.query_map([], |row| row.get::<_, String>(0))?; + rows.collect::>>()? + }; + + for table_name in table_names { + // Überspringe die Config-Tabelle selbst, sie braucht keine Trigger. + if table_name == TABLE_CRDT_CONFIGS { + continue; + } + println!("➡️ Processing table: {}", table_name); + match setup_triggers_for_table(conn, &table_name) { + Ok(TriggerSetupResult::Success) => { + println!(" ✅ Triggers created for {}", table_name) + } + Ok(TriggerSetupResult::TableNotFound) => { + println!(" ℹ️ Table {} not found, skipping.", table_name) + } + Err(e) => println!(" ❌ Could not set up triggers for {}: {}", table_name, e), + } + } + println!("✨ Done setting up CRDT triggers."); + Ok(()) +} + +// --- Private Hilfsfunktionen --- + +fn set_sync_active(conn: &mut Connection) -> RusqliteResult<()> { + let sql = format!( + "INSERT OR REPLACE INTO \"{config_table}\" (key, value) VALUES (?, '1');", + config_table = TABLE_CRDT_CONFIGS + ); + conn.execute(&sql, [SYNC_ACTIVE_KEY])?; + Ok(()) +} + +fn clear_sync_active(conn: &mut Connection) -> RusqliteResult<()> { + let sql = format!( + "DELETE FROM \"{config_table}\" WHERE key = ?;", + config_table = TABLE_CRDT_CONFIGS + ); + conn.execute(&sql, [SYNC_ACTIVE_KEY])?; + Ok(()) +} + +fn is_safe_identifier(name: &str) -> bool { + !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_') +} + +fn setup_triggers_for_table( + conn: &mut Connection, + table_name: &str, +) -> Result { + if !is_safe_identifier(table_name) { + return Err(rusqlite::Error::InvalidParameterName(format!( + "Invalid table name: {}", + table_name + )) + .into()); + } + let columns = get_table_schema(conn, table_name)?; + if columns.is_empty() { + return Ok(TriggerSetupResult::TableNotFound); + } + if !columns.iter().any(|c| c.name == TOMBSTONE_COLUMN) { + return Err(CrdtSetupError::TombstoneColumnMissing { + table_name: table_name.to_string(), + column_name: TOMBSTONE_COLUMN.to_string(), + }); + } + let pks: Vec = columns + .iter() + .filter(|c| c.is_pk) + .map(|c| c.name.clone()) + .collect(); + if pks.is_empty() { + return Err(CrdtSetupError::PrimaryKeyMissing { + table_name: table_name.to_string(), + }); + } + let cols_to_track: Vec = columns + .iter() + .filter(|c| !c.is_pk && c.name != TOMBSTONE_COLUMN && c.name != HLC_TIMESTAMP_COLUMN) + .map(|c| c.name.clone()) + .collect(); + + let insert_trigger_sql = generate_insert_trigger_sql(table_name, &pks, &cols_to_track); + let update_trigger_sql = generate_update_trigger_sql(table_name, &pks, &cols_to_track); + let drop_insert_trigger_sql = + drop_trigger_sql(INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name)); + let drop_update_trigger_sql = + drop_trigger_sql(UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name)); + + let tx = conn.transaction()?; + tx.execute_batch(&format!( + "{}\n{}\n{}\n{}", + drop_insert_trigger_sql, drop_update_trigger_sql, insert_trigger_sql, update_trigger_sql + ))?; + tx.commit()?; + + Ok(TriggerSetupResult::Success) +} + +fn get_table_schema(conn: &Connection, table_name: &str) -> RusqliteResult> { + let sql = format!("PRAGMA table_info(\"{}\");", table_name); + let mut stmt = conn.prepare(&sql)?; + let rows = stmt.query_map([], ColumnInfo::from_row)?; + rows.collect() +} + +fn drop_trigger_sql(trigger_name: String) -> String { + format!("DROP TRIGGER IF EXISTS \"{}\";", trigger_name) +} + +fn generate_insert_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String { + let pk_json_payload = pks + .iter() + .map(|pk| format!("'{}', NEW.\"{}\"", pk, pk)) + .collect::>() + .join(", "); + let column_inserts = cols.iter().fold(String::new(), |mut acc, col| { + writeln!(&mut acc, " INSERT INTO \"{log_table}\" (hlc_timestamp, op_type, table_name, row_pk, column_name, value) VALUES (NEW.\"{hlc_col}\", 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));", log_table = TABLE_CRDT_LOGS, hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, pk_payload = pk_json_payload, column = col).unwrap(); + acc + }); + let trigger_name = INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name); + format!( + "CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\"\n" + + " AFTER INSERT ON \"{table_name}\"\n" + + " WHEN (SELECT value FROM \"{config_table}\" WHERE key = '{sync_key}') IS NOT '1'\n" + + " FOR EACH ROW\n" + + " BEGIN\n" + + " {column_inserts}\n" + + " END;", + config_table = TABLE_CRDT_CONFIGS, + sync_key = SYNC_ACTIVE_KEY + ) +} + +fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String { + let pk_json_payload = pks + .iter() + .map(|pk| format!("'{}', NEW.\"{}\"", pk, pk)) + .collect::>() + .join(", "); + let column_updates = cols.iter().fold(String::new(), |mut acc, col| { + writeln!(&mut acc, " IF NEW.\"{column}\" IS NOT OLD.\"{column}\" THEN INSERT INTO \"{log_table}\" (hlc_timestamp, op_type, table_name, row_pk, column_name, value, old_value) VALUES (NEW.\"{hlc_col}\", 'UPDATE', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"), json_object('value', OLD.\"{column}\")); END IF;", log_table = TABLE_CRDT_LOGS, hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, pk_payload = pk_json_payload, column = col).unwrap(); + acc + }); + let soft_delete_logic = format!( + " IF NEW.\"{tombstone_col}\" = 1 AND OLD.\"{tombstone_col}\" = 0 THEN INSERT INTO \"{log_table}\" (hlc_timestamp, op_type, table_name, row_pk) VALUES (NEW.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload})); END IF;", log_table = TABLE_CRDT_LOGS, hlc_col = HLC_TIMESTAMP_COLUMN, tombstone_col = TOMBSTONE_COLUMN, table = table_name, pk_payload = pk_json_payload); + let trigger_name = UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name); + format!( + "CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\"\n" + + " AFTER UPDATE ON \"{table_name}\"\n" + + " WHEN (SELECT value FROM \"{config_table}\" WHERE key = '{sync_key}') IS NOT '1'\n" + + " FOR EACH ROW\n" + + " BEGIN\n" + + " {column_updates}\n" + + " {soft_delete_logic}\n" + + " END;", + config_table = TABLE_CRDT_CONFIGS, + sync_key = SYNC_ACTIVE_KEY + ) +} diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index 96912e6..11310ad 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -10,6 +10,7 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use tauri::{path::BaseDirectory, AppHandle, Manager, State}; +use crate::crdt::trigger; use crate::database::core::open_and_init_db; pub struct HlcService(pub Mutex); pub struct DbConnection(pub Arc>>); @@ -163,6 +164,8 @@ pub fn create_encrypted_database( println!("resource_path: {}", resource_path.display()); + // erstelle Trigger für haex_tables + conn.close().unwrap(); let new_conn = open_and_init_db(&path, &key, false)?; @@ -199,6 +202,7 @@ pub fn open_encrypted_database( 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()); Ok(format!("success")) @@ -279,17 +283,3 @@ pub fn update_hlc_from_remote( hlc.update_with_timestamp(&remote_ts) .map_err(|e| format!("HLC update failed: {:?}", e)) } - -#[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/lib.rs b/src-tauri/src/lib.rs index 1e348f3..531d76c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -70,7 +70,6 @@ pub fn run() { database::test, database::update_hlc_from_remote, extension::copy_directory, - extension::database::extension_sql_execute, extension::database::extension_sql_select, /* android_storage::request_storage_permission, android_storage::has_storage_permission, diff --git a/src/stores/vault/index.ts b/src/stores/vault/index.ts index f23c67a..72439ac 100644 --- a/src/stores/vault/index.ts +++ b/src/stores/vault/index.ts @@ -1,10 +1,7 @@ import { drizzle } from 'drizzle-orm/sqlite-proxy' 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