From 41fd49d601c64f3e1333ffea2371927c8594e069 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Tue, 11 Nov 2025 10:40:49 +0200 Subject: [PATCH 01/12] Update drizzle-orm version. --- packages/drizzle-driver/package.json | 4 +- packages/node/package.json | 4 +- pnpm-lock.yaml | 149 ++++++++++++--------------- 3 files changed, 68 insertions(+), 89 deletions(-) diff --git a/packages/drizzle-driver/package.json b/packages/drizzle-driver/package.json index dc1c26730..954233dc2 100644 --- a/packages/drizzle-driver/package.json +++ b/packages/drizzle-driver/package.json @@ -50,9 +50,9 @@ "@powersync/web": "workspace:*", "@journeyapps/wa-sqlite": "^1.3.2", "@types/node": "^20.17.6", - "drizzle-orm": "^0.35.2", + "drizzle-orm": "^0.44.7", "vite": "^6.1.0", "vite-plugin-top-level-await": "^1.4.4", "vite-plugin-wasm": "^3.3.0" } -} +} \ No newline at end of file diff --git a/packages/node/package.json b/packages/node/package.json index 8479f835f..5f052f4bd 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -76,7 +76,7 @@ "@powersync/drizzle-driver": "workspace:*", "@types/node": "^24.2.0", "better-sqlite3": "^12.2.0", - "drizzle-orm": "^0.35.2", + "drizzle-orm": "^0.44.7", "rollup": "4.14.3", "typescript": "^5.5.3", "vitest": "^3.2.4" @@ -88,4 +88,4 @@ "real-time data stream", "live data" ] -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84a04de08..df883b702 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,10 +113,10 @@ importers: devDependencies: '@angular-builders/custom-webpack': specifier: ^19.0.0 - version: 19.0.1(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + version: 19.0.1(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) '@angular-devkit/build-angular': specifier: ^19.2.5 - version: 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + version: 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) '@angular/cli': specifier: ^19.2.5 version: 19.2.14(@types/node@24.2.0)(chokidar@4.0.3) @@ -143,7 +143,7 @@ importers: version: 4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/vector-icons': specifier: ^14.0.0 - version: 14.1.0(a6850416216e8b64df60af23d5183c0b) + version: 14.1.0(wm3bvfp4qcetscjld4hplpimri) '@journeyapps/react-native-quick-sqlite': specifier: ^2.4.9 version: 2.4.9(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -164,7 +164,7 @@ importers: version: 0.1.11(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) '@react-navigation/drawer': specifier: ^7.1.1 - version: 7.4.1(1d85788bd68a0e12619f848d71cbac62) + version: 7.4.1(j6abyuabi5plzpedpvxbnwhrsi) '@react-navigation/native': specifier: ^7.0.14 version: 7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -188,7 +188,7 @@ importers: version: 2.1.10 expo-router: specifier: 4.0.21 - version: 4.0.21(e063c8109134fcdd1c97e4d6a4caf625) + version: 4.0.21(xdzi7taj2dri7edfzwov6a63va) expo-splash-screen: specifier: ~0.29.22 version: 0.29.24(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -236,7 +236,7 @@ importers: version: 10.2.0 react-navigation-stack: specifier: ^2.10.4 - version: 2.10.4(1b7f2cbbd098c1646b3c5f57acc57915) + version: 2.10.4(4a23q4g4mav7ddr6jlhxnyzzo4) typed-async-storage: specifier: ^3.1.2 version: 3.1.2 @@ -919,7 +919,7 @@ importers: version: 7.0.5(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) expo-router: specifier: 4.0.21 - version: 4.0.21(b0bddf53ba1689b30337428eee4dc275) + version: 4.0.21(cpo3xaw6yrjernjvkkkt7bisia) expo-splash-screen: specifier: ~0.29.22 version: 0.29.24(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -992,7 +992,7 @@ importers: version: 1.0.2 '@expo/vector-icons': specifier: ^14.0.3 - version: 14.1.0(a6850416216e8b64df60af23d5183c0b) + version: 14.1.0(wm3bvfp4qcetscjld4hplpimri) '@journeyapps/react-native-quick-sqlite': specifier: ^2.4.9 version: 2.4.9(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -1013,7 +1013,7 @@ importers: version: 0.1.11(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) '@react-navigation/drawer': specifier: ^7.1.1 - version: 7.4.1(1d85788bd68a0e12619f848d71cbac62) + version: 7.4.1(j6abyuabi5plzpedpvxbnwhrsi) '@react-navigation/native': specifier: ^7.0.14 version: 7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -1037,7 +1037,7 @@ importers: version: 0.13.3(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) expo-camera: specifier: ~16.0.18 - version: 16.0.18(55c6da9df988ca7f1678935d82e9238e) + version: 16.0.18(hml277kvlorqbj6gijmq6joh24) expo-constants: specifier: ~17.0.8 version: 17.0.8(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -1052,7 +1052,7 @@ importers: version: 7.0.5(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) expo-router: specifier: 4.0.21 - version: 4.0.21(e063c8109134fcdd1c97e4d6a4caf625) + version: 4.0.21(xdzi7taj2dri7edfzwov6a63va) expo-secure-store: specifier: ~14.0.1 version: 14.0.1(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -1094,7 +1094,7 @@ importers: version: 4.4.0(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) react-navigation-stack: specifier: ^2.10.4 - version: 2.10.4(1b7f2cbbd098c1646b3c5f57acc57915) + version: 2.10.4(4a23q4g4mav7ddr6jlhxnyzzo4) devDependencies: '@babel/core': specifier: ^7.26.10 @@ -1131,7 +1131,7 @@ importers: version: 4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/vector-icons': specifier: ^14.0.2 - version: 14.1.0(a6850416216e8b64df60af23d5183c0b) + version: 14.1.0(wm3bvfp4qcetscjld4hplpimri) '@journeyapps/react-native-quick-sqlite': specifier: ^2.4.9 version: 2.4.9(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -1158,7 +1158,7 @@ importers: version: 7.3.14(@react-navigation/native@7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-screens@4.4.0(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) '@react-navigation/drawer': specifier: ^7.1.1 - version: 7.4.1(1d85788bd68a0e12619f848d71cbac62) + version: 7.4.1(j6abyuabi5plzpedpvxbnwhrsi) '@react-navigation/native': specifier: ^7.0.14 version: 7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -1179,7 +1179,7 @@ importers: version: 14.0.3(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) expo-camera: specifier: ~16.0.18 - version: 16.0.18(55c6da9df988ca7f1678935d82e9238e) + version: 16.0.18(hml277kvlorqbj6gijmq6joh24) expo-constants: specifier: ~17.0.5 version: 17.0.8(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -1197,7 +1197,7 @@ importers: version: 7.0.5(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) expo-router: specifier: 4.0.21 - version: 4.0.21(e063c8109134fcdd1c97e4d6a4caf625) + version: 4.0.21(xdzi7taj2dri7edfzwov6a63va) expo-secure-store: specifier: ^14.0.1 version: 14.0.1(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -1212,7 +1212,7 @@ importers: version: 0.2.2(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) expo-system-ui: specifier: ~4.0.8 - version: 4.0.9(fa4ab2ddb2d13a20299c682fc53644db) + version: 4.0.9(l76mjoke3yk4s56nokhxoxxpou) expo-web-browser: specifier: ~14.0.2 version: 14.0.2(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -1273,7 +1273,7 @@ importers: version: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.9.2)) jest-expo: specifier: ~52.0.3 - version: 52.0.6(3635c191458c5fa90af52243d15b5fda) + version: 52.0.6(ghmzvfvfblgo7fvi646fzprkjq) react-test-renderer: specifier: 18.3.1 version: 18.3.1(react@18.3.1) @@ -2030,8 +2030,8 @@ importers: specifier: ^20.17.6 version: 20.17.57 drizzle-orm: - specifier: ^0.35.2 - version: 0.35.3(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(@types/sql.js@1.4.9)(better-sqlite3@12.2.0)(kysely@0.28.2)(react@19.0.0)(sql.js@1.13.0) + specifier: ^0.44.7 + version: 0.44.7(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-sqlite3@12.2.0)(kysely@0.28.2)(sql.js@1.13.0) vite: specifier: ^6.1.0 version: 6.3.5(@types/node@20.17.57)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0)(tsx@4.19.4)(yaml@2.8.0) @@ -2098,8 +2098,8 @@ importers: specifier: ^12.2.0 version: 12.2.0 drizzle-orm: - specifier: ^0.35.2 - version: 0.35.3(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(@types/sql.js@1.4.9)(better-sqlite3@12.2.0)(kysely@0.28.2)(react@19.0.0)(sql.js@1.13.0) + specifier: ^0.44.7 + version: 0.44.7(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-sqlite3@12.2.0)(kysely@0.28.2)(sql.js@1.13.0) rollup: specifier: 4.14.3 version: 4.14.3 @@ -12210,36 +12210,36 @@ packages: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} - drizzle-orm@0.35.3: - resolution: {integrity: sha512-Uv6N+b36x4BaZlxc96e+ag7RnMapBLGhc4SSi2F7RDwqYJipWjaU/P68RUp1FbW9r+mxoDp8nMz2Eece8PJxfA==} + drizzle-orm@0.44.7: + resolution: {integrity: sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' - '@neondatabase/serverless': '>=0.1' + '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' + '@planetscale/database': '>=1.13' '@prisma/client': '*' '@tidbcloud/serverless': '*' '@types/better-sqlite3': '*' '@types/pg': '*' - '@types/react': '>=18' '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' '@vercel/postgres': '>=0.8.0' '@xata.io/client': '*' better-sqlite3: '>=7' bun-types: '*' - expo-sqlite: '>=13.2.0' + expo-sqlite: '>=14.0.0' + gel: '>=2' knex: '*' kysely: '*' mysql2: '>=2' pg: '>=8' postgres: '>=3' prisma: '*' - react: '>=18' sql.js: '>=1' sqlite3: '>=5' peerDependenciesMeta: @@ -12251,6 +12251,8 @@ packages: optional: true '@libsql/client': optional: true + '@libsql/client-wasm': + optional: true '@neondatabase/serverless': optional: true '@op-engineering/op-sqlite': @@ -12267,10 +12269,10 @@ packages: optional: true '@types/pg': optional: true - '@types/react': - optional: true '@types/sql.js': optional: true + '@upstash/redis': + optional: true '@vercel/postgres': optional: true '@xata.io/client': @@ -12281,6 +12283,8 @@ packages: optional: true expo-sqlite: optional: true + gel: + optional: true knex: optional: true kysely: @@ -12293,8 +12297,6 @@ packages: optional: true prisma: optional: true - react: - optional: true sql.js: optional: true sqlite3: @@ -21688,11 +21690,11 @@ snapshots: - chokidar - typescript - '@angular-builders/custom-webpack@19.0.1(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': + '@angular-builders/custom-webpack@19.0.1(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': dependencies: '@angular-builders/common': 3.0.1(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(typescript@5.5.4) '@angular-devkit/architect': 0.1902.14(chokidar@4.0.3) - '@angular-devkit/build-angular': 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + '@angular-devkit/build-angular': 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) '@angular-devkit/core': 19.2.14(chokidar@4.0.3) '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4) lodash: 4.17.21 @@ -21741,7 +21743,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': + '@angular-devkit/build-angular@19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(@angular/compiler@19.2.14)(@angular/service-worker@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/node@24.2.0)(chokidar@4.0.3)(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.14(chokidar@4.0.3) @@ -21760,7 +21762,7 @@ snapshots: '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 19.2.14(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4))(typescript@5.5.4)(webpack@5.98.0(@swc/core@1.11.29)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.29)) @@ -25951,13 +25953,13 @@ snapshots: '@expo/timeago.js@1.0.0': {} - '@expo/vector-icons@14.1.0(99f35dc9d27b76831378288730881035)': + '@expo/vector-icons@14.1.0(ka6rgkktlsuut5gotrymd2sdni)': dependencies: expo-font: 13.0.4(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react@18.3.1) react: 18.3.1 react-native: 0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1) - '@expo/vector-icons@14.1.0(a6850416216e8b64df60af23d5183c0b)': + '@expo/vector-icons@14.1.0(wm3bvfp4qcetscjld4hplpimri)': dependencies: expo-font: 13.0.4(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -26802,10 +26804,12 @@ snapshots: dependencies: '@libsql/core': 0.15.9 js-base64: 3.7.7 + optional: true '@libsql/core@0.15.9': dependencies: js-base64: 3.7.7 + optional: true '@listr2/prompt-adapter-inquirer@2.0.18(@inquirer/prompts@7.3.2(@types/node@24.2.0))': dependencies: @@ -29432,7 +29436,7 @@ snapshots: use-latest-callback: 0.2.3(react@18.3.1) use-sync-external-store: 1.5.0(react@18.3.1) - '@react-navigation/drawer@7.4.1(1d85788bd68a0e12619f848d71cbac62)': + '@react-navigation/drawer@7.4.1(j6abyuabi5plzpedpvxbnwhrsi)': dependencies: '@react-navigation/elements': 2.4.3(@react-navigation/native@7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) '@react-navigation/native': 7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -29448,7 +29452,7 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/drawer@7.4.1(f2502081aada8c22c3fd2dbf46b9d114)': + '@react-navigation/drawer@7.4.1(nyxmcqdttlojx3ihgax6eihdpu)': dependencies: '@react-navigation/elements': 2.4.3(@react-navigation/native@7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) '@react-navigation/native': 7.1.10(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -32514,10 +32518,6 @@ snapshots: dependencies: vite: 6.2.7(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - '@vitejs/plugin-basic-ssl@1.2.0(vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': - dependencies: - vite: 6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - '@vitejs/plugin-react@4.5.0(vite@5.4.19(@types/node@20.17.57)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))': dependencies: '@babel/core': 7.26.10 @@ -35419,17 +35419,14 @@ snapshots: dotenv@16.5.0: {} - drizzle-orm@0.35.3(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(@types/sql.js@1.4.9)(better-sqlite3@12.2.0)(kysely@0.28.2)(react@19.0.0)(sql.js@1.13.0): - dependencies: - '@libsql/client-wasm': 0.15.8 + drizzle-orm@0.44.7(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-sqlite3@12.2.0)(kysely@0.28.2)(sql.js@1.13.0): optionalDependencies: + '@libsql/client-wasm': 0.15.8 '@op-engineering/op-sqlite': 14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0) '@types/better-sqlite3': 7.6.13 - '@types/react': 19.1.6 '@types/sql.js': 1.4.9 better-sqlite3: 12.2.0 kysely: 0.28.2 - react: 19.0.0 sql.js: 1.13.0 dtrace-provider@0.8.8: @@ -36036,7 +36033,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -36098,7 +36095,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.7.8 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -36210,7 +36207,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -36696,7 +36693,7 @@ snapshots: expo: 52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) semver: 7.7.2 - expo-camera@16.0.18(55c6da9df988ca7f1678935d82e9238e): + expo-camera@16.0.18(hml277kvlorqbj6gijmq6joh24): dependencies: expo: 52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) invariant: 2.2.4 @@ -36856,7 +36853,7 @@ snapshots: dependencies: invariant: 2.2.4 - expo-router@4.0.21(b0bddf53ba1689b30337428eee4dc275): + expo-router@4.0.21(cpo3xaw6yrjernjvkkkt7bisia): dependencies: '@expo/metro-runtime': 4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/server': 0.5.3 @@ -36877,7 +36874,7 @@ snapshots: semver: 7.6.3 server-only: 0.0.1 optionalDependencies: - '@react-navigation/drawer': 7.4.1(f2502081aada8c22c3fd2dbf46b9d114) + '@react-navigation/drawer': 7.4.1(nyxmcqdttlojx3ihgax6eihdpu) react-native-reanimated: 3.16.7(@babel/core@7.26.10)(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -36886,7 +36883,7 @@ snapshots: - react-native - supports-color - expo-router@4.0.21(e063c8109134fcdd1c97e4d6a4caf625): + expo-router@4.0.21(xdzi7taj2dri7edfzwov6a63va): dependencies: '@expo/metro-runtime': 4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/server': 0.5.3 @@ -36907,7 +36904,7 @@ snapshots: semver: 7.6.3 server-only: 0.0.1 optionalDependencies: - '@react-navigation/drawer': 7.4.1(1d85788bd68a0e12619f848d71cbac62) + '@react-navigation/drawer': 7.4.1(j6abyuabi5plzpedpvxbnwhrsi) react-native-reanimated: 3.16.7(@babel/core@7.26.10)(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -36949,7 +36946,7 @@ snapshots: expo: 52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) sf-symbols-typescript: 2.1.0 - expo-system-ui@4.0.9(fa4ab2ddb2d13a20299c682fc53644db): + expo-system-ui@4.0.9(l76mjoke3yk4s56nokhxoxxpou): dependencies: '@react-native/normalize-colors': 0.76.8 debug: 4.4.1(supports-color@8.1.1) @@ -36977,7 +36974,7 @@ snapshots: '@expo/config-plugins': 9.0.17 '@expo/fingerprint': 0.11.11 '@expo/metro-config': 0.19.12 - '@expo/vector-icons': 14.1.0(99f35dc9d27b76831378288730881035) + '@expo/vector-icons': 14.1.0(ka6rgkktlsuut5gotrymd2sdni) babel-preset-expo: 12.0.11(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10)) expo-asset: 11.0.5(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) expo-constants: 17.0.8(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -37013,7 +37010,7 @@ snapshots: '@expo/config-plugins': 9.0.17 '@expo/fingerprint': 0.11.11 '@expo/metro-config': 0.19.12 - '@expo/vector-icons': 14.1.0(a6850416216e8b64df60af23d5183c0b) + '@expo/vector-icons': 14.1.0(wm3bvfp4qcetscjld4hplpimri) babel-preset-expo: 12.0.11(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10)) expo-asset: 11.0.5(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) expo-constants: 17.0.8(expo@52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)))(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.12.5(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -39237,7 +39234,7 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@52.0.6(3635c191458c5fa90af52243d15b5fda): + jest-expo@52.0.6(ghmzvfvfblgo7fvi646fzprkjq): dependencies: '@expo/config': 10.0.11 '@expo/json-file': 9.1.4 @@ -39573,7 +39570,8 @@ snapshots: js-base64@3.7.2: {} - js-base64@3.7.7: {} + js-base64@3.7.7: + optional: true js-logger@1.6.1: {} @@ -44391,7 +44389,7 @@ snapshots: - supports-color - utf-8-validate - react-navigation-stack@2.10.4(1b7f2cbbd098c1646b3c5f57acc57915): + react-navigation-stack@2.10.4(4a23q4g4mav7ddr6jlhxnyzzo4): dependencies: '@react-native-community/masked-view': 0.1.11(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.9.2))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) color: 3.2.1 @@ -47511,25 +47509,6 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 - vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): - dependencies: - esbuild: 0.25.5 - fdir: 6.4.5(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.4 - rollup: 4.41.1 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 24.2.0 - fsevents: 2.3.3 - jiti: 2.4.2 - less: 4.2.2 - lightningcss: 1.30.1 - sass: 1.85.0 - terser: 5.39.0 - tsx: 4.19.4 - yaml: 2.8.0 - vite@6.3.5(@types/node@24.2.0)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: esbuild: 0.25.5 From 63a191a8dd865d80fd1d709c3db1da755dd74d98 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Tue, 11 Nov 2025 10:53:41 +0200 Subject: [PATCH 02/12] Run queries on read-only connections when we can. --- .../src/sqlite/PowerSyncSQLiteBaseSession.ts | 16 ++++- .../sqlite/PowerSyncSQLitePreparedQuery.ts | 29 ++++++++-- .../src/sqlite/PowerSyncSQLiteSession.ts | 5 +- .../drizzle-driver/src/sqlite/QueryContext.ts | 58 +++++++++++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 packages/drizzle-driver/src/sqlite/QueryContext.ts diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts index 6a330d8d2..4c981d96e 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts @@ -1,4 +1,5 @@ import { LockContext, QueryResult } from '@powersync/common'; +import type { WithCacheConfig } from 'drizzle-orm/cache/core/types'; import { entityKind } from 'drizzle-orm/entity'; import type { Logger } from 'drizzle-orm/logger'; import { NoopLogger } from 'drizzle-orm/logger'; @@ -14,6 +15,7 @@ import { type SQLiteTransactionConfig } from 'drizzle-orm/sqlite-core/session'; import { PowerSyncSQLitePreparedQuery } from './PowerSyncSQLitePreparedQuery.js'; +import { QueryContext } from './QueryContext.js'; export interface PowerSyncSQLiteSessionOptions { logger?: Logger; @@ -39,7 +41,7 @@ export class PowerSyncSQLiteBaseSession< protected logger: Logger; constructor( - protected db: LockContext, + protected db: QueryContext, protected dialect: SQLiteAsyncDialect, protected schema: RelationalSchemaConfig | undefined, protected options: PowerSyncSQLiteSessionOptions = {} @@ -53,7 +55,12 @@ export class PowerSyncSQLiteBaseSession< fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, isResponseInArrayMode: boolean, - customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown + customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, + queryMetadata?: { + type: 'select' | 'update' | 'delete' | 'insert'; + tables: string[]; + }, + cacheConfig?: WithCacheConfig ): PowerSyncSQLitePreparedQuery { return new PowerSyncSQLitePreparedQuery( this.db, @@ -62,7 +69,10 @@ export class PowerSyncSQLiteBaseSession< fields, executeMethod, isResponseInArrayMode, - customResultMapper + customResultMapper, + undefined, // cache not supported yet + queryMetadata, + cacheConfig ); } diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts index 8a5779aca..12d30e20a 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts @@ -1,5 +1,7 @@ -import { LockContext, QueryResult } from '@powersync/common'; +import { QueryResult } from '@powersync/common'; import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm'; +import type { Cache } from 'drizzle-orm/cache/core'; +import type { WithCacheConfig } from 'drizzle-orm/cache/core/types'; import { entityKind, is } from 'drizzle-orm/entity'; import type { Logger } from 'drizzle-orm/logger'; import { fillPlaceholders, type Query } from 'drizzle-orm/sql/sql'; @@ -10,6 +12,7 @@ import { type SQLiteExecuteMethod, SQLitePreparedQuery } from 'drizzle-orm/sqlite-core/session'; +import { QueryContext } from './QueryContext.js'; type PreparedQueryConfig = Omit; @@ -25,16 +28,27 @@ export class PowerSyncSQLitePreparedQuery< }> { static readonly [entityKind]: string = 'PowerSyncSQLitePreparedQuery'; + private readOnly = false; + constructor( - private db: LockContext, + private db: QueryContext, query: Query, private logger: Logger, private fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, private _isResponseInArrayMode: boolean, - private customResultMapper?: (rows: unknown[][]) => unknown + private customResultMapper?: (rows: unknown[][]) => unknown, + cache?: Cache | undefined, + queryMetadata?: + | { + type: 'select' | 'update' | 'delete' | 'insert'; + tables: string[]; + } + | undefined, + cacheConfig?: WithCacheConfig | undefined ) { - super('async', executeMethod, query); + super('async', executeMethod, query, cache, queryMetadata, cacheConfig); + this.readOnly = queryMetadata?.type == 'select'; } async run(placeholderValues?: Record): Promise { @@ -90,6 +104,13 @@ export class PowerSyncSQLitePreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); + // When calling on the database (not in a transaction), and this is a select/read-only query, + // use a read context for the query. + if (this.readOnly) { + return this.db.getAllRaw(this.query.sql, params); + } + + // This uses a write lock, unless we're already in a read transaction return await this.db.executeRaw(this.query.sql, params); } diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts index f4ffcb96d..2cb82798b 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts @@ -8,6 +8,7 @@ import { PowerSyncSQLiteTransaction, PowerSyncSQLiteTransactionConfig } from './PowerSyncSQLiteBaseSession.js'; +import { DatabaseQueryContext, LockQueryContext } from './QueryContext.js'; export class PowerSyncSQLiteSession< TFullSchema extends Record, @@ -21,7 +22,7 @@ export class PowerSyncSQLiteSession< schema: RelationalSchemaConfig | undefined, options: PowerSyncSQLiteSessionOptions = {} ) { - super(db, dialect, schema, options); + super(new DatabaseQueryContext(db), dialect, schema, options); this.client = db; } @@ -46,7 +47,7 @@ export class PowerSyncSQLiteSession< const tx = new PowerSyncSQLiteTransaction( 'async', (this as any).dialect, - new PowerSyncSQLiteBaseSession(connection, this.dialect, this.schema, this.options), + new PowerSyncSQLiteBaseSession(new LockQueryContext(connection), this.dialect, this.schema, this.options), this.schema ); diff --git a/packages/drizzle-driver/src/sqlite/QueryContext.ts b/packages/drizzle-driver/src/sqlite/QueryContext.ts new file mode 100644 index 000000000..3ebbcccc5 --- /dev/null +++ b/packages/drizzle-driver/src/sqlite/QueryContext.ts @@ -0,0 +1,58 @@ +import { AbstractPowerSyncDatabase, LockContext, QueryResult } from '@powersync/common'; + +/** + * Like LockContext, but only includes the specific methods needed for Drizzle. + * + * We extend it by adding getAllRaw, to support read-only queries with executeRaw. + */ +export interface QueryContext { + execute(query: string, params?: any[] | undefined): Promise; + executeRaw(query: string, params?: any[] | undefined): Promise; + + get(sql: string, parameters?: any[]): Promise; + getAll(sql: string, parameters?: any[]): Promise; + /** + * Like executeRaw, but for read-only queries. + */ + getAllRaw(query: string, params?: any[] | undefined): Promise; +} + +export class DatabaseQueryContext implements QueryContext { + constructor(private db: AbstractPowerSyncDatabase) {} + execute(query: string, params?: any[] | undefined): Promise { + return this.db.execute(query, params); + } + executeRaw(query: string, params?: any[] | undefined) { + return this.db.executeRaw(query, params); + } + get(sql: string, parameters?: any[]) { + return this.db.get(sql, parameters); + } + getAll(sql: string, parameters?: any[]) { + return this.db.getAll(sql, parameters); + } + getAllRaw(query: string, params?: any[] | undefined) { + return this.db.readLock(async (ctx) => { + return ctx.executeRaw(query, params); + }); + } +} + +export class LockQueryContext implements QueryContext { + constructor(private ctx: LockContext) {} + execute(query: string, params?: any[] | undefined): Promise { + return this.ctx.execute(query, params); + } + executeRaw(query: string, params?: any[] | undefined) { + return this.ctx.executeRaw(query, params); + } + get(sql: string, parameters?: any[]) { + return this.ctx.get(sql, parameters); + } + getAll(sql: string, parameters?: any[]) { + return this.ctx.getAll(sql, parameters); + } + getAllRaw(query: string, params?: any[] | undefined) { + return this.ctx.executeRaw(query, params); + } +} From cbd3324a2160a7a2abf494b22f079554c6bb481e Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Tue, 11 Nov 2025 10:56:52 +0200 Subject: [PATCH 03/12] Add tests to check Drizzle query concurrency. --- packages/node/tests/DrizzleNode.test.ts | 134 +++++++++++++++++++++++- packages/node/tests/utils.ts | 28 +++-- 2 files changed, 150 insertions(+), 12 deletions(-) diff --git a/packages/node/tests/DrizzleNode.test.ts b/packages/node/tests/DrizzleNode.test.ts index 9598aceb7..981b6e0e1 100644 --- a/packages/node/tests/DrizzleNode.test.ts +++ b/packages/node/tests/DrizzleNode.test.ts @@ -1,7 +1,7 @@ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { eq, relations } from 'drizzle-orm'; -import { databaseTest } from './utils'; +import { customDatabaseTest, databaseTest } from './utils'; import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver'; import { PowerSyncDatabase } from '../lib'; import { expect } from 'vitest'; @@ -53,6 +53,15 @@ databaseTest('should retrieve a list with todos', async ({ database }) => { expect(result).toEqual([{ id: '1', name: 'list 1', todos: [{ id: '33', content: 'Post content', list_id: '1' }] }]); }); +databaseTest('insert returning', async ({ database }) => { + const db = await setupDrizzle(database); + + // This is a special case since it's an insert query that returns values + const result = await db.insert(drizzleLists).values({ id: '2', name: 'list 2' }).returning(); + + expect(result).toEqual([{ id: '2', name: 'list 2' }]); +}); + databaseTest('should retrieve a todo with its list', async ({ database }) => { const db = await setupDrizzle(database); @@ -97,3 +106,126 @@ databaseTest('should return a list and todos using fullJoin', async ({ database expect(result[0].lists).toEqual({ id: '1', name: 'list 1' }); expect(result[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' }); }); + +customDatabaseTest({ database: { readWorkerCount: 2 } as any })( + 'should execute transactions concurrently', + {}, + async ({ database }) => { + // This test opens one write transaction and two read transactions, testing that they can all execute + // concurrently. + const db = await setupDrizzle(database); + + const openedWrite = deferred(); + const openedRead1 = deferred(); + const openedRead2 = deferred(); + const completedWrite = deferred(); + + const t1 = db.transaction(async (tx) => { + await tx.insert(drizzleLists).values({ id: '2', name: 'list 2' }); + + openedWrite.resolve(); + + await openedRead1.promise; + await openedRead2.promise; + + completedWrite.resolve(); + }); + + await openedWrite.promise; + + const t2 = db.transaction( + async (tx) => { + const result = await tx.query.lists.findMany(); + expect(result).toEqual([{ id: '1', name: 'list 1' }]); + openedRead1.resolve(); + await completedWrite.promise; + }, + { accessMode: 'read only' } + ); + + const t3 = db.transaction( + async (tx) => { + await openedRead1.promise; + const result = await tx.query.lists.findMany(); + expect(result).toEqual([{ id: '1', name: 'list 1' }]); + + openedRead2.resolve(); + await completedWrite.promise; + }, + { accessMode: 'read only' } + ); + + await Promise.all([t1, t2, t3]); + + const result = await db.query.lists.findMany(); + expect(result).toEqual([ + { id: '1', name: 'list 1' }, + { id: '2', name: 'list 2' } + ]); + } +); + +customDatabaseTest({ database: { readWorkerCount: 2 } as any })( + 'should execute select queries concurrently', + async ({ database }) => { + // This test opens one write transaction and two read transactions, testing that they can all execute + // concurrently. + const db = await setupDrizzle(database); + + const openedWrite = deferred(); + const completedRead = deferred(); + const completedWrite = deferred(); + + const t1 = db.transaction(async (tx) => { + await tx.insert(drizzleLists).values({ id: '2', name: 'list 2' }); + + openedWrite.resolve(); + + await completedRead.promise; + + completedWrite.resolve(); + }); + + await openedWrite.promise; + + const result1 = await db.select().from(drizzleLists); + expect(result1).toEqual([{ id: '1', name: 'list 1' }]); + + const result2 = await db + .select() + .from(drizzleLists) + .fullJoin(drizzleTodos, eq(drizzleLists.id, drizzleTodos.list_id)); + + expect(result2[0].lists).toEqual({ id: '1', name: 'list 1' }); + expect(result2[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' }); + + // Note: This case is not supported yet (drizzle 0.44.7), since it doesn't set + // queryMetadata for these queries + // const result3 = await db.query.lists.findMany(); + // expect(result3).toEqual([{ id: '1', name: 'list 1' }]); + + completedRead.resolve(); + + await t1; + + const resultAfter = await db.query.lists.findMany(); + expect(resultAfter).toEqual([ + { id: '1', name: 'list 1' }, + { id: '2', name: 'list 2' } + ]); + } +); + +function deferred() { + let resolve: (value: T) => void; + let reject: (err) => void; + const promise = new Promise((a, b) => { + resolve = a; + reject = b; + }); + return { + promise, + resolve, + reject + }; +} diff --git a/packages/node/tests/utils.ts b/packages/node/tests/utils.ts index f9fad7090..679a5bb33 100644 --- a/packages/node/tests/utils.ts +++ b/packages/node/tests/utils.ts @@ -14,6 +14,7 @@ import { PowerSyncBackendConnector, PowerSyncCredentials, PowerSyncDatabase, + PowerSyncDatabaseOptions, Schema, StreamingSyncCheckpoint, StreamingSyncLine, @@ -65,27 +66,32 @@ export async function createDatabase( const database = new PowerSyncDatabase({ schema: AppSchema, + ...options, + logger: defaultLogger, database: { dbFilename: 'test.db', dbLocation: tmpdir, // Using a single read worker (instead of multiple, the default) seems to improve the reliability of tests in GH // actions. So far, we've not been able to reproduce these failures locally. - readWorkerCount: 1 - }, - logger: defaultLogger, - ...options + readWorkerCount: 1, + ...options.database + } }); await database.init(); return database; } -export const databaseTest = tempDirectoryTest.extend<{ database: PowerSyncDatabase }>({ - database: async ({ tmpdir }, use) => { - const db = await createDatabase(tmpdir); - await use(db); - await db.close(); - } -}); +export const customDatabaseTest = (options?: Partial) => { + return tempDirectoryTest.extend<{ database: PowerSyncDatabase }>({ + database: async ({ tmpdir }, use) => { + const db = await createDatabase(tmpdir, options); + await use(db); + await db.close(); + } + }); +}; + +export const databaseTest = customDatabaseTest(); // TODO: Unify this with the test setup for the web SDK. export const mockSyncServiceTest = tempDirectoryTest.extend<{ From 94afea2ce23ae9e910ab6a5ee763899857eef6f3 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Tue, 11 Nov 2025 10:57:56 +0200 Subject: [PATCH 04/12] Add changeset. --- .changeset/hot-socks-love.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/hot-socks-love.md diff --git a/.changeset/hot-socks-love.md b/.changeset/hot-socks-love.md new file mode 100644 index 000000000..159b5d7e1 --- /dev/null +++ b/.changeset/hot-socks-love.md @@ -0,0 +1,6 @@ +--- +'@powersync/drizzle-driver': minor +'@powersync/node': minor +--- + +Add support for concurrent read queries with Drizzle. From 5c91ebb79562c818355cf02f82842b2bbf83df21 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 11:01:10 +0200 Subject: [PATCH 05/12] fix: drizzle locks --- .changeset/nice-mangos-fail.md | 5 + .../src/sqlite/PowerSyncSQLiteBaseSession.ts | 12 +- .../sqlite/PowerSyncSQLitePreparedQuery.ts | 47 +- .../src/sqlite/PowerSyncSQLiteSession.ts | 26 +- .../tests/sqlite/lock-usage.test.ts | 422 ++++++++++++++++++ .../drizzle-driver/tests/sqlite/query.test.ts | 44 +- 6 files changed, 531 insertions(+), 25 deletions(-) create mode 100644 .changeset/nice-mangos-fail.md create mode 100644 packages/drizzle-driver/tests/sqlite/lock-usage.test.ts diff --git a/.changeset/nice-mangos-fail.md b/.changeset/nice-mangos-fail.md new file mode 100644 index 000000000..136af29cf --- /dev/null +++ b/.changeset/nice-mangos-fail.md @@ -0,0 +1,5 @@ +--- +'@powersync/drizzle-driver': patch +--- + +Fixed issue where read queries would not use a readLock. diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts index 6a330d8d2..10503fb6b 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts @@ -1,4 +1,4 @@ -import { LockContext, QueryResult } from '@powersync/common'; +import { QueryResult } from '@powersync/common'; import { entityKind } from 'drizzle-orm/entity'; import type { Logger } from 'drizzle-orm/logger'; import { NoopLogger } from 'drizzle-orm/logger'; @@ -7,13 +7,13 @@ import { type Query } from 'drizzle-orm/sql/sql'; import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types'; import { - type PreparedQueryConfig as PreparedQueryConfigBase, - type SQLiteExecuteMethod, SQLiteSession, SQLiteTransaction, + type PreparedQueryConfig as PreparedQueryConfigBase, + type SQLiteExecuteMethod, type SQLiteTransactionConfig } from 'drizzle-orm/sqlite-core/session'; -import { PowerSyncSQLitePreparedQuery } from './PowerSyncSQLitePreparedQuery.js'; +import { ContextProvider, PowerSyncSQLitePreparedQuery } from './PowerSyncSQLitePreparedQuery.js'; export interface PowerSyncSQLiteSessionOptions { logger?: Logger; @@ -39,7 +39,7 @@ export class PowerSyncSQLiteBaseSession< protected logger: Logger; constructor( - protected db: LockContext, + protected contextProvider: ContextProvider, protected dialect: SQLiteAsyncDialect, protected schema: RelationalSchemaConfig | undefined, protected options: PowerSyncSQLiteSessionOptions = {} @@ -56,7 +56,7 @@ export class PowerSyncSQLiteBaseSession< customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown ): PowerSyncSQLitePreparedQuery { return new PowerSyncSQLitePreparedQuery( - this.db, + this.contextProvider, query, this.logger, fields, diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts index 8a5779aca..4db9d8098 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts @@ -1,18 +1,35 @@ import { LockContext, QueryResult } from '@powersync/common'; -import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm'; +import { Column, DriverValueDecoder, SQL, getTableName } from 'drizzle-orm'; import { entityKind, is } from 'drizzle-orm/entity'; import type { Logger } from 'drizzle-orm/logger'; import { fillPlaceholders, type Query } from 'drizzle-orm/sql/sql'; import { SQLiteColumn } from 'drizzle-orm/sqlite-core'; import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types'; import { + SQLitePreparedQuery, type PreparedQueryConfig as PreparedQueryConfigBase, - type SQLiteExecuteMethod, - SQLitePreparedQuery + type SQLiteExecuteMethod } from 'drizzle-orm/sqlite-core/session'; type PreparedQueryConfig = Omit; +/** + * Callback which uses a LockContext for database operations. + */ +export type LockCallback = (ctx: LockContext) => Promise; + +/** + * Provider for specific database contexts. + * Handlers are provided a context to the provided callback. + * This does not necessarily need to acquire a database lock for each call. + * Calls might use the same lock context for multiple operations. + * The read/write context may relate to a single read OR write context. + */ +export type ContextProvider = { + useReadContext: (fn: LockCallback) => Promise; + useWriteContext: (fn: LockCallback) => Promise; +}; + export class PowerSyncSQLitePreparedQuery< T extends PreparedQueryConfig = PreparedQueryConfig > extends SQLitePreparedQuery<{ @@ -26,7 +43,7 @@ export class PowerSyncSQLitePreparedQuery< static readonly [entityKind]: string = 'PowerSyncSQLitePreparedQuery'; constructor( - private db: LockContext, + private contextProvider: ContextProvider, query: Query, private logger: Logger, private fields: SelectedFieldsOrdered | undefined, @@ -40,8 +57,13 @@ export class PowerSyncSQLitePreparedQuery< async run(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); - const rs = await this.db.execute(this.query.sql, params); - return rs; + /** + * Run operations are teated as potential mutations, so they use the write context. + */ + return this.contextProvider.useWriteContext(async (ctx) => { + const rs = await ctx.execute(this.query.sql, params); + return rs; + }); } async all(placeholderValues?: Record): Promise { @@ -49,8 +71,9 @@ export class PowerSyncSQLitePreparedQuery< if (!fields && !customResultMapper) { const params = fillPlaceholders(query.params, placeholderValues ?? {}); logger.logQuery(query.sql, params); - const rs = await this.db.execute(this.query.sql, params); - return rs.rows?._array ?? []; + return await this.contextProvider.useReadContext(async (ctx) => { + return await ctx.getAll(this.query.sql, params); + }); } const rows = (await this.values(placeholderValues)) as unknown[][]; @@ -69,7 +92,9 @@ export class PowerSyncSQLitePreparedQuery< const { fields, customResultMapper } = this; const joinsNotNullableMap = (this as any).joinsNotNullableMap; if (!fields && !customResultMapper) { - return this.db.get(this.query.sql, params); + return this.contextProvider.useReadContext(async (ctx) => { + return await ctx.get(this.query.sql, params); + }); } const rows = (await this.values(placeholderValues)) as unknown[][]; @@ -90,7 +115,9 @@ export class PowerSyncSQLitePreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); - return await this.db.executeRaw(this.query.sql, params); + return await this.contextProvider.useReadContext(async (ctx) => { + return await ctx.executeRaw(this.query.sql, params); + }); } isResponseInArrayMode(): boolean { diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts index f4ffcb96d..d86cdd7d7 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteSession.ts @@ -1,4 +1,4 @@ -import { AbstractPowerSyncDatabase, DBAdapter } from '@powersync/common'; +import { AbstractPowerSyncDatabase, LockContext } from '@powersync/common'; import { entityKind } from 'drizzle-orm/entity'; import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations'; import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect'; @@ -21,7 +21,16 @@ export class PowerSyncSQLiteSession< schema: RelationalSchemaConfig | undefined, options: PowerSyncSQLiteSessionOptions = {} ) { - super(db, dialect, schema, options); + super( + // Top level operations use the respective locks. + { + useReadContext: (callback) => db.readLock(callback), + useWriteContext: (callback) => db.writeLock(callback) + }, + dialect, + schema, + options + ); this.client = db; } @@ -39,14 +48,23 @@ export class PowerSyncSQLiteSession< } protected async internalTransaction( - connection: DBAdapter, + connection: LockContext, fn: (tx: PowerSyncSQLiteTransaction) => T, config: PowerSyncSQLiteTransactionConfig = {} ): Promise { const tx = new PowerSyncSQLiteTransaction( 'async', (this as any).dialect, - new PowerSyncSQLiteBaseSession(connection, this.dialect, this.schema, this.options), + new PowerSyncSQLiteBaseSession( + { + // We already have a fixed context here. We need to use it for both "read" and "write" operations. + useReadContext: (callback) => callback(connection), + useWriteContext: (callback) => callback(connection) + }, + this.dialect, + this.schema, + this.options + ), this.schema ); diff --git a/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts b/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts new file mode 100644 index 000000000..236b2d0ec --- /dev/null +++ b/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts @@ -0,0 +1,422 @@ +import { eq, sql } from 'drizzle-orm'; +import { describe, expect, test, vi } from 'vitest'; +import { toCompilableQuery } from '../../src/utils/compilableQuery.js'; +import { drizzleUsers, getDrizzleDb, getPowerSyncDb } from '../setup/db.js'; + +type TestContext = { + powerSyncDb: ReturnType; + readLockSpy: ReturnType; + writeLockSpy: ReturnType; + db: ReturnType; +}; + +export const lockUsageTest = test.extend({ + powerSyncDb: async ({}, use) => { + const powerSyncDb = getPowerSyncDb(); + await use(powerSyncDb); + await powerSyncDb.disconnectAndClear(); + }, + readLockSpy: async ({ powerSyncDb }, use) => { + const readLockSpy = vi.spyOn(powerSyncDb, 'readLock' as any) as any; + readLockSpy.mockClear(); + await use(readLockSpy); + readLockSpy.mockRestore(); + }, + writeLockSpy: async ({ powerSyncDb }, use) => { + const writeLockSpy = vi.spyOn(powerSyncDb, 'writeLock' as any) as any; + writeLockSpy.mockClear(); + await use(writeLockSpy); + writeLockSpy.mockRestore(); + }, + db: async ({ powerSyncDb, readLockSpy, writeLockSpy }, use) => { + const db = getDrizzleDb(powerSyncDb); + // Insert initial test data (Alice) and clear spies after setup + await db.insert(drizzleUsers).values({ id: '1', name: 'Alice' }); + readLockSpy.mockClear(); + writeLockSpy.mockClear(); + await use(db); + } +}); + +describe('Lock Usage Tests', () => { + describe('SELECT queries', () => { + lockUsageTest('should use readLock for select().from()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use readLock for select().from().get()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers).get(); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use readLock for select().from().all()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers).all(); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use readLock for select with where clause', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers).where(eq(drizzleUsers.id, '1')); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use readLock for select with limit', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers).limit(1); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use readLock for select with orderBy', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers).orderBy(drizzleUsers.name); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + }); + + describe('INSERT queries', () => { + lockUsageTest('should use writeLock for insert().values()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest( + 'should use writeLock for insert().values() with multiple rows', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.insert(drizzleUsers).values([ + { id: '2', name: 'Bob' }, + { id: '3', name: 'Charlie' } + ]); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + }); + + describe('UPDATE queries', () => { + lockUsageTest('should use writeLock for update().set()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.update(drizzleUsers).set({ name: 'Alice Smith' }); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use writeLock for update().set().where()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.update(drizzleUsers).set({ name: 'Alice Smith' }).where(eq(drizzleUsers.id, '1')); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + }); + + describe('DELETE queries', () => { + lockUsageTest('should use writeLock for delete().from()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.delete(drizzleUsers); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use writeLock for delete().from().where()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.delete(drizzleUsers).where(eq(drizzleUsers.id, '1')); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Raw SQL queries', () => { + lockUsageTest( + 'should use writeLock for raw SELECT queries via run()', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.run(sql` + SELECT + * + FROM + users + `); + + // run() always uses writeLock since it can execute any SQL statement + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest('should use writeLock for raw INSERT queries', async ({ db, readLockSpy, writeLockSpy }) => { + await db.run(sql` + INSERT INTO + users (id, name) + VALUES + ('2', 'Bob') + `); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use writeLock for raw UPDATE queries', async ({ db, readLockSpy, writeLockSpy }) => { + await db.run(sql` + UPDATE users + SET + name = 'Alice Smith' + WHERE + id = '1' + `); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use writeLock for raw DELETE queries', async ({ db, readLockSpy, writeLockSpy }) => { + await db.run(sql` + DELETE FROM users + WHERE + id = '1' + `); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Complex query patterns', () => { + lockUsageTest('should use readLock for select with groupBy', async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers).groupBy(drizzleUsers.name); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest('should use readLock for select with having', async ({ db, readLockSpy, writeLockSpy }) => { + // Add more test data for meaningful having clause + await db.insert(drizzleUsers).values({ id: '2', name: 'Alice' }); + await db.insert(drizzleUsers).values({ id: '3', name: 'Bob' }); + readLockSpy.mockClear(); + writeLockSpy.mockClear(); + + // Use having with count aggregate + await db + .select({ name: drizzleUsers.name, count: sql`count(*)`.as('count') }) + .from(drizzleUsers) + .groupBy(drizzleUsers.name) + .having(sql`count(*) > 1`); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Multiple operations', () => { + lockUsageTest( + 'should use writeLock for insert, then readLock for select', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + expect(writeLockSpy).toHaveBeenCalledTimes(1); + expect(readLockSpy).not.toHaveBeenCalled(); + + readLockSpy.mockClear(); + writeLockSpy.mockClear(); + + await db.select().from(drizzleUsers); + expect(readLockSpy).toHaveBeenCalledTimes(1); + expect(writeLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use writeLock for each write operation separately', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + await db.insert(drizzleUsers).values({ id: '3', name: 'Charlie' }); + await db.update(drizzleUsers).set({ name: 'Alice Updated' }).where(eq(drizzleUsers.id, '1')); + + expect(writeLockSpy).toHaveBeenCalledTimes(3); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use readLock for each read operation separately', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.select().from(drizzleUsers); + await db.select().from(drizzleUsers).get(); + await db.select().from(drizzleUsers).where(eq(drizzleUsers.id, '1')); + + expect(readLockSpy).toHaveBeenCalledTimes(3); + expect(writeLockSpy).not.toHaveBeenCalled(); + } + ); + }); + + describe('Transaction operations', () => { + lockUsageTest( + 'should use writeLock for read-write transaction (default)', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.transaction(async (tx) => { + await tx.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + }); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use writeLock for explicit read-write transaction', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.transaction( + async (tx) => { + await tx.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + }, + { accessMode: 'read write' } + ); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest('should use readLock for read-only transaction', async ({ db, readLockSpy, writeLockSpy }) => { + await db.transaction( + async (tx) => { + await tx.select().from(drizzleUsers); + }, + { accessMode: 'read only' } + ); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); + + lockUsageTest( + 'should use readLock for read-only transaction with multiple reads', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.transaction( + async (tx) => { + await tx.select().from(drizzleUsers); + await tx.select().from(drizzleUsers).get(); + }, + { accessMode: 'read only' } + ); + + expect(readLockSpy).toHaveBeenCalledTimes(1); // Transaction itself uses readLock once + expect(writeLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use writeLock for read-write transaction with multiple operations', + async ({ db, readLockSpy, writeLockSpy }) => { + await db.transaction( + async (tx) => { + await tx.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + await tx.update(drizzleUsers).set({ name: 'Alice Updated' }).where(eq(drizzleUsers.id, '1')); + }, + { accessMode: 'read write' } + ); + + expect(writeLockSpy).toHaveBeenCalledTimes(1); // Transaction itself uses writeLock once + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + }); + + describe('toCompilableQuery', () => { + lockUsageTest( + 'should use readLock when executing a compilable select query', + async ({ db, readLockSpy, writeLockSpy }) => { + const selectQuery = db.select().from(drizzleUsers); + const compilableQuery = toCompilableQuery(selectQuery); + + await compilableQuery.execute(); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use readLock when executing a compilable select query with where clause', + async ({ db, readLockSpy, writeLockSpy }) => { + const selectQuery = db.select().from(drizzleUsers).where(eq(drizzleUsers.id, '1')); + const compilableQuery = toCompilableQuery(selectQuery); + + await compilableQuery.execute(); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + // It's probably a bad practice to perform a mutation in a watched query, but this still works + 'should use writeLock when executing a compilable insert query', + async ({ db, readLockSpy, writeLockSpy }) => { + const insertQuery = db.insert(drizzleUsers).values({ id: '2', name: 'Bob' }); + const compilableQuery = toCompilableQuery(insertQuery); + + await compilableQuery.execute(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use writeLock when executing a compilable update query', + async ({ db, readLockSpy, writeLockSpy }) => { + const updateQuery = db.update(drizzleUsers).set({ name: 'Alice Updated' }).where(eq(drizzleUsers.id, '1')); + const compilableQuery = toCompilableQuery(updateQuery); + + await compilableQuery.execute(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use writeLock when executing a compilable delete query', + async ({ db, readLockSpy, writeLockSpy }) => { + const deleteQuery = db.delete(drizzleUsers).where(eq(drizzleUsers.id, '1')); + const compilableQuery = toCompilableQuery(deleteQuery); + + await compilableQuery.execute(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + } + ); + + lockUsageTest( + 'should use readLock when compiling and executing a select query separately', + async ({ db, readLockSpy, writeLockSpy }) => { + const selectQuery = db.select().from(drizzleUsers); + const compilableQuery = toCompilableQuery(selectQuery); + + // Compile first + const compiled = compilableQuery.compile(); + expect(compiled.sql).toContain('select'); + expect(compiled.parameters).toBeDefined(); + + // Then execute + await compilableQuery.execute(); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + } + ); + }); +}); diff --git a/packages/drizzle-driver/tests/sqlite/query.test.ts b/packages/drizzle-driver/tests/sqlite/query.test.ts index 28e51c6de..e34075de6 100644 --- a/packages/drizzle-driver/tests/sqlite/query.test.ts +++ b/packages/drizzle-driver/tests/sqlite/query.test.ts @@ -2,9 +2,15 @@ import { AbstractPowerSyncDatabase } from '@powersync/web'; import { Query } from 'drizzle-orm/sql/sql'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { PowerSyncSQLiteDatabase } from '../../src/sqlite/PowerSyncSQLiteDatabase.js'; -import { PowerSyncSQLitePreparedQuery } from '../../src/sqlite/PowerSyncSQLitePreparedQuery.js'; +import { ContextProvider, PowerSyncSQLitePreparedQuery } from '../../src/sqlite/PowerSyncSQLitePreparedQuery.js'; import { DrizzleSchema, drizzleUsers, getDrizzleDb, getPowerSyncDb } from '../setup/db.js'; +function toContextProvider(db: AbstractPowerSyncDatabase): ContextProvider { + return { + useReadContext: (callback) => db.readLock(callback), + useWriteContext: (callback) => db.writeLock(callback) + }; +} describe('PowerSyncSQLitePreparedQuery', () => { let powerSyncDb: AbstractPowerSyncDatabase; let db: PowerSyncSQLiteDatabase; @@ -24,7 +30,14 @@ describe('PowerSyncSQLitePreparedQuery', () => { it('should execute a query in run()', async () => { const query: Query = { sql: `SELECT * FROM users WHERE id = ?`, params: [1] }; - const preparedQuery = new PowerSyncSQLitePreparedQuery(powerSyncDb, query, loggerMock, undefined, 'run', false); + const preparedQuery = new PowerSyncSQLitePreparedQuery( + toContextProvider(powerSyncDb), + query, + loggerMock, + undefined, + 'run', + false + ); const result = await preparedQuery.run(); expect(result.rows?._array).toEqual([{ id: '1', name: 'Alice' }]); @@ -32,7 +45,14 @@ describe('PowerSyncSQLitePreparedQuery', () => { it('should retrieve all rows in all()', async () => { const query: Query = { sql: `SELECT * FROM users`, params: [] } as Query; - const preparedQuery = new PowerSyncSQLitePreparedQuery(powerSyncDb, query, loggerMock, undefined, 'all', false); + const preparedQuery = new PowerSyncSQLitePreparedQuery( + toContextProvider(powerSyncDb), + query, + loggerMock, + undefined, + 'all', + false + ); const rows = await preparedQuery.all(); expect(rows).toEqual([ @@ -44,7 +64,14 @@ describe('PowerSyncSQLitePreparedQuery', () => { it('should retrieve a single row in get()', async () => { const query: Query = { sql: `SELECT * FROM users WHERE id = ?`, params: [1] }; - const preparedQuery = new PowerSyncSQLitePreparedQuery(powerSyncDb, query, loggerMock, undefined, 'get', false); + const preparedQuery = new PowerSyncSQLitePreparedQuery( + toContextProvider(powerSyncDb), + query, + loggerMock, + undefined, + 'get', + false + ); const row = await preparedQuery.get(); expect(row).toEqual({ id: '1', name: 'Alice' }); @@ -53,7 +80,14 @@ describe('PowerSyncSQLitePreparedQuery', () => { it('should retrieve values in values()', async () => { const query: Query = { sql: `SELECT * FROM users`, params: [] } as Query; - const preparedQuery = new PowerSyncSQLitePreparedQuery(powerSyncDb, query, loggerMock, undefined, 'all', false); + const preparedQuery = new PowerSyncSQLitePreparedQuery( + toContextProvider(powerSyncDb), + query, + loggerMock, + undefined, + 'all', + false + ); const values = await preparedQuery.values(); From 4eba02746858b74ace838f61526b1687a28c816a Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 11:17:33 +0200 Subject: [PATCH 06/12] add test for returning --- .../tests/sqlite/lock-usage.test.ts | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts b/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts index 236b2d0ec..13a404b00 100644 --- a/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts +++ b/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts @@ -103,6 +103,54 @@ describe('Lock Usage Tests', () => { expect(readLockSpy).not.toHaveBeenCalled(); } ); + + lockUsageTest( + 'should use writeLock for insert().values().returning()', + async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db.insert(drizzleUsers).values({ id: '2', name: 'Bob' }).returning(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ id: '2', name: 'Bob' }); + } + ); + + lockUsageTest( + 'should use writeLock for insert().values().returning() with specific columns', + async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db + .insert(drizzleUsers) + .values({ id: '2', name: 'Bob' }) + .returning({ id: drizzleUsers.id, name: drizzleUsers.name }); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ id: '2', name: 'Bob' }); + } + ); + + lockUsageTest( + 'should use writeLock for insert().values() with multiple rows and returning()', + async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db + .insert(drizzleUsers) + .values([ + { id: '2', name: 'Bob' }, + { id: '3', name: 'Charlie' } + ]) + .returning(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(2); + expect(result).toEqual([ + { id: '2', name: 'Bob' }, + { id: '3', name: 'Charlie' } + ]); + } + ); }); describe('UPDATE queries', () => { @@ -119,6 +167,35 @@ describe('Lock Usage Tests', () => { expect(writeLockSpy).toHaveBeenCalled(); expect(readLockSpy).not.toHaveBeenCalled(); }); + + lockUsageTest('should use writeLock for update().set().returning()', async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db + .update(drizzleUsers) + .set({ name: 'Alice Smith' }) + .where(eq(drizzleUsers.id, '1')) + .returning(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ id: '1', name: 'Alice Smith' }); + }); + + lockUsageTest( + 'should use writeLock for update().set().returning() with specific columns', + async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db + .update(drizzleUsers) + .set({ name: 'Alice Smith' }) + .where(eq(drizzleUsers.id, '1')) + .returning({ id: drizzleUsers.id, name: drizzleUsers.name }); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ id: '1', name: 'Alice Smith' }); + } + ); }); describe('DELETE queries', () => { @@ -135,6 +212,30 @@ describe('Lock Usage Tests', () => { expect(writeLockSpy).toHaveBeenCalled(); expect(readLockSpy).not.toHaveBeenCalled(); }); + + lockUsageTest('should use writeLock for delete().returning()', async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db.delete(drizzleUsers).where(eq(drizzleUsers.id, '1')).returning(); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ id: '1', name: 'Alice' }); + }); + + lockUsageTest( + 'should use writeLock for delete().returning() with specific columns', + async ({ db, readLockSpy, writeLockSpy }) => { + const result = await db + .delete(drizzleUsers) + .where(eq(drizzleUsers.id, '1')) + .returning({ id: drizzleUsers.id, name: drizzleUsers.name }); + + expect(writeLockSpy).toHaveBeenCalled(); + expect(readLockSpy).not.toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ id: '1', name: 'Alice' }); + } + ); }); describe('Raw SQL queries', () => { From 55509738b4be52edb9d4a4b41e1cf0aa7eb59ea7 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 11:39:11 +0200 Subject: [PATCH 07/12] cleanup --- .../sqlite/PowerSyncSQLitePreparedQuery.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts index bffcb7dfb..bf6329b0d 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts @@ -1,5 +1,5 @@ -import type { QueryResult } from '@powersync/common'; -import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm'; +import type { LockContext, QueryResult } from '@powersync/common'; +import { Column, DriverValueDecoder, SQL, getTableName } from 'drizzle-orm'; import type { Cache } from 'drizzle-orm/cache/core'; import type { WithCacheConfig } from 'drizzle-orm/cache/core/types'; import { entityKind, is } from 'drizzle-orm/entity'; @@ -70,10 +70,7 @@ export class PowerSyncSQLitePreparedQuery< async run(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); - /** - * Run operations are teated as potential mutations, so they use the write context. - */ - return this.contextProvider.useWriteContext(async (ctx) => { + return this.useContext(async (ctx) => { const rs = await ctx.execute(this.query.sql, params); return rs; }); @@ -90,7 +87,6 @@ export class PowerSyncSQLitePreparedQuery< } const rows = (await this.values(placeholderValues)) as unknown[][]; - if (customResultMapper) { const mapped = customResultMapper(rows) as T['all']; return mapped; @@ -128,20 +124,22 @@ export class PowerSyncSQLitePreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); - if (this.readOnly) { - return await this.contextProvider.useReadContext(async (ctx) => { - return await ctx.executeRaw(this.query.sql, params); - }); - } else { - return await this.contextProvider.useWriteContext(async (ctx) => { - return await ctx.executeRaw(this.query.sql, params); - }); - } + return await this.useContext(async (ctx) => { + return await ctx.executeRaw(this.query.sql, params); + }); } isResponseInArrayMode(): boolean { return this._isResponseInArrayMode; } + + protected useContext(callback: LockCallback): Promise { + if (this.readOnly) { + return this.contextProvider.useReadContext(callback); + } else { + return this.contextProvider.useWriteContext(callback); + } + } } /** From 2776b30f98cc23606439a1d7a0c6e741c2177e3b Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 11:40:03 +0200 Subject: [PATCH 08/12] remove duplicate changeset --- .changeset/nice-mangos-fail.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/nice-mangos-fail.md diff --git a/.changeset/nice-mangos-fail.md b/.changeset/nice-mangos-fail.md deleted file mode 100644 index 136af29cf..000000000 --- a/.changeset/nice-mangos-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@powersync/drizzle-driver': patch ---- - -Fixed issue where read queries would not use a readLock. From 4413d09f3f462198cfb7ba305dc75aa46f333b2e Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 11:51:32 +0200 Subject: [PATCH 09/12] Remove unused class. Add readme limitation --- packages/drizzle-driver/README.md | 1 + .../sqlite/PowerSyncSQLitePreparedQuery.ts | 7 +-- .../drizzle-driver/src/sqlite/QueryContext.ts | 58 ------------------- 3 files changed, 4 insertions(+), 62 deletions(-) delete mode 100644 packages/drizzle-driver/src/sqlite/QueryContext.ts diff --git a/packages/drizzle-driver/README.md b/packages/drizzle-driver/README.md index 7ee8c29da..0b2f4cb43 100644 --- a/packages/drizzle-driver/README.md +++ b/packages/drizzle-driver/README.md @@ -155,3 +155,4 @@ For more information on how to use Drizzle queries in PowerSync, see [here](http ## Known limitations - The integration does not currently support nested transactions (also known as `savepoints`). +- `findMany` queries execute with a write connection if not wrapped in an explicit `read only` transaction. diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts index bf6329b0d..bb8161eae 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts @@ -71,8 +71,7 @@ export class PowerSyncSQLitePreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); this.logger.logQuery(this.query.sql, params); return this.useContext(async (ctx) => { - const rs = await ctx.execute(this.query.sql, params); - return rs; + return await ctx.execute(this.query.sql, params); }); } @@ -81,7 +80,7 @@ export class PowerSyncSQLitePreparedQuery< if (!fields && !customResultMapper) { const params = fillPlaceholders(query.params, placeholderValues ?? {}); logger.logQuery(query.sql, params); - return await this.contextProvider.useReadContext(async (ctx) => { + return await this.useContext(async (ctx) => { return await ctx.getAll(this.query.sql, params); }); } @@ -101,7 +100,7 @@ export class PowerSyncSQLitePreparedQuery< const { fields, customResultMapper } = this; const joinsNotNullableMap = (this as any).joinsNotNullableMap; if (!fields && !customResultMapper) { - return this.contextProvider.useReadContext(async (ctx) => { + return this.useContext(async (ctx) => { return await ctx.get(this.query.sql, params); }); } diff --git a/packages/drizzle-driver/src/sqlite/QueryContext.ts b/packages/drizzle-driver/src/sqlite/QueryContext.ts deleted file mode 100644 index 3ebbcccc5..000000000 --- a/packages/drizzle-driver/src/sqlite/QueryContext.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { AbstractPowerSyncDatabase, LockContext, QueryResult } from '@powersync/common'; - -/** - * Like LockContext, but only includes the specific methods needed for Drizzle. - * - * We extend it by adding getAllRaw, to support read-only queries with executeRaw. - */ -export interface QueryContext { - execute(query: string, params?: any[] | undefined): Promise; - executeRaw(query: string, params?: any[] | undefined): Promise; - - get(sql: string, parameters?: any[]): Promise; - getAll(sql: string, parameters?: any[]): Promise; - /** - * Like executeRaw, but for read-only queries. - */ - getAllRaw(query: string, params?: any[] | undefined): Promise; -} - -export class DatabaseQueryContext implements QueryContext { - constructor(private db: AbstractPowerSyncDatabase) {} - execute(query: string, params?: any[] | undefined): Promise { - return this.db.execute(query, params); - } - executeRaw(query: string, params?: any[] | undefined) { - return this.db.executeRaw(query, params); - } - get(sql: string, parameters?: any[]) { - return this.db.get(sql, parameters); - } - getAll(sql: string, parameters?: any[]) { - return this.db.getAll(sql, parameters); - } - getAllRaw(query: string, params?: any[] | undefined) { - return this.db.readLock(async (ctx) => { - return ctx.executeRaw(query, params); - }); - } -} - -export class LockQueryContext implements QueryContext { - constructor(private ctx: LockContext) {} - execute(query: string, params?: any[] | undefined): Promise { - return this.ctx.execute(query, params); - } - executeRaw(query: string, params?: any[] | undefined) { - return this.ctx.executeRaw(query, params); - } - get(sql: string, parameters?: any[]) { - return this.ctx.get(sql, parameters); - } - getAll(sql: string, parameters?: any[]) { - return this.ctx.getAll(sql, parameters); - } - getAllRaw(query: string, params?: any[] | undefined) { - return this.ctx.executeRaw(query, params); - } -} From 580531e5ee41e1f3b403d3fb199afd07a301ca88 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 11:52:59 +0200 Subject: [PATCH 10/12] cleanup import --- .../drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts index b89994393..9698c35e1 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts @@ -14,7 +14,7 @@ import { type SQLiteExecuteMethod, type SQLiteTransactionConfig } from 'drizzle-orm/sqlite-core/session'; -import { ContextProvider, PowerSyncSQLitePreparedQuery } from './PowerSyncSQLitePreparedQuery.js'; +import { PowerSyncSQLitePreparedQuery, type ContextProvider } from './PowerSyncSQLitePreparedQuery.js'; export interface PowerSyncSQLiteSessionOptions { logger?: Logger; From dd31ff5edceb5d735b0871ceaedb6cb421ce4e45 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 13:12:50 +0200 Subject: [PATCH 11/12] test: findMany use read lock --- packages/drizzle-driver/README.md | 1 - .../src/sqlite/PowerSyncSQLiteDatabase.ts | 37 ++++++++++++++++++- .../tests/sqlite/lock-usage.test.ts | 7 ++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/drizzle-driver/README.md b/packages/drizzle-driver/README.md index 0b2f4cb43..7ee8c29da 100644 --- a/packages/drizzle-driver/README.md +++ b/packages/drizzle-driver/README.md @@ -155,4 +155,3 @@ For more information on how to use Drizzle queries in PowerSync, see [here](http ## Known limitations - The integration does not currently support nested transactions (also known as `savepoints`). -- `findMany` queries execute with a write connection if not wrapped in an explicit `read only` transaction. diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts index d61ca71bc..43c55649f 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts @@ -11,15 +11,17 @@ import { createTableRelationsHelpers, extractTablesRelationalConfig, ExtractTablesWithRelations, + TableRelationalConfig, type RelationalSchemaConfig, type TablesRelationalConfig } from 'drizzle-orm/relations'; -import { SQLiteTransaction } from 'drizzle-orm/sqlite-core'; +import { SQLiteSession, SQLiteTable, SQLiteTransaction } from 'drizzle-orm/sqlite-core'; import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core/db'; import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect'; +import { RelationalQueryBuilder } from 'drizzle-orm/sqlite-core/query-builders/query'; import type { DrizzleConfig } from 'drizzle-orm/utils'; import { toCompilableQuery } from './../utils/compilableQuery.js'; -import { PowerSyncSQLiteTransactionConfig } from './PowerSyncSQLiteBaseSession.js'; +import { PowerSyncSQLiteBaseSession, PowerSyncSQLiteTransactionConfig } from './PowerSyncSQLiteBaseSession.js'; import { PowerSyncSQLiteSession } from './PowerSyncSQLiteSession.js'; export type DrizzleQuery = { toSQL(): Query; execute(): Promise }; @@ -54,6 +56,37 @@ export class PowerSyncSQLiteDatabase< super('async', dialect, session as any, schema as any); this.db = db; + + /** + * A hack in order to use read locks for `db.query.users.findMany()` etc queries. + * We don't currently get queryMetadata for these queries, so we can't use the regular session. + * This session always uses read locks. + */ + const querySession = new PowerSyncSQLiteBaseSession( + { + useReadContext: (callback) => db.readLock(callback), + useWriteContext: (callback) => db.readLock(callback) + }, + dialect, + schema, + { + logger + } + ); + if (this._.schema) { + for (const [tableName, columns] of Object.entries(this._.schema)) { + this.query[tableName as keyof typeof this.query] = new RelationalQueryBuilder( + 'async', + schema!.fullSchema, + this._.schema, + this._.tableNamesMap, + schema!.fullSchema[tableName] as SQLiteTable, + columns as TableRelationalConfig, + dialect, + querySession as SQLiteSession as any + ) as any; + } + } } transaction( diff --git a/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts b/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts index 13a404b00..1e5f04c34 100644 --- a/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts +++ b/packages/drizzle-driver/tests/sqlite/lock-usage.test.ts @@ -81,6 +81,13 @@ describe('Lock Usage Tests', () => { expect(readLockSpy).toHaveBeenCalled(); expect(writeLockSpy).not.toHaveBeenCalled(); }); + + lockUsageTest('should use readLock for query.findMany()', async ({ db, readLockSpy, writeLockSpy }) => { + await db.query.users.findMany(); + + expect(readLockSpy).toHaveBeenCalled(); + expect(writeLockSpy).not.toHaveBeenCalled(); + }); }); describe('INSERT queries', () => { From 9eae32a3226674ce44445c41b32d124fc83e036b Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 14:24:50 +0200 Subject: [PATCH 12/12] slightly cleaner hack --- .../src/sqlite/PowerSyncSQLiteDatabase.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts index 43c55649f..c203ab7c2 100644 --- a/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts +++ b/packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts @@ -74,8 +74,12 @@ export class PowerSyncSQLiteDatabase< } ); if (this._.schema) { + // https://github.com/drizzle-team/drizzle-orm/blob/ad4ddd444d066b339ffd5765cb6ec3bf49380189/drizzle-orm/src/sqlite-core/db.ts#L72 + const query = this.query as { + [K in keyof TSchema]: RelationalQueryBuilder<'async', any, any, any>; + }; for (const [tableName, columns] of Object.entries(this._.schema)) { - this.query[tableName as keyof typeof this.query] = new RelationalQueryBuilder( + query[tableName as keyof TSchema] = new RelationalQueryBuilder( 'async', schema!.fullSchema, this._.schema, @@ -83,8 +87,8 @@ export class PowerSyncSQLiteDatabase< schema!.fullSchema[tableName] as SQLiteTable, columns as TableRelationalConfig, dialect, - querySession as SQLiteSession as any - ) as any; + querySession as SQLiteSession<'async', any, any, any> + ); } } }