From 4325279eaad4b99356c8d20a3437e22d182c35f2 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 09:15:31 +0200 Subject: [PATCH 01/13] Merge remote-tracking branch 'origin/main' into tanstack-demo --- pnpm-lock.yaml | 1695 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 1216 insertions(+), 479 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf3a170d6..d33447f4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 12.1.4(rollup@4.14.3)(tslib@2.8.1)(typescript@5.9.2) '@vitest/browser': specifier: ^3.2.4 - version: 3.2.4(playwright@1.52.0)(vite@6.3.5(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))(vitest@3.2.4) + version: 3.2.4(playwright@1.52.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))(vitest@3.2.4) husky: specifier: ^9.0.11 version: 9.1.7 @@ -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))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17)(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) + 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))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17)(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) + 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(wm3bvfp4qcetscjld4hplpimri) + version: 14.1.0(a6850416216e8b64df60af23d5183c0b) '@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(j6abyuabi5plzpedpvxbnwhrsi) + version: 7.4.1(1d85788bd68a0e12619f848d71cbac62) '@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(xdzi7taj2dri7edfzwov6a63va) + version: 4.0.21(e063c8109134fcdd1c97e4d6a4caf625) 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(4a23q4g4mav7ddr6jlhxnyzzo4) + version: 2.10.4(1b7f2cbbd098c1646b3c5f57acc57915) typed-async-storage: specifier: ^3.1.2 version: 3.1.2 @@ -558,7 +558,7 @@ importers: version: 0.15.0 next: specifier: 14.2.3 - version: 14.2.3(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1) + version: 14.2.3(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1) react: specifier: ^18.2.0 version: 18.3.1 @@ -580,10 +580,10 @@ importers: version: 10.4.21(postcss@8.5.4) babel-loader: specifier: ^9.1.3 - version: 9.2.1(@babel/core@7.26.10)(webpack@5.99.9) + version: 9.2.1(@babel/core@7.26.10)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) css-loader: specifier: ^6.11.0 - version: 6.11.0(@rspack/core@1.3.13)(webpack@5.99.9) + version: 6.11.0(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) eslint: specifier: ^8.57.0 version: 8.57.1 @@ -598,13 +598,13 @@ importers: version: 1.89.1 sass-loader: specifier: ^13.3.3 - version: 13.3.3(sass@1.89.1)(webpack@5.99.9) + version: 13.3.3(sass@1.89.1)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) style-loader: specifier: ^3.3.4 - version: 3.3.4(webpack@5.99.9) + version: 3.3.4(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) tailwindcss: specifier: ^3.4.3 - version: 3.4.17(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + version: 3.4.17(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/node@20.17.57)(typescript@5.9.2)) demos/example-node: dependencies: @@ -680,10 +680,10 @@ importers: devDependencies: '@types/webpack': specifier: ^5.28.5 - version: 5.28.5(webpack-cli@5.1.4(webpack@5.99.9)) + version: 5.28.5(webpack-cli@5.1.4) html-webpack-plugin: specifier: ^5.6.0 - version: 5.6.3(@rspack/core@1.3.13)(webpack@5.99.9(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9) serve: specifier: ^14.2.1 version: 14.2.4 @@ -826,7 +826,7 @@ importers: version: 0.77.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10)) '@react-native/eslint-config': specifier: 0.77.0 - version: 0.77.0(eslint@8.57.1)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0))(prettier@3.5.3)(typescript@5.9.2) + version: 0.77.0(eslint@8.57.1)(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.9.2)))(prettier@3.5.3)(typescript@5.9.2) '@react-native/metro-config': specifier: 0.77.0 version: 0.77.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10)) @@ -910,7 +910,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(cpo3xaw6yrjernjvkkkt7bisia) + version: 4.0.21(b0bddf53ba1689b30337428eee4dc275) 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)) @@ -983,7 +983,7 @@ importers: version: 1.0.2 '@expo/vector-icons': specifier: ^14.0.3 - version: 14.1.0(wm3bvfp4qcetscjld4hplpimri) + version: 14.1.0(a6850416216e8b64df60af23d5183c0b) '@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) @@ -1004,7 +1004,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(j6abyuabi5plzpedpvxbnwhrsi) + version: 7.4.1(1d85788bd68a0e12619f848d71cbac62) '@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) @@ -1028,7 +1028,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(hml277kvlorqbj6gijmq6joh24) + version: 16.0.18(55c6da9df988ca7f1678935d82e9238e) 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)) @@ -1043,7 +1043,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(xdzi7taj2dri7edfzwov6a63va) + version: 4.0.21(e063c8109134fcdd1c97e4d6a4caf625) 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)) @@ -1085,7 +1085,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(4a23q4g4mav7ddr6jlhxnyzzo4) + version: 2.10.4(1b7f2cbbd098c1646b3c5f57acc57915) devDependencies: '@babel/core': specifier: ^7.26.10 @@ -1122,7 +1122,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(wm3bvfp4qcetscjld4hplpimri) + version: 14.1.0(a6850416216e8b64df60af23d5183c0b) '@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) @@ -1149,7 +1149,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(j6abyuabi5plzpedpvxbnwhrsi) + version: 7.4.1(1d85788bd68a0e12619f848d71cbac62) '@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) @@ -1170,7 +1170,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(hml277kvlorqbj6gijmq6joh24) + version: 16.0.18(55c6da9df988ca7f1678935d82e9238e) 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)) @@ -1188,7 +1188,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(xdzi7taj2dri7edfzwov6a63va) + version: 4.0.21(e063c8109134fcdd1c97e4d6a4caf625) 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)) @@ -1203,7 +1203,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(l76mjoke3yk4s56nokhxoxxpou) + version: 4.0.9(fa4ab2ddb2d13a20299c682fc53644db) 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)) @@ -1261,10 +1261,10 @@ importers: version: 18.3.1 jest: specifier: ^29.2.1 - version: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + 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(hjrfme3xxu7xcbl6wzt3m2hgh4) + version: 52.0.6(3635c191458c5fa90af52243d15b5fda) react-test-renderer: specifier: 18.3.1 version: 18.3.1(react@18.3.1) @@ -1710,10 +1710,10 @@ importers: dependencies: '@docusaurus/core': specifier: ^3.7.0 - version: 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + version: 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/preset-classic': specifier: ^3.7.0 - version: 3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) + version: 3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) '@mdx-js/react': specifier: ^3.1.0 version: 3.1.0(@types/react@19.1.6)(react@18.3.1) @@ -1732,19 +1732,19 @@ importers: devDependencies: '@docusaurus/faster': specifier: ^3.7.0 - version: 3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13) '@docusaurus/module-type-aliases': specifier: ^3.7.0 - version: 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-classic': specifier: ^3.7.0 - version: 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + version: 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/tsconfig': specifier: 3.7.0 version: 3.7.0 '@docusaurus/types': specifier: 3.7.0 - version: 3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/node': specifier: ^20.17.12 version: 20.17.57 @@ -1917,6 +1917,25 @@ importers: specifier: 3.2.1 version: 3.2.1 + packages/core: + dependencies: + ts-codec: + specifier: ^1.3.0 + version: 1.3.0 + devDependencies: + '@powersync/service-core': + specifier: ^1.15.8 + version: 1.15.8(babel-plugin-macros@3.1.0) + '@powersync/service-jsonbig': + specifier: ^0.17.11 + version: 0.17.11 + '@powersync/service-sync-rules': + specifier: ^0.29.5 + version: 0.29.5 + '@types/node': + specifier: ^20.5.9 + version: 20.17.57 + packages/drizzle-driver: dependencies: '@powersync/common': @@ -1934,7 +1953,7 @@ importers: 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@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) + 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))(@opentelemetry/api@1.9.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) 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) @@ -2005,7 +2024,7 @@ importers: 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@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) + 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))(@opentelemetry/api@1.9.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) rollup: specifier: 4.14.3 version: 4.14.3 @@ -2237,13 +2256,13 @@ importers: version: 4.0.1 source-map-loader: specifier: ^5.0.0 - version: 5.0.0(webpack@5.99.9(webpack-cli@5.1.4)) + version: 5.0.0(webpack@5.99.9) stream-browserify: specifier: ^3.0.0 version: 3.0.0 terser-webpack-plugin: specifier: ^5.3.9 - version: 5.3.14(webpack@5.99.9(webpack-cli@5.1.4)) + version: 5.3.14(webpack@5.99.9) uuid: specifier: ^9.0.1 version: 9.0.1 @@ -2443,7 +2462,7 @@ importers: version: 0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10)) '@react-native/eslint-config': specifier: 0.78.0 - version: 0.78.0(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4) + version: 0.78.0(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4) '@react-native/metro-config': specifier: 0.78.0 version: 0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10)) @@ -2485,7 +2504,7 @@ importers: version: 4.1.0 detox: specifier: ^20.34.4 - version: 20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))) + version: 20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))) eslint: specifier: ^8.19.0 version: 8.57.1 @@ -2494,7 +2513,7 @@ importers: version: 3.3.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + version: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) prettier: specifier: 2.8.8 version: 2.8.8 @@ -2503,7 +2522,7 @@ importers: version: 19.0.0(react@19.0.0) ts-jest: specifier: ^29.2.6 - version: 29.3.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4) + version: 29.3.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4) typescript: specifier: 5.0.4 version: 5.0.4 @@ -5483,6 +5502,10 @@ packages: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} + '@humanwhocodes/momoa@2.0.4': + resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==} + engines: {node: '>=10.10.0'} + '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead @@ -5805,6 +5828,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-sdsl/ordered-set@4.4.2': + resolution: {integrity: sha512-ieYQ8WlBPKYzEo81H3q0DFbd8WtFRXXABb4+vRCF0AO3WWtJZFxYvRGdipUXGrd6tlSySmqhcPuO3J6SCodCxg==} + '@jsonjoy.com/base64@1.1.2': resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} @@ -6490,6 +6516,90 @@ packages: react: '*' react-native: '*' + '@opentelemetry/api-logs@0.203.0': + resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/core@2.0.1': + resolution: {integrity: sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-metrics-otlp-http@0.203.0': + resolution: {integrity: sha512-HFSW10y8lY6BTZecGNpV3GpoSy7eaO0Z6GATwZasnT4bEsILp8UJXNG5OmEsz4SdwCSYvyCbTJdNbZP3/8LGCQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.203.0': + resolution: {integrity: sha512-2jLuNuw5m4sUj/SncDf/mFPabUxMZmmYetx5RKIMIQyPnl6G6ooFzfeE8aXNRf8YD1ZXNlCnRPcISxjveGJHNg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.203.0': + resolution: {integrity: sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.203.0': + resolution: {integrity: sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.0.1': + resolution: {integrity: sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.203.0': + resolution: {integrity: sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.0.1': + resolution: {integrity: sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.0.1': + resolution: {integrity: sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.37.0': + resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==} + engines: {node: '>=14'} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -6782,9 +6892,60 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@powersync/lib-services-framework@0.7.8': + resolution: {integrity: sha512-xG0fIYOaIA6r1G9bTMAYdTR1eKFnEKspqcDl5sOHYLHy/ozJREH1IjXYeIk0YPLv25IP/hunlGDKsc3QD6omfw==} + + '@powersync/service-core@1.15.8': + resolution: {integrity: sha512-TeEkN2XPxnNLLkR+Vx88L8A/qNMpQ5KdkfoU2nHL7OLnXz4/kPe4+ZrCa/qgNPXwURaq2mygrz7cfKt0v9jmrw==} + + '@powersync/service-errors@0.3.4': + resolution: {integrity: sha512-pujG9a0hx3p045CRlznbKgADqFS89NMxbJDPhwEAUKZUv/ZCiK6XxWTr4VEEHoCLkXDcrOT7zIHrX+Tez7svJQ==} + + '@powersync/service-jsonbig@0.17.11': + resolution: {integrity: sha512-wHvM2hJ6q7oypzEFgNhyC1n3ej/qmGTFTu4nJgft1ISizzwF2gBBODte9se9kZdIuZq242VNl6ZAn+rk601N0Q==} + + '@powersync/service-rsocket-router@0.2.5': + resolution: {integrity: sha512-ada+EprvIhZ+u076ns1wT+YyL/S+qt3s8KD+lQ4TkqtXHwShy2GQKsiawwjpG9LjwpqCfS3YnISn4YMxufoYNA==} + + '@powersync/service-sync-rules@0.29.5': + resolution: {integrity: sha512-D2LMMw3bIkqmeQ2KvQ2XaaLqQElMDXjyy6s6jlchmdZAGndeeH7YCp5HHzhSMldE1IGUBsbJNYau87GxtlYmSA==} + + '@powersync/service-types@0.13.0': + resolution: {integrity: sha512-nvfHwjah4/GDOKOlzEiaeExLciXbm2sUMiwgQlw5ma2MeBYODsgNP5CdGWKF1jdN7RbTOsMptOks4SY2+QlT9Q==} + '@powersync/sql-js@0.0.5': resolution: {integrity: sha512-+rjUyEzwQIM51dJSbw5bpWyquwiHa5Pyu0Pb6oXhddfxhvuLHMWEM/+bN9em2LkFZ/jtLI9HBCcCsOORDeL5hg==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/react-compose-refs@1.0.0': resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} peerDependencies: @@ -8327,6 +8488,9 @@ packages: '@swc/types@0.1.21': resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + '@syncpoint/wkx@0.5.2': + resolution: {integrity: sha512-o3gaGp38Gg31Pl2jULfLmOpSEyg2gcgrccCZ5kdEjKwhjV+lC38s0p2fLpDijpoL6JsCYSmgNSJ6JJBuN67YDQ==} + '@szmarczak/http-timer@4.0.6': resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -9200,9 +9364,15 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@13.13.52': + resolution: {integrity: sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==} + '@types/node@14.18.63': resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@15.14.9': + resolution: {integrity: sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==} + '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} @@ -10455,6 +10625,12 @@ packages: resolution: {integrity: sha512-p4AF8uYzm9Fwu8m/hSVTCPXrRBPmB34hQpHsec2KOaR9CZmgoU8IOv4Cvwq4hgz2p4hLMNbsdNl5XeA6XbAQwA==} engines: {node: '>=14.0.0'} + better-ajv-errors@1.2.0: + resolution: {integrity: sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + ajv: 4.11.8 - 8 + better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} @@ -10610,6 +10786,10 @@ packages: resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==} engines: {node: '>=16.20.1'} + bson@6.10.4: + resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} + engines: {node: '>=16.20.1'} + btoa@1.2.1: resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==} engines: {node: '>= 0.4.0'} @@ -11295,6 +11475,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + corser@2.0.1: resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} engines: {node: '>= 0.4.0'} @@ -14407,6 +14591,9 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + ix@5.0.0: + resolution: {integrity: sha512-6LyyrHnvNrSy5pKtW/KA+KKusHrB223aBJCJlIGPN7QBfDkEEtNrAkAz9lLLShIcdJntq6BiPCHuKaCM/9wwXw==} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -14635,6 +14822,9 @@ packages: join-component@1.1.0: resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==} + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + js-base64@3.7.2: resolution: {integrity: sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==} @@ -15219,6 +15409,9 @@ packages: long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -15226,6 +15419,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lossless-json@2.0.11: + resolution: {integrity: sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g==} + loupe@3.1.4: resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} @@ -16868,6 +17064,9 @@ packages: performant-array-to-tree@1.11.0: resolution: {integrity: sha512-YwCqIDvnaebXaKuKQhI5yJD6ryDc3FxvoeX/5ougXTKDUWb7s5S2BuBgIyftCa4sBe1+ZU5Kmi4RJy+pjjjrpw==} + pgsql-ast-parser@11.2.0: + resolution: {integrity: sha512-/8KCcQjePoQDOtfZQuoV/4Y3WpmQVp7E+RFayAdjJpdBdu2dBnKnuQe9XU4g5Td5qC0G+i/fFK/DlNjvWwg+FA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -17619,6 +17818,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -19760,6 +19963,9 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-codec@1.3.0: + resolution: {integrity: sha512-OOaGvS0UwjyOychFZwjqSm47K65lzTCSup47RDG30crZr2MGnQCHQ13duAI4OcnzuYITNN6JDdS8RrtB0g204Q==} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -21413,11 +21619,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))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17)(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-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))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17)(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': 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 @@ -21466,13 +21672,13 @@ 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))(jiti@2.4.2)(lightningcss@1.30.1)(tailwindcss@3.4.17)(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@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) - '@angular-devkit/build-webpack': 0.1902.14(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)))(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + '@angular-devkit/build-webpack': 0.1902.14(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(@swc/core@1.11.29)))(webpack@5.98.0(@swc/core@1.11.29)) '@angular-devkit/core': 19.2.14(chokidar@4.0.3) - '@angular/build': 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))(@types/node@24.2.0)(chokidar@4.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(postcss@8.5.2)(tailwindcss@3.4.17)(terser@5.39.0)(tsx@4.19.4)(typescript@5.5.4)(yaml@2.8.0) + '@angular/build': 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))(@types/node@24.2.0)(chokidar@4.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(postcss@8.5.2)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(terser@5.39.0)(tsx@4.19.4)(typescript@5.5.4)(yaml@2.8.0) '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -21484,14 +21690,14 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.10) '@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)(esbuild@0.25.4)) + '@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.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)(esbuild@0.25.4)) + babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.29)) browserslist: 4.25.0 - copy-webpack-plugin: 12.0.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) - css-loader: 7.1.2(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + copy-webpack-plugin: 12.0.2(webpack@5.98.0(@swc/core@1.11.29)) + css-loader: 7.1.2(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)) esbuild-wasm: 0.25.4 fast-glob: 3.3.3 http-proxy-middleware: 3.0.5 @@ -21499,38 +21705,38 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.2.2 - less-loader: 12.2.0(@rspack/core@1.3.13)(less@4.2.2)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) - license-webpack-plugin: 4.0.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + less-loader: 12.2.0(@rspack/core@1.3.13)(less@4.2.2)(webpack@5.98.0(@swc/core@1.11.29)) + license-webpack-plugin: 4.0.2(webpack@5.98.0(@swc/core@1.11.29)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.9.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + mini-css-extract-plugin: 2.9.2(webpack@5.98.0(@swc/core@1.11.29)) open: 10.1.0 ora: 5.4.1 picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(@rspack/core@1.3.13)(postcss@8.5.2)(typescript@5.5.4)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + postcss-loader: 8.1.1(@rspack/core@1.3.13)(postcss@8.5.2)(typescript@5.5.4)(webpack@5.98.0(@swc/core@1.11.29)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 - sass-loader: 16.0.5(@rspack/core@1.3.13)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + sass-loader: 16.0.5(@rspack/core@1.3.13)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.11.29)) semver: 7.7.1 - source-map-loader: 5.0.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + source-map-loader: 5.0.0(webpack@5.98.0(@swc/core@1.11.29)) source-map-support: 0.5.21 terser: 5.39.0 tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.5.4 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) - webpack-dev-server: 5.2.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.11.29)) + webpack-dev-server: 5.2.0(webpack@5.98.0(@swc/core@1.11.29)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(webpack@5.98.0(@swc/core@1.11.29)) optionalDependencies: '@angular/service-worker': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) esbuild: 0.25.4 - jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.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)) jest-environment-jsdom: 29.7.0 - tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)) transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -21554,12 +21760,12 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.1902.14(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)))(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4))': + '@angular-devkit/build-webpack@0.1902.14(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(@swc/core@1.11.29)))(webpack@5.98.0(@swc/core@1.11.29))': dependencies: '@angular-devkit/architect': 0.1902.14(chokidar@4.0.3) rxjs: 7.8.1 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) - webpack-dev-server: 5.2.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + webpack-dev-server: 5.2.0(webpack@5.98.0(@swc/core@1.11.29)) transitivePeerDependencies: - chokidar @@ -21590,7 +21796,7 @@ snapshots: '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) tslib: 2.8.1 - '@angular/build@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))(@types/node@24.2.0)(chokidar@4.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(postcss@8.5.2)(tailwindcss@3.4.17)(terser@5.39.0)(tsx@4.19.4)(typescript@5.5.4)(yaml@2.8.0)': + '@angular/build@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))(@types/node@24.2.0)(chokidar@4.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(postcss@8.5.2)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)))(terser@5.39.0)(tsx@4.19.4)(typescript@5.5.4)(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.14(chokidar@4.0.3) @@ -21626,7 +21832,7 @@ snapshots: less: 4.2.2 lmdb: 3.2.6 postcss: 8.5.2 - tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)) transitivePeerDependencies: - '@types/node' - chokidar @@ -23439,7 +23645,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/babel@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/core': 7.26.10 '@babel/generator': 7.27.3 @@ -23452,7 +23658,7 @@ snapshots: '@babel/runtime-corejs3': 7.27.4 '@babel/traverse': 7.27.4 '@docusaurus/logger': 3.8.0 - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) babel-plugin-dynamic-import-node: 2.3.3 fs-extra: 11.3.0 tslib: 2.8.1 @@ -23466,34 +23672,34 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/bundler@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: '@babel/core': 7.26.10 - '@docusaurus/babel': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/babel': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/cssnano-preset': 3.8.0 '@docusaurus/logger': 3.8.0 - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.99.9(@swc/core@1.11.29)) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) clean-css: 5.3.3 - copy-webpack-plugin: 11.0.0(webpack@5.99.9(@swc/core@1.11.29)) - css-loader: 6.11.0(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.11.29)) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(lightningcss@1.30.1)(webpack@5.99.9(@swc/core@1.11.29)) + copy-webpack-plugin: 11.0.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) + css-loader: 6.11.0(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(lightningcss@1.30.1)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) cssnano: 6.1.2(postcss@8.5.4) - file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29)) + file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) html-minifier-terser: 7.2.0 - mini-css-extract-plugin: 2.9.2(webpack@5.99.9(@swc/core@1.11.29)) - null-loader: 4.0.1(webpack@5.99.9(@swc/core@1.11.29)) + mini-css-extract-plugin: 2.9.2(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) + null-loader: 4.0.1(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) postcss: 8.5.4 - postcss-loader: 7.3.4(postcss@8.5.4)(typescript@5.9.2)(webpack@5.99.9(@swc/core@1.11.29)) + postcss-loader: 7.3.4(postcss@8.5.4)(typescript@5.9.2)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) postcss-preset-env: 10.2.0(postcss@8.5.4) - terser-webpack-plugin: 5.3.14(@swc/core@1.11.29)(webpack@5.99.9(@swc/core@1.11.29)) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29)))(webpack@5.99.9(@swc/core@1.11.29)) - webpack: 5.99.9(@swc/core@1.11.29) - webpackbar: 6.0.1(webpack@5.99.9(@swc/core@1.11.29)) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) + webpackbar: 6.0.1(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) optionalDependencies: - '@docusaurus/faster': 3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/faster': 3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13) transitivePeerDependencies: - '@parcel/css' - '@rspack/core' @@ -23510,15 +23716,15 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/core@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/babel': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/bundler': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/babel': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/bundler': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/logger': 3.8.0 - '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdx-js/react': 3.1.0(@types/react@19.1.6)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 @@ -23534,7 +23740,7 @@ snapshots: execa: 5.1.1 fs-extra: 11.3.0 html-tags: 3.3.1 - html-webpack-plugin: 5.6.3(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.11.29)) + html-webpack-plugin: 5.6.3(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) leven: 3.1.0 lodash: 4.17.21 open: 8.4.2 @@ -23544,7 +23750,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.99.9(@swc/core@1.11.29)) + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) react-router: 5.3.4(react@18.3.1) react-router-config: 5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1) react-router-dom: 5.3.4(react@18.3.1) @@ -23553,9 +23759,9 @@ snapshots: tinypool: 1.1.1 tslib: 2.8.1 update-notifier: 6.0.2 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 4.15.2(debug@4.4.1)(webpack@5.99.9(@swc/core@1.11.29)) + webpack-dev-server: 4.15.2(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) webpack-merge: 6.0.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -23582,17 +23788,17 @@ snapshots: postcss-sort-media-queries: 5.2.0(postcss@8.5.4) tslib: 2.8.1 - '@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13)': dependencies: - '@docusaurus/types': 3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rspack/core': 1.3.13 - '@swc/core': 1.11.29 + '@docusaurus/types': 3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + '@swc/core': 1.11.29(@swc/helpers@0.5.13) '@swc/html': 1.11.29 browserslist: 4.25.0 lightningcss: 1.30.1 - swc-loader: 0.2.6(@swc/core@1.11.29)(webpack@5.99.9(@swc/core@1.11.29)) + swc-loader: 0.2.6(@swc/core@1.11.29(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) tslib: 2.8.1 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@swc/helpers' - esbuild @@ -23604,16 +23810,16 @@ snapshots: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/mdx-loader@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@docusaurus/logger': 3.8.0 - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdx-js/mdx': 3.1.0(acorn@8.14.1) '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 estree-util-value-to-estree: 3.4.0 - file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29)) + file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) fs-extra: 11.3.0 image-size: 2.0.2 mdast-util-mdx: 3.0.0 @@ -23629,9 +23835,9 @@ snapshots: tslib: 2.8.1 unified: 11.0.5 unist-util-visit: 5.0.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29)))(webpack@5.99.9(@swc/core@1.11.29)) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) vfile: 6.0.3 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@swc/core' - acorn @@ -23640,9 +23846,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/module-type-aliases@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 '@types/react': 18.3.23 '@types/react-router-config': 5.0.11 @@ -23659,17 +23865,17 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-content-blog@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/logger': 3.8.0 - '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.3.0 @@ -23681,7 +23887,7 @@ snapshots: tslib: 2.8.1 unist-util-visit: 5.0.0 utility-types: 3.11.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -23701,17 +23907,17 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/logger': 3.8.0 - '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.3.0 @@ -23722,7 +23928,7 @@ snapshots: schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -23742,18 +23948,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-content-pages@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -23773,11 +23979,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-css-cascade-layers@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -23800,11 +24006,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-debug@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -23829,11 +24035,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-google-analytics@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -23856,11 +24062,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-google-gtag@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/gtag.js': 0.0.12 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -23884,11 +24090,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-google-tag-manager@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -23911,14 +24117,14 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-sitemap@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/logger': 3.8.0 - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -23943,18 +24149,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-svgr@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@svgr/core': 8.1.0(typescript@5.9.2) '@svgr/webpack': 8.1.0(typescript@5.9.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -23974,23 +24180,23 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2)': - dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-blog': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-pages': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-css-cascade-layers': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-debug': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-google-analytics': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-google-gtag': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-google-tag-manager': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-sitemap': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-svgr': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-classic': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-search-algolia': 3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/preset-classic@3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2)': + dependencies: + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-content-blog': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-content-pages': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-css-cascade-layers': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-debug': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-google-analytics': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-google-gtag': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-google-tag-manager': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-sitemap': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-svgr': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/theme-classic': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-search-algolia': 3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -24020,21 +24226,21 @@ snapshots: '@types/react': 18.3.23 react: 18.3.1 - '@docusaurus/theme-classic@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/theme-classic@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/logger': 3.8.0 - '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-pages': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-blog': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/plugin-content-pages': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-translations': 3.8.0 - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdx-js/react': 3.1.0(@types/react@19.1.6)(react@18.3.1) clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 @@ -24069,13 +24275,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/theme-common@3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/mdx-loader': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 '@types/react': 18.3.23 '@types/react-router-config': 5.0.11 @@ -24094,16 +24300,16 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2)': + '@docusaurus/theme-search-algolia@3.8.0(@algolia/client-search@5.25.0)(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/react@19.1.6)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2)': dependencies: '@docsearch/react': 3.9.0(@algolia/client-search@5.25.0)(@types/react@19.1.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) - '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/core': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@docusaurus/logger': 3.8.0 - '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13)(@swc/core@1.11.29)(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/theme-common': 3.8.0(@docusaurus/plugin-content-docs@3.8.0(@docusaurus/faster@3.8.0(@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/helpers@0.5.13))(@mdx-js/react@3.1.0(@types/react@19.1.6)(react@18.3.1))(@rspack/core@1.3.13(@swc/helpers@0.5.13))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(lightningcss@1.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-translations': 3.8.0 - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) algoliasearch: 5.25.0 algoliasearch-helper: 3.25.0(algoliasearch@5.25.0) clsx: 2.1.1 @@ -24143,7 +24349,7 @@ snapshots: '@docusaurus/tsconfig@3.7.0': {} - '@docusaurus/types@3.7.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/types@3.7.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.1) '@types/history': 4.7.11 @@ -24154,7 +24360,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' utility-types: 3.11.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) webpack-merge: 5.10.0 transitivePeerDependencies: - '@swc/core' @@ -24164,7 +24370,7 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/types@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/types@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.1) '@types/history': 4.7.11 @@ -24175,7 +24381,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' utility-types: 3.11.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) webpack-merge: 5.10.0 transitivePeerDependencies: - '@swc/core' @@ -24185,9 +24391,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-common@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/utils-common@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' @@ -24199,11 +24405,11 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/utils-validation@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@docusaurus/logger': 3.8.0 - '@docusaurus/utils': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.3.0 joi: 17.13.3 js-yaml: 4.1.0 @@ -24219,14 +24425,14 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/utils@3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@docusaurus/logger': 3.8.0 - '@docusaurus/types': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29)(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.8.0(@swc/core@1.11.29(@swc/helpers@0.5.13))(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) escape-string-regexp: 4.0.0 execa: 5.1.1 - file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29)) + file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) fs-extra: 11.3.0 github-slugger: 1.5.0 globby: 11.1.0 @@ -24239,9 +24445,9 @@ snapshots: prompts: 2.4.2 resolve-pathname: 3.0.0 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29)))(webpack@5.99.9(@swc/core@1.11.29)) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) utility-types: 3.11.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - '@swc/core' - acorn @@ -25693,13 +25899,13 @@ snapshots: '@expo/timeago.js@1.0.0': {} - '@expo/vector-icons@14.1.0(ka6rgkktlsuut5gotrymd2sdni)': + '@expo/vector-icons@14.1.0(99f35dc9d27b76831378288730881035)': 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(wm3bvfp4qcetscjld4hplpimri)': + '@expo/vector-icons@14.1.0(a6850416216e8b64df60af23d5183c0b)': 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 @@ -25787,6 +25993,8 @@ snapshots: '@humanwhocodes/module-importer@1.0.1': {} + '@humanwhocodes/momoa@2.0.4': {} + '@humanwhocodes/object-schema@2.0.3': {} '@inquirer/checkbox@4.1.8(@types/node@24.2.0)': @@ -26051,7 +26259,42 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2))': + '@jest/core@29.7.0(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))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.57 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 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-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -26065,7 +26308,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + jest-config: 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@22.15.29)(typescript@5.0.4)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -26086,7 +26329,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))': + '@jest/core@29.7.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))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -26100,7 +26343,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest-config: 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@24.2.0)(typescript@5.5.4)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -26120,6 +26363,43 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true + + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.57 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 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@24.2.0)(typescript@5.9.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + optional: true '@jest/create-cache-key-function@29.7.0': dependencies: @@ -26303,6 +26583,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@js-sdsl/ordered-set@4.4.2': {} + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -26703,7 +26985,7 @@ snapshots: '@module-federation/manifest': 0.13.1(typescript@5.9.2)(vue-tsc@2.0.6(typescript@5.9.2)) '@module-federation/runtime-tools': 0.13.1 '@module-federation/sdk': 0.13.1 - '@rspack/core': 1.3.13 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) btoa: 1.2.1 optionalDependencies: typescript: 5.9.2 @@ -27094,7 +27376,7 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.3': optional: true - '@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)(esbuild@0.25.4))': + '@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))': dependencies: '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.5.4) typescript: 5.5.4 @@ -27289,6 +27571,95 @@ snapshots: react: 19.0.0 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) + '@opentelemetry/api-logs@0.203.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.37.0 + + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.37.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-prometheus@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + + '@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.37.0 + + '@opentelemetry/semantic-conventions@1.37.0': {} + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -27653,8 +28024,115 @@ snapshots: '@popperjs/core@2.11.8': {} + '@powersync/lib-services-framework@0.7.8': + dependencies: + '@powersync/service-errors': 0.3.4 + '@powersync/service-sync-rules': 0.29.5 + ajv: 8.17.1 + better-ajv-errors: 1.2.0(ajv@8.17.1) + bson: 6.10.4 + dotenv: 16.5.0 + ipaddr.js: 2.2.0 + lodash: 4.17.21 + ts-codec: 1.3.0 + uuid: 11.1.0 + winston: 3.17.0 + zod: 3.25.48 + + '@powersync/service-core@1.15.8(babel-plugin-macros@3.1.0)': + dependencies: + '@js-sdsl/ordered-set': 4.4.2 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/exporter-metrics-otlp-http': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) + '@powersync/lib-services-framework': 0.7.8 + '@powersync/service-jsonbig': 0.17.11 + '@powersync/service-rsocket-router': 0.2.5 + '@powersync/service-sync-rules': 0.29.5 + '@powersync/service-types': 0.13.0(babel-plugin-macros@3.1.0) + async: 3.2.6 + async-mutex: 0.5.0 + bson: 6.10.4 + commander: 12.1.0 + cors: 2.8.5 + ipaddr.js: 2.2.0 + ix: 5.0.0 + jose: 4.15.9 + lodash: 4.17.21 + lru-cache: 10.4.3 + negotiator: 1.0.0 + node-fetch: 3.3.2 + ts-codec: 1.3.0 + uri-js: 4.4.1 + uuid: 11.1.0 + winston: 3.17.0 + yaml: 2.8.0 + transitivePeerDependencies: + - babel-plugin-macros + - bufferutil + - utf-8-validate + + '@powersync/service-errors@0.3.4': {} + + '@powersync/service-jsonbig@0.17.11': + dependencies: + lossless-json: 2.0.11 + + '@powersync/service-rsocket-router@0.2.5': + dependencies: + '@powersync/lib-services-framework': 0.7.8 + rsocket-core: 1.0.0-alpha.3 + ts-codec: 1.3.0 + uuid: 11.1.0 + ws: 8.18.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@powersync/service-sync-rules@0.29.5': + dependencies: + '@powersync/service-jsonbig': 0.17.11 + '@syncpoint/wkx': 0.5.2 + ajv: 8.17.1 + pgsql-ast-parser: 11.2.0 + uuid: 11.1.0 + yaml: 2.8.0 + + '@powersync/service-types@0.13.0(babel-plugin-macros@3.1.0)': + dependencies: + dedent: 1.6.0(babel-plugin-macros@3.1.0) + ts-codec: 1.3.0 + uri-js: 4.4.1 + transitivePeerDependencies: + - babel-plugin-macros + '@powersync/sql-js@0.0.5': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)': dependencies: '@babel/runtime': 7.27.6 @@ -28845,7 +29323,7 @@ snapshots: - supports-color - typescript - '@react-native/eslint-config@0.77.0(eslint@8.57.1)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0))(prettier@3.5.3)(typescript@5.9.2)': + '@react-native/eslint-config@0.77.0(eslint@8.57.1)(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.9.2)))(prettier@3.5.3)(typescript@5.9.2)': dependencies: '@babel/core': 7.26.10 '@babel/eslint-parser': 7.27.1(@babel/core@7.26.10)(eslint@8.57.1) @@ -28856,7 +29334,7 @@ snapshots: eslint-config-prettier: 8.10.0(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.27.1(@babel/core@7.26.10)(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0))(typescript@5.9.2) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(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.9.2)))(typescript@5.9.2) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) eslint-plugin-react-native: 4.1.0(eslint@8.57.1) @@ -28866,7 +29344,7 @@ snapshots: - supports-color - typescript - '@react-native/eslint-config@0.78.0(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4)': + '@react-native/eslint-config@0.78.0(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)))(prettier@2.8.8)(typescript@5.0.4)': dependencies: '@babel/core': 7.26.10 '@babel/eslint-parser': 7.27.1(@babel/core@7.26.10)(eslint@8.57.1) @@ -28877,7 +29355,7 @@ snapshots: eslint-config-prettier: 8.10.0(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.27.1(@babel/core@7.26.10)(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) eslint-plugin-react-native: 4.1.0(eslint@8.57.1) @@ -28962,9 +29440,7 @@ snapshots: transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' - - bufferutil - supports-color - - utf-8-validate '@react-native/metro-config@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))': dependencies: @@ -29100,7 +29576,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(j6abyuabi5plzpedpvxbnwhrsi)': + '@react-navigation/drawer@7.4.1(1d85788bd68a0e12619f848d71cbac62)': 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) @@ -29116,7 +29592,7 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/drawer@7.4.1(nyxmcqdttlojx3ihgax6eihdpu)': + '@react-navigation/drawer@7.4.1(f2502081aada8c22c3fd2dbf46b9d114)': 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) @@ -29596,11 +30072,13 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.3.13 '@rspack/binding-win32-x64-msvc': 1.3.13 - '@rspack/core@1.3.13': + '@rspack/core@1.3.13(@swc/helpers@0.5.13)': dependencies: '@module-federation/runtime-tools': 0.14.3 '@rspack/binding': 1.3.13 '@rspack/lite-tapable': 1.0.1 + optionalDependencies: + '@swc/helpers': 0.5.13 '@rspack/lite-tapable@1.0.1': {} @@ -29962,7 +30440,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.6.13': optional: true - '@swc/core@1.11.29': + '@swc/core@1.11.29(@swc/helpers@0.5.13)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.21 @@ -29977,6 +30455,7 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.11.29 '@swc/core-win32-ia32-msvc': 1.11.29 '@swc/core-win32-x64-msvc': 1.11.29 + '@swc/helpers': 0.5.13 '@swc/core@1.6.13': dependencies: @@ -30054,6 +30533,10 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@syncpoint/wkx@0.5.2': + dependencies: + '@types/node': 15.14.9 + '@szmarczak/http-timer@4.0.6': dependencies: defer-to-connect: 2.0.1 @@ -31364,8 +31847,12 @@ snapshots: '@types/node@12.20.55': {} + '@types/node@13.13.52': {} + '@types/node@14.18.63': {} + '@types/node@15.14.9': {} + '@types/node@17.0.45': {} '@types/node@20.17.57': @@ -31536,7 +32023,7 @@ snapshots: dependencies: vue: 2.7.16 - '@types/webpack@5.28.5(webpack-cli@5.1.4(webpack@5.99.9))': + '@types/webpack@5.28.5(webpack-cli@5.1.4)': dependencies: '@types/node': 20.17.57 tapable: 2.2.2 @@ -32073,11 +32560,11 @@ snapshots: - vite optional: true - '@vitest/browser@3.2.4(playwright@1.52.0)(vite@6.3.5(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))(vitest@3.2.4)': + '@vitest/browser@3.2.4(playwright@1.52.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))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0)) + '@vitest/mocker': 3.2.4(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)) '@vitest/utils': 3.2.4 magic-string: 0.30.17 sirv: 3.0.1 @@ -32108,7 +32595,7 @@ snapshots: optionalDependencies: vite: 5.4.19(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))': + '@vitest/mocker@3.2.4(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: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 @@ -32272,7 +32759,7 @@ snapshots: vue: 3.4.21(typescript@5.9.2) vue-demi: 0.13.11(vue@3.4.21(typescript@5.9.2)) - '@vuetify/loader-shared@2.1.0(vue@3.4.21(typescript@5.9.2))(vuetify@3.6.8(typescript@5.9.2)(vite-plugin-vuetify@2.1.1)(vue@3.4.21(typescript@5.9.2)))': + '@vuetify/loader-shared@2.1.0(vue@3.4.21(typescript@5.9.2))(vuetify@3.6.8)': dependencies: upath: 2.0.1 vue: 3.4.21(typescript@5.9.2) @@ -32403,17 +32890,17 @@ snapshots: - vue-tsc - webpack-cli - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack@5.99.9))(webpack@5.99.9(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.99.9)': dependencies: webpack: 5.99.9(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.99.9) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack@5.99.9))(webpack@5.99.9(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.99.9)': dependencies: webpack: 5.99.9(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.99.9) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack@5.99.9))(webpack@5.99.9(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.99.9)': dependencies: webpack: 5.99.9(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.99.9) @@ -32426,10 +32913,10 @@ snapshots: optionalDependencies: expect: 29.7.0 - '@wix-pilot/detox@1.0.11(@wix-pilot/core@3.3.2(expect@29.7.0))(detox@20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))))(expect@29.7.0)': + '@wix-pilot/detox@1.0.11(@wix-pilot/core@3.3.2(expect@29.7.0))(detox@20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))))(expect@29.7.0)': dependencies: '@wix-pilot/core': 3.3.2(expect@29.7.0) - detox: 20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))) + detox: 20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))) expect: 29.7.0 '@xmldom/xmldom@0.7.13': {} @@ -32927,19 +33414,19 @@ snapshots: find-up: 5.0.0 webpack: 5.99.9(@swc/core@1.6.13) - babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: '@babel/core': 7.26.10 find-cache-dir: 4.0.0 schema-utils: 4.3.2 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) - babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.99.9(@swc/core@1.11.29)): + babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: '@babel/core': 7.26.10 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.99.9(@swc/core@1.6.13)): dependencies: @@ -32948,13 +33435,6 @@ snapshots: schema-utils: 4.3.2 webpack: 5.99.9(@swc/core@1.6.13) - babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.99.9): - dependencies: - '@babel/core': 7.26.10 - find-cache-dir: 4.0.0 - schema-utils: 4.3.2 - webpack: 5.99.9 - babel-plugin-dynamic-import-node@2.3.3: dependencies: object.assign: 4.1.7 @@ -33139,6 +33619,15 @@ snapshots: postcss: 8.5.4 postcss-media-query-parser: 0.2.3 + better-ajv-errors@1.2.0(ajv@8.17.1): + dependencies: + '@babel/code-frame': 7.27.1 + '@humanwhocodes/momoa': 2.0.4 + ajv: 8.17.1 + chalk: 4.1.2 + jsonpointer: 5.0.1 + leven: 3.1.0 + better-opn@3.0.2: dependencies: open: 8.4.2 @@ -33369,6 +33858,8 @@ snapshots: bson@6.10.3: {} + bson@6.10.4: {} + btoa@1.2.1: {} buffer-alloc-unsafe@1.1.0: {} @@ -34073,7 +34564,7 @@ snapshots: copy-text-to-clipboard@3.2.0: {} - copy-webpack-plugin@11.0.0(webpack@5.99.9(@swc/core@1.11.29)): + copy-webpack-plugin@11.0.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -34081,9 +34572,9 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) - copy-webpack-plugin@12.0.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + copy-webpack-plugin@12.0.2(webpack@5.98.0(@swc/core@1.11.29)): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -34112,6 +34603,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + corser@2.0.1: {} cosmiconfig@5.2.1: @@ -34204,13 +34700,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)): + create-jest@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)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + jest-config: 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-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -34219,13 +34715,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)): + create-jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest-config: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -34234,13 +34730,29 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): + create-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)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) + jest-config: 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)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + + create-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.9.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 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.9.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -34339,7 +34851,7 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 - css-loader@6.11.0(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.11.29)): + css-loader@6.11.0(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: icss-utils: 5.1.0(postcss@8.5.4) postcss: 8.5.4 @@ -34350,10 +34862,10 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - '@rspack/core': 1.3.13 - webpack: 5.99.9(@swc/core@1.11.29) + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) - css-loader@6.11.0(@rspack/core@1.3.13)(webpack@5.99.9): + css-loader@6.11.0(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.11.29)): dependencies: icss-utils: 5.1.0(postcss@8.5.4) postcss: 8.5.4 @@ -34364,10 +34876,10 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - '@rspack/core': 1.3.13 - webpack: 5.99.9 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + webpack: 5.99.9(@swc/core@1.11.29) - css-loader@7.1.2(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + css-loader@7.1.2(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: icss-utils: 5.1.0(postcss@8.5.4) postcss: 8.5.4 @@ -34378,7 +34890,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - '@rspack/core': 1.3.13 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) css-loader@7.1.2(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.6.13)): @@ -34392,10 +34904,10 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - '@rspack/core': 1.3.13 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) webpack: 5.99.9(@swc/core@1.6.13) - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(lightningcss@1.30.1)(webpack@5.99.9(@swc/core@1.11.29)): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(lightningcss@1.30.1)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: '@jridgewell/trace-mapping': 0.3.25 cssnano: 6.1.2(postcss@8.5.4) @@ -34403,7 +34915,7 @@ snapshots: postcss: 8.5.4 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) optionalDependencies: clean-css: 5.3.3 lightningcss: 1.30.1 @@ -34755,10 +35267,10 @@ snapshots: transitivePeerDependencies: - supports-color - detox@20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))): + detox@20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))): dependencies: '@wix-pilot/core': 3.3.2(expect@29.7.0) - '@wix-pilot/detox': 1.0.11(@wix-pilot/core@3.3.2(expect@29.7.0))(detox@20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))))(expect@29.7.0) + '@wix-pilot/detox': 1.0.11(@wix-pilot/core@3.3.2(expect@29.7.0))(detox@20.39.0(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(expect@29.7.0)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))))(expect@29.7.0) ajv: 8.17.1 bunyan: 1.8.15 bunyan-debug-stream: 3.1.1(bunyan@1.8.15) @@ -34770,7 +35282,7 @@ snapshots: funpermaproxy: 1.1.0 glob: 8.1.0 ini: 1.3.8 - jest-environment-emit: 1.0.8(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))) + jest-environment-emit: 1.0.8(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))) json-cycle: 1.5.0 lodash: 4.17.21 multi-sort-stream: 1.0.4 @@ -34795,7 +35307,7 @@ snapshots: yargs-parser: 21.1.1 yargs-unparser: 2.0.0 optionalDependencies: - jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) transitivePeerDependencies: - '@jest/environment' - '@jest/types' @@ -34928,11 +35440,12 @@ snapshots: dotenv@16.5.0: {} - drizzle-orm@0.35.3(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(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): + 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))(@opentelemetry/api@1.9.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 optionalDependencies: '@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) + '@opentelemetry/api': 1.9.0 '@types/better-sqlite3': 7.6.13 '@types/react': 19.1.6 '@types/sql.js': 1.4.9 @@ -35758,24 +36271,24 @@ snapshots: - supports-color - typescript - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.0.4))(eslint@8.57.1)(typescript@5.0.4) - jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0))(typescript@5.9.2): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(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.9.2)))(typescript@5.9.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) - jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.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.9.2)) transitivePeerDependencies: - supports-color - typescript @@ -36205,7 +36718,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(hml277kvlorqbj6gijmq6joh24): + expo-camera@16.0.18(55c6da9df988ca7f1678935d82e9238e): 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 @@ -36365,7 +36878,7 @@ snapshots: dependencies: invariant: 2.2.4 - expo-router@4.0.21(cpo3xaw6yrjernjvkkkt7bisia): + expo-router@4.0.21(b0bddf53ba1689b30337428eee4dc275): 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 @@ -36386,7 +36899,7 @@ snapshots: semver: 7.6.3 server-only: 0.0.1 optionalDependencies: - '@react-navigation/drawer': 7.4.1(nyxmcqdttlojx3ihgax6eihdpu) + '@react-navigation/drawer': 7.4.1(f2502081aada8c22c3fd2dbf46b9d114) 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' @@ -36395,7 +36908,7 @@ snapshots: - react-native - supports-color - expo-router@4.0.21(xdzi7taj2dri7edfzwov6a63va): + expo-router@4.0.21(e063c8109134fcdd1c97e4d6a4caf625): 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 @@ -36416,7 +36929,7 @@ snapshots: semver: 7.6.3 server-only: 0.0.1 optionalDependencies: - '@react-navigation/drawer': 7.4.1(j6abyuabi5plzpedpvxbnwhrsi) + '@react-navigation/drawer': 7.4.1(1d85788bd68a0e12619f848d71cbac62) 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' @@ -36458,7 +36971,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(l76mjoke3yk4s56nokhxoxxpou): + expo-system-ui@4.0.9(fa4ab2ddb2d13a20299c682fc53644db): dependencies: '@react-native/normalize-colors': 0.76.8 debug: 4.4.1(supports-color@8.1.1) @@ -36486,7 +36999,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(ka6rgkktlsuut5gotrymd2sdni) + '@expo/vector-icons': 14.1.0(99f35dc9d27b76831378288730881035) 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)) @@ -36522,7 +37035,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(wm3bvfp4qcetscjld4hplpimri) + '@expo/vector-icons': 14.1.0(a6850416216e8b64df60af23d5183c0b) 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)) @@ -36725,11 +37238,11 @@ snapshots: dependencies: flat-cache: 3.2.0 - file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29)): + file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) file-uri-to-path@1.0.0: {} @@ -37616,7 +38129,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)): + html-webpack-plugin@5.6.3(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -37624,11 +38137,10 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - '@rspack/core': 1.3.13 - webpack: 5.98.0(@swc/core@1.11.29) - optional: true + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) - html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.11.29)): + html-webpack-plugin@5.6.3(@rspack/core@1.3.13(@swc/helpers@0.5.13))(webpack@5.99.9): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -37636,10 +38148,10 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - '@rspack/core': 1.3.13 - webpack: 5.99.9(@swc/core@1.11.29) + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + webpack: 5.99.9(webpack-cli@5.1.4) - html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.99.9(webpack-cli@5.1.4)): + html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -37647,8 +38159,20 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - '@rspack/core': 1.3.13 - webpack: 5.99.9(webpack-cli@5.1.4) + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) + optional: true + + html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.99.9(@swc/core@1.11.29)): + dependencies: + '@types/html-minifier-terser': 6.1.0 + html-minifier-terser: 6.1.0 + lodash: 4.17.21 + pretty-error: 4.0.0 + tapable: 2.2.2 + optionalDependencies: + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) + webpack: 5.99.9(@swc/core@1.11.29) htmlparser2@10.0.0: dependencies: @@ -38309,6 +38833,11 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + ix@5.0.0: + dependencies: + '@types/node': 13.13.52 + tslib: 2.8.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -38372,16 +38901,35 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)): + jest-cli@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)): + dependencies: + '@jest/core': 29.7.0(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/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 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)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 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-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-cli@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + create-jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -38391,16 +38939,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)): + jest-cli@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)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + '@jest/core': 29.7.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)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + create-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)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest-config: 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)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -38409,17 +38957,18 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true - jest-cli@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): + jest-cli@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.9.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) + create-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.9.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) + jest-config: 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.9.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -38430,7 +38979,38 @@ snapshots: - ts-node optional: true - jest-config@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)): + jest-config@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)): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.57 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@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@22.15.29)(typescript@5.0.4)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -38456,12 +39036,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.57 - ts-node: 10.9.2(@types/node@20.17.57)(typescript@5.9.2) + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)): + jest-config@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@24.2.0)(typescript@5.5.4)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -38487,12 +39067,45 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.57 - ts-node: 10.9.2(@types/node@22.15.29)(typescript@5.0.4) + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4) transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true - jest-config@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)): + jest-config@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@24.2.0)(typescript@5.9.2)): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.57 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + + jest-config@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -38518,12 +39131,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.15.29 - ts-node: 10.9.2(@types/node@22.15.29)(typescript@5.0.4) + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0): + jest-config@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)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -38549,6 +39162,39 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 24.2.0 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + + jest-config@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.9.2)): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 24.2.0 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -38573,7 +39219,7 @@ snapshots: jest-util: 29.7.0 pretty-format: 29.7.0 - jest-environment-emit@1.0.8(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4))): + jest-environment-emit@1.0.8(@jest/environment@29.7.0)(@jest/types@29.6.3)(@types/bunyan@1.8.11)(jest-environment-jsdom@29.7.0)(jest-environment-node@29.7.0)(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4))): dependencies: bunyamin: 1.6.3(@types/bunyan@1.8.11)(bunyan@2.0.5) bunyan: 2.0.5 @@ -38586,7 +39232,7 @@ snapshots: optionalDependencies: '@jest/environment': 29.7.0 '@jest/types': 29.6.3 - jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) jest-environment-jsdom: 29.7.0 jest-environment-node: 29.7.0 transitivePeerDependencies: @@ -38616,7 +39262,7 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@52.0.6(hjrfme3xxu7xcbl6wzt3m2hgh4): + jest-expo@52.0.6(3635c191458c5fa90af52243d15b5fda): dependencies: '@expo/config': 10.0.11 '@expo/json-file': 9.1.4 @@ -38629,11 +39275,11 @@ snapshots: jest-environment-jsdom: 29.7.0 jest-snapshot: 29.7.0 jest-watch-select-projects: 2.0.0 - jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2))) + jest-watch-typeahead: 2.2.1(jest@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))) json5: 2.2.3 lodash: 4.17.21 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-server-dom-webpack: 19.0.0-rc-6230622a1a-20240610(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.99.9) + react-server-dom-webpack: 19.0.0-rc-6230622a1a-20240610(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.99.9(@swc/core@1.11.29)) react-test-renderer: 18.3.1(react@18.3.1) server-only: 0.0.1 stacktrace-js: 2.0.2 @@ -38835,11 +39481,11 @@ snapshots: chalk: 3.0.0 prompts: 2.4.2 - jest-watch-typeahead@2.2.1(jest@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2))): + jest-watch-typeahead@2.2.1(jest@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))): dependencies: ansi-escapes: 6.2.1 chalk: 4.1.2 - jest: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + jest: 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-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -38870,36 +39516,49 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)): + jest@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)): + dependencies: + '@jest/core': 29.7.0(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/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 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)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.57)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + jest-cli: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)): + 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)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + '@jest/core': 29.7.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)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest-cli: 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)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node + optional: true - jest@29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.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.9.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@24.2.0)(babel-plugin-macros@3.1.0) + jest-cli: 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.9.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -38937,6 +39596,8 @@ snapshots: join-component@1.1.0: {} + jose@4.15.9: {} + js-base64@3.7.2: {} js-base64@3.7.7: {} @@ -39243,11 +39904,11 @@ snapshots: dependencies: readable-stream: 2.3.8 - less-loader@12.2.0(@rspack/core@1.3.13)(less@4.2.2)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + less-loader@12.2.0(@rspack/core@1.3.13)(less@4.2.2)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: less: 4.2.2 optionalDependencies: - '@rspack/core': 1.3.13 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) less@4.2.2: @@ -39277,7 +39938,7 @@ snapshots: dependencies: isomorphic.js: 0.2.5 - license-webpack-plugin@4.0.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + license-webpack-plugin@4.0.2(webpack@5.98.0(@swc/core@1.11.29)): dependencies: webpack-sources: 3.3.0 optionalDependencies: @@ -39601,12 +40262,16 @@ snapshots: long-timeout@0.1.1: {} + long@5.3.2: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + lossless-json@2.0.11: {} + loupe@3.1.4: {} lower-case@2.0.2: @@ -41011,17 +41676,17 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + mini-css-extract-plugin@2.9.2(webpack@5.98.0(@swc/core@1.11.29)): dependencies: schema-utils: 4.3.2 tapable: 2.2.2 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) - mini-css-extract-plugin@2.9.2(webpack@5.99.9(@swc/core@1.11.29)): + mini-css-extract-plugin@2.9.2(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: schema-utils: 4.3.2 tapable: 2.2.2 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) mini-css-extract-plugin@2.9.2(webpack@5.99.9(@swc/core@1.6.13)): dependencies: @@ -41295,7 +41960,7 @@ snapshots: nested-error-stacks@2.0.1: {} - next@14.2.3(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1): + next@14.2.3(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1): dependencies: '@next/env': 14.2.3 '@swc/helpers': 0.5.5 @@ -41316,6 +41981,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.3 '@next/swc-win32-ia32-msvc': 14.2.3 '@next/swc-win32-x64-msvc': 14.2.3 + '@opentelemetry/api': 1.9.0 sass: 1.89.1 transitivePeerDependencies: - '@babel/core' @@ -41569,11 +42235,11 @@ snapshots: dependencies: boolbase: 1.0.0 - null-loader@4.0.1(webpack@5.99.9(@swc/core@1.11.29)): + null-loader@4.0.1(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) nullthrows@1.1.1: {} @@ -42062,6 +42728,11 @@ snapshots: performant-array-to-tree@1.11.0: {} + pgsql-ast-parser@11.2.0: + dependencies: + moo: 0.5.2 + nearley: 2.20.1 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -42286,32 +42957,41 @@ snapshots: '@csstools/utilities': 2.0.0(postcss@8.5.4) postcss: 8.5.4 - postcss-load-config@4.0.2(postcss@8.5.4)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)): + postcss-load-config@4.0.2(postcss@8.5.4)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/node@20.17.57)(typescript@5.9.2)): + dependencies: + lilconfig: 3.1.3 + yaml: 2.8.0 + optionalDependencies: + postcss: 8.5.4 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.9.2) + + postcss-load-config@4.0.2(postcss@8.5.4)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)): dependencies: lilconfig: 3.1.3 yaml: 2.8.0 optionalDependencies: postcss: 8.5.4 - ts-node: 10.9.2(@types/node@20.17.57)(typescript@5.9.2) + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4) + optional: true - postcss-loader@7.3.4(postcss@8.5.4)(typescript@5.9.2)(webpack@5.99.9(@swc/core@1.11.29)): + postcss-loader@7.3.4(postcss@8.5.4)(typescript@5.9.2)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: cosmiconfig: 8.3.6(typescript@5.9.2) jiti: 1.21.7 postcss: 8.5.4 semver: 7.7.2 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) transitivePeerDependencies: - typescript - postcss-loader@8.1.1(@rspack/core@1.3.13)(postcss@8.5.2)(typescript@5.5.4)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + postcss-loader@8.1.1(@rspack/core@1.3.13)(postcss@8.5.2)(typescript@5.5.4)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: cosmiconfig: 9.0.0(typescript@5.5.4) jiti: 1.21.7 postcss: 8.5.2 semver: 7.7.2 optionalDependencies: - '@rspack/core': 1.3.13 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) transitivePeerDependencies: - typescript @@ -42890,6 +43570,21 @@ snapshots: proto-list@1.2.4: {} + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.17.57 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -43078,11 +43773,11 @@ snapshots: dependencies: react: 18.3.1 - react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.99.9(@swc/core@1.11.29)): + react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: '@babel/runtime': 7.27.6 react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) react-native-builder-bob@0.30.3(typescript@5.9.2): dependencies: @@ -43748,7 +44443,7 @@ snapshots: - supports-color - utf-8-validate - react-navigation-stack@2.10.4(4a23q4g4mav7ddr6jlhxnyzzo4): + react-navigation-stack@2.10.4(1b7f2cbbd098c1646b3c5f57acc57915): 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 @@ -43834,13 +44529,13 @@ snapshots: '@remix-run/router': 1.23.0 react: 18.3.1 - react-server-dom-webpack@19.0.0-rc-6230622a1a-20240610(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.99.9): + react-server-dom-webpack@19.0.0-rc-6230622a1a-20240610(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.99.9(@swc/core@1.11.29)): dependencies: acorn-loose: 8.5.0 neo-async: 2.6.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - webpack: 5.99.9 + webpack: 5.99.9(@swc/core@1.11.29) react-shallow-renderer@16.15.0(react@18.3.1): dependencies: @@ -44547,18 +45242,18 @@ snapshots: dependencies: truncate-utf8-bytes: 1.0.2 - sass-loader@13.3.3(sass@1.89.1)(webpack@5.99.9): + sass-loader@13.3.3(sass@1.89.1)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: neo-async: 2.6.2 - webpack: 5.99.9 + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) optionalDependencies: sass: 1.89.1 - sass-loader@16.0.5(@rspack/core@1.3.13)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + sass-loader@16.0.5(@rspack/core@1.3.13)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: neo-async: 2.6.2 optionalDependencies: - '@rspack/core': 1.3.13 + '@rspack/core': 1.3.13(@swc/helpers@0.5.13) sass: 1.85.0 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) @@ -44994,13 +45689,13 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + source-map-loader@5.0.0(webpack@5.98.0(@swc/core@1.11.29)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) - source-map-loader@5.0.0(webpack@5.99.9(webpack-cli@5.1.4)): + source-map-loader@5.0.0(webpack@5.99.9): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 @@ -45343,6 +46038,10 @@ snapshots: structured-headers@0.4.1: {} + style-loader@3.3.4(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): + dependencies: + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) + style-loader@3.3.4(webpack@5.99.9(@swc/core@1.11.29)): dependencies: webpack: 5.99.9(@swc/core@1.11.29) @@ -45351,10 +46050,6 @@ snapshots: dependencies: webpack: 5.99.9(@swc/core@1.6.13) - style-loader@3.3.4(webpack@5.99.9): - dependencies: - webpack: 5.99.9 - style-to-js@1.1.16: dependencies: style-to-object: 1.0.8 @@ -45470,11 +46165,11 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swc-loader@0.2.6(@swc/core@1.11.29)(webpack@5.99.9(@swc/core@1.11.29)): + swc-loader@0.2.6(@swc/core@1.11.29(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) '@swc/counter': 0.1.3 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) swiftlint@2.0.0(typescript@5.9.2): dependencies: @@ -45495,7 +46190,7 @@ snapshots: tabbable@6.2.0: {} - tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)): + tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/node@20.17.57)(typescript@5.9.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -45514,7 +46209,7 @@ snapshots: postcss: 8.5.4 postcss-import: 15.1.0(postcss@8.5.4) postcss-js: 4.0.1(postcss@8.5.4) - postcss-load-config: 4.0.2(postcss@8.5.4)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2)) + postcss-load-config: 4.0.2(postcss@8.5.4)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.13))(@types/node@20.17.57)(typescript@5.9.2)) postcss-nested: 6.2.0(postcss@8.5.4) postcss-selector-parser: 6.1.2 resolve: 1.22.10 @@ -45522,6 +46217,34 @@ snapshots: transitivePeerDependencies: - ts-node + tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.4 + postcss-import: 15.1.0(postcss@8.5.4) + postcss-js: 4.0.1(postcss@8.5.4) + postcss-load-config: 4.0.2(postcss@8.5.4)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4)) + postcss-nested: 6.2.0(postcss@8.5.4) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + optional: true + tamagui@1.79.6(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react-native-web@0.19.13(encoding@0.1.13)(react-dom@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))(react@18.3.1): dependencies: '@tamagui/accordion': 1.79.6(react@18.3.1) @@ -45678,29 +46401,28 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.3.14(@swc/core@1.11.29)(esbuild@0.25.4)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + terser-webpack-plugin@5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.40.0 - webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) optionalDependencies: - '@swc/core': 1.11.29 - esbuild: 0.25.4 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) - terser-webpack-plugin@5.3.14(@swc/core@1.11.29)(webpack@5.98.0(@swc/core@1.11.29)): + terser-webpack-plugin@5.3.14(@swc/core@1.11.29)(esbuild@0.25.4)(webpack@5.98.0(@swc/core@1.11.29)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.40.0 - webpack: 5.98.0(@swc/core@1.11.29) + webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) optionalDependencies: - '@swc/core': 1.11.29 - optional: true + '@swc/core': 1.11.29(@swc/helpers@0.5.13) + esbuild: 0.25.4 terser-webpack-plugin@5.3.14(@swc/core@1.11.29)(webpack@5.99.9(@swc/core@1.11.29)): dependencies: @@ -45711,7 +46433,7 @@ snapshots: terser: 5.40.0 webpack: 5.99.9(@swc/core@1.11.29) optionalDependencies: - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) terser-webpack-plugin@5.3.14(@swc/core@1.6.13)(webpack@5.99.9(@swc/core@1.6.13)): dependencies: @@ -45724,15 +46446,6 @@ snapshots: optionalDependencies: '@swc/core': 1.6.13 - terser-webpack-plugin@5.3.14(webpack@5.99.9(webpack-cli@5.1.4)): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 4.3.2 - serialize-javascript: 6.0.2 - terser: 5.40.0 - webpack: 5.99.9(webpack-cli@5.1.4) - terser-webpack-plugin@5.3.14(webpack@5.99.9): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -45740,7 +46453,7 @@ snapshots: schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.40.0 - webpack: 5.99.9 + webpack: 5.99.9(webpack-cli@5.1.4) terser@5.39.0: dependencies: @@ -45907,14 +46620,16 @@ snapshots: dependencies: typescript: 5.9.2 + ts-codec@1.3.0: {} + ts-interface-checker@0.1.13: {} - ts-jest@29.3.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4): + ts-jest@29.3.4(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)))(typescript@5.0.4): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4)) + jest: 29.7.0(@types/node@22.15.29)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -45939,47 +46654,49 @@ snapshots: typescript: 5.9.2 webpack: 5.99.9(@swc/core@1.11.29) - ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.3.3): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.2.0 + '@types/node': 20.17.57 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.9.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) + optional: true - ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.0.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.2.0 + '@types/node': 22.15.29 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.4 + typescript: 5.0.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) + optional: true - ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -45993,40 +46710,40 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.9.2 + typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) - ts-node@10.9.2(@swc/core@1.6.13)(@types/node@20.17.57)(typescript@4.5.5): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.57 + '@types/node': 24.2.0 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 4.5.5 + typescript: 5.5.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.6.13 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) - ts-node@10.9.2(@types/node@20.17.57)(typescript@5.9.2): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@24.2.0)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.57 + '@types/node': 24.2.0 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -46036,26 +46753,28 @@ snapshots: typescript: 5.9.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optional: true + optionalDependencies: + '@swc/core': 1.11.29(@swc/helpers@0.5.13) - ts-node@10.9.2(@types/node@22.15.29)(typescript@5.0.4): + ts-node@10.9.2(@swc/core@1.6.13)(@types/node@20.17.57)(typescript@4.5.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.15.29 + '@types/node': 20.17.57 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.0.4 + typescript: 4.5.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optional: true + optionalDependencies: + '@swc/core': 1.6.13 ts-object-utils@0.0.5: {} @@ -46496,14 +47215,14 @@ snapshots: url-join@4.0.1: {} - url-loader@4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29)))(webpack@5.99.9(@swc/core@1.11.29)): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) optionalDependencies: - file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29)) + file-loader: 6.2.0(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) url-parse@1.5.10: dependencies: @@ -46677,7 +47396,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@2.79.2)(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: '@rollup/plugin-virtual': 3.0.2(rollup@2.79.2) - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) uuid: 10.0.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) transitivePeerDependencies: @@ -46687,7 +47406,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.41.1)(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: '@rollup/plugin-virtual': 3.0.2(rollup@4.41.1) - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) uuid: 10.0.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) transitivePeerDependencies: @@ -46697,7 +47416,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.41.1)(vite@5.4.19(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0)): dependencies: '@rollup/plugin-virtual': 3.0.2(rollup@4.41.1) - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) uuid: 10.0.0 vite: 5.4.19(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) transitivePeerDependencies: @@ -46707,7 +47426,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.41.1)(vite@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)): dependencies: '@rollup/plugin-virtual': 3.0.2(rollup@4.41.1) - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) uuid: 10.0.0 vite: 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) transitivePeerDependencies: @@ -46717,7 +47436,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.41.1)(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: '@rollup/plugin-virtual': 3.0.2(rollup@4.41.1) - '@swc/core': 1.11.29 + '@swc/core': 1.11.29(@swc/helpers@0.5.13) uuid: 10.0.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) transitivePeerDependencies: @@ -46726,7 +47445,7 @@ snapshots: vite-plugin-vuetify@2.1.1(vite@5.4.19(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))(vue@3.4.21(typescript@5.9.2))(vuetify@3.6.8): dependencies: - '@vuetify/loader-shared': 2.1.0(vue@3.4.21(typescript@5.9.2))(vuetify@3.6.8(typescript@5.9.2)(vite-plugin-vuetify@2.1.1)(vue@3.4.21(typescript@5.9.2))) + '@vuetify/loader-shared': 2.1.0(vue@3.4.21(typescript@5.9.2))(vuetify@3.6.8) debug: 4.4.1(supports-color@8.1.1) upath: 2.0.1 vite: 5.4.19(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) @@ -47013,9 +47732,9 @@ snapshots: webpack-cli@5.1.4(webpack@5.99.9): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack@5.99.9))(webpack@5.99.9(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack@5.99.9))(webpack@5.99.9(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack@5.99.9))(webpack@5.99.9(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.99.9) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.99.9) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.99.9) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -47027,6 +47746,15 @@ snapshots: webpack: 5.99.9(webpack-cli@5.1.4) webpack-merge: 5.10.0 + webpack-dev-middleware@5.3.4(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.3.2 + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) + webpack-dev-middleware@5.3.4(webpack@5.99.9(@swc/core@1.11.29)): dependencies: colorette: 2.0.20 @@ -47036,7 +47764,7 @@ snapshots: schema-utils: 4.3.2 webpack: 5.99.9(@swc/core@1.11.29) - webpack-dev-middleware@7.4.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + webpack-dev-middleware@7.4.2(webpack@5.98.0(@swc/core@1.11.29)): dependencies: colorette: 2.0.20 memfs: 4.17.2 @@ -47087,7 +47815,47 @@ snapshots: - supports-color - utf-8-validate - webpack-dev-server@5.2.0(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + webpack-dev-server@4.15.2(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.22 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.7 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.1 + ansi-html-community: 0.0.8 + bonjour-service: 1.3.0 + chokidar: 3.6.0 + colorette: 2.0.20 + compression: 1.8.0 + connect-history-api-fallback: 2.0.0 + default-gateway: 6.0.3 + express: 4.21.2 + graceful-fs: 4.2.11 + html-entities: 2.6.0 + http-proxy-middleware: 2.0.9(@types/express@4.17.22)(debug@4.4.1) + ipaddr.js: 2.2.0 + launch-editor: 2.10.0 + open: 8.4.2 + p-retry: 4.6.2 + rimraf: 3.0.2 + schema-utils: 4.3.2 + selfsigned: 2.4.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 5.3.4(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) + ws: 8.18.2 + optionalDependencies: + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + webpack-dev-server@5.2.0(webpack@5.98.0(@swc/core@1.11.29)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -47114,7 +47882,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.11.29)) ws: 8.18.2 optionalDependencies: webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) @@ -47140,7 +47908,7 @@ snapshots: webpack-sources@3.3.0: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(@rspack/core@1.3.13)(webpack@5.98.0(@swc/core@1.11.29)))(webpack@5.98.0(@swc/core@1.11.29)): dependencies: typed-assert: 1.0.9 webpack: 5.98.0(@swc/core@1.11.29)(esbuild@0.25.4) @@ -47149,37 +47917,6 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.98.0(@swc/core@1.11.29): - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.7 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.1 - browserslist: 4.25.0 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.1 - es-module-lexer: 1.7.0 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 4.3.2 - tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.11.29)(webpack@5.98.0(@swc/core@1.11.29)) - watchpack: 2.4.4 - webpack-sources: 3.3.0 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - optional: true - webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4): dependencies: '@types/eslint-scope': 3.7.7 @@ -47202,7 +47939,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.11.29)(esbuild@0.25.4)(webpack@5.98.0(@swc/core@1.11.29)(esbuild@0.25.4)) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.29)(esbuild@0.25.4)(webpack@5.98.0(@swc/core@1.11.29)) watchpack: 2.4.4 webpack-sources: 3.3.0 transitivePeerDependencies: @@ -47210,7 +47947,7 @@ snapshots: - esbuild - uglify-js - webpack@5.99.9: + webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.7 @@ -47233,7 +47970,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(webpack@5.99.9) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.13))(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))) watchpack: 2.4.4 webpack-sources: 3.3.0 transitivePeerDependencies: @@ -47326,7 +48063,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(webpack@5.99.9(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.14(webpack@5.99.9) watchpack: 2.4.4 webpack-sources: 3.3.0 optionalDependencies: @@ -47336,7 +48073,7 @@ snapshots: - esbuild - uglify-js - webpackbar@6.0.1(webpack@5.99.9(@swc/core@1.11.29)): + webpackbar@6.0.1(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -47345,7 +48082,7 @@ snapshots: markdown-table: 2.0.0 pretty-time: 1.1.0 std-env: 3.9.0 - webpack: 5.99.9(@swc/core@1.11.29) + webpack: 5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13)) wrap-ansi: 7.0.0 websocket-driver@0.7.4: From 06abe83848290c0329385f39573daf43c90eabb4 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 09:15:55 +0200 Subject: [PATCH 02/13] wip: powersync lite --- packages/core/CHANGELOG.md | 1 + packages/core/README.md | 1 + packages/core/package.json | 55 +++ packages/core/src/client/BucketStorage.ts | 46 +++ packages/core/src/client/BucketStorageImpl.ts | 342 ++++++++++++++++++ packages/core/src/client/SyncClient.ts | 275 ++++++++++++++ .../core/src/client/SystemDependencies.ts | 26 ++ packages/core/src/client/bucketHelpers.ts | 25 ++ packages/core/src/client/buckets/index.ts | 15 + .../core/src/client/buckets/ps_buckets.ts | 50 +++ packages/core/src/client/buckets/ps_crud.ts | 20 + packages/core/src/client/buckets/ps_kv.ts | 18 + packages/core/src/client/buckets/ps_oplog.ts | 41 +++ .../core/src/client/buckets/ps_sync_state.ts | 18 + packages/core/src/client/buckets/ps_tx.ts | 19 + .../core/src/client/buckets/ps_untyped.ts | 22 ++ .../src/client/buckets/ps_updated_rows.ts | 20 + packages/core/src/client/checksumUtils.ts | 57 +++ packages/core/src/client/ndjson.ts | 70 ++++ packages/core/src/client/open-stream.ts | 41 +++ packages/core/src/index.ts | 0 packages/core/tests/powersync-lite.test.ts | 42 +++ packages/core/tsconfig.json | 19 + 23 files changed, 1223 insertions(+) create mode 100644 packages/core/CHANGELOG.md create mode 100644 packages/core/README.md create mode 100644 packages/core/package.json create mode 100644 packages/core/src/client/BucketStorage.ts create mode 100644 packages/core/src/client/BucketStorageImpl.ts create mode 100644 packages/core/src/client/SyncClient.ts create mode 100644 packages/core/src/client/SystemDependencies.ts create mode 100644 packages/core/src/client/bucketHelpers.ts create mode 100644 packages/core/src/client/buckets/index.ts create mode 100644 packages/core/src/client/buckets/ps_buckets.ts create mode 100644 packages/core/src/client/buckets/ps_crud.ts create mode 100644 packages/core/src/client/buckets/ps_kv.ts create mode 100644 packages/core/src/client/buckets/ps_oplog.ts create mode 100644 packages/core/src/client/buckets/ps_sync_state.ts create mode 100644 packages/core/src/client/buckets/ps_tx.ts create mode 100644 packages/core/src/client/buckets/ps_untyped.ts create mode 100644 packages/core/src/client/buckets/ps_updated_rows.ts create mode 100644 packages/core/src/client/checksumUtils.ts create mode 100644 packages/core/src/client/ndjson.ts create mode 100644 packages/core/src/client/open-stream.ts create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/tests/powersync-lite.test.ts create mode 100644 packages/core/tsconfig.json diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md new file mode 100644 index 000000000..64ede953d --- /dev/null +++ b/packages/core/CHANGELOG.md @@ -0,0 +1 @@ +# @powersync/core diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000..37605754b --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1 @@ +# PowerSync SDK Core diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..e7cdf4a21 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,55 @@ +{ + "name": "@powersync/core", + "version": "0.0.0", + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "description": "API definitions for JourneyApps PowerSync", + "type": "module", + "main": "dist/bundle.mjs", + "module": "dist/bundle.mjs", + "types": "lib/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.ts", + "default": "./dist/bundle.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "require": "./dist/bundle.cjs" + } + } + }, + "author": "POWERSYNC", + "license": "Apache-2.0", + "files": [ + "lib", + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/powersync-ja/powersync-js.git" + }, + "bugs": { + "url": "https://github.com/powersync-ja/powersync-js/issues" + }, + "homepage": "https://docs.powersync.com", + "scripts": { + "build": "tsc -b", + "build:prod": "tsc -b", + "clean": "rm -rf lib dist tsconfig.tsbuildinfo", + "test": "vitest", + "test:exports": "attw --pack ." + }, + "dependencies": { + "ts-codec": "^1.3.0" + }, + "devDependencies": { + "@types/node": "^20.5.9", + "@powersync/service-jsonbig": "^0.17.11", + "@powersync/service-sync-rules": "^0.29.5", + "@powersync/service-core": "^1.15.8" + } +} diff --git a/packages/core/src/client/BucketStorage.ts b/packages/core/src/client/BucketStorage.ts new file mode 100644 index 000000000..71822b3d3 --- /dev/null +++ b/packages/core/src/client/BucketStorage.ts @@ -0,0 +1,46 @@ +import type { BucketState, Checkpoint, ProtocolOplogData, SyncBucketData } from '@powersync/service-core'; + +export type SyncDataBatch = { + buckets: Array>; +}; + +export type SavedProgress = { + atLast: number; + sinceLast: number; +}; + +export type BucketOperationProgress = Record; + +export interface BucketStorage { + init: () => Promise; + + saveSyncData: (batch: SyncDataBatch) => Promise; + + removeBuckets: (buckets: Array) => Promise; + + setTargetCheckpoint: (checkpoint: Checkpoint) => Promise; + + getBucketStates: () => Promise>; + + getBucketOperationProgress: () => Promise; + + syncLocalDatabase: ( + checkpoint: Checkpoint, + priority?: number + ) => Promise<{ + checkpointValid: boolean; + ready: boolean; + failures?: Array; + }>; + + hasCompletedSync: () => Promise; + + updateLocalTarget: (cb: () => Promise) => Promise; + + getMaxOpId: () => string; + + /** + * Get an unique client id. + */ + getClientId: () => Promise; +} diff --git a/packages/core/src/client/BucketStorageImpl.ts b/packages/core/src/client/BucketStorageImpl.ts new file mode 100644 index 000000000..cc55785cd --- /dev/null +++ b/packages/core/src/client/BucketStorageImpl.ts @@ -0,0 +1,342 @@ +import type { BucketState, Checkpoint } from '@powersync/service-core'; +import type { BucketOperationProgress, BucketStorage, SyncDataBatch } from './BucketStorage.js'; +import { constructKey, toStringOrNull } from './bucketHelpers.js'; +import type { PSBucket } from './buckets/ps_buckets.js'; +import type { PSCrud } from './buckets/ps_crud.js'; +import type { PSKeyValue } from './buckets/ps_kv.js'; +import type { PSOplog } from './buckets/ps_oplog.js'; +import type { PSTx } from './buckets/ps_tx.js'; +import type { PSUntyped } from './buckets/ps_untyped.js'; +import { addChecksums, normalizeChecksum, subtractChecksums } from './checksumUtils.js'; + +export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR'; + +export const MAX_OP_ID = '9223372036854775807'; + +export class BucketStorageImpl implements BucketStorage { + protected ps_buckets: PSBucket[]; + protected ps_oplog: PSOplog[]; + protected ps_updated_rows: PSUntyped[]; + // TODO: ps_crud implementation - ignoring for now + // ps_crud tracks client-side changes that need to be uploaded to the server + protected ps_crud: PSCrud[]; + protected ps_kv: PSKeyValue[]; + protected clientId: string; + + protected ps_tx: PSTx; + + // Track sequence/counter for ps_crud (simulates SQLite sequence) + // TODO: This should be properly managed when ps_crud is implemented + protected ps_crud_seq: number = 0; + + constructor() { + this.ps_buckets = []; + this.ps_oplog = []; + this.ps_tx = { + current_tx: null, + next_tx: null + }; + this.ps_updated_rows = []; + this.ps_crud = []; + this.ps_kv = []; + this.clientId = 'TODO'; + } + + async init(): Promise {} + + getMaxOpId(): string { + return MAX_OP_ID; + } + + async getClientId(): Promise { + return this.clientId; + } + + async getBucketStates(): Promise> { + return this.ps_buckets.map((bucket) => ({ + bucket: bucket.name, + op_id: bucket.last_op.toString(), + target_op_id: bucket.target_op.toString(), + add_checksum: bucket.add_checksum.toString(), + op_checksum: bucket.op_checksum.toString(), + pending_delete: bucket.pending_delete + })); + } + + async hasCompletedSync(): Promise { + throw new Error('Method not implemented.'); + } + + async updateLocalTarget(cb: () => Promise): Promise { + // Find the '$local' bucket and check if target_op = MAX_OP_ID + // SQL: SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER) + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (!localBucket) { + // Nothing to update + return false; + } + + const maxOpIdBigint = BigInt(MAX_OP_ID); + if (localBucket.target_op !== maxOpIdBigint) { + // target_op is not MAX_OP_ID, nothing to update + return false; + } + + // TODO: ps_crud sequence tracking - proper implementation needed + // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' + // In SQLite, sequences exist independently of table data (auto-increment tracking) + // For now, we check if ps_crud has any data as a proxy for sequence existence + // Proper implementation would track sequence counter separately + if (this.ps_crud.length === 0 && this.ps_crud_seq === 0) { + // No crud data/sequence, nothing to update + return false; + } + + // Save current sequence state before calling callback + // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' + const seqBefore = this.ps_crud_seq; + + // Call the callback to get the new opId + const opId = await cb(); + + // Within transaction equivalent (in memory, we do checks atomically): + // 1. Check if ps_crud has any data (SELECT 1 FROM ps_crud LIMIT 1) + // If it has data, new data was uploaded since write checkpoint - need new write checkpoint + if (this.ps_crud.length > 0) { + // Still has crud data, can't update checkpoint + return false; + } + + // 2. Verify sequence hasn't changed (SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud') + // If sequence changed, new items were added even if later deleted + if (this.ps_crud_seq !== seqBefore) { + // New crud data may have been uploaded since we got the checkpoint. Abort. + return false; + } + + // Verify sequence exists (should not be empty) + // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' + if (this.ps_crud_seq === 0 && seqBefore === 0) { + // This shouldn't happen if we got past the first check, but defensive + throw new Error('SQLite Sequence should not be empty'); + } + + // Update the '$local' bucket's target_op to the new opId + // SQL: UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local' + const opIdBigint = normalizeChecksum(opId); + localBucket.target_op = opIdBigint; + + return true; + } + + async getBucketOperationProgress(): Promise { + // TODO + return {}; + } + + async removeBuckets(buckets: Array): Promise { + for (const bucketName of buckets) { + // Find bucket by name and get its id + const bucketIndex = this.ps_buckets.findIndex((b) => b.name === bucketName); + if (bucketIndex === -1) { + // Bucket doesn't exist, nothing to do + continue; + } + + const bucket = this.ps_buckets[bucketIndex]; + const bucketId = bucket.id; + + // Add all rows from ps_oplog for this bucket to ps_updated_rows + // (INSERT OR IGNORE logic - only add if not already exists) + const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); + for (const op of bucketOps) { + if (op.row_type && op.row_id) { + const exists = this.ps_updated_rows.some((row) => row.type === op.row_type && row.id === op.row_id); + if (!exists) { + this.ps_updated_rows.push({ + type: op.row_type, + id: op.row_id, + data: null + }); + } + } + } + + // Delete all rows from ps_oplog for this bucket + this.ps_oplog = this.ps_oplog.filter((op) => op.bucket !== bucketId); + + // Delete the bucket from ps_buckets + this.ps_buckets.splice(bucketIndex, 1); + } + } + + /** + * Helper to find or create a bucket by name + */ + protected findOrCreateBucket(bucketName: string): PSBucket { + let bucket = this.ps_buckets.find((b) => b.name === bucketName); + if (!bucket) { + const nextId = this.ps_buckets.length > 0 ? Math.max(...this.ps_buckets.map((b) => b.id)) + 1 : 1; + bucket = { + id: nextId, + name: bucketName, + last_applied_op: 0n, + last_op: 0n, + target_op: 0n, + add_checksum: 0n, + op_checksum: 0n, + pending_delete: false, + count_at_last: 0, + count_since_last: 0 + }; + this.ps_buckets.push(bucket); + } + return bucket; + } + + async saveSyncData(batch: SyncDataBatch): Promise { + for (const bucketData of batch.buckets) { + const bucketName = bucketData.bucket; + const bucket = this.findOrCreateBucket(bucketName); + const bucketId = bucket.id; + const lastAppliedOp = bucket.last_applied_op; + + // Optimization for initial sync - we can avoid persisting individual REMOVE + // operations when last_applied_op = 0 + let isEmpty = lastAppliedOp === 0n; + + let lastOp: bigint | null = null; + let addChecksum = 0n; + let opChecksum = 0n; // Start at 0, will be added to bucket's op_checksum at the end + let addedOps = 0; + + for (const line of bucketData.data) { + // line might be OplogEntry or ProtocolOplogData - access properties directly + const opId = normalizeChecksum(line.op_id); + const opType = line.op as OpType; + const objectType = line.object_type; + const objectId = line.object_id; + const subkey = line.subkey; + const checksum = normalizeChecksum(line.checksum); + const opData = toStringOrNull(line.data); + + lastOp = opId; + addedOps += 1; + + if (opType === 'PUT' || opType === 'REMOVE') { + const key = constructKey(objectType, objectId, subkey); + + // Supersede (delete) previous operations with the same key + const supersededOps: PSOplog[] = []; + const remainingOps: PSOplog[] = []; + + for (const oplog of this.ps_oplog) { + if (oplog.bucket === bucketId && oplog.key === key) { + supersededOps.push(oplog); + // Add superseded checksum to add_checksum + addChecksum = addChecksums(addChecksum, oplog.hash); + // Subtract superseded checksum from op_checksum + opChecksum = subtractChecksums(opChecksum, oplog.hash); + } else { + remainingOps.push(oplog); + } + } + + // Update ps_oplog by removing superseded operations + this.ps_oplog = remainingOps; + + // Check if we superseded an operation (only skip if bucket was empty) + const superseded = supersededOps.length > 0 && !isEmpty; + + if (opType === 'REMOVE') { + const shouldSkipRemove = !superseded; + + addChecksum = addChecksums(addChecksum, checksum); + + if (!shouldSkipRemove) { + if (objectType && objectId) { + // Insert into ps_updated_rows (or ignore if already exists) + const exists = this.ps_updated_rows.some((row) => row.type === objectType && row.id === objectId); + if (!exists) { + this.ps_updated_rows.push({ + type: objectType, + id: objectId, + data: null + }); + } + } + } + continue; + } + + // Handle PUT operation + const newOplog: PSOplog = { + bucket: bucketId, + op_id: opId, + key: key || null, + row_type: objectType || null, + row_id: objectId || null, + data: opData, + hash: checksum // Already normalized to bigint + }; + + this.ps_oplog.push(newOplog); + opChecksum = addChecksums(opChecksum, checksum); + } else if (opType === 'MOVE') { + addChecksum = addChecksums(addChecksum, checksum); + } else if (opType === 'CLEAR') { + // Any remaining PUT operations should get an implicit REMOVE + // Add all rows from ps_oplog to ps_updated_rows for this bucket + const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); + for (const op of bucketOps) { + if (op.row_type && op.row_id) { + const exists = this.ps_updated_rows.some((row) => row.type === op.row_type && row.id === op.row_id); + if (!exists) { + this.ps_updated_rows.push({ + type: op.row_type, + id: op.row_id, + data: null + }); + } + } + } + + // Delete all ops from ps_oplog for this bucket + this.ps_oplog = this.ps_oplog.filter((op) => op.bucket !== bucketId); + + // Update bucket: set last_applied_op = 0, replace add_checksum with CLEAR op checksum, reset op_checksum + bucket.last_applied_op = 0n; + // Store checksum as-is (preserve full 64-bit value from SQLite) + // The checksum comes from the operation, which is already in correct range + bucket.add_checksum = checksum; + bucket.op_checksum = 0n; + + addChecksum = 0n; + isEmpty = true; + opChecksum = 0n; + } + } + + // Update bucket state if we processed any operations + if (lastOp !== null) { + bucket.last_op = lastOp; + // addChecksums() handles 32-bit unsigned wrapping and returns the result + // We store as full bigint value (SQLite can store 64-bit INTEGERs) + bucket.add_checksum = addChecksums(bucket.add_checksum, addChecksum); + bucket.op_checksum = addChecksums(bucket.op_checksum, opChecksum); + bucket.count_since_last += addedOps; + } + } + } + + async syncLocalDatabase( + checkpoint: Checkpoint, + priority?: number + ): Promise<{ checkpointValid: boolean; ready: boolean; failures?: Array }> { + throw new Error('Method not implemented.'); + } + + async setTargetCheckpoint(checkpoint: Checkpoint): Promise { + // This is a no-op + } +} diff --git a/packages/core/src/client/SyncClient.ts b/packages/core/src/client/SyncClient.ts new file mode 100644 index 000000000..822f6f855 --- /dev/null +++ b/packages/core/src/client/SyncClient.ts @@ -0,0 +1,275 @@ +import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; +import type { BucketStorage } from './BucketStorage.js'; +import type { SystemDependencies } from './SystemDependencies.js'; +import { openHttpStream } from './open-stream.js'; + +export type PowerSyncCredentials = { + endpoint: string; + token: string; +}; + +export type Connector = { + fetchCredentials: () => Promise; +}; + +export interface SyncStatus { + connected: boolean; + connecting: boolean; + uploading: boolean; + downloading: boolean; + uploadError?: Error; + downloadError?: Error; + anyError?: Error; +} + +export interface SyncClient { + status: SyncStatus; + + connect: (connector: Connector) => Promise; + + disconnect: () => void; +} + +export type SyncClientOptions = { + connectionRetryDelayMs: number; + uploadRetryDelayMs: number; + storage: BucketStorage; + systemDependencies: SystemDependencies; +}; + +type BucketDescription = { + name: string; + priority: number; +}; + +type SyncState = { + targetCheckpoint: Checkpoint | null; + // A checkpoint that has been validated but not applied (e.g. due to pending local writes) + pendingValidatedCheckpoint: Checkpoint | null; + bucketMap: Map; +}; + +// The priority we assume when we receive checkpoint lines where no priority is set. +// This is the default priority used by the sync service, but can be set to an arbitrary +// value since sync services without priorities also won't send partial sync completion +// messages. +const FALLBACK_PRIORITY = 3; + +export class SyncClientImpl implements SyncClient { + readonly status: SyncStatus; + + protected cachedCredentials: PowerSyncCredentials | null; + protected abortController: AbortController; + + constructor(protected options: SyncClientOptions) { + this.cachedCredentials = null; + this.abortController = new AbortController(); + this.status = { + connected: false, + connecting: false, + uploading: false, + downloading: false + }; + } + + protected get bucketStorage(): BucketStorage { + return this.options.storage; + } + + async connect(connector: Connector): Promise { + // Abort any existing connection + this.abortController.abort(); + + const controller = new AbortController(); + this.abortController = controller; + + while (!this.abortController.signal.aborted) { + try { + await this.syncIteration(connector, controller.signal); + } catch (error) { + this.status.downloadError = error as Error; + + // TODO support aborts + await new Promise((resolve) => setTimeout(resolve, this.options.connectionRetryDelayMs)); + } + } + } + + disconnect() { + this.abortController.abort(); + } + + protected invalidateCredentials(): void { + this.cachedCredentials = null; + } + + protected async syncIteration(connector: Connector, signal: AbortSignal): Promise { + const credentials = this.cachedCredentials ?? (await connector.fetchCredentials()); + + if (!credentials) { + throw new Error(`No credentials found`); + } + + const stream = await openHttpStream({ + endpoint: credentials.endpoint, + token: credentials.token, + signal: signal, + clientId: 'TODO', + // TODO fetch these + bucketPositions: new Map(), + systemDependencies: this.options.systemDependencies + }); + + let syncState: SyncState = { + targetCheckpoint: null, + pendingValidatedCheckpoint: null, + bucketMap: new Map() + }; + + const reader = stream.getReader(); + try { + while (!signal.aborted) { + const { value: line, done } = await reader.read(); + if (done) break; + + // Handle various sync line types + if (`checkpoint` in line) { + const bucketsToDelete = new Set(syncState.bucketMap.keys()); + const newBuckets = new Map(); + for (const checksum of line.checkpoint.buckets) { + newBuckets.set(checksum.bucket, { + name: checksum.bucket, + priority: checksum.priority ?? FALLBACK_PRIORITY + }); + bucketsToDelete.delete(checksum.bucket); + } + syncState = { + targetCheckpoint: line.checkpoint, + pendingValidatedCheckpoint: null, + bucketMap: newBuckets + }; + await this.bucketStorage.removeBuckets(Array.from(bucketsToDelete)); + await this.bucketStorage.setTargetCheckpoint(line.checkpoint); + // TODO update sync status + // await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint); + } else if (`checkpoint_complete` in line) { + const result = await this.applyCheckpoint(syncState.targetCheckpoint!); + if (result.endIteration) { + return; + } else if (!result.applied) { + syncState.pendingValidatedCheckpoint = syncState.targetCheckpoint; + } else { + syncState.pendingValidatedCheckpoint = null; + } + } else if (`partial_checkpoint_complete` in line) { + const priority = line.partial_checkpoint_complete.priority; + console.debug(`Partial checkpoint complete`, priority); + const result = await this.bucketStorage.syncLocalDatabase(syncState.targetCheckpoint!, priority); + if (!result.checkpointValid) { + // This means checksums failed. Start again with a new checkpoint. + // TODO: better back-off + await new Promise((resolve) => setTimeout(resolve, 50)); + return; + } else if (!result.ready) { + // If we have pending uploads, we can't complete new checkpoints outside of priority 0. + // We'll resolve this for a complete checkpoint. + } else { + // We'll keep on downloading, but can report that this priority is synced now. + } + } else if (`checkpoint_diff` in line) { + // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint + if (syncState.targetCheckpoint == null) { + throw new Error(`Checkpoint diff without previous checkpoint`); + } + // New checkpoint - existing validated checkpoint is no longer valid + syncState.pendingValidatedCheckpoint = null; + const diff = line.checkpoint_diff; + const newBuckets = new Map(); + for (const checksum of syncState.targetCheckpoint.buckets) { + newBuckets.set(checksum.bucket, checksum); + } + for (const checksum of diff.updated_buckets) { + newBuckets.set(checksum.bucket, checksum); + } + for (const bucket of diff.removed_buckets) { + newBuckets.delete(bucket); + } + syncState.targetCheckpoint = { + last_op_id: diff.last_op_id, + buckets: [...newBuckets.values()], + write_checkpoint: diff.write_checkpoint, + streams: [] // TODO: implement streams + }; + + // TODO + // await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint); + + syncState.bucketMap = new Map(); + newBuckets.forEach((checksum, name) => + syncState.bucketMap.set(name, { + name: checksum.bucket, + priority: checksum.priority ?? FALLBACK_PRIORITY + }) + ); + + const bucketsToDelete = diff.removed_buckets; + if (bucketsToDelete.length > 0) { + console.debug(`Remove buckets`, bucketsToDelete); + } + await this.bucketStorage.removeBuckets(bucketsToDelete); + await this.bucketStorage.setTargetCheckpoint(syncState.targetCheckpoint!); + } else if (`data` in line) { + const { data } = line; + // TODO update sync status + await this.bucketStorage.saveSyncData({ + buckets: [data] + }); + } else if (`token_expires_in` in line) { + const { token_expires_in } = line; + + if (token_expires_in == 0) { + throw new Error(`Token already expired`); + } else if (token_expires_in < 30) { + this.invalidateCredentials(); + throw new Error(`Token will expire soon. Need to reconnect.`); + } else { + console.debug(`Received unknown sync line`, line); + } + } + } + } finally { + reader.releaseLock(); + } + } + + private async applyCheckpoint(checkpoint: Checkpoint) { + const result = await this.bucketStorage.syncLocalDatabase(checkpoint); + + if (!result.checkpointValid) { + console.debug(`Checksum mismatch in checkpoint ${checkpoint.last_op_id}, will reconnect`); + // This means checksums failed. Start again with a new checkpoint. + // TODO: better back-off + await new Promise((resolve) => setTimeout(resolve, 50)); + return { applied: false, endIteration: true }; + } else if (!result.ready) { + console.debug( + `Could not apply checkpoint ${checkpoint.last_op_id} due to local data. We will retry applying the checkpoint after that upload is completed.` + ); + + return { applied: false, endIteration: false }; + } + + console.debug(`Applied checkpoint ${checkpoint.last_op_id}`, checkpoint); + // this.updateSyncStatus({ + // connected: true, + // lastSyncedAt: new Date(), + // dataFlow: { + // downloading: false, + // downloadProgress: null, + // downloadError: undefined, + // }, + // }) + + return { applied: true, endIteration: false }; + } +} diff --git a/packages/core/src/client/SystemDependencies.ts b/packages/core/src/client/SystemDependencies.ts new file mode 100644 index 000000000..ba012d660 --- /dev/null +++ b/packages/core/src/client/SystemDependencies.ts @@ -0,0 +1,26 @@ +/** + * Implementations of the system dependencies for the PowerSync client. + */ +export type SystemDependencies = { + fetch: typeof fetch; + ReadableStream: typeof ReadableStream; + TextDecoder: typeof TextDecoder; +}; + +export const DEFAULT_SYSTEM_DEPENDENCIES = (): SystemDependencies => { + if (typeof fetch == `undefined`) { + throw new Error(`fetch is not defined`); + } + if (typeof ReadableStream == `undefined`) { + throw new Error(`ReadableStream is not defined`); + } + if (typeof TextDecoder == `undefined`) { + throw new Error(`TextDecoder is not defined`); + } + + return { + fetch: fetch, + ReadableStream: ReadableStream, + TextDecoder: TextDecoder + }; +}; diff --git a/packages/core/src/client/bucketHelpers.ts b/packages/core/src/client/bucketHelpers.ts new file mode 100644 index 000000000..fc18e6646 --- /dev/null +++ b/packages/core/src/client/bucketHelpers.ts @@ -0,0 +1,25 @@ +import type { ProtocolOplogData } from '@powersync/service-core'; + +/** + * Construct key from object_type, object_id, and subkey + * Format: "{object_type}/{object_id}/{subkey}" or empty string if object_type/object_id are missing + */ +export function constructKey( + objectType: string | undefined, + objectId: string | undefined, + subkey: string | undefined +): string { + if (objectType && objectId) { + const subkeyStr = subkey ?? 'null'; + return `${objectType}/${objectId}/${subkeyStr}`; + } + return ''; +} + +/** + * Convert ProtocolOplogData to JSON string or null + */ +export function toStringOrNull(value: ProtocolOplogData | null | undefined): string | null { + return value ? JSON.stringify(value) : null; +} + diff --git a/packages/core/src/client/buckets/index.ts b/packages/core/src/client/buckets/index.ts new file mode 100644 index 000000000..e24fdf807 --- /dev/null +++ b/packages/core/src/client/buckets/index.ts @@ -0,0 +1,15 @@ +/** + * Bucket table interfaces + * + * TypeScript interfaces for PowerSync SQLite bucket tables + */ + +export type { PsBuckets } from "./ps_buckets" +export type { PsOplog } from "./ps_oplog" +export type { PsUntyped } from "./ps_untyped" +export type { PsCrud } from "./ps_crud" +export type { PsTx } from "./ps_tx" +export type { PsKv } from "./ps_kv" +export type { PsUpdatedRows } from "./ps_updated_rows" +export type { PsSyncState } from "./ps_sync_state" + diff --git a/packages/core/src/client/buckets/ps_buckets.ts b/packages/core/src/client/buckets/ps_buckets.ts new file mode 100644 index 000000000..da41822d0 --- /dev/null +++ b/packages/core/src/client/buckets/ps_buckets.ts @@ -0,0 +1,50 @@ +/** + * ps_buckets table interface + * + * Purpose: Tracks sync buckets and their state + * + * Schema (current version, from migration 5+): + * - id (INTEGER PRIMARY KEY) + * - name (TEXT NOT NULL) - Bucket name (unique) + * - last_applied_op (INTEGER NOT NULL DEFAULT 0) + * - last_op (INTEGER NOT NULL DEFAULT 0) + * - target_op (INTEGER NOT NULL DEFAULT 0) + * - add_checksum (INTEGER NOT NULL DEFAULT 0) + * - op_checksum (INTEGER NOT NULL DEFAULT 0) + * - pending_delete (INTEGER NOT NULL DEFAULT 0) + * - count_at_last (INTEGER NOT NULL DEFAULT 0) - Added in migration 9 + * - count_since_last (INTEGER NOT NULL DEFAULT 0) - Added in migration 9 + * + * Index: ps_buckets_name (UNIQUE on name) + */ +export interface PSBucket { + /** INTEGER PRIMARY KEY */ + id: number; + + /** Bucket name (unique) - TEXT NOT NULL */ + name: string; + + /** INTEGER NOT NULL DEFAULT 0 - stored as bigint to match SQLite INTEGER (64-bit) */ + last_applied_op: bigint; + + /** INTEGER NOT NULL DEFAULT 0 - stored as bigint to match SQLite INTEGER (64-bit) */ + last_op: bigint; + + /** INTEGER NOT NULL DEFAULT 0 - stored as bigint to match SQLite INTEGER (64-bit) */ + target_op: bigint; + + /** INTEGER NOT NULL DEFAULT 0 - stored as bigint to match SQLite INTEGER (64-bit) */ + add_checksum: bigint; + + /** INTEGER NOT NULL DEFAULT 0 - stored as bigint to match SQLite INTEGER (64-bit) */ + op_checksum: bigint; + + /** INTEGER NOT NULL DEFAULT 0 */ + pending_delete: boolean; + + /** Added in migration 9 - INTEGER NOT NULL DEFAULT 0 */ + count_at_last: number; + + /** Added in migration 9 - INTEGER NOT NULL DEFAULT 0 */ + count_since_last: number; +} diff --git a/packages/core/src/client/buckets/ps_crud.ts b/packages/core/src/client/buckets/ps_crud.ts new file mode 100644 index 000000000..6065bfd6a --- /dev/null +++ b/packages/core/src/client/buckets/ps_crud.ts @@ -0,0 +1,20 @@ +/** + * ps_crud table interface + * + * Purpose: Stores CRUD operations for virtual table + * + * Schema: + * - id (INTEGER PRIMARY KEY AUTOINCREMENT) + * - data (TEXT) - JSON data for the operation + * - tx_id (INTEGER) - Transaction ID (added in migration 2) + */ +export interface PSCrud { + /** INTEGER PRIMARY KEY AUTOINCREMENT */ + id: number; + + /** JSON data for the operation - TEXT */ + data: string | null; + + /** Transaction ID (added in migration 2) - INTEGER */ + tx_id: number | null; +} diff --git a/packages/core/src/client/buckets/ps_kv.ts b/packages/core/src/client/buckets/ps_kv.ts new file mode 100644 index 000000000..7ad9596dc --- /dev/null +++ b/packages/core/src/client/buckets/ps_kv.ts @@ -0,0 +1,18 @@ +/** + * ps_kv (Key-Value Store) table interface + * + * Purpose: Stores key-value pairs (e.g., client_id) + * + * Schema: + * - key (TEXT PRIMARY KEY NOT NULL) + * - value (BLOB) + * + * Note: Added in migration 3 + */ +export interface PSKeyValue { + /** TEXT PRIMARY KEY NOT NULL */ + key: string; + + /** BLOB */ + value: Uint8Array | null; +} diff --git a/packages/core/src/client/buckets/ps_oplog.ts b/packages/core/src/client/buckets/ps_oplog.ts new file mode 100644 index 000000000..8410c9803 --- /dev/null +++ b/packages/core/src/client/buckets/ps_oplog.ts @@ -0,0 +1,41 @@ +/** + * ps_oplog (Operation Log) table interface + * + * Purpose: Stores operation log entries for sync + * + * Schema (current version, from migration 5+): + * - bucket (INTEGER NOT NULL) - Foreign key to ps_buckets.id + * - op_id (INTEGER NOT NULL) - Operation ID + * - row_type (TEXT) - Type of row (table name) + * - row_id (TEXT) - Row identifier + * - key (TEXT) - Key field + * - data (TEXT) - JSON data + * - hash (INTEGER NOT NULL) - Hash for checksumming + * + * Indexes: + * - ps_oplog_row on (row_type, row_id) + * - ps_oplog_opid on (bucket, op_id) + * - ps_oplog_key on (bucket, key) + */ +export interface PSOplog { + /** Foreign key to ps_buckets.id - INTEGER NOT NULL */ + bucket: number; + + /** Operation ID - INTEGER NOT NULL - stored as bigint to match SQLite INTEGER (64-bit) */ + op_id: bigint; + + /** Type of row (table name) - TEXT */ + row_type: string | null; + + /** Row identifier - TEXT */ + row_id: string | null; + + /** Key field - TEXT */ + key: string | null; + + /** JSON data - TEXT */ + data: string | null; + + /** Hash for checksumming - INTEGER NOT NULL - stored as bigint to match SQLite INTEGER (64-bit) */ + hash: bigint; +} diff --git a/packages/core/src/client/buckets/ps_sync_state.ts b/packages/core/src/client/buckets/ps_sync_state.ts new file mode 100644 index 000000000..58f649ac5 --- /dev/null +++ b/packages/core/src/client/buckets/ps_sync_state.ts @@ -0,0 +1,18 @@ +/** + * ps_sync_state table interface + * + * Purpose: Tracks sync state by priority + * + * Schema (current version, from migration 8+): + * - priority (INTEGER NOT NULL PRIMARY KEY) + * - last_synced_at (TEXT NOT NULL) + * + * Note: Added in migration 7, restructured in migration 8 + */ +export interface PsSyncState { + /** INTEGER NOT NULL PRIMARY KEY */ + priority: number + + /** TEXT NOT NULL */ + last_synced_at: string +} diff --git a/packages/core/src/client/buckets/ps_tx.ts b/packages/core/src/client/buckets/ps_tx.ts new file mode 100644 index 000000000..4a2596a01 --- /dev/null +++ b/packages/core/src/client/buckets/ps_tx.ts @@ -0,0 +1,19 @@ +/** + * ps_tx (Transactions) table interface + * + * Purpose: Tracks transaction state + * + * Schema: + * - id (INTEGER PRIMARY KEY NOT NULL) + * - current_tx (INTEGER) + * - next_tx (INTEGER) + * + * Note: Added in migration 2 + */ +export interface PSTx { + /** INTEGER */ + current_tx: number | null; + + /** INTEGER */ + next_tx: number | null; +} diff --git a/packages/core/src/client/buckets/ps_untyped.ts b/packages/core/src/client/buckets/ps_untyped.ts new file mode 100644 index 000000000..ddf535ff2 --- /dev/null +++ b/packages/core/src/client/buckets/ps_untyped.ts @@ -0,0 +1,22 @@ +/** + * ps_untyped table interface + * + * Purpose: Temporary storage for untyped data before schema is applied + * + * Schema: + * - type (TEXT NOT NULL) + * - id (TEXT NOT NULL) + * - data (TEXT) + * + * PRIMARY KEY (type, id) + */ +export interface PSUntyped { + /** TEXT NOT NULL */ + type: string; + + /** TEXT NOT NULL */ + id: string; + + /** TEXT */ + data: string | null; +} diff --git a/packages/core/src/client/buckets/ps_updated_rows.ts b/packages/core/src/client/buckets/ps_updated_rows.ts new file mode 100644 index 000000000..af064fb09 --- /dev/null +++ b/packages/core/src/client/buckets/ps_updated_rows.ts @@ -0,0 +1,20 @@ +/** + * ps_updated_rows table interface + * + * Purpose: Tracks which rows have been updated + * + * Schema: + * - row_type (TEXT) + * - row_id (TEXT) + * + * PRIMARY KEY (row_type, row_id) + * + * Note: Created WITHOUT ROWID (added in migration 5) + */ +export interface PsUpdatedRows { + /** TEXT */ + row_type: string | null + + /** TEXT */ + row_id: string | null +} diff --git a/packages/core/src/client/checksumUtils.ts b/packages/core/src/client/checksumUtils.ts new file mode 100644 index 000000000..56ae4c312 --- /dev/null +++ b/packages/core/src/client/checksumUtils.ts @@ -0,0 +1,57 @@ +/** + * Checksum utility functions + * + * SQLite stores checksums as 64-bit INTEGERs, so we preserve full precision in memory (bigint). + * However, the checksum algorithm uses 32-bit unsigned wrapping, so we mask during arithmetic + * to match the Rust implementation's behavior. + */ + +/** + * Normalize checksum to bigint (handles conversion from string, number, or bigint) + * SQLite INTEGER columns are 64-bit, so we use bigint to preserve precision + */ +export function normalizeChecksum(checksum: string | number | bigint): bigint { + if (typeof checksum === 'bigint') { + return checksum; + } + if (typeof checksum === 'string') { + return BigInt(checksum); + } + return BigInt(checksum); +} + +/** + * Add two checksums with 32-bit unsigned wrapping behavior + * + * SQLite stores checksums as 64-bit INTEGERs, so we preserve full precision in memory (bigint). + * However, the checksum algorithm uses 32-bit unsigned wrapping, so we mask during arithmetic + * to match the Rust implementation's behavior. The result is stored as full 64-bit value. + * + * We mask during arithmetic to ensure correct wrapping behavior, but don't mask stored values + * to preserve full 64-bit precision from SQLite. + */ +export function addChecksums(a: bigint, b: bigint): bigint { + // Mask inputs to 32-bit unsigned, add, then mask result to ensure 32-bit wrapping behavior + // This matches Rust: (a + b) & 0xffffffff + const a32 = a & 0xffffffffn; + const b32 = b & 0xffffffffn; + return (a32 + b32) & 0xffffffffn; +} + +/** + * Subtract two checksums with 32-bit unsigned wrapping behavior + * + * SQLite stores checksums as 64-bit INTEGERs, so we preserve full precision in memory (bigint). + * However, the checksum algorithm uses 32-bit unsigned wrapping, so we mask during arithmetic + * to match the Rust implementation's behavior. + */ +export function subtractChecksums(a: bigint, b: bigint): bigint { + // For subtraction, we need to handle underflow properly + // Convert to 32-bit unsigned, subtract, then mask + const a32 = a & 0xffffffffn; + const b32 = b & 0xffffffffn; + // Handle underflow by adding 2^32 before masking + const result = (a32 - b32 + 0x100000000n) & 0xffffffffn; + return result; +} + diff --git a/packages/core/src/client/ndjson.ts b/packages/core/src/client/ndjson.ts new file mode 100644 index 000000000..dbad1ac53 --- /dev/null +++ b/packages/core/src/client/ndjson.ts @@ -0,0 +1,70 @@ +import type { SystemDependencies } from './SystemDependencies.js'; + +export function ndjsonStream( + response: ReadableStream, + systemDependencies: SystemDependencies +): ReadableStream { + let is_reader: any, + cancellationRequest = false; + const { ReadableStream, TextDecoder } = systemDependencies; + return new ReadableStream({ + start: function (controller) { + const reader = response.getReader(); + is_reader = reader; + const decoder = new TextDecoder(); + let data_buf = ``; + + reader + .read() + .then(function processResult(result): void | Promise { + if (result.done) { + if (cancellationRequest) { + // Immediately exit + return; + } + + data_buf = data_buf.trim(); + if (data_buf.length !== 0) { + try { + const data_l = JSON.parse(data_buf); + controller.enqueue(data_l); + } catch (e) { + controller.error(e); + return; + } + } + controller.close(); + return; + } + + const data = decoder.decode(result.value, { stream: true }); + data_buf += data; + const lines = data_buf.split(`\n`); + for (let i = 0; i < lines.length - 1; ++i) { + const l = lines[i]?.trim() || ``; + if (l.length > 0) { + try { + const data_line = JSON.parse(l); + controller.enqueue(data_line); + } catch (e) { + controller.error(e); + cancellationRequest = true; + reader.cancel(); + return; + } + } + } + data_buf = lines[lines.length - 1] || ``; + + return reader.read().then(processResult); + }) + .catch((e) => { + controller.error(e); + }); + }, + cancel: function () { + cancellationRequest = true; + is_reader.cancel(); + } + }); +} diff --git a/packages/core/src/client/open-stream.ts b/packages/core/src/client/open-stream.ts new file mode 100644 index 000000000..bc4bf8b3b --- /dev/null +++ b/packages/core/src/client/open-stream.ts @@ -0,0 +1,41 @@ +import type { StreamingSyncLine, StreamingSyncRequest } from '@powersync/service-core'; +import type { SystemDependencies } from './SystemDependencies.js'; +import { ndjsonStream } from './ndjson.js'; + +export type SyncOptions = { + endpoint: string; + token: string; + clientId: string | undefined; + signal: AbortSignal | undefined; + bucketPositions: Map; + systemDependencies: SystemDependencies; +}; + +export async function openHttpStream(options: SyncOptions): Promise> { + const streamRequest: StreamingSyncRequest = { + raw_data: true, + client_id: options.clientId, + buckets: Array.from(options.bucketPositions.entries()).map(([bucket, after]) => ({ + name: bucket, + after: after + })) + }; + + const { fetch } = options.systemDependencies; + + const response = await fetch(options.endpoint + `/sync/stream`, { + method: `POST`, + headers: { + 'Content-Type': `application/json`, + Authorization: `Token ${options.token}` + }, + body: JSON.stringify(streamRequest), + signal: options.signal + }); + + if (response.status != 200) { + throw new Error(`Request failed with code: ${response.status}\n${await response.text()}`); + } + + return ndjsonStream(response.body!, options.systemDependencies); +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/tests/powersync-lite.test.ts b/packages/core/tests/powersync-lite.test.ts new file mode 100644 index 000000000..678fa9481 --- /dev/null +++ b/packages/core/tests/powersync-lite.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from 'vitest'; +import { BucketStorageImpl } from '../src/client/BucketStorageImpl.js'; +import { SyncClientImpl, type Connector } from '../src/client/SyncClient.js'; +import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/SystemDependencies.js'; + +describe(`PowerSync Lite`, () => { + describe(`Connection`, () => { + it(`should connect to a PowerSync server`, async () => { + const connector = { + fetchCredentials: async () => { + const tokenResponse = await fetch(`http://localhost:6060/api/auth/token`, { + method: `GET`, + headers: { + 'content-type': `application/json` + } + }); + + if (!tokenResponse.ok) { + throw new Error(`Failed to fetch token: ${tokenResponse.statusText} ${await tokenResponse.text()}`); + } + + const tokenBody = await tokenResponse.json(); + return { + endpoint: `http://localhost:8080`, + token: tokenBody.token + }; + } + } satisfies Connector; + + const syncClient = new SyncClientImpl({ + connectionRetryDelayMs: 1000, + uploadRetryDelayMs: 1000, + storage: new BucketStorageImpl(), + systemDependencies: DEFAULT_SYSTEM_DEPENDENCIES() + }); + + await syncClient.connect(connector); + + await new Promise((resolve) => setTimeout(resolve, 10000)); + }); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 000000000..59adb5a5d --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "baseUrl": "./", + "jsx": "react", + "types": ["node"], + "rootDir": "src", + "outDir": "./lib", + "lib": ["esnext"], + "declaration": true, + "module": "NodeNext", + "moduleResolution": "nodenext", + "preserveConstEnums": true, + "esModuleInterop": false, + "skipLibCheck": false, + "strictNullChecks": true + }, + "include": ["src/**/*"] +} From 475ab903f7e0c55a3cd629fa6e1aebac21c29b89 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 11:38:00 +0200 Subject: [PATCH 03/13] wip: js sync local --- packages/core/src/client/BucketStorageImpl.ts | 478 +++++++++++++++++- packages/core/src/client/SyncClient.ts | 41 +- packages/core/src/client/open-stream.ts | 17 +- packages/core/tests/powersync-lite.test.ts | 2 +- 4 files changed, 499 insertions(+), 39 deletions(-) diff --git a/packages/core/src/client/BucketStorageImpl.ts b/packages/core/src/client/BucketStorageImpl.ts index cc55785cd..aa955bcf5 100644 --- a/packages/core/src/client/BucketStorageImpl.ts +++ b/packages/core/src/client/BucketStorageImpl.ts @@ -82,16 +82,6 @@ export class BucketStorageImpl implements BucketStorage { return false; } - // TODO: ps_crud sequence tracking - proper implementation needed - // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' - // In SQLite, sequences exist independently of table data (auto-increment tracking) - // For now, we check if ps_crud has any data as a proxy for sequence existence - // Proper implementation would track sequence counter separately - if (this.ps_crud.length === 0 && this.ps_crud_seq === 0) { - // No crud data/sequence, nothing to update - return false; - } - // Save current sequence state before calling callback // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' const seqBefore = this.ps_crud_seq; @@ -107,19 +97,20 @@ export class BucketStorageImpl implements BucketStorage { return false; } - // 2. Verify sequence hasn't changed (SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud') - // If sequence changed, new items were added even if later deleted - if (this.ps_crud_seq !== seqBefore) { - // New crud data may have been uploaded since we got the checkpoint. Abort. - return false; - } - - // Verify sequence exists (should not be empty) - // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' - if (this.ps_crud_seq === 0 && seqBefore === 0) { - // This shouldn't happen if we got past the first check, but defensive - throw new Error('SQLite Sequence should not be empty'); - } + // TODO crud + // // 2. Verify sequence hasn't changed (SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud') + // // If sequence changed, new items were added even if later deleted + // if (this.ps_crud_seq !== seqBefore) { + // // New crud data may have been uploaded since we got the checkpoint. Abort. + // return false; + // } + + // // Verify sequence exists (should not be empty) + // // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' + // if (this.ps_crud_seq === 0 && seqBefore === 0) { + // // This shouldn't happen if we got past the first check, but defensive + // throw new Error('SQLite Sequence should not be empty'); + // } // Update the '$local' bucket's target_op to the new opId // SQL: UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local' @@ -333,10 +324,449 @@ export class BucketStorageImpl implements BucketStorage { checkpoint: Checkpoint, priority?: number ): Promise<{ checkpointValid: boolean; ready: boolean; failures?: Array }> { - throw new Error('Method not implemented.'); + const r = await this.validateChecksums(checkpoint, priority); + if (!r.checkpointValid) { + for (const b of r.checkpointFailures ?? []) { + await this.removeBuckets([b]); + } + return { ready: false, checkpointValid: false, failures: r.checkpointFailures }; + } + + let buckets = checkpoint.buckets; + if (priority !== undefined) { + buckets = buckets.filter((b) => hasMatchingPriority(priority, b)); + } + + // Update bucket last_op to checkpoint.last_op_id + const bucketNames = buckets.map((b) => b.bucket); + for (const bucketName of bucketNames) { + const bucket = this.ps_buckets.find((b) => b.name === bucketName); + if (bucket) { + bucket.last_op = normalizeChecksum(checkpoint.last_op_id); + } + } + + // Update '$local' bucket if write_checkpoint is provided and it's a complete sync + if (priority == null && checkpoint.write_checkpoint) { + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (localBucket) { + localBucket.last_op = normalizeChecksum(checkpoint.write_checkpoint); + } + } + + const valid = await this.updateObjectsFromBuckets(checkpoint, priority); + if (!valid) { + return { ready: false, checkpointValid: true }; + } + + return { + ready: true, + checkpointValid: true + }; + } + + /** + * Atomically update the local state to the current checkpoint. + * Ported from Rust sync_local function. + * + * This includes copying data over from the oplog to the output collections. + * For now, operations are collected but not applied (pluggable approach). + */ + private async updateObjectsFromBuckets(checkpoint: Checkpoint, priority: number | undefined): Promise { + // Check if sync can apply (can_apply_sync_changes equivalent) + if (!(await this.canApplySyncChanges(priority))) { + return false; + } + + // Collect operations that need to be applied + const operations = await this.collectFullOperations(checkpoint, priority); + + console.log('operations', operations); + + // TODO: Apply operations to output collections (pluggable approach) + // For now, we just collect them but don't actually write to output collections + // This will be handled in a pluggable manner later + + // Update last_applied_op for buckets + await this.setLastAppliedOp(checkpoint, priority); + + // Update count_at_last for complete sync + if (priority == null) { + const bucketToCount = new Map(checkpoint.buckets.map((b) => [b.bucket, b.count ?? 0])); + for (const bucket of this.ps_buckets) { + if (bucket.name !== '$local' && bucketToCount.has(bucket.name)) { + bucket.count_at_last = bucketToCount.get(bucket.name)!; + bucket.count_since_last = 0; + } + } + } + + // Mark as completed (clear ps_updated_rows for complete sync) + await this.markCompleted(priority); + + return true; + } + + /** + * Check if sync changes can be applied. + * Ported from Rust can_apply_sync_changes. + * + * Don't publish downloaded data until the upload queue is empty + * (except for downloaded data in priority 0, which is published earlier). + */ + private async canApplySyncChanges(priority: number | undefined): Promise { + const needsCheck = priority === undefined || !this.mayPublishWithOutstandingUploads(priority); + + if (needsCheck) { + // Check if '$local' bucket has target_op > last_op + // SQL: SELECT 1 FROM ps_buckets WHERE target_op > last_op AND name = '$local' + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (localBucket && localBucket.target_op > localBucket.last_op) { + return false; + } + + // Check if ps_crud has any data + // SQL: SELECT 1 FROM ps_crud LIMIT 1 + if (this.ps_crud.length > 0) { + return false; + } + } + + return true; + } + + /** + * Check if a priority may publish with outstanding uploads. + * Ported from Rust BucketPriority::may_publish_with_outstanding_uploads. + */ + private mayPublishWithOutstandingUploads(priority: number): boolean { + // Priority 0 may publish with outstanding uploads + return priority === 0; + } + + /** + * Collect full operations from oplog that need to be applied. + * Ported from Rust collect_full_operations. + * + * Returns an array of operations to apply, where each operation contains: + * - type: row_type (table name) + * - id: row_id + * - op: operation type ('PUT' or 'REMOVE') + * - data: oplog data (null for REMOVE operations) + */ + private async collectFullOperations( + checkpoint: Checkpoint, + priority: number | undefined + ): Promise> { + const operations: Array<{ type: string; id: string; op: 'PUT' | 'REMOVE'; data: string | null }> = []; + + if (priority === undefined) { + // Complete sync - collect all updated rows + // SQL equivalent: + // WITH updated_rows AS ( + // SELECT b.row_type, b.row_id FROM ps_buckets AS buckets + // CROSS JOIN ps_oplog AS b ON b.bucket = buckets.id + // AND (b.op_id > buckets.last_applied_op) + // UNION ALL SELECT row_type, row_id FROM ps_updated_rows + // ) + // SELECT b.row_type, b.row_id, + // (SELECT iif(max(r.op_id), r.data, null) + // FROM ps_oplog r + // WHERE r.row_type = b.row_type AND r.row_id = b.row_id) as data + // FROM updated_rows b + // GROUP BY b.row_type, b.row_id + + // Collect updated rows from oplog (op_id > last_applied_op) + const updatedRows = new Map(); + + for (const bucket of this.ps_buckets) { + for (const oplogEntry of this.ps_oplog) { + if (oplogEntry.bucket === bucket.id && oplogEntry.op_id > bucket.last_applied_op) { + if (oplogEntry.row_type && oplogEntry.row_id) { + const key = `${oplogEntry.row_type}:${oplogEntry.row_id}`; + const existing = updatedRows.get(key); + if (!existing || oplogEntry.op_id > existing.maxOpId) { + updatedRows.set(key, { + type: oplogEntry.row_type, + id: oplogEntry.row_id, + maxOpId: oplogEntry.op_id, + data: oplogEntry.data + }); + } + } + } + } + } + + // Add rows from ps_updated_rows + for (const row of this.ps_updated_rows) { + if (row.type && row.id) { + const key = `${row.type}:${row.id}`; + if (!updatedRows.has(key)) { + updatedRows.set(key, { + type: row.type, + id: row.id, + maxOpId: 0n, + data: null + }); + } + } + } + + // For each updated row, find the latest oplog entry across all buckets + // The updatedRows map already has the data from oplog entries with op_id > last_applied_op, + // but we need to find the absolute latest across all buckets (not just those with op_id > last_applied_op) + for (const row of updatedRows.values()) { + let latestData: string | null = row.data; + let latestOpId: bigint = row.maxOpId; + + // Find the latest oplog entry across all buckets for this row + for (const oplogEntry of this.ps_oplog) { + if (oplogEntry.row_type === row.type && oplogEntry.row_id === row.id && oplogEntry.op_id > latestOpId) { + latestOpId = oplogEntry.op_id; + latestData = oplogEntry.data; + } + } + + operations.push({ + type: row.type, + id: row.id, + op: latestData === null ? 'REMOVE' : 'PUT', + data: latestData + }); + } + } else { + // Partial sync - only collect operations for specific buckets + // SQL equivalent: + // WITH involved_buckets (id) AS MATERIALIZED ( + // SELECT id FROM ps_buckets WHERE name IN (SELECT value FROM json_each(json_extract(?1, '$.buckets'))) + // ), + // updated_rows AS ( + // SELECT b.row_type, b.row_id FROM ps_buckets AS buckets + // CROSS JOIN ps_oplog AS b ON b.bucket = buckets.id + // AND (b.op_id > buckets.last_applied_op) + // WHERE buckets.id IN (SELECT id FROM involved_buckets) + // ) + // SELECT b.row_type, b.row_id, + // (SELECT iif(max(r.op_id), r.data, null) + // FROM ps_oplog r + // WHERE r.row_type = b.row_type AND r.row_id = b.row_id + // AND r.bucket IN (SELECT id FROM involved_buckets)) as data + // FROM updated_rows b + // GROUP BY b.row_type, b.row_id + + // Get involved buckets (buckets matching the priority) + const involvedBuckets = checkpoint.buckets.filter((b) => hasMatchingPriority(priority, b)).map((b) => b.bucket); + + const involvedBucketIds = new Set( + this.ps_buckets.filter((b) => involvedBuckets.includes(b.name)).map((b) => b.id) + ); + + // Collect updated rows from oplog for involved buckets + const updatedRows = new Map(); + + for (const bucket of this.ps_buckets) { + if (involvedBucketIds.has(bucket.id)) { + for (const oplogEntry of this.ps_oplog) { + if ( + oplogEntry.bucket === bucket.id && + oplogEntry.op_id > bucket.last_applied_op && + oplogEntry.row_type && + oplogEntry.row_id + ) { + const key = `${oplogEntry.row_type}:${oplogEntry.row_id}`; + const existing = updatedRows.get(key); + if (!existing || oplogEntry.op_id > existing.maxOpId) { + updatedRows.set(key, { + type: oplogEntry.row_type, + id: oplogEntry.row_id, + maxOpId: oplogEntry.op_id, + data: oplogEntry.data + }); + } + } + } + } + } + + // For each updated row, find the latest oplog entry from involved buckets + // The updatedRows map already has the data from oplog entries with op_id > last_applied_op, + // but we need to find the absolute latest across all involved buckets + for (const row of updatedRows.values()) { + let latestData: string | null = row.data; + let latestOpId: bigint = row.maxOpId; + + // Find the latest oplog entry from involved buckets for this row + for (const oplogEntry of this.ps_oplog) { + if ( + involvedBucketIds.has(oplogEntry.bucket) && + oplogEntry.row_type === row.type && + oplogEntry.row_id === row.id && + oplogEntry.op_id > latestOpId + ) { + latestOpId = oplogEntry.op_id; + latestData = oplogEntry.data; + } + } + + operations.push({ + type: row.type, + id: row.id, + op: latestData === null ? 'REMOVE' : 'PUT', + data: latestData + }); + } + } + + return operations; + } + + /** + * Set last_applied_op for buckets. + * Ported from Rust set_last_applied_op. + */ + private async setLastAppliedOp(checkpoint: Checkpoint, priority: number | undefined): Promise { + if (priority !== undefined) { + // Partial sync - update only involved buckets + // SQL: UPDATE ps_buckets SET last_applied_op = last_op + // WHERE last_applied_op != last_op AND + // name IN (SELECT value FROM json_each(json_extract(?1, '$.buckets'))) + const involvedBuckets = checkpoint.buckets.filter((b) => hasMatchingPriority(priority, b)).map((b) => b.bucket); + + for (const bucketName of involvedBuckets) { + const bucket = this.ps_buckets.find((b) => b.name === bucketName); + if (bucket && bucket.last_applied_op !== bucket.last_op) { + bucket.last_applied_op = bucket.last_op; + } + } + } else { + // Complete sync - update all buckets + // SQL: UPDATE ps_buckets SET last_applied_op = last_op WHERE last_applied_op != last_op + for (const bucket of this.ps_buckets) { + if (bucket.last_applied_op !== bucket.last_op) { + bucket.last_applied_op = bucket.last_op; + } + } + } + } + + /** + * Mark sync as completed. + * Ported from Rust mark_completed. + */ + private async markCompleted(priority: number | undefined): Promise { + if (priority === undefined) { + // Complete sync - clear ps_updated_rows + // SQL: DELETE FROM ps_updated_rows + this.ps_updated_rows = []; + } + + // TODO: Handle ps_sync_state table if needed + // For now, we just clear ps_updated_rows for complete sync + } + + /** + * Validate checksums for a checkpoint. + * This is a placeholder that will be implemented with the Rust validation logic. + */ + async validateChecksums( + checkpoint: Checkpoint, + priority: number | undefined + ): Promise<{ checkpointValid: boolean; ready: boolean; checkpointFailures?: string[] }> { + if (priority !== undefined) { + // Only validate the buckets within the priority we care about + const newBuckets = checkpoint.buckets.filter((cs) => hasMatchingPriority(priority, cs)); + checkpoint = { ...checkpoint, buckets: newBuckets }; + } + + const result = await this.validateCheckpointInternal(checkpoint); + + if (!result) { + return { + checkpointValid: false, + ready: false, + checkpointFailures: [] + }; + } + + if (result.valid) { + return { ready: true, checkpointValid: true }; + } else { + return { + checkpointValid: false, + ready: false, + checkpointFailures: result.failed_buckets + }; + } + } + + /** + * Internal checkpoint validation implementation. + * Ported from Rust validate_checkpoint function. + * + * Validates that the checksums in the checkpoint match the actual checksums + * stored in ps_buckets. For each bucket: + * - Queries ps_buckets to get add_checksum and op_checksum + * - Calculates actual = add_checksum + op_checksum + * - Compares with expected checksum from checkpoint + * - Returns list of failed buckets if checksums don't match + */ + private async validateCheckpointInternal(checkpoint: Checkpoint): Promise<{ + valid: boolean; + failed_buckets?: string[]; + } | null> { + const failedBuckets: string[] = []; + + // Iterate through each bucket in the checkpoint + for (const bucketChecksum of checkpoint.buckets) { + // Find the bucket in ps_buckets by name + // SQL equivalent: SELECT add_checksum, op_checksum FROM ps_buckets WHERE name = ? + const bucket = this.ps_buckets.find((b) => b.name === bucketChecksum.bucket); + + // If bucket doesn't exist, use zero checksums (matching Rust behavior) + let addChecksum: bigint = 0n; + let opChecksum: bigint = 0n; + + if (bucket) { + addChecksum = bucket.add_checksum; + opChecksum = bucket.op_checksum; + } + + // Calculate actual checksum: add_checksum + op_checksum + // This matches Rust: let actual = add_checksum + oplog_checksum; + // addChecksums already handles 32-bit unsigned wrapping + const actual = addChecksums(addChecksum, opChecksum); + + // Normalize expected checksum from checkpoint (it's a number, convert to bigint) + // The checkpoint checksum is a 32-bit unsigned integer + const expected = normalizeChecksum(bucketChecksum.checksum); + + // Compare actual with expected checksum + // Both are already 32-bit masked (actual from addChecksums, expected needs masking) + // Mask expected to ensure 32-bit comparison (checksums are 32-bit unsigned) + const expected32 = expected & 0xffffffffn; + + if (actual !== expected32) { + // Checksum mismatch - add to failures + failedBuckets.push(bucketChecksum.bucket); + } + } + + // Return result matching Rust CheckpointResult structure + return { + valid: failedBuckets.length === 0, + failed_buckets: failedBuckets.length > 0 ? failedBuckets : undefined + }; } async setTargetCheckpoint(checkpoint: Checkpoint): Promise { // This is a no-op } } + +/** + * Helper function to check if a bucket matches the given priority. + */ +function hasMatchingPriority(priority: number, bucket: { priority?: number }): boolean { + return bucket.priority != null && bucket.priority <= priority; +} diff --git a/packages/core/src/client/SyncClient.ts b/packages/core/src/client/SyncClient.ts index 822f6f855..5070db452 100644 --- a/packages/core/src/client/SyncClient.ts +++ b/packages/core/src/client/SyncClient.ts @@ -1,7 +1,7 @@ import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; import type { BucketStorage } from './BucketStorage.js'; import type { SystemDependencies } from './SystemDependencies.js'; -import { openHttpStream } from './open-stream.js'; +import { BucketRequest, openHttpStream } from './open-stream.js'; export type PowerSyncCredentials = { endpoint: string; @@ -12,6 +12,7 @@ export type Connector = { fetchCredentials: () => Promise; }; +// TODO improve this export interface SyncStatus { connected: boolean; connecting: boolean; @@ -42,7 +43,7 @@ type BucketDescription = { priority: number; }; -type SyncState = { +type InternalSyncState = { targetCheckpoint: Checkpoint | null; // A checkpoint that has been validated but not applied (e.g. due to pending local writes) pendingValidatedCheckpoint: Checkpoint | null; @@ -103,6 +104,20 @@ export class SyncClientImpl implements SyncClient { this.cachedCredentials = null; } + private async collectLocalBucketState(): Promise<[BucketRequest[], Map]> { + const bucketEntries = await this.bucketStorage.getBucketStates(); + const req: BucketRequest[] = bucketEntries.map((entry) => ({ + name: entry.bucket, + after: entry.op_id + })); + const localDescriptions = new Map(); + for (const entry of bucketEntries) { + localDescriptions.set(entry.bucket, null); + } + + return [req, localDescriptions]; + } + protected async syncIteration(connector: Connector, signal: AbortSignal): Promise { const credentials = this.cachedCredentials ?? (await connector.fetchCredentials()); @@ -110,23 +125,25 @@ export class SyncClientImpl implements SyncClient { throw new Error(`No credentials found`); } + const [bucketRequests, initBucketMap] = await this.collectLocalBucketState(); + const stream = await openHttpStream({ endpoint: credentials.endpoint, token: credentials.token, signal: signal, - clientId: 'TODO', - // TODO fetch these - bucketPositions: new Map(), + clientId: await this.bucketStorage.getClientId(), + bucketPositions: bucketRequests, systemDependencies: this.options.systemDependencies }); - let syncState: SyncState = { + let syncState: InternalSyncState = { targetCheckpoint: null, pendingValidatedCheckpoint: null, - bucketMap: new Map() + bucketMap: initBucketMap }; const reader = stream.getReader(); + console.debug(`Starting sync iteration`); try { while (!signal.aborted) { const { value: line, done } = await reader.read(); @@ -134,6 +151,7 @@ export class SyncClientImpl implements SyncClient { // Handle various sync line types if (`checkpoint` in line) { + console.debug(`Received checkpoint`, line.checkpoint); const bucketsToDelete = new Set(syncState.bucketMap.keys()); const newBuckets = new Map(); for (const checksum of line.checkpoint.buckets) { @@ -153,6 +171,7 @@ export class SyncClientImpl implements SyncClient { // TODO update sync status // await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint); } else if (`checkpoint_complete` in line) { + console.debug(`Received checkpoint complete`, syncState.targetCheckpoint); const result = await this.applyCheckpoint(syncState.targetCheckpoint!); if (result.endIteration) { return; @@ -162,6 +181,7 @@ export class SyncClientImpl implements SyncClient { syncState.pendingValidatedCheckpoint = null; } } else if (`partial_checkpoint_complete` in line) { + console.debug(`Received partial checkpoint complete`, syncState.targetCheckpoint); const priority = line.partial_checkpoint_complete.priority; console.debug(`Partial checkpoint complete`, priority); const result = await this.bucketStorage.syncLocalDatabase(syncState.targetCheckpoint!, priority); @@ -177,6 +197,7 @@ export class SyncClientImpl implements SyncClient { // We'll keep on downloading, but can report that this priority is synced now. } } else if (`checkpoint_diff` in line) { + console.debug(`Received checkpoint diff`, syncState.targetCheckpoint); // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint if (syncState.targetCheckpoint == null) { throw new Error(`Checkpoint diff without previous checkpoint`); @@ -219,12 +240,14 @@ export class SyncClientImpl implements SyncClient { await this.bucketStorage.removeBuckets(bucketsToDelete); await this.bucketStorage.setTargetCheckpoint(syncState.targetCheckpoint!); } else if (`data` in line) { + console.debug(`Received data`, line.data); const { data } = line; // TODO update sync status await this.bucketStorage.saveSyncData({ buckets: [data] }); } else if (`token_expires_in` in line) { + console.debug(`Received token expires in`, line.token_expires_in); const { token_expires_in } = line; if (token_expires_in == 0) { @@ -232,9 +255,9 @@ export class SyncClientImpl implements SyncClient { } else if (token_expires_in < 30) { this.invalidateCredentials(); throw new Error(`Token will expire soon. Need to reconnect.`); - } else { - console.debug(`Received unknown sync line`, line); } + } else { + console.debug(`Received unknown sync line`, line); } } } finally { diff --git a/packages/core/src/client/open-stream.ts b/packages/core/src/client/open-stream.ts index bc4bf8b3b..4bfee2d73 100644 --- a/packages/core/src/client/open-stream.ts +++ b/packages/core/src/client/open-stream.ts @@ -2,23 +2,30 @@ import type { StreamingSyncLine, StreamingSyncRequest } from '@powersync/service import type { SystemDependencies } from './SystemDependencies.js'; import { ndjsonStream } from './ndjson.js'; +export interface BucketRequest { + name: string; + + /** + * Base-10 number. Sync all data from this bucket with op_id > after. + */ + after: string; +} + export type SyncOptions = { endpoint: string; token: string; clientId: string | undefined; signal: AbortSignal | undefined; - bucketPositions: Map; + bucketPositions: BucketRequest[]; systemDependencies: SystemDependencies; }; +// TODO This currently uses NDJSON streaming. We should add binary streaming also export async function openHttpStream(options: SyncOptions): Promise> { const streamRequest: StreamingSyncRequest = { raw_data: true, client_id: options.clientId, - buckets: Array.from(options.bucketPositions.entries()).map(([bucket, after]) => ({ - name: bucket, - after: after - })) + buckets: options.bucketPositions }; const { fetch } = options.systemDependencies; diff --git a/packages/core/tests/powersync-lite.test.ts b/packages/core/tests/powersync-lite.test.ts index 678fa9481..05f6b6de2 100644 --- a/packages/core/tests/powersync-lite.test.ts +++ b/packages/core/tests/powersync-lite.test.ts @@ -3,7 +3,7 @@ import { BucketStorageImpl } from '../src/client/BucketStorageImpl.js'; import { SyncClientImpl, type Connector } from '../src/client/SyncClient.js'; import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/SystemDependencies.js'; -describe(`PowerSync Lite`, () => { +describe(`PowerSync Lite`, { timeout: Infinity }, () => { describe(`Connection`, () => { it(`should connect to a PowerSync server`, async () => { const connector = { From 8970171086b6672959e8febf186d7f4e54db0bc1 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 12:03:09 +0200 Subject: [PATCH 04/13] cleanup package --- packages/core/src/index.ts | 0 packages/{core => lite-sdk}/CHANGELOG.md | 0 packages/{core => lite-sdk}/README.md | 0 packages/{core => lite-sdk}/package.json | 2 +- .../src/client/storage}/BucketStorage.ts | 0 .../storage/MemoryBucketStorageImpl.ts} | 42 ++++++++++++------- .../client/storage/SyncOperationsHandler.ts | 25 +++++++++++ .../src/client/storage}/bucketHelpers.ts | 0 .../src/client/storage}/checksumUtils.ts | 0 .../client/storage/storage-types}/index.ts | 0 .../storage/storage-types}/ps_buckets.ts | 0 .../client/storage/storage-types}/ps_crud.ts | 0 .../client/storage/storage-types}/ps_kv.ts | 0 .../client/storage/storage-types}/ps_oplog.ts | 0 .../storage/storage-types}/ps_sync_state.ts | 0 .../client/storage/storage-types}/ps_tx.ts | 0 .../storage/storage-types}/ps_untyped.ts | 0 .../storage/storage-types}/ps_updated_rows.ts | 0 .../src/client/sync}/SyncClient.ts | 4 +- .../src/client/sync}/ndjson.ts | 0 .../src/client/sync}/open-stream.ts | 2 +- .../src/client/system}/SystemDependencies.ts | 0 packages/lite-sdk/src/index.ts | 6 +++ .../tests/powersync-lite.test.ts | 21 +++++++--- packages/{core => lite-sdk}/tsconfig.json | 0 25 files changed, 78 insertions(+), 24 deletions(-) delete mode 100644 packages/core/src/index.ts rename packages/{core => lite-sdk}/CHANGELOG.md (100%) rename packages/{core => lite-sdk}/README.md (100%) rename packages/{core => lite-sdk}/package.json (97%) rename packages/{core/src/client => lite-sdk/src/client/storage}/BucketStorage.ts (100%) rename packages/{core/src/client/BucketStorageImpl.ts => lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts} (95%) create mode 100644 packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts rename packages/{core/src/client => lite-sdk/src/client/storage}/bucketHelpers.ts (100%) rename packages/{core/src/client => lite-sdk/src/client/storage}/checksumUtils.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/index.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_buckets.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_crud.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_kv.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_oplog.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_sync_state.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_tx.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_untyped.ts (100%) rename packages/{core/src/client/buckets => lite-sdk/src/client/storage/storage-types}/ps_updated_rows.ts (100%) rename packages/{core/src/client => lite-sdk/src/client/sync}/SyncClient.ts (98%) rename packages/{core/src/client => lite-sdk/src/client/sync}/ndjson.ts (100%) rename packages/{core/src/client => lite-sdk/src/client/sync}/open-stream.ts (94%) rename packages/{core/src/client => lite-sdk/src/client/system}/SystemDependencies.ts (100%) create mode 100644 packages/lite-sdk/src/index.ts rename packages/{core => lite-sdk}/tests/powersync-lite.test.ts (57%) rename packages/{core => lite-sdk}/tsconfig.json (100%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/core/CHANGELOG.md b/packages/lite-sdk/CHANGELOG.md similarity index 100% rename from packages/core/CHANGELOG.md rename to packages/lite-sdk/CHANGELOG.md diff --git a/packages/core/README.md b/packages/lite-sdk/README.md similarity index 100% rename from packages/core/README.md rename to packages/lite-sdk/README.md diff --git a/packages/core/package.json b/packages/lite-sdk/package.json similarity index 97% rename from packages/core/package.json rename to packages/lite-sdk/package.json index e7cdf4a21..6e6ad0bae 100644 --- a/packages/core/package.json +++ b/packages/lite-sdk/package.json @@ -1,5 +1,5 @@ { - "name": "@powersync/core", + "name": "@powersync/lite-sdk", "version": "0.0.0", "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/core/src/client/BucketStorage.ts b/packages/lite-sdk/src/client/storage/BucketStorage.ts similarity index 100% rename from packages/core/src/client/BucketStorage.ts rename to packages/lite-sdk/src/client/storage/BucketStorage.ts diff --git a/packages/core/src/client/BucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts similarity index 95% rename from packages/core/src/client/BucketStorageImpl.ts rename to packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index aa955bcf5..ede3e6c88 100644 --- a/packages/core/src/client/BucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -1,19 +1,25 @@ import type { BucketState, Checkpoint } from '@powersync/service-core'; import type { BucketOperationProgress, BucketStorage, SyncDataBatch } from './BucketStorage.js'; +import type { SyncOperation, SyncOperationsHandler } from './SyncOperationsHandler.js'; import { constructKey, toStringOrNull } from './bucketHelpers.js'; -import type { PSBucket } from './buckets/ps_buckets.js'; -import type { PSCrud } from './buckets/ps_crud.js'; -import type { PSKeyValue } from './buckets/ps_kv.js'; -import type { PSOplog } from './buckets/ps_oplog.js'; -import type { PSTx } from './buckets/ps_tx.js'; -import type { PSUntyped } from './buckets/ps_untyped.js'; import { addChecksums, normalizeChecksum, subtractChecksums } from './checksumUtils.js'; +import type { PSBucket } from './storage-types/ps_buckets.js'; +import type { PSCrud } from './storage-types/ps_crud.js'; +import type { PSKeyValue } from './storage-types/ps_kv.js'; +import type { PSOplog } from './storage-types/ps_oplog.js'; +import type { PSTx } from './storage-types/ps_tx.js'; +import type { PSUntyped } from './storage-types/ps_untyped.js'; export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR'; export const MAX_OP_ID = '9223372036854775807'; -export class BucketStorageImpl implements BucketStorage { +export type MemoryBucketStorageImplOptions = { + /** Array of handlers for processing sync operations collected from the protocol */ + operationsHandlers: SyncOperationsHandler[]; +}; + +export class MemoryBucketStorageImpl implements BucketStorage { protected ps_buckets: PSBucket[]; protected ps_oplog: PSOplog[]; protected ps_updated_rows: PSUntyped[]; @@ -29,7 +35,10 @@ export class BucketStorageImpl implements BucketStorage { // TODO: This should be properly managed when ps_crud is implemented protected ps_crud_seq: number = 0; - constructor() { + /** Handlers for processing sync operations collected from the protocol */ + protected operationsHandlers: SyncOperationsHandler[]; + + constructor(options: MemoryBucketStorageImplOptions) { this.ps_buckets = []; this.ps_oplog = []; this.ps_tx = { @@ -40,6 +49,7 @@ export class BucketStorageImpl implements BucketStorage { this.ps_crud = []; this.ps_kv = []; this.clientId = 'TODO'; + this.operationsHandlers = options.operationsHandlers; } async init(): Promise {} @@ -70,6 +80,7 @@ export class BucketStorageImpl implements BucketStorage { async updateLocalTarget(cb: () => Promise): Promise { // Find the '$local' bucket and check if target_op = MAX_OP_ID // SQL: SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER) + // TODO: maybe store local state separately const localBucket = this.ps_buckets.find((b) => b.name === '$local'); if (!localBucket) { // Nothing to update @@ -381,11 +392,12 @@ export class BucketStorageImpl implements BucketStorage { // Collect operations that need to be applied const operations = await this.collectFullOperations(checkpoint, priority); - console.log('operations', operations); - - // TODO: Apply operations to output collections (pluggable approach) - // For now, we just collect them but don't actually write to output collections - // This will be handled in a pluggable manner later + // Process operations using all handlers if provided + if (this.operationsHandlers.length > 0 && operations.length > 0) { + for (const handler of this.operationsHandlers) { + await handler.processOperations(operations); + } + } // Update last_applied_op for buckets await this.setLastAppliedOp(checkpoint, priority); @@ -457,8 +469,8 @@ export class BucketStorageImpl implements BucketStorage { private async collectFullOperations( checkpoint: Checkpoint, priority: number | undefined - ): Promise> { - const operations: Array<{ type: string; id: string; op: 'PUT' | 'REMOVE'; data: string | null }> = []; + ): Promise> { + const operations: Array = []; if (priority === undefined) { // Complete sync - collect all updated rows diff --git a/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts b/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts new file mode 100644 index 000000000..f925aed0e --- /dev/null +++ b/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts @@ -0,0 +1,25 @@ +/** + * Represents a sync operation collected from the protocol. + */ +export type SyncOperation = { + /** Type of row (table name) */ + type: string; + /** Row identifier */ + id: string; + /** Operation type */ + op: 'PUT' | 'REMOVE'; + /** Operation data (null for REMOVE operations) */ + data: string | null; +}; + +/** + * Handler interface for processing sync operations collected from the protocol. + * This handler is responsible for applying operations to output collections. + */ +export interface SyncOperationsHandler { + /** + * Process sync operations collected from the protocol. + * @param operations Array of operations to process + */ + processOperations(operations: ReadonlyArray): Promise; +} diff --git a/packages/core/src/client/bucketHelpers.ts b/packages/lite-sdk/src/client/storage/bucketHelpers.ts similarity index 100% rename from packages/core/src/client/bucketHelpers.ts rename to packages/lite-sdk/src/client/storage/bucketHelpers.ts diff --git a/packages/core/src/client/checksumUtils.ts b/packages/lite-sdk/src/client/storage/checksumUtils.ts similarity index 100% rename from packages/core/src/client/checksumUtils.ts rename to packages/lite-sdk/src/client/storage/checksumUtils.ts diff --git a/packages/core/src/client/buckets/index.ts b/packages/lite-sdk/src/client/storage/storage-types/index.ts similarity index 100% rename from packages/core/src/client/buckets/index.ts rename to packages/lite-sdk/src/client/storage/storage-types/index.ts diff --git a/packages/core/src/client/buckets/ps_buckets.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_buckets.ts similarity index 100% rename from packages/core/src/client/buckets/ps_buckets.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_buckets.ts diff --git a/packages/core/src/client/buckets/ps_crud.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_crud.ts similarity index 100% rename from packages/core/src/client/buckets/ps_crud.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_crud.ts diff --git a/packages/core/src/client/buckets/ps_kv.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts similarity index 100% rename from packages/core/src/client/buckets/ps_kv.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts diff --git a/packages/core/src/client/buckets/ps_oplog.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_oplog.ts similarity index 100% rename from packages/core/src/client/buckets/ps_oplog.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_oplog.ts diff --git a/packages/core/src/client/buckets/ps_sync_state.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts similarity index 100% rename from packages/core/src/client/buckets/ps_sync_state.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts diff --git a/packages/core/src/client/buckets/ps_tx.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts similarity index 100% rename from packages/core/src/client/buckets/ps_tx.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts diff --git a/packages/core/src/client/buckets/ps_untyped.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_untyped.ts similarity index 100% rename from packages/core/src/client/buckets/ps_untyped.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_untyped.ts diff --git a/packages/core/src/client/buckets/ps_updated_rows.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_updated_rows.ts similarity index 100% rename from packages/core/src/client/buckets/ps_updated_rows.ts rename to packages/lite-sdk/src/client/storage/storage-types/ps_updated_rows.ts diff --git a/packages/core/src/client/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts similarity index 98% rename from packages/core/src/client/SyncClient.ts rename to packages/lite-sdk/src/client/sync/SyncClient.ts index 5070db452..92631dd48 100644 --- a/packages/core/src/client/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -1,6 +1,6 @@ import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; -import type { BucketStorage } from './BucketStorage.js'; -import type { SystemDependencies } from './SystemDependencies.js'; +import type { BucketStorage } from '../storage/BucketStorage.js'; +import type { SystemDependencies } from '../system/SystemDependencies.js'; import { BucketRequest, openHttpStream } from './open-stream.js'; export type PowerSyncCredentials = { diff --git a/packages/core/src/client/ndjson.ts b/packages/lite-sdk/src/client/sync/ndjson.ts similarity index 100% rename from packages/core/src/client/ndjson.ts rename to packages/lite-sdk/src/client/sync/ndjson.ts diff --git a/packages/core/src/client/open-stream.ts b/packages/lite-sdk/src/client/sync/open-stream.ts similarity index 94% rename from packages/core/src/client/open-stream.ts rename to packages/lite-sdk/src/client/sync/open-stream.ts index 4bfee2d73..811b8a09c 100644 --- a/packages/core/src/client/open-stream.ts +++ b/packages/lite-sdk/src/client/sync/open-stream.ts @@ -1,5 +1,5 @@ import type { StreamingSyncLine, StreamingSyncRequest } from '@powersync/service-core'; -import type { SystemDependencies } from './SystemDependencies.js'; +import type { SystemDependencies } from '../system/SystemDependencies.js'; import { ndjsonStream } from './ndjson.js'; export interface BucketRequest { diff --git a/packages/core/src/client/SystemDependencies.ts b/packages/lite-sdk/src/client/system/SystemDependencies.ts similarity index 100% rename from packages/core/src/client/SystemDependencies.ts rename to packages/lite-sdk/src/client/system/SystemDependencies.ts diff --git a/packages/lite-sdk/src/index.ts b/packages/lite-sdk/src/index.ts new file mode 100644 index 000000000..04f9f63ba --- /dev/null +++ b/packages/lite-sdk/src/index.ts @@ -0,0 +1,6 @@ +export * from './client/storage/BucketStorage.js'; +export * from './client/storage/MemoryBucketStorageImpl.js'; +export * from './client/sync/SyncClient.js'; +export * from './client/sync/ndjson.js'; +export * from './client/sync/open-stream.js'; +export * from './client/system/SystemDependencies.js'; diff --git a/packages/core/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts similarity index 57% rename from packages/core/tests/powersync-lite.test.ts rename to packages/lite-sdk/tests/powersync-lite.test.ts index 05f6b6de2..4a546abb7 100644 --- a/packages/core/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -1,7 +1,8 @@ import { describe, it } from 'vitest'; -import { BucketStorageImpl } from '../src/client/BucketStorageImpl.js'; -import { SyncClientImpl, type Connector } from '../src/client/SyncClient.js'; -import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/SystemDependencies.js'; +import { MemoryBucketStorageImpl } from '../src/client/storage/MemoryBucketStorageImpl.js'; +import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; +import { SyncClientImpl, type Connector } from '../src/client/sync/SyncClient.js'; +import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/system/SystemDependencies.js'; describe(`PowerSync Lite`, { timeout: Infinity }, () => { describe(`Connection`, () => { @@ -27,16 +28,26 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { } } satisfies Connector; + const syncOperationsHandler: SyncOperationsHandler = { + processOperations: async (operations) => { + // Funnel these operations to external storage + console.log(`Processing ${operations.length} operations`); + } + }; + const syncClient = new SyncClientImpl({ connectionRetryDelayMs: 1000, + // TODO uploads uploadRetryDelayMs: 1000, - storage: new BucketStorageImpl(), + storage: new MemoryBucketStorageImpl({ + operationsHandlers: [syncOperationsHandler] + }), systemDependencies: DEFAULT_SYSTEM_DEPENDENCIES() }); await syncClient.connect(connector); - await new Promise((resolve) => setTimeout(resolve, 10000)); + // Long running test for demonstrating the sync client }); }); }); diff --git a/packages/core/tsconfig.json b/packages/lite-sdk/tsconfig.json similarity index 100% rename from packages/core/tsconfig.json rename to packages/lite-sdk/tsconfig.json From f80c3253a12e9907935e7eabc6bc6946847e38b2 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 13:28:50 +0200 Subject: [PATCH 05/13] wip: cleanup folder structure --- .../src/client/storage/BucketStorage.ts | 77 ++++++++++++++++++- .../client/storage/MemoryBucketStorageImpl.ts | 53 ++++++++----- .../src/client/storage/storage-types/index.ts | 15 ---- .../storage/storage-types/ps_buckets.ts | 4 +- .../client/storage/storage-types/ps_crud.ts | 2 +- .../src/client/storage/storage-types/ps_kv.ts | 2 - .../storage/storage-types/ps_sync_state.ts | 6 +- .../src/client/storage/storage-types/ps_tx.ts | 2 - .../storage/storage-types/ps_untyped.ts | 22 ------ .../storage/storage-types/ps_updated_rows.ts | 6 +- .../storage-types/storage-types-index.ts | 7 ++ .../lite-sdk/src/client/sync/SyncClient.ts | 67 +++++++++++++--- packages/lite-sdk/src/client/sync/ndjson.ts | 2 +- .../src/client/system/SystemDependencies.ts | 22 +++++- packages/lite-sdk/src/index.ts | 1 + .../lite-sdk/tests/powersync-lite.test.ts | 8 +- 16 files changed, 207 insertions(+), 89 deletions(-) delete mode 100644 packages/lite-sdk/src/client/storage/storage-types/index.ts delete mode 100644 packages/lite-sdk/src/client/storage/storage-types/ps_untyped.ts create mode 100644 packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts diff --git a/packages/lite-sdk/src/client/storage/BucketStorage.ts b/packages/lite-sdk/src/client/storage/BucketStorage.ts index 71822b3d3..832e22a6f 100644 --- a/packages/lite-sdk/src/client/storage/BucketStorage.ts +++ b/packages/lite-sdk/src/client/storage/BucketStorage.ts @@ -11,19 +11,73 @@ export type SavedProgress = { export type BucketOperationProgress = Record; +/** + * Interface for managing bucket storage operations in PowerSync. + * + * BucketStorage is responsible for: + * - Storing and managing sync data from the server + * - Tracking bucket states and operation progress + * - Synchronizing local database with checkpoints + * - Managing client-side CRUD operations + */ export interface BucketStorage { + /** + * Initialize the storage system. + * Should be called before any other operations to set up the storage state. + */ init: () => Promise; + /** + * Clear all data from storage. + * Removes all buckets, operations, and associated data. + */ + clear(): Promise; + + /** + * Save a batch of sync data received from the server. + * + * @param batch - The batch of sync data containing buckets and their operations + */ saveSyncData: (batch: SyncDataBatch) => Promise; + /** + * Remove one or more buckets from storage. + * + * @param buckets - Array of bucket names to remove + */ removeBuckets: (buckets: Array) => Promise; + /** + * Set a target checkpoint to sync towards. + * This checkpoint represents the desired state we want to reach. + * + * @param checkpoint - The target checkpoint containing bucket states and operation IDs + */ setTargetCheckpoint: (checkpoint: Checkpoint) => Promise; + /** + * Get the current state of all buckets. + * + * @returns Array of bucket states, each containing bucket name, operation ID, checksums, etc. + */ getBucketStates: () => Promise>; + /** + * Get progress information for bucket operations. + * Returns counts of operations at last checkpoint and since last checkpoint. + * + * @returns Record mapping bucket names to their operation progress + */ getBucketOperationProgress: () => Promise; + /** + * Synchronize the local database with a checkpoint. + * Validates checksums, applies operations, and updates bucket states. + * + * @param checkpoint - The checkpoint to sync with + * @param priority - Optional priority level to filter which buckets to sync + * @returns Object indicating if checkpoint is valid, if sync is ready, and any failures + */ syncLocalDatabase: ( checkpoint: Checkpoint, priority?: number @@ -33,14 +87,35 @@ export interface BucketStorage { failures?: Array; }>; + /** + * Check if the initial sync has completed. + * + * @returns True if the initial sync has been completed, false otherwise + */ hasCompletedSync: () => Promise; + /** + * Update the local target checkpoint atomically. + * Only updates if no new CRUD data has been added since the checkpoint was obtained. + * + * @param cb - Callback that returns the new operation ID to set as the target + * @returns True if the update was successful, false if new data was detected + */ updateLocalTarget: (cb: () => Promise) => Promise; + /** + * Get the maximum operation ID value. + * This represents the highest possible operation ID in the system. + * + * @returns The maximum operation ID as a string + */ getMaxOpId: () => string; /** - * Get an unique client id. + * Get a unique client ID for this storage instance. + * The client ID is used to identify this client in the sync protocol. + * + * @returns A unique client identifier */ getClientId: () => Promise; } diff --git a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index ede3e6c88..dd89bbafc 100644 --- a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -1,4 +1,5 @@ import type { BucketState, Checkpoint } from '@powersync/service-core'; +import { SystemDependencies } from '../system/SystemDependencies.js'; import type { BucketOperationProgress, BucketStorage, SyncDataBatch } from './BucketStorage.js'; import type { SyncOperation, SyncOperationsHandler } from './SyncOperationsHandler.js'; import { constructKey, toStringOrNull } from './bucketHelpers.js'; @@ -8,7 +9,7 @@ import type { PSCrud } from './storage-types/ps_crud.js'; import type { PSKeyValue } from './storage-types/ps_kv.js'; import type { PSOplog } from './storage-types/ps_oplog.js'; import type { PSTx } from './storage-types/ps_tx.js'; -import type { PSUntyped } from './storage-types/ps_untyped.js'; +import { PsUpdatedRows } from './storage-types/ps_updated_rows.js'; export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR'; @@ -17,12 +18,13 @@ export const MAX_OP_ID = '9223372036854775807'; export type MemoryBucketStorageImplOptions = { /** Array of handlers for processing sync operations collected from the protocol */ operationsHandlers: SyncOperationsHandler[]; + systemDependencies: SystemDependencies; }; export class MemoryBucketStorageImpl implements BucketStorage { protected ps_buckets: PSBucket[]; protected ps_oplog: PSOplog[]; - protected ps_updated_rows: PSUntyped[]; + protected ps_updated_rows: PsUpdatedRows[]; // TODO: ps_crud implementation - ignoring for now // ps_crud tracks client-side changes that need to be uploaded to the server protected ps_crud: PSCrud[]; @@ -38,7 +40,12 @@ export class MemoryBucketStorageImpl implements BucketStorage { /** Handlers for processing sync operations collected from the protocol */ protected operationsHandlers: SyncOperationsHandler[]; - constructor(options: MemoryBucketStorageImplOptions) { + constructor(protected options: MemoryBucketStorageImplOptions) { + this.operationsHandlers = options.operationsHandlers; + this.initDefaultState(); + } + + protected initDefaultState() { this.ps_buckets = []; this.ps_oplog = []; this.ps_tx = { @@ -48,12 +55,15 @@ export class MemoryBucketStorageImpl implements BucketStorage { this.ps_updated_rows = []; this.ps_crud = []; this.ps_kv = []; - this.clientId = 'TODO'; - this.operationsHandlers = options.operationsHandlers; + this.clientId = this.options.systemDependencies.crypto.randomUUID(); } async init(): Promise {} + async clear(): Promise { + this.initDefaultState(); + } + getMaxOpId(): string { return MAX_OP_ID; } @@ -153,12 +163,11 @@ export class MemoryBucketStorageImpl implements BucketStorage { const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); for (const op of bucketOps) { if (op.row_type && op.row_id) { - const exists = this.ps_updated_rows.some((row) => row.type === op.row_type && row.id === op.row_id); + const exists = this.ps_updated_rows.some((row) => row.row_type === op.row_type && row.row_id === op.row_id); if (!exists) { this.ps_updated_rows.push({ - type: op.row_type, - id: op.row_id, - data: null + row_type: op.row_type, + row_id: op.row_id }); } } @@ -258,12 +267,13 @@ export class MemoryBucketStorageImpl implements BucketStorage { if (!shouldSkipRemove) { if (objectType && objectId) { // Insert into ps_updated_rows (or ignore if already exists) - const exists = this.ps_updated_rows.some((row) => row.type === objectType && row.id === objectId); + const exists = this.ps_updated_rows.some( + (row) => row.row_type === objectType && row.row_id === objectId + ); if (!exists) { this.ps_updated_rows.push({ - type: objectType, - id: objectId, - data: null + row_type: objectType, + row_id: objectId }); } } @@ -292,12 +302,13 @@ export class MemoryBucketStorageImpl implements BucketStorage { const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); for (const op of bucketOps) { if (op.row_type && op.row_id) { - const exists = this.ps_updated_rows.some((row) => row.type === op.row_type && row.id === op.row_id); + const exists = this.ps_updated_rows.some( + (row) => row.row_type === op.row_type && row.row_id === op.row_id + ); if (!exists) { this.ps_updated_rows.push({ - type: op.row_type, - id: op.row_id, - data: null + row_type: op.row_type, + row_id: op.row_id }); } } @@ -512,12 +523,12 @@ export class MemoryBucketStorageImpl implements BucketStorage { // Add rows from ps_updated_rows for (const row of this.ps_updated_rows) { - if (row.type && row.id) { - const key = `${row.type}:${row.id}`; + if (row.row_type && row.row_id) { + const key = `${row.row_type}:${row.row_id}`; if (!updatedRows.has(key)) { updatedRows.set(key, { - type: row.type, - id: row.id, + type: row.row_type, + id: row.row_id, maxOpId: 0n, data: null }); diff --git a/packages/lite-sdk/src/client/storage/storage-types/index.ts b/packages/lite-sdk/src/client/storage/storage-types/index.ts deleted file mode 100644 index e24fdf807..000000000 --- a/packages/lite-sdk/src/client/storage/storage-types/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Bucket table interfaces - * - * TypeScript interfaces for PowerSync SQLite bucket tables - */ - -export type { PsBuckets } from "./ps_buckets" -export type { PsOplog } from "./ps_oplog" -export type { PsUntyped } from "./ps_untyped" -export type { PsCrud } from "./ps_crud" -export type { PsTx } from "./ps_tx" -export type { PsKv } from "./ps_kv" -export type { PsUpdatedRows } from "./ps_updated_rows" -export type { PsSyncState } from "./ps_sync_state" - diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_buckets.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_buckets.ts index da41822d0..44da82c12 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_buckets.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/ps_buckets.ts @@ -12,8 +12,8 @@ * - add_checksum (INTEGER NOT NULL DEFAULT 0) * - op_checksum (INTEGER NOT NULL DEFAULT 0) * - pending_delete (INTEGER NOT NULL DEFAULT 0) - * - count_at_last (INTEGER NOT NULL DEFAULT 0) - Added in migration 9 - * - count_since_last (INTEGER NOT NULL DEFAULT 0) - Added in migration 9 + * - count_at_last (INTEGER NOT NULL DEFAULT 0) + * - count_since_last (INTEGER NOT NULL DEFAULT 0) * * Index: ps_buckets_name (UNIQUE on name) */ diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_crud.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_crud.ts index 6065bfd6a..6d14ea75a 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_crud.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/ps_crud.ts @@ -6,7 +6,7 @@ * Schema: * - id (INTEGER PRIMARY KEY AUTOINCREMENT) * - data (TEXT) - JSON data for the operation - * - tx_id (INTEGER) - Transaction ID (added in migration 2) + * - tx_id (INTEGER) - Transaction ID */ export interface PSCrud { /** INTEGER PRIMARY KEY AUTOINCREMENT */ diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts index 7ad9596dc..d0fb16bd2 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts @@ -6,8 +6,6 @@ * Schema: * - key (TEXT PRIMARY KEY NOT NULL) * - value (BLOB) - * - * Note: Added in migration 3 */ export interface PSKeyValue { /** TEXT PRIMARY KEY NOT NULL */ diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts index 58f649ac5..e4e0dc62f 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts @@ -6,13 +6,11 @@ * Schema (current version, from migration 8+): * - priority (INTEGER NOT NULL PRIMARY KEY) * - last_synced_at (TEXT NOT NULL) - * - * Note: Added in migration 7, restructured in migration 8 */ export interface PsSyncState { /** INTEGER NOT NULL PRIMARY KEY */ - priority: number + priority: number; /** TEXT NOT NULL */ - last_synced_at: string + last_synced_at: string; } diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts index 4a2596a01..80f13665b 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts @@ -7,8 +7,6 @@ * - id (INTEGER PRIMARY KEY NOT NULL) * - current_tx (INTEGER) * - next_tx (INTEGER) - * - * Note: Added in migration 2 */ export interface PSTx { /** INTEGER */ diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_untyped.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_untyped.ts deleted file mode 100644 index ddf535ff2..000000000 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_untyped.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * ps_untyped table interface - * - * Purpose: Temporary storage for untyped data before schema is applied - * - * Schema: - * - type (TEXT NOT NULL) - * - id (TEXT NOT NULL) - * - data (TEXT) - * - * PRIMARY KEY (type, id) - */ -export interface PSUntyped { - /** TEXT NOT NULL */ - type: string; - - /** TEXT NOT NULL */ - id: string; - - /** TEXT */ - data: string | null; -} diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_updated_rows.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_updated_rows.ts index af064fb09..b054b007f 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_updated_rows.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/ps_updated_rows.ts @@ -9,12 +9,12 @@ * * PRIMARY KEY (row_type, row_id) * - * Note: Created WITHOUT ROWID (added in migration 5) + * Note: Created WITHOUT ROWID */ export interface PsUpdatedRows { /** TEXT */ - row_type: string | null + row_type: string | null; /** TEXT */ - row_id: string | null + row_id: string | null; } diff --git a/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts b/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts new file mode 100644 index 000000000..a064b9f1f --- /dev/null +++ b/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts @@ -0,0 +1,7 @@ +export type { PSBucket } from './ps_buckets.js'; +export type { PSCrud } from './ps_crud.js'; +export type { PSKeyValue } from './ps_kv.js'; +export type { PSOplog } from './ps_oplog.js'; +export type { PsSyncState } from './ps_sync_state.js'; +export type { PSTx } from './ps_tx.js'; +export type { PsUpdatedRows } from './ps_updated_rows.js'; diff --git a/packages/lite-sdk/src/client/sync/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts index 92631dd48..862ff6031 100644 --- a/packages/lite-sdk/src/client/sync/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -3,38 +3,88 @@ import type { BucketStorage } from '../storage/BucketStorage.js'; import type { SystemDependencies } from '../system/SystemDependencies.js'; import { BucketRequest, openHttpStream } from './open-stream.js'; +/** + * Credentials required to connect to a PowerSync instance. + */ export type PowerSyncCredentials = { + /** The PowerSync endpoint URL to connect to. */ endpoint: string; + /** Authentication token for the PowerSync service. */ token: string; }; +/** + * Provides credentials dynamically for PowerSync connections. + * This allows for credential refresh and token rotation without + * disconnecting the client. + */ export type Connector = { + /** + * Fetches the current PowerSync credentials. + * @returns A promise that resolves to credentials, or null if no credentials are available. + */ fetchCredentials: () => Promise; }; -// TODO improve this +/** + * Current synchronization status of the sync client. + * Provides real-time information about connection state and any errors. + */ export interface SyncStatus { + /** Whether the client is currently connected to the PowerSync service. */ connected: boolean; + /** Whether the client is currently attempting to connect. */ connecting: boolean; + /** Whether data is currently being uploaded to the service. */ uploading: boolean; + /** Whether data is currently being downloaded from the service. */ downloading: boolean; + /** Error that occurred during upload, if any. */ uploadError?: Error; + /** Error that occurred during download, if any. */ downloadError?: Error; + /** Any other error that occurred during sync operations. */ anyError?: Error; } +/** + * Main interface for synchronizing data with a PowerSync service. + * Handles bidirectional sync, connection management, and status tracking. + */ export interface SyncClient { + /** Current synchronization status. */ status: SyncStatus; + /** + * Establishes a connection to the PowerSync service and begins syncing. + * The connection will automatically retry on failure using the configured retry delay. + * @param connector Provides credentials for authentication. Can be called multiple times + * to refresh credentials as needed. + * @returns A promise that resolves when the connection process starts. The promise may + * not resolve if the connection is maintained indefinitely. + */ connect: (connector: Connector) => Promise; + /** + * Disconnects from the PowerSync service and stops all sync operations. + * Any ongoing sync operations will be aborted. + */ disconnect: () => void; } +/** + * Configuration options for creating a SyncClient instance. + */ export type SyncClientOptions = { + /** Delay in milliseconds before retrying a failed connection attempt. */ connectionRetryDelayMs: number; + /** Delay in milliseconds before retrying a failed upload operation. */ uploadRetryDelayMs: number; + /** Whether to enable debug logging for sync operations. */ + debugMode?: boolean; + /** Storage implementation for managing bucket data and synchronization state. */ storage: BucketStorage; + /** System-level dependencies (HTTP client, timers, etc.) required for sync operations. */ systemDependencies: SystemDependencies; }; @@ -151,7 +201,7 @@ export class SyncClientImpl implements SyncClient { // Handle various sync line types if (`checkpoint` in line) { - console.debug(`Received checkpoint`, line.checkpoint); + this.options.debugMode && console.debug(`Received checkpoint`, line.checkpoint); const bucketsToDelete = new Set(syncState.bucketMap.keys()); const newBuckets = new Map(); for (const checksum of line.checkpoint.buckets) { @@ -171,7 +221,7 @@ export class SyncClientImpl implements SyncClient { // TODO update sync status // await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint); } else if (`checkpoint_complete` in line) { - console.debug(`Received checkpoint complete`, syncState.targetCheckpoint); + this.options.debugMode && console.debug(`Received checkpoint complete`, syncState.targetCheckpoint); const result = await this.applyCheckpoint(syncState.targetCheckpoint!); if (result.endIteration) { return; @@ -181,9 +231,8 @@ export class SyncClientImpl implements SyncClient { syncState.pendingValidatedCheckpoint = null; } } else if (`partial_checkpoint_complete` in line) { - console.debug(`Received partial checkpoint complete`, syncState.targetCheckpoint); + this.options.debugMode && console.debug(`Received partial checkpoint complete`, syncState.targetCheckpoint); const priority = line.partial_checkpoint_complete.priority; - console.debug(`Partial checkpoint complete`, priority); const result = await this.bucketStorage.syncLocalDatabase(syncState.targetCheckpoint!, priority); if (!result.checkpointValid) { // This means checksums failed. Start again with a new checkpoint. @@ -197,7 +246,7 @@ export class SyncClientImpl implements SyncClient { // We'll keep on downloading, but can report that this priority is synced now. } } else if (`checkpoint_diff` in line) { - console.debug(`Received checkpoint diff`, syncState.targetCheckpoint); + this.options.debugMode && console.debug(`Received checkpoint diff`, syncState.targetCheckpoint); // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint if (syncState.targetCheckpoint == null) { throw new Error(`Checkpoint diff without previous checkpoint`); @@ -240,14 +289,14 @@ export class SyncClientImpl implements SyncClient { await this.bucketStorage.removeBuckets(bucketsToDelete); await this.bucketStorage.setTargetCheckpoint(syncState.targetCheckpoint!); } else if (`data` in line) { - console.debug(`Received data`, line.data); + this.options.debugMode && console.debug(`Received data`, line.data); const { data } = line; // TODO update sync status await this.bucketStorage.saveSyncData({ buckets: [data] }); } else if (`token_expires_in` in line) { - console.debug(`Received token expires in`, line.token_expires_in); + this.options.debugMode && console.debug(`Received token expires in`, line.token_expires_in); const { token_expires_in } = line; if (token_expires_in == 0) { @@ -282,7 +331,7 @@ export class SyncClientImpl implements SyncClient { return { applied: false, endIteration: false }; } - console.debug(`Applied checkpoint ${checkpoint.last_op_id}`, checkpoint); + this.options.debugMode && console.debug(`Applied checkpoint ${checkpoint.last_op_id}`, checkpoint); // this.updateSyncStatus({ // connected: true, // lastSyncedAt: new Date(), diff --git a/packages/lite-sdk/src/client/sync/ndjson.ts b/packages/lite-sdk/src/client/sync/ndjson.ts index dbad1ac53..0bca6d6ba 100644 --- a/packages/lite-sdk/src/client/sync/ndjson.ts +++ b/packages/lite-sdk/src/client/sync/ndjson.ts @@ -1,4 +1,4 @@ -import type { SystemDependencies } from './SystemDependencies.js'; +import type { SystemDependencies } from '../system/SystemDependencies.js'; export function ndjsonStream( response: ReadableStream, diff --git a/packages/lite-sdk/src/client/system/SystemDependencies.ts b/packages/lite-sdk/src/client/system/SystemDependencies.ts index ba012d660..cca8371e3 100644 --- a/packages/lite-sdk/src/client/system/SystemDependencies.ts +++ b/packages/lite-sdk/src/client/system/SystemDependencies.ts @@ -1,3 +1,7 @@ +export interface Crypto { + randomUUID: () => string; +} + /** * Implementations of the system dependencies for the PowerSync client. */ @@ -5,22 +9,32 @@ export type SystemDependencies = { fetch: typeof fetch; ReadableStream: typeof ReadableStream; TextDecoder: typeof TextDecoder; + crypto: Crypto; }; export const DEFAULT_SYSTEM_DEPENDENCIES = (): SystemDependencies => { + const errors: string[] = []; if (typeof fetch == `undefined`) { - throw new Error(`fetch is not defined`); + errors.push(`fetch is not defined`); } if (typeof ReadableStream == `undefined`) { - throw new Error(`ReadableStream is not defined`); + errors.push(`ReadableStream is not defined`); } if (typeof TextDecoder == `undefined`) { - throw new Error(`TextDecoder is not defined`); + errors.push(`TextDecoder is not defined`); + } + if (typeof crypto == `undefined`) { + errors.push(`crypto is not defined`); + } + + if (errors.length > 0) { + throw new Error(`Missing system dependencies: ${errors.join(`, `)}`); } return { fetch: fetch, ReadableStream: ReadableStream, - TextDecoder: TextDecoder + TextDecoder: TextDecoder, + crypto: crypto }; }; diff --git a/packages/lite-sdk/src/index.ts b/packages/lite-sdk/src/index.ts index 04f9f63ba..04e1801ec 100644 --- a/packages/lite-sdk/src/index.ts +++ b/packages/lite-sdk/src/index.ts @@ -1,5 +1,6 @@ export * from './client/storage/BucketStorage.js'; export * from './client/storage/MemoryBucketStorageImpl.js'; +export * from './client/storage/storage-types/storage-types-index.js'; export * from './client/sync/SyncClient.js'; export * from './client/sync/ndjson.js'; export * from './client/sync/open-stream.js'; diff --git a/packages/lite-sdk/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts index 4a546abb7..ad0bd8300 100644 --- a/packages/lite-sdk/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -32,17 +32,21 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { processOperations: async (operations) => { // Funnel these operations to external storage console.log(`Processing ${operations.length} operations`); + console.log(operations); } }; + const systemDependencies = DEFAULT_SYSTEM_DEPENDENCIES(); const syncClient = new SyncClientImpl({ connectionRetryDelayMs: 1000, + debugMode: false, // TODO uploads uploadRetryDelayMs: 1000, storage: new MemoryBucketStorageImpl({ - operationsHandlers: [syncOperationsHandler] + operationsHandlers: [syncOperationsHandler], + systemDependencies: systemDependencies }), - systemDependencies: DEFAULT_SYSTEM_DEPENDENCIES() + systemDependencies }); await syncClient.connect(connector); From 13b3b1620998d857081752231338910521670a19 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 13:36:21 +0200 Subject: [PATCH 06/13] Add README --- packages/lite-sdk/README.md | 271 +++++++++++++++++++++++++++++++++++- 1 file changed, 270 insertions(+), 1 deletion(-) diff --git a/packages/lite-sdk/README.md b/packages/lite-sdk/README.md index 37605754b..7bfac2990 100644 --- a/packages/lite-sdk/README.md +++ b/packages/lite-sdk/README.md @@ -1 +1,270 @@ -# PowerSync SDK Core +# PowerSync Lite SDK + +An experimental SDK that provides a lightweight interface to connect to a PowerSync service and interact with its synchronization protocol. This SDK is designed to expose sync bucket and stream operations to agnostic data APIs, such as TanStack DB collections, enabling flexible integration with various data storage solutions. + +## Overview + +PowerSync Lite SDK focuses on the core synchronization protocol, allowing you to: + +- Connect to a PowerSync service and manage bidirectional sync +- Process sync operations from buckets and streams +- Integrate with external data storage systems (e.g., TanStack DB, in-memory stores, custom databases) +- Handle connection management, credential refresh, and automatic retries + +The SDK is designed to be storage-agnostic, giving you full control over how synchronized data is stored and accessed in your application. + +## Installation + +```bash +npm install @powersync/lite-sdk +``` + +## Quick Start + +### Basic Example + +Here's a basic example of connecting to a PowerSync service and processing sync operations: + +```typescript +import { + MemoryBucketStorageImpl, + SyncClientImpl, + DEFAULT_SYSTEM_DEPENDENCIES, + type Connector, + type SyncOperationsHandler +} from '@powersync/lite-sdk'; + +// Define a connector to fetch credentials +const connector: Connector = { + fetchCredentials: async () => { + const tokenResponse = await fetch(`http://localhost:6060/api/auth/token`, { + method: `GET`, + headers: { + 'content-type': `application/json` + } + }); + + if (!tokenResponse.ok) { + throw new Error(`Failed to fetch token: ${tokenResponse.statusText}`); + } + + const tokenBody = await tokenResponse.json(); + return { + endpoint: `http://localhost:8080`, + token: tokenBody.token + }; + } +}; + +// Define a handler to process sync operations +const syncOperationsHandler: SyncOperationsHandler = { + processOperations: async (operations) => { + // Process operations and apply them to your external storage + // For example, update TanStack DB collections, your database, etc. + console.log(`Processing ${operations.length} operations`); + + for (const operation of operations) { + if (operation.op === 'PUT') { + // Insert or update data in your storage + console.log(`PUT ${operation.type}:${operation.id}`, operation.data); + } else if (operation.op === 'REMOVE') { + // Remove data from your storage + console.log(`REMOVE ${operation.type}:${operation.id}`); + } + } + } +}; + +// Create system dependencies (default uses browser/Node.js globals) +const systemDependencies = DEFAULT_SYSTEM_DEPENDENCIES(); + +// Create storage implementation +const storage = new MemoryBucketStorageImpl({ + operationsHandlers: [syncOperationsHandler], + systemDependencies: systemDependencies +}); + +// Create and connect the sync client +const syncClient = new SyncClientImpl({ + connectionRetryDelayMs: 1000, + uploadRetryDelayMs: 1000, + debugMode: false, + storage: storage, + systemDependencies: systemDependencies +}); + +// Connect to the PowerSync service +await syncClient.connect(connector); + +// The client will now continuously sync data +// When you're done, you can disconnect: +// syncClient.disconnect(); +``` + +## Core Concepts + +### SyncClient + +The `SyncClient` is the main interface for synchronizing data with a PowerSync service. It handles: + +- Connection management and automatic reconnection +- Bidirectional data synchronization +- Credential refresh via the `Connector` interface +- Status tracking (connection state, errors, etc.) + +### BucketStorage + +`BucketStorage` is an interface for managing bucket data and synchronization state. The SDK provides: + +- `MemoryBucketStorageImpl`: An in-memory implementation for cases where persistence is not required +- You can implement your own `BucketStorage` to integrate with your preferred storage solution + +### SyncOperationsHandler + +`SyncOperationsHandler` receives sync operations from the protocol and allows you to apply them to your external data storage. Each operation contains: + +- `type`: The table/collection name +- `id`: The row identifier +- `op`: The operation type (`PUT` or `REMOVE`) +- `data`: The operation data (null for `REMOVE` operations) + +### Connector + +The `Connector` interface provides a way to dynamically fetch credentials for authentication. This enables: + +- Token refresh without disconnecting +- Dynamic credential rotation +- Custom authentication logic + +## API Reference + +### SyncClient + +#### `connect(connector: Connector): Promise` + +Establishes a connection to the PowerSync service and begins syncing. The connection will automatically retry on failure using the configured retry delay. + +#### `disconnect(): void` + +Disconnects from the PowerSync service and stops all sync operations. + +#### `status: SyncStatus` + +The current synchronization status, including: + +- `connected`: Whether the client is currently connected +- `connecting`: Whether the client is attempting to connect +- `uploading`: Whether data is currently being uploaded +- `downloading`: Whether data is currently being downloaded +- `uploadError`: Error that occurred during upload, if any +- `downloadError`: Error that occurred during download, if any +- `anyError`: Any other error that occurred during sync operations + +### SyncClientImpl Options + +```typescript +{ + connectionRetryDelayMs: number; // Delay before retrying failed connection attempts + uploadRetryDelayMs: number; // Delay before retrying failed upload operations + debugMode?: boolean; // Enable debug logging + storage: BucketStorage; // Storage implementation + systemDependencies: SystemDependencies; // System-level dependencies +} +``` + +### SyncOperationsHandler + +```typescript +interface SyncOperationsHandler { + processOperations(operations: ReadonlyArray): Promise; +} +``` + +### Connector + +```typescript +interface Connector { + fetchCredentials(): Promise; +} +``` + +### SyncOperation + +```typescript +type SyncOperation = { + type: string; // Table/collection name + id: string; // Row identifier + op: 'PUT' | 'REMOVE'; // Operation type + data: string | null; // Operation data (null for REMOVE) +}; +``` + +## Integration Examples + +### TanStack DB Integration + +```typescript +import { syncClient } from './sync-client'; +import { db } from './db'; + +const syncOperationsHandler: SyncOperationsHandler = { + processOperations: async (operations) => { + for (const operation of operations) { + const table = db[operation.type]; + + if (operation.op === 'PUT') { + const data = JSON.parse(operation.data!); + await table.upsert(data); + } else if (operation.op === 'REMOVE') { + await table.delete(operation.id); + } + } + } +}; +``` + +### Custom Database Integration + +```typescript +const syncOperationsHandler: SyncOperationsHandler = { + processOperations: async (operations) => { + await db.transaction(async (tx) => { + for (const operation of operations) { + if (operation.op === 'PUT') { + const data = JSON.parse(operation.data!); + await tx.insert(operation.type).values(data).onConflictDoUpdate(); + } else if (operation.op === 'REMOVE') { + await tx.delete(operation.type).where({ id: operation.id }); + } + } + }); + } +}; +``` + +## System Dependencies + +The SDK requires system-level dependencies for HTTP requests, streams, and cryptographic operations. The `DEFAULT_SYSTEM_DEPENDENCIES()` function provides a default implementation that uses browser/Node.js globals (`fetch`, `ReadableStream`, `TextDecoder`, `crypto`). + +For custom environments (e.g., React Native), you can provide your own implementation: + +```typescript +const systemDependencies: SystemDependencies = { + fetch: customFetch, + ReadableStream: customReadableStream, + TextDecoder: customTextDecoder, + crypto: customCrypto +}; +``` + +## Current Limitations + +⚠️ **This is an experimental package and is still under active development.** + +The following features are planned but not yet fully implemented: + +1. **Sync Status Indicators**: The sync status tracking is partially implemented but needs completion. Status updates for connection state, sync progress, and error reporting are not fully functional. + +2. **CRUD Functionality**: Client-side CRUD operations (create, read, update, delete) that need to be uploaded to the server are not yet fully implemented. The `ps_crud` storage layer exists but is not complete. + +3. **Sync Streams Support**: Support for sync streams is mentioned in the codebase but may need additional implementation work. The checkpoint structure includes streams, but full stream synchronization support may be pending. From 724fdb39936e3a375dc80c96fe29c4ddce4b4e92 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 16:22:23 +0200 Subject: [PATCH 07/13] Catch auth errors on connect. Remove unused bucket APIs. Implement hasCompletedSync --- packages/lite-sdk/README.md | 8 ++++++++ .../src/client/storage/BucketStorage.ts | 16 ---------------- .../client/storage/MemoryBucketStorageImpl.ts | 15 ++++----------- .../lite-sdk/src/client/sync/SyncClient.ts | 10 +++++++++- .../lite-sdk/src/client/sync/open-stream.ts | 18 ++++++++++++++++++ 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/packages/lite-sdk/README.md b/packages/lite-sdk/README.md index 7bfac2990..6d0282101 100644 --- a/packages/lite-sdk/README.md +++ b/packages/lite-sdk/README.md @@ -268,3 +268,11 @@ The following features are planned but not yet fully implemented: 2. **CRUD Functionality**: Client-side CRUD operations (create, read, update, delete) that need to be uploaded to the server are not yet fully implemented. The `ps_crud` storage layer exists but is not complete. 3. **Sync Streams Support**: Support for sync streams is mentioned in the codebase but may need additional implementation work. The checkpoint structure includes streams, but full stream synchronization support may be pending. + +## Known Limitations + +The following features are not supported in the Lite SDK: + +1. **Bucket Priority Sync Statuses**: The SDK does not support bucket priority sync statuses. All buckets are synced with equal priority. + +2. **Download Progress Statuses**: The SDK does not provide download progress statuses. While the sync status tracks overall downloading state, granular progress information for individual downloads is not available. diff --git a/packages/lite-sdk/src/client/storage/BucketStorage.ts b/packages/lite-sdk/src/client/storage/BucketStorage.ts index 832e22a6f..97c8c64d7 100644 --- a/packages/lite-sdk/src/client/storage/BucketStorage.ts +++ b/packages/lite-sdk/src/client/storage/BucketStorage.ts @@ -47,14 +47,6 @@ export interface BucketStorage { */ removeBuckets: (buckets: Array) => Promise; - /** - * Set a target checkpoint to sync towards. - * This checkpoint represents the desired state we want to reach. - * - * @param checkpoint - The target checkpoint containing bucket states and operation IDs - */ - setTargetCheckpoint: (checkpoint: Checkpoint) => Promise; - /** * Get the current state of all buckets. * @@ -62,14 +54,6 @@ export interface BucketStorage { */ getBucketStates: () => Promise>; - /** - * Get progress information for bucket operations. - * Returns counts of operations at last checkpoint and since last checkpoint. - * - * @returns Record mapping bucket names to their operation progress - */ - getBucketOperationProgress: () => Promise; - /** * Synchronize the local database with a checkpoint. * Validates checksums, applies operations, and updates bucket states. diff --git a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index dd89bbafc..b10ae71a3 100644 --- a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -1,6 +1,6 @@ import type { BucketState, Checkpoint } from '@powersync/service-core'; import { SystemDependencies } from '../system/SystemDependencies.js'; -import type { BucketOperationProgress, BucketStorage, SyncDataBatch } from './BucketStorage.js'; +import type { BucketStorage, SyncDataBatch } from './BucketStorage.js'; import type { SyncOperation, SyncOperationsHandler } from './SyncOperationsHandler.js'; import { constructKey, toStringOrNull } from './bucketHelpers.js'; import { addChecksums, normalizeChecksum, subtractChecksums } from './checksumUtils.js'; @@ -32,6 +32,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { protected clientId: string; protected ps_tx: PSTx; + protected lastSyncedAt: Date | null; // Track sequence/counter for ps_crud (simulates SQLite sequence) // TODO: This should be properly managed when ps_crud is implemented @@ -54,6 +55,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { }; this.ps_updated_rows = []; this.ps_crud = []; + this.lastSyncedAt = null; this.ps_kv = []; this.clientId = this.options.systemDependencies.crypto.randomUUID(); } @@ -84,7 +86,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { } async hasCompletedSync(): Promise { - throw new Error('Method not implemented.'); + return !!this.ps_buckets.find((b) => b.last_applied_op > 0); } async updateLocalTarget(cb: () => Promise): Promise { @@ -141,11 +143,6 @@ export class MemoryBucketStorageImpl implements BucketStorage { return true; } - async getBucketOperationProgress(): Promise { - // TODO - return {}; - } - async removeBuckets(buckets: Array): Promise { for (const bucketName of buckets) { // Find bucket by name and get its id @@ -781,10 +778,6 @@ export class MemoryBucketStorageImpl implements BucketStorage { failed_buckets: failedBuckets.length > 0 ? failedBuckets : undefined }; } - - async setTargetCheckpoint(checkpoint: Checkpoint): Promise { - // This is a no-op - } } /** diff --git a/packages/lite-sdk/src/client/sync/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts index 862ff6031..6abdd30f1 100644 --- a/packages/lite-sdk/src/client/sync/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -1,7 +1,7 @@ import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; import type { BucketStorage } from '../storage/BucketStorage.js'; import type { SystemDependencies } from '../system/SystemDependencies.js'; -import { BucketRequest, openHttpStream } from './open-stream.js'; +import { AuthorizationError, BucketRequest, openHttpStream } from './open-stream.js'; /** * Credentials required to connect to a PowerSync instance. @@ -37,6 +37,8 @@ export interface SyncStatus { connecting: boolean; /** Whether data is currently being uploaded to the service. */ uploading: boolean; + /** Whether the client has synced all data from the PowerSync service. */ + hasSynced: boolean; /** Whether data is currently being downloaded from the service. */ downloading: boolean; /** Error that occurred during upload, if any. */ @@ -117,6 +119,7 @@ export class SyncClientImpl implements SyncClient { this.abortController = new AbortController(); this.status = { connected: false, + hasSynced: false, connecting: false, uploading: false, downloading: false @@ -184,6 +187,11 @@ export class SyncClientImpl implements SyncClient { clientId: await this.bucketStorage.getClientId(), bucketPositions: bucketRequests, systemDependencies: this.options.systemDependencies + }).catch((ex) => { + if (ex instanceof AuthorizationError) { + this.invalidateCredentials(); + } + throw ex; }); let syncState: InternalSyncState = { diff --git a/packages/lite-sdk/src/client/sync/open-stream.ts b/packages/lite-sdk/src/client/sync/open-stream.ts index 811b8a09c..af67af3e8 100644 --- a/packages/lite-sdk/src/client/sync/open-stream.ts +++ b/packages/lite-sdk/src/client/sync/open-stream.ts @@ -2,6 +2,19 @@ import type { StreamingSyncLine, StreamingSyncRequest } from '@powersync/service import type { SystemDependencies } from '../system/SystemDependencies.js'; import { ndjsonStream } from './ndjson.js'; +export class AuthorizationError extends Error { + constructor(message: string) { + super(message); + // Set the prototype explicitly + Object.setPrototypeOf(this, AuthorizationError.prototype); + + // Capture stack trace + if (Error.captureStackTrace) { + Error.captureStackTrace(this, AuthorizationError); + } + } +} + export interface BucketRequest { name: string; @@ -40,6 +53,11 @@ export async function openHttpStream(options: SyncOptions): Promise= 400 && response.status < 500) { + const errorText = await response.text(); + throw new AuthorizationError(`Authorization failed: ${errorText}`); + } + if (response.status != 200) { throw new Error(`Request failed with code: ${response.status}\n${await response.text()}`); } From 2c816c293ad99985d72f92c90d96e96047c27e34 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 16:58:03 +0200 Subject: [PATCH 08/13] Report sync status changes --- .../client/storage/MemoryBucketStorageImpl.ts | 3 - .../src/client/storage/storage-types/ps_kv.ts | 16 - .../storage/storage-types/ps_sync_state.ts | 16 - .../storage-types/storage-types-index.ts | 2 - .../lite-sdk/src/client/sync/SyncClient.ts | 291 +-------------- .../src/client/sync/SyncClientImpl.ts | 333 ++++++++++++++++++ packages/lite-sdk/src/index.ts | 1 + packages/lite-sdk/src/utils/BaseObserver.ts | 41 +++ .../lite-sdk/tests/powersync-lite.test.ts | 3 +- 9 files changed, 393 insertions(+), 313 deletions(-) delete mode 100644 packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts delete mode 100644 packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts create mode 100644 packages/lite-sdk/src/client/sync/SyncClientImpl.ts create mode 100644 packages/lite-sdk/src/utils/BaseObserver.ts diff --git a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index b10ae71a3..e0f405498 100644 --- a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -6,7 +6,6 @@ import { constructKey, toStringOrNull } from './bucketHelpers.js'; import { addChecksums, normalizeChecksum, subtractChecksums } from './checksumUtils.js'; import type { PSBucket } from './storage-types/ps_buckets.js'; import type { PSCrud } from './storage-types/ps_crud.js'; -import type { PSKeyValue } from './storage-types/ps_kv.js'; import type { PSOplog } from './storage-types/ps_oplog.js'; import type { PSTx } from './storage-types/ps_tx.js'; import { PsUpdatedRows } from './storage-types/ps_updated_rows.js'; @@ -28,7 +27,6 @@ export class MemoryBucketStorageImpl implements BucketStorage { // TODO: ps_crud implementation - ignoring for now // ps_crud tracks client-side changes that need to be uploaded to the server protected ps_crud: PSCrud[]; - protected ps_kv: PSKeyValue[]; protected clientId: string; protected ps_tx: PSTx; @@ -56,7 +54,6 @@ export class MemoryBucketStorageImpl implements BucketStorage { this.ps_updated_rows = []; this.ps_crud = []; this.lastSyncedAt = null; - this.ps_kv = []; this.clientId = this.options.systemDependencies.crypto.randomUUID(); } diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts deleted file mode 100644 index d0fb16bd2..000000000 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_kv.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * ps_kv (Key-Value Store) table interface - * - * Purpose: Stores key-value pairs (e.g., client_id) - * - * Schema: - * - key (TEXT PRIMARY KEY NOT NULL) - * - value (BLOB) - */ -export interface PSKeyValue { - /** TEXT PRIMARY KEY NOT NULL */ - key: string; - - /** BLOB */ - value: Uint8Array | null; -} diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts deleted file mode 100644 index e4e0dc62f..000000000 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_sync_state.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * ps_sync_state table interface - * - * Purpose: Tracks sync state by priority - * - * Schema (current version, from migration 8+): - * - priority (INTEGER NOT NULL PRIMARY KEY) - * - last_synced_at (TEXT NOT NULL) - */ -export interface PsSyncState { - /** INTEGER NOT NULL PRIMARY KEY */ - priority: number; - - /** TEXT NOT NULL */ - last_synced_at: string; -} diff --git a/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts b/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts index a064b9f1f..006e91fdf 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts @@ -1,7 +1,5 @@ export type { PSBucket } from './ps_buckets.js'; export type { PSCrud } from './ps_crud.js'; -export type { PSKeyValue } from './ps_kv.js'; export type { PSOplog } from './ps_oplog.js'; -export type { PsSyncState } from './ps_sync_state.js'; export type { PSTx } from './ps_tx.js'; export type { PsUpdatedRows } from './ps_updated_rows.js'; diff --git a/packages/lite-sdk/src/client/sync/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts index 6abdd30f1..a9a873c87 100644 --- a/packages/lite-sdk/src/client/sync/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -1,7 +1,6 @@ -import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; +import { BaseListener, BaseObserverInterface, Disposable } from '../../utils/BaseObserver.js'; import type { BucketStorage } from '../storage/BucketStorage.js'; import type { SystemDependencies } from '../system/SystemDependencies.js'; -import { AuthorizationError, BucketRequest, openHttpStream } from './open-stream.js'; /** * Credentials required to connect to a PowerSync instance. @@ -35,27 +34,33 @@ export interface SyncStatus { connected: boolean; /** Whether the client is currently attempting to connect. */ connecting: boolean; - /** Whether data is currently being uploaded to the service. */ - uploading: boolean; + /** The last time the client synced data from the PowerSync service. */ + lastSyncedAt: Date | null; /** Whether the client has synced all data from the PowerSync service. */ hasSynced: boolean; /** Whether data is currently being downloaded from the service. */ downloading: boolean; - /** Error that occurred during upload, if any. */ - uploadError?: Error; /** Error that occurred during download, if any. */ - downloadError?: Error; - /** Any other error that occurred during sync operations. */ - anyError?: Error; + downloadError: Error | null; +} + +/** + * Listener interface for sync client status changes. + */ +export interface SyncClientListener extends BaseListener { + /** + * Triggers whenever the status' members have changed in value + */ + statusChanged?: ((status: SyncStatus) => void) | undefined; } /** * Main interface for synchronizing data with a PowerSync service. * Handles bidirectional sync, connection management, and status tracking. */ -export interface SyncClient { +export interface SyncClient extends BaseObserverInterface, Disposable { /** Current synchronization status. */ - status: SyncStatus; + readonly status: SyncStatus; /** * Establishes a connection to the PowerSync service and begins syncing. @@ -89,267 +94,3 @@ export type SyncClientOptions = { /** System-level dependencies (HTTP client, timers, etc.) required for sync operations. */ systemDependencies: SystemDependencies; }; - -type BucketDescription = { - name: string; - priority: number; -}; - -type InternalSyncState = { - targetCheckpoint: Checkpoint | null; - // A checkpoint that has been validated but not applied (e.g. due to pending local writes) - pendingValidatedCheckpoint: Checkpoint | null; - bucketMap: Map; -}; - -// The priority we assume when we receive checkpoint lines where no priority is set. -// This is the default priority used by the sync service, but can be set to an arbitrary -// value since sync services without priorities also won't send partial sync completion -// messages. -const FALLBACK_PRIORITY = 3; - -export class SyncClientImpl implements SyncClient { - readonly status: SyncStatus; - - protected cachedCredentials: PowerSyncCredentials | null; - protected abortController: AbortController; - - constructor(protected options: SyncClientOptions) { - this.cachedCredentials = null; - this.abortController = new AbortController(); - this.status = { - connected: false, - hasSynced: false, - connecting: false, - uploading: false, - downloading: false - }; - } - - protected get bucketStorage(): BucketStorage { - return this.options.storage; - } - - async connect(connector: Connector): Promise { - // Abort any existing connection - this.abortController.abort(); - - const controller = new AbortController(); - this.abortController = controller; - - while (!this.abortController.signal.aborted) { - try { - await this.syncIteration(connector, controller.signal); - } catch (error) { - this.status.downloadError = error as Error; - - // TODO support aborts - await new Promise((resolve) => setTimeout(resolve, this.options.connectionRetryDelayMs)); - } - } - } - - disconnect() { - this.abortController.abort(); - } - - protected invalidateCredentials(): void { - this.cachedCredentials = null; - } - - private async collectLocalBucketState(): Promise<[BucketRequest[], Map]> { - const bucketEntries = await this.bucketStorage.getBucketStates(); - const req: BucketRequest[] = bucketEntries.map((entry) => ({ - name: entry.bucket, - after: entry.op_id - })); - const localDescriptions = new Map(); - for (const entry of bucketEntries) { - localDescriptions.set(entry.bucket, null); - } - - return [req, localDescriptions]; - } - - protected async syncIteration(connector: Connector, signal: AbortSignal): Promise { - const credentials = this.cachedCredentials ?? (await connector.fetchCredentials()); - - if (!credentials) { - throw new Error(`No credentials found`); - } - - const [bucketRequests, initBucketMap] = await this.collectLocalBucketState(); - - const stream = await openHttpStream({ - endpoint: credentials.endpoint, - token: credentials.token, - signal: signal, - clientId: await this.bucketStorage.getClientId(), - bucketPositions: bucketRequests, - systemDependencies: this.options.systemDependencies - }).catch((ex) => { - if (ex instanceof AuthorizationError) { - this.invalidateCredentials(); - } - throw ex; - }); - - let syncState: InternalSyncState = { - targetCheckpoint: null, - pendingValidatedCheckpoint: null, - bucketMap: initBucketMap - }; - - const reader = stream.getReader(); - console.debug(`Starting sync iteration`); - try { - while (!signal.aborted) { - const { value: line, done } = await reader.read(); - if (done) break; - - // Handle various sync line types - if (`checkpoint` in line) { - this.options.debugMode && console.debug(`Received checkpoint`, line.checkpoint); - const bucketsToDelete = new Set(syncState.bucketMap.keys()); - const newBuckets = new Map(); - for (const checksum of line.checkpoint.buckets) { - newBuckets.set(checksum.bucket, { - name: checksum.bucket, - priority: checksum.priority ?? FALLBACK_PRIORITY - }); - bucketsToDelete.delete(checksum.bucket); - } - syncState = { - targetCheckpoint: line.checkpoint, - pendingValidatedCheckpoint: null, - bucketMap: newBuckets - }; - await this.bucketStorage.removeBuckets(Array.from(bucketsToDelete)); - await this.bucketStorage.setTargetCheckpoint(line.checkpoint); - // TODO update sync status - // await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint); - } else if (`checkpoint_complete` in line) { - this.options.debugMode && console.debug(`Received checkpoint complete`, syncState.targetCheckpoint); - const result = await this.applyCheckpoint(syncState.targetCheckpoint!); - if (result.endIteration) { - return; - } else if (!result.applied) { - syncState.pendingValidatedCheckpoint = syncState.targetCheckpoint; - } else { - syncState.pendingValidatedCheckpoint = null; - } - } else if (`partial_checkpoint_complete` in line) { - this.options.debugMode && console.debug(`Received partial checkpoint complete`, syncState.targetCheckpoint); - const priority = line.partial_checkpoint_complete.priority; - const result = await this.bucketStorage.syncLocalDatabase(syncState.targetCheckpoint!, priority); - if (!result.checkpointValid) { - // This means checksums failed. Start again with a new checkpoint. - // TODO: better back-off - await new Promise((resolve) => setTimeout(resolve, 50)); - return; - } else if (!result.ready) { - // If we have pending uploads, we can't complete new checkpoints outside of priority 0. - // We'll resolve this for a complete checkpoint. - } else { - // We'll keep on downloading, but can report that this priority is synced now. - } - } else if (`checkpoint_diff` in line) { - this.options.debugMode && console.debug(`Received checkpoint diff`, syncState.targetCheckpoint); - // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint - if (syncState.targetCheckpoint == null) { - throw new Error(`Checkpoint diff without previous checkpoint`); - } - // New checkpoint - existing validated checkpoint is no longer valid - syncState.pendingValidatedCheckpoint = null; - const diff = line.checkpoint_diff; - const newBuckets = new Map(); - for (const checksum of syncState.targetCheckpoint.buckets) { - newBuckets.set(checksum.bucket, checksum); - } - for (const checksum of diff.updated_buckets) { - newBuckets.set(checksum.bucket, checksum); - } - for (const bucket of diff.removed_buckets) { - newBuckets.delete(bucket); - } - syncState.targetCheckpoint = { - last_op_id: diff.last_op_id, - buckets: [...newBuckets.values()], - write_checkpoint: diff.write_checkpoint, - streams: [] // TODO: implement streams - }; - - // TODO - // await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint); - - syncState.bucketMap = new Map(); - newBuckets.forEach((checksum, name) => - syncState.bucketMap.set(name, { - name: checksum.bucket, - priority: checksum.priority ?? FALLBACK_PRIORITY - }) - ); - - const bucketsToDelete = diff.removed_buckets; - if (bucketsToDelete.length > 0) { - console.debug(`Remove buckets`, bucketsToDelete); - } - await this.bucketStorage.removeBuckets(bucketsToDelete); - await this.bucketStorage.setTargetCheckpoint(syncState.targetCheckpoint!); - } else if (`data` in line) { - this.options.debugMode && console.debug(`Received data`, line.data); - const { data } = line; - // TODO update sync status - await this.bucketStorage.saveSyncData({ - buckets: [data] - }); - } else if (`token_expires_in` in line) { - this.options.debugMode && console.debug(`Received token expires in`, line.token_expires_in); - const { token_expires_in } = line; - - if (token_expires_in == 0) { - throw new Error(`Token already expired`); - } else if (token_expires_in < 30) { - this.invalidateCredentials(); - throw new Error(`Token will expire soon. Need to reconnect.`); - } - } else { - console.debug(`Received unknown sync line`, line); - } - } - } finally { - reader.releaseLock(); - } - } - - private async applyCheckpoint(checkpoint: Checkpoint) { - const result = await this.bucketStorage.syncLocalDatabase(checkpoint); - - if (!result.checkpointValid) { - console.debug(`Checksum mismatch in checkpoint ${checkpoint.last_op_id}, will reconnect`); - // This means checksums failed. Start again with a new checkpoint. - // TODO: better back-off - await new Promise((resolve) => setTimeout(resolve, 50)); - return { applied: false, endIteration: true }; - } else if (!result.ready) { - console.debug( - `Could not apply checkpoint ${checkpoint.last_op_id} due to local data. We will retry applying the checkpoint after that upload is completed.` - ); - - return { applied: false, endIteration: false }; - } - - this.options.debugMode && console.debug(`Applied checkpoint ${checkpoint.last_op_id}`, checkpoint); - // this.updateSyncStatus({ - // connected: true, - // lastSyncedAt: new Date(), - // dataFlow: { - // downloading: false, - // downloadProgress: null, - // downloadError: undefined, - // }, - // }) - - return { applied: true, endIteration: false }; - } -} diff --git a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts new file mode 100644 index 000000000..08f55f805 --- /dev/null +++ b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts @@ -0,0 +1,333 @@ +import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; +import { BaseObserver } from '../../utils/BaseObserver.js'; +import type { BucketStorage } from '../storage/BucketStorage.js'; +import type { + Connector, + PowerSyncCredentials, + SyncClient, + SyncClientListener, + SyncClientOptions, + SyncStatus +} from './SyncClient.js'; +import { AuthorizationError, openHttpStream, type BucketRequest } from './open-stream.js'; + +type BucketDescription = { + name: string; + priority: number; +}; + +type InternalSyncState = { + targetCheckpoint: Checkpoint | null; + // A checkpoint that has been validated but not applied (e.g. due to pending local writes) + pendingValidatedCheckpoint: Checkpoint | null; + bucketMap: Map; +}; + +// The priority we assume when we receive checkpoint lines where no priority is set. +// This is the default priority used by the sync service, but can be set to an arbitrary +// value since sync services without priorities also won't send partial sync completion +// messages. +const FALLBACK_PRIORITY = 3; + +export class SyncClientImpl extends BaseObserver implements SyncClient { + status: SyncStatus; + + protected cachedCredentials: PowerSyncCredentials | null; + protected abortController: AbortController; + + constructor(protected options: SyncClientOptions) { + super(); + this.cachedCredentials = null; + this.abortController = new AbortController(); + this.status = { + connected: false, + lastSyncedAt: null, + hasSynced: false, + connecting: false, + downloading: false, + downloadError: null + }; + } + + protected get bucketStorage(): BucketStorage { + return this.options.storage; + } + + /** + * Updates the sync status with the provided options. + * Emits to listeners for status changes. + */ + protected updateSyncStatus(options: Partial): void { + const oldStatus = { ...this.status }; + this.status = { + ...this.status, + ...options + }; + + // Only emit full status if something actually changed + if (!this.isStatusEqual(oldStatus, this.status)) { + this.iterateListeners((listener) => { + listener.statusChanged?.(this.status); + }); + } + } + + async connect(connector: Connector): Promise { + // Abort any existing connection + this.abortController.abort(); + + const controller = new AbortController(); + this.abortController = controller; + + while (!this.abortController.signal.aborted) { + try { + this.updateSyncStatus({ connecting: true }); + await this.syncIteration(connector, controller.signal); + } catch (error) { + this.updateSyncStatus({ + connected: false, + downloading: false, + downloadError: error as Error + }); + + // TODO support aborts + await new Promise((resolve) => setTimeout(resolve, this.options.connectionRetryDelayMs)); + } finally { + this.updateSyncStatus({ connecting: false }); + } + } + } + + disconnect() { + this.abortController.abort(); + } + + protected invalidateCredentials(): void { + this.cachedCredentials = null; + } + + private async collectLocalBucketState(): Promise<[BucketRequest[], Map]> { + const bucketEntries = await this.bucketStorage.getBucketStates(); + const req: BucketRequest[] = bucketEntries.map((entry) => ({ + name: entry.bucket, + after: entry.op_id + })); + const localDescriptions = new Map(); + for (const entry of bucketEntries) { + localDescriptions.set(entry.bucket, null); + } + + return [req, localDescriptions]; + } + + protected async syncIteration(connector: Connector, signal: AbortSignal): Promise { + const credentials = this.cachedCredentials ?? (await connector.fetchCredentials()); + + if (!credentials) { + throw new Error(`No credentials found`); + } + + const [bucketRequests, initBucketMap] = await this.collectLocalBucketState(); + + const stream = await openHttpStream({ + endpoint: credentials.endpoint, + token: credentials.token, + signal: signal, + clientId: await this.bucketStorage.getClientId(), + bucketPositions: bucketRequests, + systemDependencies: this.options.systemDependencies + }).catch((ex) => { + if (ex instanceof AuthorizationError) { + this.invalidateCredentials(); + } + throw ex; + }); + + let syncState: InternalSyncState = { + targetCheckpoint: null, + pendingValidatedCheckpoint: null, + bucketMap: initBucketMap + }; + + this.updateSyncStatus({ + connected: true, + connecting: false, + downloadError: null + }); + + const reader = stream.getReader(); + console.debug(`Starting sync iteration`); + try { + while (!signal.aborted) { + const { value: line, done } = await reader.read(); + if (done) break; + + // Handle various sync line types + if (`checkpoint` in line) { + this.options.debugMode && console.debug(`Received checkpoint`, line.checkpoint); + const bucketsToDelete = new Set(syncState.bucketMap.keys()); + const newBuckets = new Map(); + for (const checksum of line.checkpoint.buckets) { + newBuckets.set(checksum.bucket, { + name: checksum.bucket, + priority: checksum.priority ?? FALLBACK_PRIORITY + }); + bucketsToDelete.delete(checksum.bucket); + } + syncState = { + targetCheckpoint: line.checkpoint, + pendingValidatedCheckpoint: null, + bucketMap: newBuckets + }; + await this.bucketStorage.removeBuckets(Array.from(bucketsToDelete)); + this.updateSyncStatus({ + downloading: true + }); + } else if (`checkpoint_complete` in line) { + this.options.debugMode && console.debug(`Received checkpoint complete`, syncState.targetCheckpoint); + const result = await this.applyCheckpoint(syncState.targetCheckpoint!); + if (result.endIteration) { + return; + } else if (!result.applied) { + syncState.pendingValidatedCheckpoint = syncState.targetCheckpoint; + } else { + syncState.pendingValidatedCheckpoint = null; + // Status updated in applyCheckpoint when applied successfully + } + } else if (`partial_checkpoint_complete` in line) { + this.options.debugMode && console.debug(`Received partial checkpoint complete`, syncState.targetCheckpoint); + const priority = line.partial_checkpoint_complete.priority; + const result = await this.bucketStorage.syncLocalDatabase(syncState.targetCheckpoint!, priority); + if (!result.checkpointValid) { + // This means checksums failed. Start again with a new checkpoint. + // TODO: better back-off + await new Promise((resolve) => setTimeout(resolve, 50)); + return; + } else if (!result.ready) { + // If we have pending uploads, we can't complete new checkpoints outside of priority 0. + // We'll resolve this for a complete checkpoint. + } else { + // We'll keep on downloading, but can report that this priority is synced now. + } + } else if (`checkpoint_diff` in line) { + this.options.debugMode && console.debug(`Received checkpoint diff`, syncState.targetCheckpoint); + // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint + if (syncState.targetCheckpoint == null) { + throw new Error(`Checkpoint diff without previous checkpoint`); + } + // New checkpoint - existing validated checkpoint is no longer valid + syncState.pendingValidatedCheckpoint = null; + const diff = line.checkpoint_diff; + const newBuckets = new Map(); + for (const checksum of syncState.targetCheckpoint.buckets) { + newBuckets.set(checksum.bucket, checksum); + } + for (const checksum of diff.updated_buckets) { + newBuckets.set(checksum.bucket, checksum); + } + for (const bucket of diff.removed_buckets) { + newBuckets.delete(bucket); + } + syncState.targetCheckpoint = { + last_op_id: diff.last_op_id, + buckets: [...newBuckets.values()], + write_checkpoint: diff.write_checkpoint, + streams: [] // TODO: implement streams + }; + + this.updateSyncStatus({ + downloading: true + }); + + syncState.bucketMap = new Map(); + newBuckets.forEach((checksum, name) => + syncState.bucketMap.set(name, { + name: checksum.bucket, + priority: checksum.priority ?? FALLBACK_PRIORITY + }) + ); + + const bucketsToDelete = diff.removed_buckets; + if (bucketsToDelete.length > 0) { + console.debug(`Remove buckets`, bucketsToDelete); + } + await this.bucketStorage.removeBuckets(bucketsToDelete); + } else if (`data` in line) { + this.options.debugMode && console.debug(`Received data`, line.data); + const { data } = line; + // Update status to indicate we're downloading data + this.updateSyncStatus({ + downloading: true + }); + await this.bucketStorage.saveSyncData({ + buckets: [data] + }); + } else if (`token_expires_in` in line) { + this.options.debugMode && console.debug(`Received token expires in`, line.token_expires_in); + const { token_expires_in } = line; + + if (token_expires_in == 0) { + throw new Error(`Token already expired`); + } else if (token_expires_in < 30) { + this.invalidateCredentials(); + throw new Error(`Token will expire soon. Need to reconnect.`); + } + } else { + console.debug(`Received unknown sync line`, line); + } + } + } finally { + reader.releaseLock(); + } + } + + private async applyCheckpoint(checkpoint: Checkpoint) { + const result = await this.bucketStorage.syncLocalDatabase(checkpoint); + + if (!result.checkpointValid) { + console.debug(`Checksum mismatch in checkpoint ${checkpoint.last_op_id}, will reconnect`); + // This means checksums failed. Start again with a new checkpoint. + // TODO: better back-off + await new Promise((resolve) => setTimeout(resolve, 50)); + return { applied: false, endIteration: true }; + } else if (!result.ready) { + console.debug( + `Could not apply checkpoint ${checkpoint.last_op_id} due to local data. We will retry applying the checkpoint after that upload is completed.` + ); + + return { applied: false, endIteration: false }; + } + + this.options.debugMode && console.debug(`Applied checkpoint ${checkpoint.last_op_id}`, checkpoint); + this.updateSyncStatus({ + hasSynced: true, + downloading: false, + lastSyncedAt: new Date() + }); + + return { applied: true, endIteration: false }; + } + + /** + * Compares two status objects to determine if they are equal. + * Handles Error objects properly by serializing their properties. + */ + private isStatusEqual(a: SyncStatus, b: SyncStatus): boolean { + /** + * By default Error objects are serialized to an empty object. + * This replaces Errors with more useful information before serialization. + */ + const replacer = (_: string, value: any) => { + if (value instanceof Error) { + return { + name: value.name, + message: value.message, + stack: value.stack + }; + } + return value; + }; + + return JSON.stringify(a, replacer) === JSON.stringify(b, replacer); + } +} diff --git a/packages/lite-sdk/src/index.ts b/packages/lite-sdk/src/index.ts index 04e1801ec..dbe33b6a5 100644 --- a/packages/lite-sdk/src/index.ts +++ b/packages/lite-sdk/src/index.ts @@ -2,6 +2,7 @@ export * from './client/storage/BucketStorage.js'; export * from './client/storage/MemoryBucketStorageImpl.js'; export * from './client/storage/storage-types/storage-types-index.js'; export * from './client/sync/SyncClient.js'; +export * from './client/sync/SyncClientImpl.js'; export * from './client/sync/ndjson.js'; export * from './client/sync/open-stream.js'; export * from './client/system/SystemDependencies.js'; diff --git a/packages/lite-sdk/src/utils/BaseObserver.ts b/packages/lite-sdk/src/utils/BaseObserver.ts new file mode 100644 index 000000000..fa8067226 --- /dev/null +++ b/packages/lite-sdk/src/utils/BaseObserver.ts @@ -0,0 +1,41 @@ +export interface Disposable { + dispose: () => Promise | void; +} + +export type BaseListener = Record any) | undefined>; + +export interface BaseObserverInterface { + registerListener(listener: Partial): () => void; +} + +export class BaseObserver implements BaseObserverInterface { + protected listeners = new Set>(); + + constructor() {} + + dispose(): void { + this.listeners.clear(); + } + + /** + * Register a listener for updates to the PowerSync client. + */ + registerListener(listener: Partial): () => void { + this.listeners.add(listener); + return () => { + this.listeners.delete(listener); + }; + } + + iterateListeners(cb: (listener: Partial) => any) { + for (const listener of this.listeners) { + cb(listener); + } + } + + async iterateAsyncListeners(cb: (listener: Partial) => Promise) { + for (let i of Array.from(this.listeners.values())) { + await cb(i); + } + } +} diff --git a/packages/lite-sdk/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts index ad0bd8300..408bb7a86 100644 --- a/packages/lite-sdk/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -1,7 +1,8 @@ import { describe, it } from 'vitest'; import { MemoryBucketStorageImpl } from '../src/client/storage/MemoryBucketStorageImpl.js'; import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; -import { SyncClientImpl, type Connector } from '../src/client/sync/SyncClient.js'; +import { type Connector } from '../src/client/sync/SyncClient.js'; +import { SyncClientImpl } from '../src/client/sync/SyncClientImpl.js'; import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/system/SystemDependencies.js'; describe(`PowerSync Lite`, { timeout: Infinity }, () => { From 4a83028870379d7adaaa3b78e437e2510a86907f Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 5 Nov 2025 17:53:25 +0200 Subject: [PATCH 09/13] Add basic tests --- .../lite-sdk/src/client/sync/SyncClient.ts | 17 + .../src/client/sync/SyncClientImpl.ts | 5 +- .../lite-sdk/src/client/sync/open-stream.ts | 13 +- .../lite-sdk/tests/powersync-lite.test.ts | 2 +- packages/lite-sdk/tests/sync-client.test.ts | 310 ++++++++++++++++++ packages/lite-sdk/tests/utils.ts | 213 ++++++++++++ 6 files changed, 547 insertions(+), 13 deletions(-) create mode 100644 packages/lite-sdk/tests/sync-client.test.ts create mode 100644 packages/lite-sdk/tests/utils.ts diff --git a/packages/lite-sdk/src/client/sync/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts index a9a873c87..e8a85e018 100644 --- a/packages/lite-sdk/src/client/sync/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -1,3 +1,4 @@ +import type { BucketRequest, StreamingSyncLine } from '@powersync/service-core'; import { BaseListener, BaseObserverInterface, Disposable } from '../../utils/BaseObserver.js'; import type { BucketStorage } from '../storage/BucketStorage.js'; import type { SystemDependencies } from '../system/SystemDependencies.js'; @@ -79,6 +80,20 @@ export interface SyncClient extends BaseObserverInterface, D disconnect: () => void; } +export type StreamOptions = { + endpoint: string; + token: string; + clientId: string | undefined; + signal: AbortSignal | undefined; + bucketPositions: BucketRequest[]; + systemDependencies: SystemDependencies; +}; + +/** + * Function type for opening a sync stream. Can be overridden in tests. + */ +export type StreamOpener = (options: StreamOptions) => Promise>; + /** * Configuration options for creating a SyncClient instance. */ @@ -93,4 +108,6 @@ export type SyncClientOptions = { storage: BucketStorage; /** System-level dependencies (HTTP client, timers, etc.) required for sync operations. */ systemDependencies: SystemDependencies; + /** Optional function to open a sync stream. Defaults to openHttpStream. Used for testing. */ + streamOpener?: StreamOpener; }; diff --git a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts index 08f55f805..89f6e8ff7 100644 --- a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts +++ b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts @@ -4,6 +4,7 @@ import type { BucketStorage } from '../storage/BucketStorage.js'; import type { Connector, PowerSyncCredentials, + StreamOpener, SyncClient, SyncClientListener, SyncClientOptions, @@ -34,6 +35,7 @@ export class SyncClientImpl extends BaseObserver implements protected cachedCredentials: PowerSyncCredentials | null; protected abortController: AbortController; + protected openStreamFn: StreamOpener; constructor(protected options: SyncClientOptions) { super(); @@ -47,6 +49,7 @@ export class SyncClientImpl extends BaseObserver implements downloading: false, downloadError: null }; + this.openStreamFn = options.streamOpener ?? openHttpStream; } protected get bucketStorage(): BucketStorage { @@ -129,7 +132,7 @@ export class SyncClientImpl extends BaseObserver implements const [bucketRequests, initBucketMap] = await this.collectLocalBucketState(); - const stream = await openHttpStream({ + const stream = await this.openStreamFn({ endpoint: credentials.endpoint, token: credentials.token, signal: signal, diff --git a/packages/lite-sdk/src/client/sync/open-stream.ts b/packages/lite-sdk/src/client/sync/open-stream.ts index af67af3e8..4585754b9 100644 --- a/packages/lite-sdk/src/client/sync/open-stream.ts +++ b/packages/lite-sdk/src/client/sync/open-stream.ts @@ -1,5 +1,5 @@ import type { StreamingSyncLine, StreamingSyncRequest } from '@powersync/service-core'; -import type { SystemDependencies } from '../system/SystemDependencies.js'; +import type { StreamOptions } from './SyncClient.js'; import { ndjsonStream } from './ndjson.js'; export class AuthorizationError extends Error { @@ -24,17 +24,8 @@ export interface BucketRequest { after: string; } -export type SyncOptions = { - endpoint: string; - token: string; - clientId: string | undefined; - signal: AbortSignal | undefined; - bucketPositions: BucketRequest[]; - systemDependencies: SystemDependencies; -}; - // TODO This currently uses NDJSON streaming. We should add binary streaming also -export async function openHttpStream(options: SyncOptions): Promise> { +export async function openHttpStream(options: StreamOptions): Promise> { const streamRequest: StreamingSyncRequest = { raw_data: true, client_id: options.clientId, diff --git a/packages/lite-sdk/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts index 408bb7a86..caba4aa53 100644 --- a/packages/lite-sdk/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -7,7 +7,7 @@ import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/system/SystemDependen describe(`PowerSync Lite`, { timeout: Infinity }, () => { describe(`Connection`, () => { - it(`should connect to a PowerSync server`, async () => { + it.skip(`should connect to a PowerSync server`, async () => { const connector = { fetchCredentials: async () => { const tokenResponse = await fetch(`http://localhost:6060/api/auth/token`, { diff --git a/packages/lite-sdk/tests/sync-client.test.ts b/packages/lite-sdk/tests/sync-client.test.ts new file mode 100644 index 000000000..227156bb9 --- /dev/null +++ b/packages/lite-sdk/tests/sync-client.test.ts @@ -0,0 +1,310 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; +import type { Connector, PowerSyncCredentials } from '../src/client/sync/SyncClient.js'; +import { MockStreamFactory, checkpoint, createTestSyncClient, waitForSyncStatus } from './utils.js'; + +describe('SyncClient', () => { + let mockStreamFactory: MockStreamFactory; + const defaultOperationsHandler: SyncOperationsHandler = { + processOperations: async () => { + // No-op for tests + } + }; + + beforeEach(() => { + mockStreamFactory = new MockStreamFactory(); + }); + + afterEach(() => { + mockStreamFactory.closeAll(); + }); + + it('should connect and receive checkpoint', async () => { + const client = createTestSyncClient({}, mockStreamFactory, { + processOperations: async () => { + // No-op for tests + } + }); + + const connector: Connector = { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } + }; + + client.connect(connector); + + // Wait for connection + await waitForSyncStatus(client, (status) => status.connected === true, 1000); + + // Send checkpoint + const controllers = mockStreamFactory.getControllers(); + expect(controllers.length).eq(1); + + const checkpointLine = checkpoint({ + last_op_id: 0, + buckets: [] + }); + + mockStreamFactory.pushLine(checkpointLine); + + // Wait for checkpoint to be processed + await waitForSyncStatus(client, (status) => status.downloading === true, 1000); + + // Send checkpoint complete + mockStreamFactory.pushLine({ + checkpoint_complete: { last_op_id: '0' } + }); + + // Wait for sync to complete + await waitForSyncStatus(client, (status) => status.hasSynced === true, 2000); + + client.disconnect(); + }); + + it('should set last sync time after checkpoint complete', async () => { + const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); + const connector: Connector = { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } + }; + + client.connect(connector); + await waitForSyncStatus(client, (status) => status.connected === true, 1000); + + const now = Date.now(); + + // Send checkpoint and complete + mockStreamFactory.pushLine( + checkpoint({ + last_op_id: 0, + buckets: [] + }) + ); + + await waitForSyncStatus(client, (status) => status.downloading === true, 1000); + + mockStreamFactory.pushLine({ + checkpoint_complete: { last_op_id: '0' } + }); + + await waitForSyncStatus(client, (status) => status.hasSynced === true && status.lastSyncedAt !== null, 2000); + + const status = client.status; + expect(status.lastSyncedAt).not.toBeNull(); + if (status.lastSyncedAt) { + const lastSyncedAt = status.lastSyncedAt.getTime(); + // The reported time should be close to the current time (5s is generous) + expect(Math.abs(lastSyncedAt - now)).toBeLessThan(5000); + } + + client.disconnect(); + }); + + it('should handle data lines', async () => { + const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); + const connector: Connector = { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } + }; + + client.connect(connector); + await waitForSyncStatus(client, (status) => status.connected === true, 1000); + + // Send checkpoint + mockStreamFactory.pushLine( + checkpoint({ + last_op_id: 0, + buckets: [ + { + bucket: 'test-bucket', + count: 1, + checksum: 0, + priority: 3, + subscriptions: [] + } + ] + }) + ); + + await waitForSyncStatus(client, (status) => status.downloading === true, 1000); + + // Send data line + mockStreamFactory.pushLine({ + data: { + bucket: 'test-bucket', + data: [ + { + op_id: '1', + op: 'PUT', + object_id: 'obj1', + object_type: 'test', + checksum: 0, + data: '{"test": "data"}' + } + ], + has_more: false, + after: '0', + next_after: '1' + } + }); + + // Send checkpoint complete - should match the highest op_id from the data + mockStreamFactory.pushLine({ + checkpoint_complete: { last_op_id: '1' } + }); + + await waitForSyncStatus(client, (status) => status.hasSynced === true, 2000); + + client.disconnect(); + }); + + it('should handle checkpoint diff', async () => { + const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); + const connector: Connector = { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } + }; + + client.connect(connector); + await waitForSyncStatus(client, (status) => status.connected === true, 1000); + + // Initial checkpoint + mockStreamFactory.pushLine( + checkpoint({ + last_op_id: 0, + buckets: [ + { + bucket: 'bucket-a', + count: 5, + checksum: 0, + priority: 3, + subscriptions: [] + } + ] + }) + ); + + await waitForSyncStatus(client, (status) => status.downloading === true, 1000); + + // Send checkpoint diff + mockStreamFactory.pushLine({ + checkpoint_diff: { + last_op_id: '2', + updated_buckets: [ + { + bucket: 'bucket-a', + count: 7, + checksum: 0, + priority: 3, + subscriptions: [] + } + ], + removed_buckets: [] + } + }); + + await waitForSyncStatus(client, (status) => status.downloading === true, 1000); + + // Complete checkpoint + mockStreamFactory.pushLine({ + checkpoint_complete: { last_op_id: '2' } + }); + + await waitForSyncStatus(client, (status) => status.hasSynced === true, 2000); + + client.disconnect(); + }); + + it('should handle partial checkpoint complete', async () => { + const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); + const connector: Connector = { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } + }; + + client.connect(connector); + await waitForSyncStatus(client, (status) => status.connected === true, 1000); + + // Send checkpoint with multiple buckets + mockStreamFactory.pushLine( + checkpoint({ + last_op_id: 0, + buckets: [ + { + bucket: 'bucket-a', + count: 5, + checksum: 0, + priority: 0, + subscriptions: [] + }, + { + bucket: 'bucket-b', + count: 5, + checksum: 0, + priority: 2, + subscriptions: [] + } + ] + }) + ); + + await waitForSyncStatus(client, (status) => status.downloading === true, 1000); + + // Send partial checkpoint complete for priority 0 + mockStreamFactory.pushLine({ + partial_checkpoint_complete: { + last_op_id: '0', + priority: 0 + } + }); + + // Send checkpoint complete + mockStreamFactory.pushLine({ + checkpoint_complete: { last_op_id: '0' } + }); + + await waitForSyncStatus(client, (status) => status.hasSynced === true, 2000); + + client.disconnect(); + }); + + it('should track connection status', async () => { + const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); + const connector: Connector = { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } + }; + + expect(client.status.connected).toBe(false); + expect(client.status.connecting).toBe(false); + + client.connect(connector); + + // Should be connecting + await waitForSyncStatus(client, (status) => status.connecting === true, 1000); + }); +}); diff --git a/packages/lite-sdk/tests/utils.ts b/packages/lite-sdk/tests/utils.ts new file mode 100644 index 000000000..47568881b --- /dev/null +++ b/packages/lite-sdk/tests/utils.ts @@ -0,0 +1,213 @@ +import type { StreamingSyncCheckpoint, StreamingSyncLine, StreamingSyncRequest } from '@powersync/service-core'; +import { MemoryBucketStorageImpl } from '../src/client/storage/MemoryBucketStorageImpl.js'; +import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; +import type { StreamOptions, SyncClient, SyncClientOptions, SyncStatus } from '../src/client/sync/SyncClient.js'; +import { SyncClientImpl } from '../src/client/sync/SyncClientImpl.js'; +import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/system/SystemDependencies.js'; + +export interface MockStreamController { + /** + * Push a sync line to the stream + */ + pushLine(line: StreamingSyncLine): void; + /** + * Close the stream + */ + close(): void; + /** + * Get the request that was made to open the stream + */ + getRequest(): StreamingSyncRequest | null; +} + +/** + * Mock stream factory that tracks stream controllers and allows manual control + */ +export class MockStreamFactory { + private listeners: Array<{ + request: StreamingSyncRequest; + controller: ReadableStreamDefaultController; + mockController: MockStreamController; + }> = []; + + /** + * The last created stream controller (most recent) + */ + get lastController(): MockStreamController | null { + return this.listeners.length > 0 ? this.listeners[this.listeners.length - 1].mockController : null; + } + + /** + * Get all active stream controllers + */ + getControllers(): MockStreamController[] { + return this.listeners.map((l) => l.mockController); + } + + /** + * Get the requests made to open streams + */ + getRequests(): StreamingSyncRequest[] { + return this.listeners.map((l) => l.request); + } + + /** + * Push a line to all active streams + */ + pushLine(line: StreamingSyncLine): void { + for (const listener of this.listeners) { + listener.controller.enqueue(line); + } + } + + /** + * Push a line to the last created stream only + */ + pushLineToLast(line: StreamingSyncLine): void { + const last = this.listeners[this.listeners.length - 1]; + if (last) { + last.controller.enqueue(line); + } + } + + /** + * Close all active streams + */ + closeAll(): void { + for (const listener of this.listeners) { + listener.controller.close(); + } + this.listeners.length = 0; + } + + /** + * Create a mock stream that will be tracked by this factory + */ + createStream(options: StreamOptions): ReadableStream { + const streamRequest: StreamingSyncRequest = { + raw_data: true, + client_id: options.clientId, + buckets: options.bucketPositions + }; + + let streamController: ReadableStreamDefaultController | null = null; + + const mockController: MockStreamController = { + pushLine(line: StreamingSyncLine) { + if (streamController) { + streamController.enqueue(line); + } + }, + close() { + if (streamController) { + streamController.close(); + } + }, + getRequest() { + return streamRequest; + } + }; + + const stream = new options.systemDependencies.ReadableStream({ + start: (controller) => { + streamController = controller; + this.listeners.push({ + request: streamRequest, + controller, + mockController + }); + }, + cancel: () => { + const index = this.listeners.findIndex((l) => l.controller === streamController); + if (index !== -1) { + this.listeners.splice(index, 1); + } + } + }); + + return stream; + } +} + +/** + * Helper function to create a test sync client with a mock stream factory + */ +export function createTestSyncClient( + partialOptions: Partial, + mockFactory: MockStreamFactory, + operationsHandler: SyncOperationsHandler +): SyncClient { + const systemDependencies = partialOptions.systemDependencies ?? DEFAULT_SYSTEM_DEPENDENCIES(); + + const defaultOptions: SyncClientOptions = { + connectionRetryDelayMs: 100, + uploadRetryDelayMs: 100, + debugMode: false, + storage: + partialOptions.storage ?? + new MemoryBucketStorageImpl({ + operationsHandlers: [operationsHandler], + systemDependencies + }), + systemDependencies, + // Create an arrow function that uses the mock factory + streamOpener: async (options: StreamOptions) => mockFactory.createStream(options), + ...partialOptions + }; + + return new SyncClientImpl(defaultOptions); +} + +/** + * Helper function to wait for a specific sync status condition + */ +export function waitForSyncStatus( + client: SyncClient, + matcher: (status: SyncStatus) => boolean, + timeout = 5000 +): Promise { + return new Promise((resolve, reject) => { + if (matcher(client.status)) { + return resolve(client.status); + } + + const timeoutId = setTimeout(() => { + dispose(); + reject(new Error(`Timeout waiting for sync status`)); + }, timeout); + + const dispose = client.registerListener({ + statusChanged: (status) => { + try { + if (matcher(status)) { + clearTimeout(timeoutId); + dispose(); + resolve(status); + } + } catch (e) { + clearTimeout(timeoutId); + dispose(); + reject(e); + } + } + }); + }); +} + +/** + * Helper to create a checkpoint sync line + */ +export function checkpoint(options: { + last_op_id: number | string; + buckets?: any[]; + write_checkpoint?: string | null; +}): StreamingSyncCheckpoint { + return { + checkpoint: { + last_op_id: `${options.last_op_id}`, + buckets: options.buckets ?? [], + write_checkpoint: options.write_checkpoint ?? undefined, + streams: [] + } + }; +} From a24258e119449602720c36f838fb474661a7a28e Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 09:10:52 +0200 Subject: [PATCH 10/13] wip: lite sdk --- .../src/client/AbstractPowerSyncDatabase.ts | 9 +- .../src/client/storage/BucketStorage.ts | 9 + .../client/storage/MemoryBucketStorageImpl.ts | 520 ++++++++++-------- .../client/storage/SyncOperationsHandler.ts | 7 +- .../src/client/storage/storage-types/ps_tx.ts | 17 - .../storage-types/storage-types-index.ts | 1 - .../lite-sdk/src/client/sync/Connector.ts | 52 ++ .../lite-sdk/src/client/sync/CrudManager.ts | 31 ++ .../lite-sdk/src/client/sync/SyncClient.ts | 45 +- .../src/client/sync/SyncClientImpl.ts | 132 ++++- packages/lite-sdk/src/index.ts | 1 + packages/lite-sdk/src/utils/Lock.ts | 24 + .../lite-sdk/tests/powersync-lite.test.ts | 55 +- packages/lite-sdk/tests/sync-client.test.ts | 69 +-- packages/lite-sdk/tsconfig.json | 5 +- pnpm-lock.yaml | 38 +- 16 files changed, 619 insertions(+), 396 deletions(-) delete mode 100644 packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts create mode 100644 packages/lite-sdk/src/client/sync/Connector.ts create mode 100644 packages/lite-sdk/src/client/sync/CrudManager.ts create mode 100644 packages/lite-sdk/src/utils/Lock.ts diff --git a/packages/common/src/client/AbstractPowerSyncDatabase.ts b/packages/common/src/client/AbstractPowerSyncDatabase.ts index 6c7ed1901..ba4cc0994 100644 --- a/packages/common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/common/src/client/AbstractPowerSyncDatabase.ts @@ -1,4 +1,3 @@ -import { Mutex } from 'async-mutex'; import { EventIterator } from 'event-iterator'; import Logger, { ILogger } from 'js-logger'; import { @@ -38,13 +37,13 @@ import { type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js'; +import { CoreSyncStatus, coreStatusToJs } from './sync/stream/core-instruction.js'; +import { SyncStream } from './sync/sync-streams.js'; import { TriggerManager } from './triggers/TriggerManager.js'; import { TriggerManagerImpl } from './triggers/TriggerManagerImpl.js'; import { DEFAULT_WATCH_THROTTLE_MS, WatchCompatibleQuery } from './watched/WatchedQuery.js'; import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js'; import { WatchedQueryComparator } from './watched/processors/comparators.js'; -import { coreStatusToJs, CoreSyncStatus } from './sync/stream/core-instruction.js'; -import { SyncStream } from './sync/sync-streams.js'; export interface DisconnectAndClearOptions { /** When set to false, data in local-only tables is preserved. */ @@ -195,7 +194,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver Promise; + /** + * Updates the local target checkpoint after CRUD items have been uploaded. + * @param writeCheckpoint - Optional write checkpoint to set the local target to + * - If provided, only updates if all CRUD items have been uploaded + * - If not provided, sets the local target to the max op id + * @returns void + */ + handleCrudUploaded(writeCheckpoint?: string): Promise; + /** * Update the local target checkpoint atomically. * Only updates if no new CRUD data has been added since the checkpoint was obtained. diff --git a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index e0f405498..0d7f84fa2 100644 --- a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -1,13 +1,13 @@ import type { BucketState, Checkpoint } from '@powersync/service-core'; +import { Lock } from '../../utils/Lock.js'; +import { CrudManager } from '../sync/CrudManager.js'; import { SystemDependencies } from '../system/SystemDependencies.js'; import type { BucketStorage, SyncDataBatch } from './BucketStorage.js'; import type { SyncOperation, SyncOperationsHandler } from './SyncOperationsHandler.js'; import { constructKey, toStringOrNull } from './bucketHelpers.js'; import { addChecksums, normalizeChecksum, subtractChecksums } from './checksumUtils.js'; import type { PSBucket } from './storage-types/ps_buckets.js'; -import type { PSCrud } from './storage-types/ps_crud.js'; import type { PSOplog } from './storage-types/ps_oplog.js'; -import type { PSTx } from './storage-types/ps_tx.js'; import { PsUpdatedRows } from './storage-types/ps_updated_rows.js'; export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR'; @@ -15,6 +15,7 @@ export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR'; export const MAX_OP_ID = '9223372036854775807'; export type MemoryBucketStorageImplOptions = { + crudManager?: CrudManager; /** Array of handlers for processing sync operations collected from the protocol */ operationsHandlers: SyncOperationsHandler[]; systemDependencies: SystemDependencies; @@ -24,35 +25,26 @@ export class MemoryBucketStorageImpl implements BucketStorage { protected ps_buckets: PSBucket[]; protected ps_oplog: PSOplog[]; protected ps_updated_rows: PsUpdatedRows[]; - // TODO: ps_crud implementation - ignoring for now - // ps_crud tracks client-side changes that need to be uploaded to the server - protected ps_crud: PSCrud[]; protected clientId: string; - protected ps_tx: PSTx; protected lastSyncedAt: Date | null; - - // Track sequence/counter for ps_crud (simulates SQLite sequence) - // TODO: This should be properly managed when ps_crud is implemented - protected ps_crud_seq: number = 0; + protected lock: Lock; /** Handlers for processing sync operations collected from the protocol */ protected operationsHandlers: SyncOperationsHandler[]; + protected crudManager?: CrudManager; constructor(protected options: MemoryBucketStorageImplOptions) { this.operationsHandlers = options.operationsHandlers; + this.crudManager = options.crudManager; + this.lock = new Lock(); this.initDefaultState(); } protected initDefaultState() { this.ps_buckets = []; this.ps_oplog = []; - this.ps_tx = { - current_tx: null, - next_tx: null - }; this.ps_updated_rows = []; - this.ps_crud = []; this.lastSyncedAt = null; this.clientId = this.options.systemDependencies.crypto.randomUUID(); } @@ -72,107 +64,148 @@ export class MemoryBucketStorageImpl implements BucketStorage { } async getBucketStates(): Promise> { - return this.ps_buckets.map((bucket) => ({ - bucket: bucket.name, - op_id: bucket.last_op.toString(), - target_op_id: bucket.target_op.toString(), - add_checksum: bucket.add_checksum.toString(), - op_checksum: bucket.op_checksum.toString(), - pending_delete: bucket.pending_delete - })); + return await this.lock.runExclusive(async () => { + return this.ps_buckets.map((bucket) => ({ + bucket: bucket.name, + op_id: bucket.last_op.toString(), + target_op_id: bucket.target_op.toString(), + add_checksum: bucket.add_checksum.toString(), + op_checksum: bucket.op_checksum.toString(), + pending_delete: bucket.pending_delete + })); + }); } async hasCompletedSync(): Promise { - return !!this.ps_buckets.find((b) => b.last_applied_op > 0); + return await this.lock.runExclusive(async () => { + return !!this.ps_buckets.find((b) => b.last_applied_op > 0); + }); + } + + async handleCrudUploaded(writeCheckpoint?: string) { + await this.lock.runExclusive(async () => { + if (writeCheckpoint) { + // Custom write checkpoints are used + // only update if all crud items + if (await this.crudManager?.hasCrud()) { + return; + } + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (!localBucket) { + throw new Error('Local bucket not found'); + } + localBucket.target_op = normalizeChecksum(writeCheckpoint); + } else { + // Set the target to the max op id + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (!localBucket) { + throw new Error('Local bucket not found'); + } + localBucket.target_op = normalizeChecksum(MAX_OP_ID); + } + }); } async updateLocalTarget(cb: () => Promise): Promise { + /** + * This is typically called after completing uploads. + * An upload handler will typically complete crud transactions before the upload process is completed. + * If not using custom write checkpoints: + * - The `complete` method will set the $local target to the max op id. + * - If the target is not the max op id, we should not proceed. + * - We get a checkpoint from the callback + * - If there are no pending CRUD items, we set the target checkpoint to this value + * If using custom write checkpoints: + * - The complete method will set the target checkpoint to the custom write checkpoint (if there are no pending CRUD items) + * - We return early here since the value is not the max op id + */ + // Find the '$local' bucket and check if target_op = MAX_OP_ID // SQL: SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER) // TODO: maybe store local state separately - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (!localBucket) { - // Nothing to update - return false; - } + const shouldProceed = await this.lock.runExclusive(async () => { + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (!localBucket) { + // Nothing to update + return false; + } + + const maxOpIdBigint = BigInt(MAX_OP_ID); + if (localBucket.target_op !== maxOpIdBigint) { + // target_op is not MAX_OP_ID, nothing to update + return false; + } + // Should proceed + return true; + }); - const maxOpIdBigint = BigInt(MAX_OP_ID); - if (localBucket.target_op !== maxOpIdBigint) { - // target_op is not MAX_OP_ID, nothing to update + if (!shouldProceed) { return false; } - // Save current sequence state before calling callback - // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' - const seqBefore = this.ps_crud_seq; - // Call the callback to get the new opId const opId = await cb(); // Within transaction equivalent (in memory, we do checks atomically): // 1. Check if ps_crud has any data (SELECT 1 FROM ps_crud LIMIT 1) // If it has data, new data was uploaded since write checkpoint - need new write checkpoint - if (this.ps_crud.length > 0) { + if (await this.crudManager?.hasCrud()) { // Still has crud data, can't update checkpoint return false; } - // TODO crud - // // 2. Verify sequence hasn't changed (SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud') - // // If sequence changed, new items were added even if later deleted - // if (this.ps_crud_seq !== seqBefore) { - // // New crud data may have been uploaded since we got the checkpoint. Abort. - // return false; - // } - - // // Verify sequence exists (should not be empty) - // // SQL: SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud' - // if (this.ps_crud_seq === 0 && seqBefore === 0) { - // // This shouldn't happen if we got past the first check, but defensive - // throw new Error('SQLite Sequence should not be empty'); - // } - - // Update the '$local' bucket's target_op to the new opId - // SQL: UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local' - const opIdBigint = normalizeChecksum(opId); - localBucket.target_op = opIdBigint; + // This is typically called after completing uploads which can + // be concurrent. + return await this.lock.runExclusive(async () => { + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (!localBucket) { + // Nothing to update + return false; + } - return true; + // Update the '$local' bucket's target_op to the new opId + // SQL: UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local' + const opIdBigint = normalizeChecksum(opId); + localBucket.target_op = opIdBigint; + return true; + }); } async removeBuckets(buckets: Array): Promise { - for (const bucketName of buckets) { - // Find bucket by name and get its id - const bucketIndex = this.ps_buckets.findIndex((b) => b.name === bucketName); - if (bucketIndex === -1) { - // Bucket doesn't exist, nothing to do - continue; - } + await this.lock.runExclusive(async () => { + for (const bucketName of buckets) { + // Find bucket by name and get its id + const bucketIndex = this.ps_buckets.findIndex((b) => b.name === bucketName); + if (bucketIndex === -1) { + // Bucket doesn't exist, nothing to do + continue; + } - const bucket = this.ps_buckets[bucketIndex]; - const bucketId = bucket.id; - - // Add all rows from ps_oplog for this bucket to ps_updated_rows - // (INSERT OR IGNORE logic - only add if not already exists) - const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); - for (const op of bucketOps) { - if (op.row_type && op.row_id) { - const exists = this.ps_updated_rows.some((row) => row.row_type === op.row_type && row.row_id === op.row_id); - if (!exists) { - this.ps_updated_rows.push({ - row_type: op.row_type, - row_id: op.row_id - }); + const bucket = this.ps_buckets[bucketIndex]; + const bucketId = bucket.id; + + // Add all rows from ps_oplog for this bucket to ps_updated_rows + // (INSERT OR IGNORE logic - only add if not already exists) + const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); + for (const op of bucketOps) { + if (op.row_type && op.row_id) { + const exists = this.ps_updated_rows.some((row) => row.row_type === op.row_type && row.row_id === op.row_id); + if (!exists) { + this.ps_updated_rows.push({ + row_type: op.row_type, + row_id: op.row_id + }); + } } } - } - // Delete all rows from ps_oplog for this bucket - this.ps_oplog = this.ps_oplog.filter((op) => op.bucket !== bucketId); + // Delete all rows from ps_oplog for this bucket + this.ps_oplog = this.ps_oplog.filter((op) => op.bucket !== bucketId); - // Delete the bucket from ps_buckets - this.ps_buckets.splice(bucketIndex, 1); - } + // Delete the bucket from ps_buckets + this.ps_buckets.splice(bucketIndex, 1); + } + }); } /** @@ -200,140 +233,142 @@ export class MemoryBucketStorageImpl implements BucketStorage { } async saveSyncData(batch: SyncDataBatch): Promise { - for (const bucketData of batch.buckets) { - const bucketName = bucketData.bucket; - const bucket = this.findOrCreateBucket(bucketName); - const bucketId = bucket.id; - const lastAppliedOp = bucket.last_applied_op; - - // Optimization for initial sync - we can avoid persisting individual REMOVE - // operations when last_applied_op = 0 - let isEmpty = lastAppliedOp === 0n; - - let lastOp: bigint | null = null; - let addChecksum = 0n; - let opChecksum = 0n; // Start at 0, will be added to bucket's op_checksum at the end - let addedOps = 0; - - for (const line of bucketData.data) { - // line might be OplogEntry or ProtocolOplogData - access properties directly - const opId = normalizeChecksum(line.op_id); - const opType = line.op as OpType; - const objectType = line.object_type; - const objectId = line.object_id; - const subkey = line.subkey; - const checksum = normalizeChecksum(line.checksum); - const opData = toStringOrNull(line.data); - - lastOp = opId; - addedOps += 1; - - if (opType === 'PUT' || opType === 'REMOVE') { - const key = constructKey(objectType, objectId, subkey); - - // Supersede (delete) previous operations with the same key - const supersededOps: PSOplog[] = []; - const remainingOps: PSOplog[] = []; - - for (const oplog of this.ps_oplog) { - if (oplog.bucket === bucketId && oplog.key === key) { - supersededOps.push(oplog); - // Add superseded checksum to add_checksum - addChecksum = addChecksums(addChecksum, oplog.hash); - // Subtract superseded checksum from op_checksum - opChecksum = subtractChecksums(opChecksum, oplog.hash); - } else { - remainingOps.push(oplog); + await this.lock.runExclusive(async () => { + for (const bucketData of batch.buckets) { + const bucketName = bucketData.bucket; + const bucket = this.findOrCreateBucket(bucketName); + const bucketId = bucket.id; + const lastAppliedOp = bucket.last_applied_op; + + // Optimization for initial sync - we can avoid persisting individual REMOVE + // operations when last_applied_op = 0 + let isEmpty = lastAppliedOp === 0n; + + let lastOp: bigint | null = null; + let addChecksum = 0n; + let opChecksum = 0n; // Start at 0, will be added to bucket's op_checksum at the end + let addedOps = 0; + + for (const line of bucketData.data) { + // line might be OplogEntry or ProtocolOplogData - access properties directly + const opId = normalizeChecksum(line.op_id); + const opType = line.op as OpType; + const objectType = line.object_type; + const objectId = line.object_id; + const subkey = line.subkey; + const checksum = normalizeChecksum(line.checksum); + const opData = toStringOrNull(line.data); + + lastOp = opId; + addedOps += 1; + + if (opType === 'PUT' || opType === 'REMOVE') { + const key = constructKey(objectType, objectId, subkey); + + // Supersede (delete) previous operations with the same key + const supersededOps: PSOplog[] = []; + const remainingOps: PSOplog[] = []; + + for (const oplog of this.ps_oplog) { + if (oplog.bucket === bucketId && oplog.key === key) { + supersededOps.push(oplog); + // Add superseded checksum to add_checksum + addChecksum = addChecksums(addChecksum, oplog.hash); + // Subtract superseded checksum from op_checksum + opChecksum = subtractChecksums(opChecksum, oplog.hash); + } else { + remainingOps.push(oplog); + } } - } - - // Update ps_oplog by removing superseded operations - this.ps_oplog = remainingOps; - // Check if we superseded an operation (only skip if bucket was empty) - const superseded = supersededOps.length > 0 && !isEmpty; - - if (opType === 'REMOVE') { - const shouldSkipRemove = !superseded; + // Update ps_oplog by removing superseded operations + this.ps_oplog = remainingOps; + + // Check if we superseded an operation (only skip if bucket was empty) + const superseded = supersededOps.length > 0 && !isEmpty; + + if (opType === 'REMOVE') { + const shouldSkipRemove = !superseded; + + addChecksum = addChecksums(addChecksum, checksum); + + if (!shouldSkipRemove) { + if (objectType && objectId) { + // Insert into ps_updated_rows (or ignore if already exists) + const exists = this.ps_updated_rows.some( + (row) => row.row_type === objectType && row.row_id === objectId + ); + if (!exists) { + this.ps_updated_rows.push({ + row_type: objectType, + row_id: objectId + }); + } + } + } + continue; + } + // Handle PUT operation + const newOplog: PSOplog = { + bucket: bucketId, + op_id: opId, + key: key || null, + row_type: objectType || null, + row_id: objectId || null, + data: opData, + hash: checksum // Already normalized to bigint + }; + + this.ps_oplog.push(newOplog); + opChecksum = addChecksums(opChecksum, checksum); + } else if (opType === 'MOVE') { addChecksum = addChecksums(addChecksum, checksum); - - if (!shouldSkipRemove) { - if (objectType && objectId) { - // Insert into ps_updated_rows (or ignore if already exists) + } else if (opType === 'CLEAR') { + // Any remaining PUT operations should get an implicit REMOVE + // Add all rows from ps_oplog to ps_updated_rows for this bucket + const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); + for (const op of bucketOps) { + if (op.row_type && op.row_id) { const exists = this.ps_updated_rows.some( - (row) => row.row_type === objectType && row.row_id === objectId + (row) => row.row_type === op.row_type && row.row_id === op.row_id ); if (!exists) { this.ps_updated_rows.push({ - row_type: objectType, - row_id: objectId + row_type: op.row_type, + row_id: op.row_id }); } } } - continue; - } - - // Handle PUT operation - const newOplog: PSOplog = { - bucket: bucketId, - op_id: opId, - key: key || null, - row_type: objectType || null, - row_id: objectId || null, - data: opData, - hash: checksum // Already normalized to bigint - }; - - this.ps_oplog.push(newOplog); - opChecksum = addChecksums(opChecksum, checksum); - } else if (opType === 'MOVE') { - addChecksum = addChecksums(addChecksum, checksum); - } else if (opType === 'CLEAR') { - // Any remaining PUT operations should get an implicit REMOVE - // Add all rows from ps_oplog to ps_updated_rows for this bucket - const bucketOps = this.ps_oplog.filter((op) => op.bucket === bucketId); - for (const op of bucketOps) { - if (op.row_type && op.row_id) { - const exists = this.ps_updated_rows.some( - (row) => row.row_type === op.row_type && row.row_id === op.row_id - ); - if (!exists) { - this.ps_updated_rows.push({ - row_type: op.row_type, - row_id: op.row_id - }); - } - } - } - // Delete all ops from ps_oplog for this bucket - this.ps_oplog = this.ps_oplog.filter((op) => op.bucket !== bucketId); + // Delete all ops from ps_oplog for this bucket + this.ps_oplog = this.ps_oplog.filter((op) => op.bucket !== bucketId); - // Update bucket: set last_applied_op = 0, replace add_checksum with CLEAR op checksum, reset op_checksum - bucket.last_applied_op = 0n; - // Store checksum as-is (preserve full 64-bit value from SQLite) - // The checksum comes from the operation, which is already in correct range - bucket.add_checksum = checksum; - bucket.op_checksum = 0n; + // Update bucket: set last_applied_op = 0, replace add_checksum with CLEAR op checksum, reset op_checksum + bucket.last_applied_op = 0n; + // Store checksum as-is (preserve full 64-bit value from SQLite) + // The checksum comes from the operation, which is already in correct range + bucket.add_checksum = checksum; + bucket.op_checksum = 0n; - addChecksum = 0n; - isEmpty = true; - opChecksum = 0n; + addChecksum = 0n; + isEmpty = true; + opChecksum = 0n; + } } - } - // Update bucket state if we processed any operations - if (lastOp !== null) { - bucket.last_op = lastOp; - // addChecksums() handles 32-bit unsigned wrapping and returns the result - // We store as full bigint value (SQLite can store 64-bit INTEGERs) - bucket.add_checksum = addChecksums(bucket.add_checksum, addChecksum); - bucket.op_checksum = addChecksums(bucket.op_checksum, opChecksum); - bucket.count_since_last += addedOps; + // Update bucket state if we processed any operations + if (lastOp !== null) { + bucket.last_op = lastOp; + // addChecksums() handles 32-bit unsigned wrapping and returns the result + // We store as full bigint value (SQLite can store 64-bit INTEGERs) + bucket.add_checksum = addChecksums(bucket.add_checksum, addChecksum); + bucket.op_checksum = addChecksums(bucket.op_checksum, opChecksum); + bucket.count_since_last += addedOps; + } } - } + }); } async syncLocalDatabase( @@ -353,22 +388,24 @@ export class MemoryBucketStorageImpl implements BucketStorage { buckets = buckets.filter((b) => hasMatchingPriority(priority, b)); } - // Update bucket last_op to checkpoint.last_op_id - const bucketNames = buckets.map((b) => b.bucket); - for (const bucketName of bucketNames) { - const bucket = this.ps_buckets.find((b) => b.name === bucketName); - if (bucket) { - bucket.last_op = normalizeChecksum(checkpoint.last_op_id); + await this.lock.runExclusive(async () => { + // Update bucket last_op to checkpoint.last_op_id + const bucketNames = buckets.map((b) => b.bucket); + for (const bucketName of bucketNames) { + const bucket = this.ps_buckets.find((b) => b.name === bucketName); + if (bucket) { + bucket.last_op = normalizeChecksum(checkpoint.last_op_id); + } } - } - // Update '$local' bucket if write_checkpoint is provided and it's a complete sync - if (priority == null && checkpoint.write_checkpoint) { - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (localBucket) { - localBucket.last_op = normalizeChecksum(checkpoint.write_checkpoint); + // Update '$local' bucket if write_checkpoint is provided and it's a complete sync + if (priority == null && checkpoint.write_checkpoint) { + const localBucket = this.ps_buckets.find((b) => b.name === '$local'); + if (localBucket) { + localBucket.last_op = normalizeChecksum(checkpoint.write_checkpoint); + } } - } + }); const valid = await this.updateObjectsFromBuckets(checkpoint, priority); if (!valid) { @@ -389,39 +426,44 @@ export class MemoryBucketStorageImpl implements BucketStorage { * For now, operations are collected but not applied (pluggable approach). */ private async updateObjectsFromBuckets(checkpoint: Checkpoint, priority: number | undefined): Promise { - // Check if sync can apply (can_apply_sync_changes equivalent) - if (!(await this.canApplySyncChanges(priority))) { - return false; - } + return await this.lock.runExclusive(async () => { + // Check if sync can apply (can_apply_sync_changes equivalent) + if (!(await this.canApplySyncChanges(priority))) { + return false; + } - // Collect operations that need to be applied - const operations = await this.collectFullOperations(checkpoint, priority); + // Collect operations that need to be applied + const operations = await this.collectFullOperations(checkpoint, priority); - // Process operations using all handlers if provided - if (this.operationsHandlers.length > 0 && operations.length > 0) { - for (const handler of this.operationsHandlers) { - await handler.processOperations(operations); + // Process operations using all handlers if provided + if (this.operationsHandlers.length > 0 && operations.length > 0) { + for (const handler of this.operationsHandlers) { + await handler.handleCheckpoint({ + checkpoint: checkpoint.last_op_id, + pendingOperations: operations + }); + } } - } - // Update last_applied_op for buckets - await this.setLastAppliedOp(checkpoint, priority); + // Update last_applied_op for buckets + await this.setLastAppliedOp(checkpoint, priority); - // Update count_at_last for complete sync - if (priority == null) { - const bucketToCount = new Map(checkpoint.buckets.map((b) => [b.bucket, b.count ?? 0])); - for (const bucket of this.ps_buckets) { - if (bucket.name !== '$local' && bucketToCount.has(bucket.name)) { - bucket.count_at_last = bucketToCount.get(bucket.name)!; - bucket.count_since_last = 0; + // Update count_at_last for complete sync + if (priority == null) { + const bucketToCount = new Map(checkpoint.buckets.map((b) => [b.bucket, b.count ?? 0])); + for (const bucket of this.ps_buckets) { + if (bucket.name !== '$local' && bucketToCount.has(bucket.name)) { + bucket.count_at_last = bucketToCount.get(bucket.name)!; + bucket.count_since_last = 0; + } } } - } - // Mark as completed (clear ps_updated_rows for complete sync) - await this.markCompleted(priority); + // Mark as completed (clear ps_updated_rows for complete sync) + await this.markCompleted(priority); - return true; + return true; + }); } /** @@ -432,7 +474,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { * (except for downloaded data in priority 0, which is published earlier). */ private async canApplySyncChanges(priority: number | undefined): Promise { - const needsCheck = priority === undefined || !this.mayPublishWithOutstandingUploads(priority); + const needsCheck = priority == undefined || !this.mayPublishWithOutstandingUploads(priority); if (needsCheck) { // Check if '$local' bucket has target_op > last_op @@ -444,7 +486,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { // Check if ps_crud has any data // SQL: SELECT 1 FROM ps_crud LIMIT 1 - if (this.ps_crud.length > 0) { + if (await this.crudManager?.hasCrud()) { return false; } } diff --git a/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts b/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts index f925aed0e..7342a8e38 100644 --- a/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts +++ b/packages/lite-sdk/src/client/storage/SyncOperationsHandler.ts @@ -12,6 +12,11 @@ export type SyncOperation = { data: string | null; }; +export type CheckpointEvent = { + checkpoint: string; + pendingOperations: ReadonlyArray; +}; + /** * Handler interface for processing sync operations collected from the protocol. * This handler is responsible for applying operations to output collections. @@ -21,5 +26,5 @@ export interface SyncOperationsHandler { * Process sync operations collected from the protocol. * @param operations Array of operations to process */ - processOperations(operations: ReadonlyArray): Promise; + handleCheckpoint(event: CheckpointEvent): Promise; } diff --git a/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts b/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts deleted file mode 100644 index 80f13665b..000000000 --- a/packages/lite-sdk/src/client/storage/storage-types/ps_tx.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * ps_tx (Transactions) table interface - * - * Purpose: Tracks transaction state - * - * Schema: - * - id (INTEGER PRIMARY KEY NOT NULL) - * - current_tx (INTEGER) - * - next_tx (INTEGER) - */ -export interface PSTx { - /** INTEGER */ - current_tx: number | null; - - /** INTEGER */ - next_tx: number | null; -} diff --git a/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts b/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts index 006e91fdf..5af7a8115 100644 --- a/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts +++ b/packages/lite-sdk/src/client/storage/storage-types/storage-types-index.ts @@ -1,5 +1,4 @@ export type { PSBucket } from './ps_buckets.js'; export type { PSCrud } from './ps_crud.js'; export type { PSOplog } from './ps_oplog.js'; -export type { PSTx } from './ps_tx.js'; export type { PsUpdatedRows } from './ps_updated_rows.js'; diff --git a/packages/lite-sdk/src/client/sync/Connector.ts b/packages/lite-sdk/src/client/sync/Connector.ts new file mode 100644 index 000000000..048278536 --- /dev/null +++ b/packages/lite-sdk/src/client/sync/Connector.ts @@ -0,0 +1,52 @@ +/** + * Credentials required to connect to a PowerSync instance. + */ +export type PowerSyncCredentials = { + /** The PowerSync endpoint URL to connect to. */ + endpoint: string; + /** Authentication token for the PowerSync service. */ + token: string; +}; +/** + * Provides credentials dynamically for PowerSync connections. + * This allows for credential refresh and token rotation without + * disconnecting the client. + */ +export abstract class Connector { + protected _credentials: PowerSyncCredentials | null; + protected _prefetchPromise: Promise | null; + + constructor() { + this._credentials = null; + this._prefetchPromise = null; + } + + get cachedCredentials(): PowerSyncCredentials | null { + return this._credentials; + } + + /** + * Called when cached credentials are detected to be invalid. + */ + invalidateCredentials() { + this._credentials = null; + } + + async prefetchCredentials() { + if (this._prefetchPromise) { + return this._prefetchPromise; + } + return (this._prefetchPromise = this.fetchCredentials() + .then((credentials) => { + this._credentials = credentials; + return this._credentials; + }) + .finally(() => (this._prefetchPromise = null))); + } + + /** + * Fetches the current PowerSync credentials. + * @returns A promise that resolves to credentials, or null if no credentials are available. + */ + abstract fetchCredentials(): Promise; +} diff --git a/packages/lite-sdk/src/client/sync/CrudManager.ts b/packages/lite-sdk/src/client/sync/CrudManager.ts new file mode 100644 index 000000000..3c28ff9ea --- /dev/null +++ b/packages/lite-sdk/src/client/sync/CrudManager.ts @@ -0,0 +1,31 @@ +export type UploadContext = { + /** + * @param writeCheckpoint - Optional write checkpoint to set the local target to + * - If provided, only updates if all CRUD items have been uploaded + * - If not provided, sets the local target to the max op id + * @returns + */ + complete: (writeCheckpoint?: string) => Promise; +}; + +/** + * Interface for managing CRUD operations in PowerSync. + * + * CrudManager is responsible for: + * - Tracking CRUD items + * - Performing uploads of CRUD items + * - Marking CRUD items as completed + */ +export interface CrudManager { + /** + * Checks if there are any CRUD items to upload. + * @returns true if there are any CRUD items to upload, false otherwise + */ + hasCrud: () => Promise; + /** + * Performs the upload of the CRUD items. + * @param context - The upload context + * @returns void + */ + performUpload: (context: UploadContext) => Promise; +} diff --git a/packages/lite-sdk/src/client/sync/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts index e8a85e018..4364bfd6d 100644 --- a/packages/lite-sdk/src/client/sync/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -2,29 +2,8 @@ import type { BucketRequest, StreamingSyncLine } from '@powersync/service-core'; import { BaseListener, BaseObserverInterface, Disposable } from '../../utils/BaseObserver.js'; import type { BucketStorage } from '../storage/BucketStorage.js'; import type { SystemDependencies } from '../system/SystemDependencies.js'; - -/** - * Credentials required to connect to a PowerSync instance. - */ -export type PowerSyncCredentials = { - /** The PowerSync endpoint URL to connect to. */ - endpoint: string; - /** Authentication token for the PowerSync service. */ - token: string; -}; - -/** - * Provides credentials dynamically for PowerSync connections. - * This allows for credential refresh and token rotation without - * disconnecting the client. - */ -export type Connector = { - /** - * Fetches the current PowerSync credentials. - * @returns A promise that resolves to credentials, or null if no credentials are available. - */ - fetchCredentials: () => Promise; -}; +import { Connector } from './Connector.js'; +import { CrudManager } from './CrudManager.js'; /** * Current synchronization status of the sync client. @@ -41,8 +20,12 @@ export interface SyncStatus { hasSynced: boolean; /** Whether data is currently being downloaded from the service. */ downloading: boolean; + /** Whether data is currently being uploaded to the service. */ + uploading: boolean; /** Error that occurred during download, if any. */ downloadError: Error | null; + /** Error that occurred during upload, if any. */ + uploadError: Error | null; } /** @@ -55,6 +38,11 @@ export interface SyncClientListener extends BaseListener { statusChanged?: ((status: SyncStatus) => void) | undefined; } +export type CreateCheckpointResponse = { + targetUpdated: boolean; + targetCheckpoint?: string; +}; + /** * Main interface for synchronizing data with a PowerSync service. * Handles bidirectional sync, connection management, and status tracking. @@ -78,6 +66,13 @@ export interface SyncClient extends BaseObserverInterface, D * Any ongoing sync operations will be aborted. */ disconnect: () => void; + + /** + * Sets the target write checkpoint. + * @param customCheckpoint - Optional custom checkpoint to set the target to + * defaults to creating a managed PowerSync checkpoint + */ + checkpoint: (customCheckpoint?: string) => Promise; } export type StreamOptions = { @@ -98,10 +93,12 @@ export type StreamOpener = (options: StreamOptions) => Promise implements SyncClient { status: SyncStatus; - protected cachedCredentials: PowerSyncCredentials | null; protected abortController: AbortController; protected openStreamFn: StreamOpener; + protected uploadPromise: Promise | null; + protected connector: Connector | null; constructor(protected options: SyncClientOptions) { super(); - this.cachedCredentials = null; + this.connector = null; this.abortController = new AbortController(); this.status = { connected: false, @@ -47,8 +49,11 @@ export class SyncClientImpl extends BaseObserver implements hasSynced: false, connecting: false, downloading: false, + uploading: false, + uploadError: null, downloadError: null }; + this.uploadPromise = null; this.openStreamFn = options.streamOpener ?? openHttpStream; } @@ -56,6 +61,10 @@ export class SyncClientImpl extends BaseObserver implements return this.options.storage; } + protected get crudManager(): CrudManager | undefined { + return this.options.crudManager; + } + /** * Updates the sync status with the provided options. * Emits to listeners for status changes. @@ -78,6 +87,7 @@ export class SyncClientImpl extends BaseObserver implements async connect(connector: Connector): Promise { // Abort any existing connection this.abortController.abort(); + this.connector = connector; const controller = new AbortController(); this.abortController = controller; @@ -105,8 +115,107 @@ export class SyncClientImpl extends BaseObserver implements this.abortController.abort(); } - protected invalidateCredentials(): void { - this.cachedCredentials = null; + async checkpoint(customCheckpoint?: string): Promise { + let targetCheckpoint: string | undefined = customCheckpoint; + const targetUpdated = await this.bucketStorage.updateLocalTarget(async () => { + if (targetCheckpoint) { + return targetCheckpoint; + } + targetCheckpoint = await this.getWriteCheckpoint(); + return targetCheckpoint; + }); + return { + targetUpdated, + targetCheckpoint + }; + } + + protected triggerCrudUpload(): void { + if (!this.crudManager) { + return; + } + // We already have an operation queued + if (this.uploadPromise) { + return; + } + + const process = async () => { + try { + const hasCrud = await this.crudManager?.hasCrud().catch(() => false); + if (!hasCrud) { + // We are done + this.uploadPromise = null; + this.updateSyncStatus({ + uploading: false + }); + return; + } + + this.updateSyncStatus({ + uploading: true, + uploadError: null + }); + + try { + await this.crudManager?.performUpload({ + complete: this.bucketStorage.handleCrudUploaded + }); + + const hasCrudAfter = await this.crudManager?.hasCrud().catch(() => false); + if (hasCrudAfter) { + // Keep the chain running + // Implement throttling + await new Promise((resolve) => setTimeout(resolve, this.options.uploadThrottleMs)); + return (this.uploadPromise = process() ?? null); + } else { + // We are done with this iteration + this.uploadPromise = null; + this.updateSyncStatus({ + uploading: false, + uploadError: null + }); + // Get a write checkpoint + await this.bucketStorage.updateLocalTarget(() => this.getWriteCheckpoint()); + } + } catch (error) { + this.updateSyncStatus({ + uploadError: error as Error + }); + } finally { + // Implement throttling + await new Promise((resolve) => setTimeout(resolve, this.options.uploadThrottleMs)); + } + } catch (error) { + this.updateSyncStatus({ + uploadError: error as Error + }); + } + }; + // Start by performing an upload + this.uploadPromise = process() ?? null; + } + + protected async getWriteCheckpoint(): Promise { + if (!this.connector) { + throw new Error(`No connector found`); + } + const credentials = await this.connector.prefetchCredentials(); + if (!credentials) { + throw new Error(`No credentials found`); + } + const clientId = await this.bucketStorage.getClientId(); + let path = `/write-checkpoint2.json?client_id=${clientId}`; + const response = await fetch(`${credentials.endpoint}${path}`, { + headers: { + Authorization: `Bearer ${credentials.token}` + } + }); + if (!response.ok) { + throw new Error(`Failed to get write checkpoint: ${response.statusText}`); + } + const data = (await response.json()) as { data: { write_checkpoint: string } }; + const checkpoint = data['data']['write_checkpoint'] as string; + return checkpoint; } private async collectLocalBucketState(): Promise<[BucketRequest[], Map]> { @@ -124,7 +233,7 @@ export class SyncClientImpl extends BaseObserver implements } protected async syncIteration(connector: Connector, signal: AbortSignal): Promise { - const credentials = this.cachedCredentials ?? (await connector.fetchCredentials()); + const credentials = connector.cachedCredentials ?? (await connector.fetchCredentials()); if (!credentials) { throw new Error(`No credentials found`); @@ -141,7 +250,7 @@ export class SyncClientImpl extends BaseObserver implements systemDependencies: this.options.systemDependencies }).catch((ex) => { if (ex instanceof AuthorizationError) { - this.invalidateCredentials(); + connector.invalidateCredentials(); } throw ex; }); @@ -160,6 +269,10 @@ export class SyncClientImpl extends BaseObserver implements const reader = stream.getReader(); console.debug(`Starting sync iteration`); + + // Trigger a CRUD upload immediately after connecting + Promise.resolve().then(() => this.triggerCrudUpload()); + try { while (!signal.aborted) { const { value: line, done } = await reader.read(); @@ -272,9 +385,10 @@ export class SyncClientImpl extends BaseObserver implements if (token_expires_in == 0) { throw new Error(`Token already expired`); } else if (token_expires_in < 30) { - this.invalidateCredentials(); + connector.invalidateCredentials(); throw new Error(`Token will expire soon. Need to reconnect.`); } + this.triggerCrudUpload(); } else { console.debug(`Received unknown sync line`, line); } diff --git a/packages/lite-sdk/src/index.ts b/packages/lite-sdk/src/index.ts index dbe33b6a5..f31fe1da7 100644 --- a/packages/lite-sdk/src/index.ts +++ b/packages/lite-sdk/src/index.ts @@ -1,6 +1,7 @@ export * from './client/storage/BucketStorage.js'; export * from './client/storage/MemoryBucketStorageImpl.js'; export * from './client/storage/storage-types/storage-types-index.js'; +export * from './client/sync/Connector.js'; export * from './client/sync/SyncClient.js'; export * from './client/sync/SyncClientImpl.js'; export * from './client/sync/ndjson.js'; diff --git a/packages/lite-sdk/src/utils/Lock.ts b/packages/lite-sdk/src/utils/Lock.ts new file mode 100644 index 000000000..a89524eb3 --- /dev/null +++ b/packages/lite-sdk/src/utils/Lock.ts @@ -0,0 +1,24 @@ +/** + * Basic serializing async lock. + */ +export class Lock { + // Used to serialize requests + private promise: Promise; + + constructor() { + this.promise = Promise.resolve(); + } + + async runExclusive(callback: () => Promise): Promise { + return (this.promise = this.promise.catch().then( + () => + new Promise(async (resolve, reject) => { + try { + resolve(await callback()); + } catch (error) { + reject(error); + } + }) + )); + } +} diff --git a/packages/lite-sdk/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts index caba4aa53..d851af1a8 100644 --- a/packages/lite-sdk/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -1,39 +1,39 @@ import { describe, it } from 'vitest'; import { MemoryBucketStorageImpl } from '../src/client/storage/MemoryBucketStorageImpl.js'; import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; -import { type Connector } from '../src/client/sync/SyncClient.js'; +import { Connector, PowerSyncCredentials } from '../src/client/sync/Connector.js'; import { SyncClientImpl } from '../src/client/sync/SyncClientImpl.js'; import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/system/SystemDependencies.js'; -describe(`PowerSync Lite`, { timeout: Infinity }, () => { - describe(`Connection`, () => { - it.skip(`should connect to a PowerSync server`, async () => { - const connector = { - fetchCredentials: async () => { - const tokenResponse = await fetch(`http://localhost:6060/api/auth/token`, { - method: `GET`, - headers: { - 'content-type': `application/json` - } - }); +class TestConnector extends Connector { + async fetchCredentials(): Promise { + const tokenResponse = await fetch(`http://localhost:6060/api/auth/token`, { + method: `GET`, + headers: { + 'content-type': `application/json` + } + }); - if (!tokenResponse.ok) { - throw new Error(`Failed to fetch token: ${tokenResponse.statusText} ${await tokenResponse.text()}`); - } + if (!tokenResponse.ok) { + throw new Error(`Failed to fetch token: ${tokenResponse.statusText} ${await tokenResponse.text()}`); + } - const tokenBody = await tokenResponse.json(); - return { - endpoint: `http://localhost:8080`, - token: tokenBody.token - }; - } - } satisfies Connector; + const tokenBody = await tokenResponse.json(); + return { + endpoint: `http://localhost:8080`, + token: tokenBody.token + }; + } +} +describe(`PowerSync Lite`, { timeout: Infinity }, () => { + describe(`Connection`, () => { + it(`should connect to a PowerSync server`, async () => { const syncOperationsHandler: SyncOperationsHandler = { - processOperations: async (operations) => { + handleCheckpoint: async (event) => { // Funnel these operations to external storage - console.log(`Processing ${operations.length} operations`); - console.log(operations); + console.log(`Processing ${event.pendingOperations.length} operations`); + console.log(event.checkpoint, event.pendingOperations); } }; @@ -41,8 +41,7 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { const syncClient = new SyncClientImpl({ connectionRetryDelayMs: 1000, debugMode: false, - // TODO uploads - uploadRetryDelayMs: 1000, + uploadThrottleMs: 1000, storage: new MemoryBucketStorageImpl({ operationsHandlers: [syncOperationsHandler], systemDependencies: systemDependencies @@ -50,7 +49,7 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { systemDependencies }); - await syncClient.connect(connector); + await syncClient.connect(new TestConnector()); // Long running test for demonstrating the sync client }); diff --git a/packages/lite-sdk/tests/sync-client.test.ts b/packages/lite-sdk/tests/sync-client.test.ts index 227156bb9..293a1bfe2 100644 --- a/packages/lite-sdk/tests/sync-client.test.ts +++ b/packages/lite-sdk/tests/sync-client.test.ts @@ -1,12 +1,21 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; -import type { Connector, PowerSyncCredentials } from '../src/client/sync/SyncClient.js'; +import { Connector, type PowerSyncCredentials } from '../src/client/sync/Connector.js'; import { MockStreamFactory, checkpoint, createTestSyncClient, waitForSyncStatus } from './utils.js'; +class PlaceholderConnector extends Connector { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } +} + describe('SyncClient', () => { let mockStreamFactory: MockStreamFactory; const defaultOperationsHandler: SyncOperationsHandler = { - processOperations: async () => { + handleCheckpoint: async () => { // No-op for tests } }; @@ -21,19 +30,12 @@ describe('SyncClient', () => { it('should connect and receive checkpoint', async () => { const client = createTestSyncClient({}, mockStreamFactory, { - processOperations: async () => { + handleCheckpoint: async () => { // No-op for tests } }); - const connector: Connector = { - async fetchCredentials(): Promise { - return { - endpoint: 'https://powersync.example.org', - token: 'test-token' - }; - } - }; + const connector = new PlaceholderConnector(); client.connect(connector); @@ -67,14 +69,7 @@ describe('SyncClient', () => { it('should set last sync time after checkpoint complete', async () => { const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); - const connector: Connector = { - async fetchCredentials(): Promise { - return { - endpoint: 'https://powersync.example.org', - token: 'test-token' - }; - } - }; + const connector = new PlaceholderConnector(); client.connect(connector); await waitForSyncStatus(client, (status) => status.connected === true, 1000); @@ -110,14 +105,7 @@ describe('SyncClient', () => { it('should handle data lines', async () => { const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); - const connector: Connector = { - async fetchCredentials(): Promise { - return { - endpoint: 'https://powersync.example.org', - token: 'test-token' - }; - } - }; + const connector = new PlaceholderConnector(); client.connect(connector); await waitForSyncStatus(client, (status) => status.connected === true, 1000); @@ -172,14 +160,7 @@ describe('SyncClient', () => { it('should handle checkpoint diff', async () => { const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); - const connector: Connector = { - async fetchCredentials(): Promise { - return { - endpoint: 'https://powersync.example.org', - token: 'test-token' - }; - } - }; + const connector = new PlaceholderConnector(); client.connect(connector); await waitForSyncStatus(client, (status) => status.connected === true, 1000); @@ -233,14 +214,7 @@ describe('SyncClient', () => { it('should handle partial checkpoint complete', async () => { const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); - const connector: Connector = { - async fetchCredentials(): Promise { - return { - endpoint: 'https://powersync.example.org', - token: 'test-token' - }; - } - }; + const connector = new PlaceholderConnector(); client.connect(connector); await waitForSyncStatus(client, (status) => status.connected === true, 1000); @@ -290,14 +264,7 @@ describe('SyncClient', () => { it('should track connection status', async () => { const client = createTestSyncClient({}, mockStreamFactory, defaultOperationsHandler); - const connector: Connector = { - async fetchCredentials(): Promise { - return { - endpoint: 'https://powersync.example.org', - token: 'test-token' - }; - } - }; + const connector = new PlaceholderConnector(); expect(client.status.connected).toBe(false); expect(client.status.connecting).toBe(false); diff --git a/packages/lite-sdk/tsconfig.json b/packages/lite-sdk/tsconfig.json index 59adb5a5d..691726dcb 100644 --- a/packages/lite-sdk/tsconfig.json +++ b/packages/lite-sdk/tsconfig.json @@ -12,8 +12,9 @@ "moduleResolution": "nodenext", "preserveConstEnums": true, "esModuleInterop": false, - "skipLibCheck": false, + "skipLibCheck": true, "strictNullChecks": true }, - "include": ["src/**/*"] + "include": ["src/**/*"], + "exclude": ["node_modules", "lib"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d33447f4d..ae9f9015f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1917,25 +1917,6 @@ importers: specifier: 3.2.1 version: 3.2.1 - packages/core: - dependencies: - ts-codec: - specifier: ^1.3.0 - version: 1.3.0 - devDependencies: - '@powersync/service-core': - specifier: ^1.15.8 - version: 1.15.8(babel-plugin-macros@3.1.0) - '@powersync/service-jsonbig': - specifier: ^0.17.11 - version: 0.17.11 - '@powersync/service-sync-rules': - specifier: ^0.29.5 - version: 0.29.5 - '@types/node': - specifier: ^20.5.9 - version: 20.17.57 - packages/drizzle-driver: dependencies: '@powersync/common': @@ -1992,6 +1973,25 @@ importers: specifier: ^3.3.0 version: 3.4.1(vite@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)) + packages/lite-sdk: + dependencies: + ts-codec: + specifier: ^1.3.0 + version: 1.3.0 + devDependencies: + '@powersync/service-core': + specifier: ^1.15.8 + version: 1.15.8(babel-plugin-macros@3.1.0) + '@powersync/service-jsonbig': + specifier: ^0.17.11 + version: 0.17.11 + '@powersync/service-sync-rules': + specifier: ^0.29.5 + version: 0.29.5 + '@types/node': + specifier: ^20.5.9 + version: 20.17.57 + packages/node: dependencies: '@powersync/common': From 129c287e46273a3428d124006c1a0267ae0782ac Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 11 Nov 2025 17:43:23 +0200 Subject: [PATCH 11/13] wip: uploads --- .../src/client/storage/BucketStorage.ts | 12 ++ .../client/storage/MemoryBucketStorageImpl.ts | 64 +++++----- .../lite-sdk/src/client/sync/SyncClient.ts | 21 +++- .../src/client/sync/SyncClientImpl.ts | 51 +++++++- .../lite-sdk/tests/powersync-lite.test.ts | 109 +++++++++++++++++- 5 files changed, 215 insertions(+), 42 deletions(-) diff --git a/packages/lite-sdk/src/client/storage/BucketStorage.ts b/packages/lite-sdk/src/client/storage/BucketStorage.ts index c2c93642b..2f30c16fd 100644 --- a/packages/lite-sdk/src/client/storage/BucketStorage.ts +++ b/packages/lite-sdk/src/client/storage/BucketStorage.ts @@ -9,6 +9,11 @@ export type SavedProgress = { sinceLast: number; }; +export interface LocalState { + lastOpId: bigint; + targetOpId: bigint; +} + export type BucketOperationProgress = Record; /** @@ -54,6 +59,13 @@ export interface BucketStorage { */ getBucketStates: () => Promise>; + /** + * Get the current state of the local bucket. + * FIXME maybe a better name? + * @returns The local bucket state, containing bucket name, operation ID, checksums, etc. + */ + getLocalState: () => Promise; + /** * Synchronize the local database with a checkpoint. * Validates checksums, applies operations, and updates bucket states. diff --git a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index 0d7f84fa2..2cc05adff 100644 --- a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -2,7 +2,7 @@ import type { BucketState, Checkpoint } from '@powersync/service-core'; import { Lock } from '../../utils/Lock.js'; import { CrudManager } from '../sync/CrudManager.js'; import { SystemDependencies } from '../system/SystemDependencies.js'; -import type { BucketStorage, SyncDataBatch } from './BucketStorage.js'; +import type { BucketStorage, LocalState, SyncDataBatch } from './BucketStorage.js'; import type { SyncOperation, SyncOperationsHandler } from './SyncOperationsHandler.js'; import { constructKey, toStringOrNull } from './bucketHelpers.js'; import { addChecksums, normalizeChecksum, subtractChecksums } from './checksumUtils.js'; @@ -23,6 +23,8 @@ export type MemoryBucketStorageImplOptions = { export class MemoryBucketStorageImpl implements BucketStorage { protected ps_buckets: PSBucket[]; + // TODO cleanup + protected localBucket: PSBucket; protected ps_oplog: PSOplog[]; protected ps_updated_rows: PsUpdatedRows[]; protected clientId: string; @@ -43,6 +45,18 @@ export class MemoryBucketStorageImpl implements BucketStorage { protected initDefaultState() { this.ps_buckets = []; + this.localBucket = { + id: 0, + name: '$local', + last_applied_op: 0n, + last_op: 0n, + target_op: 0n, + add_checksum: 0n, + op_checksum: 0n, + pending_delete: false, + count_at_last: 0, + count_since_last: 0 + }; this.ps_oplog = []; this.ps_updated_rows = []; this.lastSyncedAt = null; @@ -76,6 +90,15 @@ export class MemoryBucketStorageImpl implements BucketStorage { }); } + async getLocalState(): Promise { + return await this.lock.runExclusive(async () => { + return { + lastOpId: this.localBucket.last_op, + targetOpId: this.localBucket.target_op + }; + }); + } + async hasCompletedSync(): Promise { return await this.lock.runExclusive(async () => { return !!this.ps_buckets.find((b) => b.last_applied_op > 0); @@ -90,18 +113,9 @@ export class MemoryBucketStorageImpl implements BucketStorage { if (await this.crudManager?.hasCrud()) { return; } - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (!localBucket) { - throw new Error('Local bucket not found'); - } - localBucket.target_op = normalizeChecksum(writeCheckpoint); + this.localBucket.target_op = normalizeChecksum(writeCheckpoint); } else { - // Set the target to the max op id - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (!localBucket) { - throw new Error('Local bucket not found'); - } - localBucket.target_op = normalizeChecksum(MAX_OP_ID); + this.localBucket.target_op = normalizeChecksum(MAX_OP_ID); } }); } @@ -124,14 +138,8 @@ export class MemoryBucketStorageImpl implements BucketStorage { // SQL: SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER) // TODO: maybe store local state separately const shouldProceed = await this.lock.runExclusive(async () => { - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (!localBucket) { - // Nothing to update - return false; - } - const maxOpIdBigint = BigInt(MAX_OP_ID); - if (localBucket.target_op !== maxOpIdBigint) { + if (this.localBucket.target_op !== maxOpIdBigint) { // target_op is not MAX_OP_ID, nothing to update return false; } @@ -157,16 +165,10 @@ export class MemoryBucketStorageImpl implements BucketStorage { // This is typically called after completing uploads which can // be concurrent. return await this.lock.runExclusive(async () => { - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (!localBucket) { - // Nothing to update - return false; - } - // Update the '$local' bucket's target_op to the new opId // SQL: UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local' const opIdBigint = normalizeChecksum(opId); - localBucket.target_op = opIdBigint; + this.localBucket.target_op = opIdBigint; return true; }); } @@ -400,10 +402,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { // Update '$local' bucket if write_checkpoint is provided and it's a complete sync if (priority == null && checkpoint.write_checkpoint) { - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (localBucket) { - localBucket.last_op = normalizeChecksum(checkpoint.write_checkpoint); - } + this.localBucket.last_op = normalizeChecksum(checkpoint.write_checkpoint); } }); @@ -452,7 +451,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { if (priority == null) { const bucketToCount = new Map(checkpoint.buckets.map((b) => [b.bucket, b.count ?? 0])); for (const bucket of this.ps_buckets) { - if (bucket.name !== '$local' && bucketToCount.has(bucket.name)) { + if (bucketToCount.has(bucket.name)) { bucket.count_at_last = bucketToCount.get(bucket.name)!; bucket.count_since_last = 0; } @@ -479,8 +478,7 @@ export class MemoryBucketStorageImpl implements BucketStorage { if (needsCheck) { // Check if '$local' bucket has target_op > last_op // SQL: SELECT 1 FROM ps_buckets WHERE target_op > last_op AND name = '$local' - const localBucket = this.ps_buckets.find((b) => b.name === '$local'); - if (localBucket && localBucket.target_op > localBucket.last_op) { + if (this.localBucket.target_op > this.localBucket.last_op) { return false; } diff --git a/packages/lite-sdk/src/client/sync/SyncClient.ts b/packages/lite-sdk/src/client/sync/SyncClient.ts index 4364bfd6d..522847b54 100644 --- a/packages/lite-sdk/src/client/sync/SyncClient.ts +++ b/packages/lite-sdk/src/client/sync/SyncClient.ts @@ -1,4 +1,4 @@ -import type { BucketRequest, StreamingSyncLine } from '@powersync/service-core'; +import type { BucketRequest, Checkpoint, StreamingSyncLine } from '@powersync/service-core'; import { BaseListener, BaseObserverInterface, Disposable } from '../../utils/BaseObserver.js'; import type { BucketStorage } from '../storage/BucketStorage.js'; import type { SystemDependencies } from '../system/SystemDependencies.js'; @@ -36,11 +36,17 @@ export interface SyncClientListener extends BaseListener { * Triggers whenever the status' members have changed in value */ statusChanged?: ((status: SyncStatus) => void) | undefined; + /** + * Triggers whenever a checkpoint is completed. + * @param checkpoint - The checkpoint that was completed + */ + checkpointCompleted?: ((checkpoint: Checkpoint) => void) | undefined; } export type CreateCheckpointResponse = { targetUpdated: boolean; targetCheckpoint?: string; + waitForValidation: (signal?: AbortSignal) => Promise; }; /** @@ -71,8 +77,21 @@ export interface SyncClient extends BaseObserverInterface, D * Sets the target write checkpoint. * @param customCheckpoint - Optional custom checkpoint to set the target to * defaults to creating a managed PowerSync checkpoint + * + * FIXME + * This is a no-op if there are pending CRUD items. + * This does not answer the generic question "have I synced to this point in time?" + * It answers the question "have I synced to after uploads have been completed?" */ checkpoint: (customCheckpoint?: string) => Promise; + + /** + * Triggers a CRUD upload. + * This will perform an upload of the CRUD items if there are any. + * If there are no CRUD items, it will do nothing. + * If there are CRUD items, the {@link CrudManager} will be used to perform the upload. + */ + triggerCrudUpload: () => void; } export type StreamOptions = { diff --git a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts index e8a4f7f1e..23ef80be3 100644 --- a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts +++ b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts @@ -1,6 +1,7 @@ import type { Checkpoint, CheckpointBucket } from '@powersync/service-core'; import { BaseObserver } from '../../utils/BaseObserver.js'; import type { BucketStorage } from '../storage/BucketStorage.js'; +import { normalizeChecksum } from '../storage/checksumUtils.js'; import { Connector } from './Connector.js'; import { CrudManager } from './CrudManager.js'; import type { @@ -92,10 +93,15 @@ export class SyncClientImpl extends BaseObserver implements const controller = new AbortController(); this.abortController = controller; - while (!this.abortController.signal.aborted) { + // Don't await this, this will run until the connection is aborted + this.connectInternal(connector, controller.signal); + } + + protected async connectInternal(connector: Connector, signal: AbortSignal): Promise { + while (!signal.aborted) { try { this.updateSyncStatus({ connecting: true }); - await this.syncIteration(connector, controller.signal); + await this.syncIteration(connector, signal); } catch (error) { this.updateSyncStatus({ connected: false, @@ -110,13 +116,16 @@ export class SyncClientImpl extends BaseObserver implements } } } - disconnect() { this.abortController.abort(); } async checkpoint(customCheckpoint?: string): Promise { let targetCheckpoint: string | undefined = customCheckpoint; + // FIXME, could this be optimized? + // If there are no crud items, we set the target to the custom checkpoint or the max op id + // We then set the local target to the new checkpoint + await this.bucketStorage.handleCrudUploaded(customCheckpoint); const targetUpdated = await this.bucketStorage.updateLocalTarget(async () => { if (targetCheckpoint) { return targetCheckpoint; @@ -126,11 +135,39 @@ export class SyncClientImpl extends BaseObserver implements }); return { targetUpdated, - targetCheckpoint + targetCheckpoint, + waitForValidation: async (signal?: AbortSignal) => { + if (!targetCheckpoint || !targetUpdated) { + // FIXME throw an error in this case? + return; + } + const localBucketState = await this.bucketStorage.getLocalState(); + if (localBucketState.lastOpId >= normalizeChecksum(targetCheckpoint)!) { + return; + } + + return new Promise((resolve, reject) => { + let disposeListener: (() => void) | null = null; + const signalListener = () => { + reject(new Error('Aborted')); + disposeListener?.(); + }; + disposeListener = this.registerListener({ + checkpointCompleted: (checkpoint) => { + if (checkpoint.write_checkpoint && checkpoint.write_checkpoint >= targetCheckpoint!) { + resolve(); + disposeListener?.(); + signal?.removeEventListener('abort', signalListener); + } + } + }); + signal?.addEventListener('abort', disposeListener, { once: true }); + }); + } }; } - protected triggerCrudUpload(): void { + triggerCrudUpload(): void { if (!this.crudManager) { return; } @@ -422,6 +459,10 @@ export class SyncClientImpl extends BaseObserver implements lastSyncedAt: new Date() }); + this.iterateListeners((listener) => { + listener.checkpointCompleted?.(checkpoint); + }); + return { applied: true, endIteration: false }; } diff --git a/packages/lite-sdk/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts index d851af1a8..8b30a5eec 100644 --- a/packages/lite-sdk/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -1,7 +1,8 @@ -import { describe, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { MemoryBucketStorageImpl } from '../src/client/storage/MemoryBucketStorageImpl.js'; -import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; +import { SyncOperation, SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; import { Connector, PowerSyncCredentials } from '../src/client/sync/Connector.js'; +import { CrudManager } from '../src/client/sync/CrudManager.js'; import { SyncClientImpl } from '../src/client/sync/SyncClientImpl.js'; import { DEFAULT_SYSTEM_DEPENDENCIES } from '../src/client/system/SystemDependencies.js'; @@ -28,7 +29,8 @@ class TestConnector extends Connector { describe(`PowerSync Lite`, { timeout: Infinity }, () => { describe(`Connection`, () => { - it(`should connect to a PowerSync server`, async () => { + // This is just for local testing of the sync client + it.skip(`should connect to a PowerSync server`, async () => { const syncOperationsHandler: SyncOperationsHandler = { handleCheckpoint: async (event) => { // Funnel these operations to external storage @@ -53,5 +55,106 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { // Long running test for demonstrating the sync client }); + + it(`should handle uploads`, async () => { + let lastPut: SyncOperation | null = null; + + const syncOperationsHandler: SyncOperationsHandler = { + handleCheckpoint: async (event) => { + // Funnel these operations to external storage + console.log(`Processing ${event.pendingOperations.length} operations`); + lastPut = [...event.pendingOperations].reverse().find((op) => op.op === 'PUT') ?? null; + } + }; + + const checkpointSpy = vi.spyOn(syncOperationsHandler, 'handleCheckpoint'); + + const crudManager = { + // We start by reporting there are no CRUD items + hasCrud: vi.fn(async () => false), + performUpload: vi.fn(async () => { + // Perform some upload + if (!lastPut) { + console.log('no last put'); + return; + } + + const credentials = await new TestConnector().fetchCredentials(); + if (!credentials) { + console.log('no credentials'); + return; + } + + const payload = { + batch: [ + { + id: lastPut.id, + table: lastPut.type, + op: 'PUT', + data: { + // FIXME double parse + ...JSON.parse(JSON.parse(lastPut.data!)), + name: 'updated' + } + } + ] + }; + + const response = await fetch(`http://localhost:6060/api/data`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + throw new Error(`Received ${response.status} from /api/data: ${await response.text()}`); + } + + // hack + crudManager.hasCrud.mockImplementation(async () => false); + + // FIXME circular dependency here + // Wait for the last upload to create a write checkpoint and for it + // to have been processed. + // If there is still pending crud, this will not create a new checkpoint. + const createCheckpointResponse = await syncClient.checkpoint(); + await createCheckpointResponse.waitForValidation(); + console.log('checkpoint validated', createCheckpointResponse.targetCheckpoint); + }) + } satisfies CrudManager; + + const systemDependencies = DEFAULT_SYSTEM_DEPENDENCIES(); + const syncClient = new SyncClientImpl({ + connectionRetryDelayMs: 1000, + crudManager, + debugMode: true, + uploadThrottleMs: 0, + storage: new MemoryBucketStorageImpl({ + operationsHandlers: [syncOperationsHandler], + systemDependencies: systemDependencies + }), + systemDependencies + }); + + await syncClient.connect(new TestConnector()); + + // Wait for the checkpoint to be processed + await vi.waitFor(() => expect(checkpointSpy.mock.calls.length).toBeGreaterThan(0)); + + // Mark that we have crud now + crudManager.hasCrud.mockImplementation(async () => true); + + // Trigger a CRUD upload + syncClient.triggerCrudUpload(); + + // Wait for the CRUD upload to be processed + await vi.waitFor(() => expect(crudManager.performUpload.mock.calls.length).toBeGreaterThan(0)); + + console.log('processed'); + + await new Promise((resolve) => setTimeout(resolve, 1000000)); + }); }); }); From 573dfad2fc30f16b7af93931bc96f1d3c1b524b2 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 12 Nov 2025 13:15:45 +0200 Subject: [PATCH 12/13] Add Crud unit tests --- packages/lite-sdk/package.json | 8 +- .../src/client/storage/BucketStorage.ts | 6 + .../client/storage/MemoryBucketStorageImpl.ts | 5 + .../src/client/sync/SyncClientImpl.ts | 25 +- .../lite-sdk/tests/powersync-lite.test.ts | 6 +- packages/lite-sdk/tests/uploads.test.ts | 269 ++++++++++++++++++ packages/lite-sdk/tests/utils.ts | 5 +- 7 files changed, 307 insertions(+), 17 deletions(-) create mode 100644 packages/lite-sdk/tests/uploads.test.ts diff --git a/packages/lite-sdk/package.json b/packages/lite-sdk/package.json index 6e6ad0bae..c7f97f19c 100644 --- a/packages/lite-sdk/package.json +++ b/packages/lite-sdk/package.json @@ -44,12 +44,12 @@ "test:exports": "attw --pack ." }, "dependencies": { - "ts-codec": "^1.3.0" - }, - "devDependencies": { - "@types/node": "^20.5.9", + "ts-codec": "^1.3.0", "@powersync/service-jsonbig": "^0.17.11", "@powersync/service-sync-rules": "^0.29.5", "@powersync/service-core": "^1.15.8" + }, + "devDependencies": { + "@types/node": "^20.5.9" } } diff --git a/packages/lite-sdk/src/client/storage/BucketStorage.ts b/packages/lite-sdk/src/client/storage/BucketStorage.ts index 2f30c16fd..39aa3f63b 100644 --- a/packages/lite-sdk/src/client/storage/BucketStorage.ts +++ b/packages/lite-sdk/src/client/storage/BucketStorage.ts @@ -10,7 +10,13 @@ export type SavedProgress = { }; export interface LocalState { + /** + * The last write checkpoint that was applied to the local database. + */ lastOpId: bigint; + /** + * The target write checkpoint that the local database is syncing to. + */ targetOpId: bigint; } diff --git a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts index 2cc05adff..81bf4bdce 100644 --- a/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts +++ b/packages/lite-sdk/src/client/storage/MemoryBucketStorageImpl.ts @@ -151,6 +151,11 @@ export class MemoryBucketStorageImpl implements BucketStorage { return false; } + if (await this.crudManager?.hasCrud()) { + // Still has crud data, can't update checkpoint + return false; + } + // Call the callback to get the new opId const opId = await cb(); diff --git a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts index 23ef80be3..c3a891e9b 100644 --- a/packages/lite-sdk/src/client/sync/SyncClientImpl.ts +++ b/packages/lite-sdk/src/client/sync/SyncClientImpl.ts @@ -58,7 +58,7 @@ export class SyncClientImpl extends BaseObserver implements this.openStreamFn = options.streamOpener ?? openHttpStream; } - protected get bucketStorage(): BucketStorage { + get bucketStorage(): BucketStorage { return this.options.storage; } @@ -123,9 +123,9 @@ export class SyncClientImpl extends BaseObserver implements async checkpoint(customCheckpoint?: string): Promise { let targetCheckpoint: string | undefined = customCheckpoint; // FIXME, could this be optimized? - // If there are no crud items, we set the target to the custom checkpoint or the max op id - // We then set the local target to the new checkpoint - await this.bucketStorage.handleCrudUploaded(customCheckpoint); + // We essentially set the target op to the max_id then if there is no crud we set it to the target later + // Don't pass in `customCheckpoint` here, that makes it difficult to test if we applied it later + await this.bucketStorage.handleCrudUploaded(); const targetUpdated = await this.bucketStorage.updateLocalTarget(async () => { if (targetCheckpoint) { return targetCheckpoint; @@ -317,7 +317,7 @@ export class SyncClientImpl extends BaseObserver implements // Handle various sync line types if (`checkpoint` in line) { - this.options.debugMode && console.debug(`Received checkpoint`, line.checkpoint); + this.options.debugMode && console.debug(`Received checkpoint`, JSON.stringify(line, null, '\t')); const bucketsToDelete = new Set(syncState.bucketMap.keys()); const newBuckets = new Map(); for (const checksum of line.checkpoint.buckets) { @@ -337,7 +337,8 @@ export class SyncClientImpl extends BaseObserver implements downloading: true }); } else if (`checkpoint_complete` in line) { - this.options.debugMode && console.debug(`Received checkpoint complete`, syncState.targetCheckpoint); + this.options.debugMode && + console.debug(`Received checkpoint complete`, JSON.stringify(line, null, '\t'), syncState.targetCheckpoint); const result = await this.applyCheckpoint(syncState.targetCheckpoint!); if (result.endIteration) { return; @@ -348,7 +349,12 @@ export class SyncClientImpl extends BaseObserver implements // Status updated in applyCheckpoint when applied successfully } } else if (`partial_checkpoint_complete` in line) { - this.options.debugMode && console.debug(`Received partial checkpoint complete`, syncState.targetCheckpoint); + this.options.debugMode && + console.debug( + `Received partial checkpoint complete`, + JSON.stringify(line, null, '\t'), + syncState.targetCheckpoint + ); const priority = line.partial_checkpoint_complete.priority; const result = await this.bucketStorage.syncLocalDatabase(syncState.targetCheckpoint!, priority); if (!result.checkpointValid) { @@ -363,7 +369,8 @@ export class SyncClientImpl extends BaseObserver implements // We'll keep on downloading, but can report that this priority is synced now. } } else if (`checkpoint_diff` in line) { - this.options.debugMode && console.debug(`Received checkpoint diff`, syncState.targetCheckpoint); + this.options.debugMode && + console.debug(`Received checkpoint diff`, JSON.stringify(line, null, '\t'), syncState.targetCheckpoint); // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint if (syncState.targetCheckpoint == null) { throw new Error(`Checkpoint diff without previous checkpoint`); @@ -406,7 +413,7 @@ export class SyncClientImpl extends BaseObserver implements } await this.bucketStorage.removeBuckets(bucketsToDelete); } else if (`data` in line) { - this.options.debugMode && console.debug(`Received data`, line.data); + this.options.debugMode && console.debug(`Received data`, JSON.stringify(line, null, '\t')); const { data } = line; // Update status to indicate we're downloading data this.updateSyncStatus({ diff --git a/packages/lite-sdk/tests/powersync-lite.test.ts b/packages/lite-sdk/tests/powersync-lite.test.ts index 8b30a5eec..a32b39528 100644 --- a/packages/lite-sdk/tests/powersync-lite.test.ts +++ b/packages/lite-sdk/tests/powersync-lite.test.ts @@ -42,7 +42,7 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { const systemDependencies = DEFAULT_SYSTEM_DEPENDENCIES(); const syncClient = new SyncClientImpl({ connectionRetryDelayMs: 1000, - debugMode: false, + debugMode: true, uploadThrottleMs: 1000, storage: new MemoryBucketStorageImpl({ operationsHandlers: [syncOperationsHandler], @@ -53,10 +53,12 @@ describe(`PowerSync Lite`, { timeout: Infinity }, () => { await syncClient.connect(new TestConnector()); + await new Promise((resolve) => setTimeout(resolve, 1000000)); + // Long running test for demonstrating the sync client }); - it(`should handle uploads`, async () => { + it.skip(`should handle uploads`, async () => { let lastPut: SyncOperation | null = null; const syncOperationsHandler: SyncOperationsHandler = { diff --git a/packages/lite-sdk/tests/uploads.test.ts b/packages/lite-sdk/tests/uploads.test.ts new file mode 100644 index 000000000..e5ab96378 --- /dev/null +++ b/packages/lite-sdk/tests/uploads.test.ts @@ -0,0 +1,269 @@ +import { Checkpoint, StreamingSyncLine } from '@powersync/service-core'; +import { MockInstance, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { SyncOperationsHandler } from '../src/client/storage/SyncOperationsHandler.js'; +import { Connector, type PowerSyncCredentials } from '../src/client/sync/Connector.js'; +import { MockStreamFactory, createTestSyncClient, waitForSyncStatus } from './utils.js'; + +class PlaceholderConnector extends Connector { + async fetchCredentials(): Promise { + return { + endpoint: 'https://powersync.example.org', + token: 'test-token' + }; + } +} + +const MOCK_EVENTS: StreamingSyncLine[] = [ + // Initial checkpoint + { + checkpoint: { + last_op_id: '3', + buckets: [ + { + bucket: 'global[]', + checksum: -1638234279, + count: 3, + priority: 3, + subscriptions: [ + { + default: 0 + } + ] + } + ], + streams: [ + { + name: 'global', + is_default: true, + errors: [] + } + ] + } + }, + // Initial data + { + data: { + bucket: 'global[]', + after: '0', + has_more: false, + data: [ + { + op_id: '1', + op: 'PUT', + object_type: 'lists', + object_id: '75f89104-d95a-4f16-8309-5363f1bb377a', + checksum: 4029462408, + subkey: '69145d63903a98286d921fa4/59a1abe9-f2fd-50c3-94d4-235ed2cbaba5', + data: '{"id":"75f89104-d95a-4f16-8309-5363f1bb377a","created_at":"2025-11-12 10:11:41.568515Z","name":"Getting Started","owner_id":"fa428d64-038e-4764-9759-f57611831722"}' + }, + { + op_id: '2', + op: 'PUT', + object_type: 'todos', + object_id: '4115b3f3-f33a-4c5d-b8a3-e837df1914d0', + checksum: 320476424, + subkey: '69145d63903a98286d921fa5/922996ae-eced-5644-9030-9d503af9df53', + data: '{"id":"4115b3f3-f33a-4c5d-b8a3-e837df1914d0","created_at":"2025-11-12 10:11:41.569654Z","completed_at":null,"description":"Create a todo here. Query the todos table via a Postgres connection. Your todo should be synced","completed":0,"created_by":null,"completed_by":null,"list_id":"75f89104-d95a-4f16-8309-5363f1bb377a","photo_id":null}' + }, + { + op_id: '3', + op: 'PUT', + object_type: 'todos', + object_id: '8e4af737-bfed-4a65-ac2b-03b47a0e5d7f', + checksum: 2601761481, + subkey: '69145d63903a98286d921fa5/b64718a0-8a3e-53be-82ad-803bb7a0148b', + data: '{"id":"8e4af737-bfed-4a65-ac2b-03b47a0e5d7f","created_at":"2025-11-12 10:11:41.569289Z","completed_at":null,"description":"Run services locally","completed":1,"created_by":null,"completed_by":null,"list_id":"75f89104-d95a-4f16-8309-5363f1bb377a","photo_id":null}' + } + ], + next_after: '3' + } + }, + // Checkpoint complete + { + checkpoint_complete: { + last_op_id: '3' + } + } +]; + +describe('Sync Uploads', () => { + let mockStreamFactory: MockStreamFactory; + const defaultOperationsHandler: SyncOperationsHandler = { + handleCheckpoint: async () => { + // No-op for tests + } + }; + + beforeEach(() => { + mockStreamFactory = new MockStreamFactory(); + }); + + afterEach(() => { + mockStreamFactory.closeAll(); + }); + + it('should connect and receive checkpoints', async () => { + const crudManager = { + hasCrud: vi.fn(async () => false), + performUpload: vi.fn(async () => { + // No-op for tests + }) + }; + + const checkpointHandler = { + handleCheckpoint: vi.fn(async () => {}) + }; + + const client = createTestSyncClient( + { + crudManager + }, + mockStreamFactory, + checkpointHandler + ); + + const connector = new PlaceholderConnector(); + + client.connect(connector); + + // Wait for connection + await waitForSyncStatus(client, (status) => status.connected === true, 1000); + + // Send checkpoint + const controllers = mockStreamFactory.getControllers(); + expect(controllers.length).eq(1); + + // Send initial events + for (const event of MOCK_EVENTS) { + mockStreamFactory.pushLine(event); + } + + // Wait for sync to complete + await waitForSyncStatus(client, (status) => status.hasSynced === true, 2000); + + // We should have applied the ops for the checkpoint + expect(checkpointHandler.handleCheckpoint.mock.calls.length).toBe(1); + + // Now we can perform some uploads + const target = await client.bucketStorage.getLocalState(); + // We haven't uploaded any data yet, so the write checkpoint target should be 0 + expect(target.targetOpId).toBe(0n); + + // Simulate an upload + // Report that we have CRUD items to upload + crudManager.hasCrud.mockImplementation(async () => true); + + crudManager.performUpload.mockImplementation(async () => { + // Don't need to actually do any upload, lets create a write checkpoint + + // If we still report that there is crud, the write checkpoint should not be updated + const response = await client.checkpoint(); + expect(response.targetUpdated).toBe(false); + + // Now mock that the "CRUD queue" has been emptied + crudManager.hasCrud.mockImplementation(async () => false); + + // Now the write checkpoint should be updated + // We use a custom checkpoint here since that requires less mocks + const responseAfter = await client.checkpoint('1'); + expect(responseAfter.targetUpdated).toBe(true); + expect(responseAfter.targetCheckpoint).toEqual('1'); + }); + + client.triggerCrudUpload(); + await vi.waitFor(() => expect(crudManager.performUpload.mock.calls.length).toBe(1)); + + await waitForSyncStatus(client, (status) => status.uploading == false, 2000); + + // The target checkpoint should be the write checkpoint after uploading + const localStateAfter = await client.bucketStorage.getLocalState(); + expect(localStateAfter.targetOpId).toEqual(1n); + + // Now if we receive more data where the write checkpoint is not reached. + // The data should not be presented to the operations handler until we validate the checkpoint correlating to the target write checkpoint + + // Send more data + + // Spy on the applyCheckpoint method + const appliedCheckpointSpy = vi.spyOn(client as any, 'applyCheckpoint') as MockInstance< + (checkpoint: Checkpoint) => Promise<{ applied: boolean; endIteration: boolean }> + >; + + mockStreamFactory.pushLine({ + checkpoint_diff: { + last_op_id: '4', + removed_buckets: [], + updated_buckets: [ + { + bucket: 'global[]', + checksum: -1298840993, + count: 4, + priority: 3, + subscriptions: [ + { + default: 0 + } + ] + } + ] + } + }); + mockStreamFactory.pushLine({ + data: { + bucket: 'global[]', + after: '3', + has_more: false, + data: [ + { + op_id: '4', + op: 'PUT', + object_type: 'lists', + object_id: '75f89104-d95a-4f16-8309-5363f1bb377a', + checksum: 339393286, + subkey: '69145d63903a98286d921fa4/59a1abe9-f2fd-50c3-94d4-235ed2cbaba5', + data: '{"id":"75f89104-d95a-4f16-8309-5363f1bb377a","created_at":"2025-11-12 10:11:41.568515Z","name":"Getting Started Updated","owner_id":"fa428d64-038e-4764-9759-f57611831722"}' + } + ], + next_after: '4' + } + }); + + mockStreamFactory.pushLine({ + checkpoint_complete: { + last_op_id: '4' + } + }); + + // It should have attempted to apply the checkpoint + await vi.waitFor(() => expect(appliedCheckpointSpy.mock.calls.length).toBe(1)); + // It should not have applied the checkpoint yet + expect(appliedCheckpointSpy.mock.calls[0][0].last_op_id).toBe('4'); + expect((await appliedCheckpointSpy.mock.results[0].value).applied).toBe(false); + expect((await appliedCheckpointSpy.mock.results[0].value).endIteration).toBe(false); + + // Now send a diff to update the target checkpoint + mockStreamFactory.pushLine({ + checkpoint_diff: { + last_op_id: '5', + updated_buckets: [], + removed_buckets: [], + write_checkpoint: '1' + } + }); + + mockStreamFactory.pushLine({ + checkpoint_complete: { + last_op_id: '5' + } + }); + + await vi.waitFor(() => expect(appliedCheckpointSpy.mock.calls.length).toBe(2)); + // It should have applied the checkpoint + expect(appliedCheckpointSpy.mock.calls[1][0].last_op_id).toBe('5'); + expect((await appliedCheckpointSpy.mock.results[1].value).applied).toBe(true); + expect((await appliedCheckpointSpy.mock.results[1].value).endIteration).toBe(false); + expect(checkpointHandler.handleCheckpoint.mock.calls.length).toBe(2); + + client.disconnect(); + }); +}); diff --git a/packages/lite-sdk/tests/utils.ts b/packages/lite-sdk/tests/utils.ts index 47568881b..9f58c7b3a 100644 --- a/packages/lite-sdk/tests/utils.ts +++ b/packages/lite-sdk/tests/utils.ts @@ -136,16 +136,17 @@ export function createTestSyncClient( partialOptions: Partial, mockFactory: MockStreamFactory, operationsHandler: SyncOperationsHandler -): SyncClient { +): SyncClientImpl { const systemDependencies = partialOptions.systemDependencies ?? DEFAULT_SYSTEM_DEPENDENCIES(); const defaultOptions: SyncClientOptions = { connectionRetryDelayMs: 100, - uploadRetryDelayMs: 100, + uploadThrottleMs: 100, debugMode: false, storage: partialOptions.storage ?? new MemoryBucketStorageImpl({ + ...partialOptions, operationsHandlers: [operationsHandler], systemDependencies }), From d7e83eb3baa2acf896af2929e196645eecae0295 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 13 Nov 2025 09:10:20 +0200 Subject: [PATCH 13/13] Add temporary tanstack playground --- packages/temp-tanstackdb/package.json | 56 +++++++++++++++++++ packages/temp-tanstackdb/src/index.ts | 51 +++++++++++++++++ packages/temp-tanstackdb/tsconfig.json | 20 +++++++ pnpm-lock.yaml | 77 ++++++++++++++++++++++++-- 4 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 packages/temp-tanstackdb/package.json create mode 100644 packages/temp-tanstackdb/src/index.ts create mode 100644 packages/temp-tanstackdb/tsconfig.json diff --git a/packages/temp-tanstackdb/package.json b/packages/temp-tanstackdb/package.json new file mode 100644 index 000000000..c105ae08e --- /dev/null +++ b/packages/temp-tanstackdb/package.json @@ -0,0 +1,56 @@ +{ + "name": "@powersync/temp-tanstackdb", + "private": true, + "version": "0.0.0", + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "description": "API definitions for JourneyApps PowerSync", + "type": "module", + "main": "dist/bundle.mjs", + "module": "dist/bundle.mjs", + "types": "lib/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.ts", + "default": "./dist/bundle.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "require": "./dist/bundle.cjs" + } + } + }, + "author": "POWERSYNC", + "license": "Apache-2.0", + "files": [ + "lib", + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/powersync-ja/powersync-js.git" + }, + "bugs": { + "url": "https://github.com/powersync-ja/powersync-js/issues" + }, + "homepage": "https://docs.powersync.com", + "scripts": { + "build": "tsc -b", + "build:prod": "tsc -b", + "clean": "rm -rf lib dist tsconfig.tsbuildinfo", + "test": "vitest", + "test:exports": "attw --pack ." + }, + "dependencies": { + "@powersync/lite-sdk": "workspace:*", + "@standard-schema/spec": "^1.0.0", + "@tanstack/db": "0.4.20", + "@tanstack/store": "^0.8.0" + }, + "devDependencies": { + "@types/node": "^20.5.9" + } +} diff --git a/packages/temp-tanstackdb/src/index.ts b/packages/temp-tanstackdb/src/index.ts new file mode 100644 index 000000000..c125c6d94 --- /dev/null +++ b/packages/temp-tanstackdb/src/index.ts @@ -0,0 +1,51 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec'; +import { BaseCollectionConfig, CollectionConfig, SyncConfig } from '@tanstack/db'; + +export type BasePowerSyncCollectionConfig< + TTable extends Record, + TSchema extends StandardSchemaV1 = never +> = Omit, `onInsert` | `onUpdate` | `onDelete` | `getKey`> & {}; + +export function powerSyncCollectionOptions< + TTable extends Record, + TSchema extends StandardSchemaV1 = never +>(config: BasePowerSyncCollectionConfig) { + const { ...restConfig } = config; + + const sync: SyncConfig = { + sync: (params) => { + const { begin, write, commit, markReady } = params; + const abortController = new AbortController(); + + // The sync function needs to be synchronous + async function start() {} + + start().catch((error) => console.error(`Could not start syncing process for TODO into TODO`, error)); + + return () => { + console.info(`Sync has been stopped for TODO into TODO`); + abortController.abort(); + }; + } + }; + + const getKey = (record: TTable) => { + if ('id' in record) { + return record.id as string; + } + throw new Error('Record has no id'); + }; + + const outputConfig: CollectionConfig = { + ...restConfig, + getKey, + // Syncing should start immediately since we need to monitor the changes for mutations + startSync: true, + sync, + onInsert: async (params) => {}, + onUpdate: async (params) => {}, + onDelete: async (params) => {}, + utils: {} + }; + return outputConfig; +} diff --git a/packages/temp-tanstackdb/tsconfig.json b/packages/temp-tanstackdb/tsconfig.json new file mode 100644 index 000000000..691726dcb --- /dev/null +++ b/packages/temp-tanstackdb/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "baseUrl": "./", + "jsx": "react", + "types": ["node"], + "rootDir": "src", + "outDir": "./lib", + "lib": ["esnext"], + "declaration": true, + "module": "NodeNext", + "moduleResolution": "nodenext", + "preserveConstEnums": true, + "esModuleInterop": false, + "skipLibCheck": true, + "strictNullChecks": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "lib"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae9f9015f..ee85ad03a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1975,10 +1975,6 @@ importers: packages/lite-sdk: dependencies: - ts-codec: - specifier: ^1.3.0 - version: 1.3.0 - devDependencies: '@powersync/service-core': specifier: ^1.15.8 version: 1.15.8(babel-plugin-macros@3.1.0) @@ -1988,6 +1984,10 @@ importers: '@powersync/service-sync-rules': specifier: ^0.29.5 version: 0.29.5 + ts-codec: + specifier: ^1.3.0 + version: 1.3.0 + devDependencies: '@types/node': specifier: ^20.5.9 version: 20.17.57 @@ -2206,6 +2206,25 @@ importers: specifier: 18.3.1 version: 18.3.1 + packages/temp-tanstackdb: + dependencies: + '@powersync/lite-sdk': + specifier: workspace:* + version: link:../lite-sdk + '@standard-schema/spec': + specifier: ^1.0.0 + version: 1.0.0 + '@tanstack/db': + specifier: 0.4.20 + version: 0.4.20(typescript@5.9.2) + '@tanstack/store': + specifier: ^0.8.0 + version: 0.8.0 + devDependencies: + '@types/node': + specifier: ^20.5.9 + version: 20.17.57 + packages/vue: devDependencies: '@powersync/common': @@ -8154,6 +8173,9 @@ packages: '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@stencil/core@4.36.3': resolution: {integrity: sha512-C9DOaAjm+hSYRuVoUuYWG/lrYT8+4DG0AL0m1Ea9+G5v2Y6ApVpNJLbXvFlRZIdDMGecH86s6v0Gp39uockLxg==} engines: {node: '>=16.0.0', npm: '>=7.10.0'} @@ -8960,6 +8982,20 @@ packages: peerDependencies: react: '*' + '@tanstack/db-ivm@0.1.12': + resolution: {integrity: sha512-QTs2dgrjTCkATZh12nb854K/Of80QyyR6URYyxnGPnJgXFchvw25Ve3TtFW51fHR5hheQltkbtpPgBjSZvoMVA==} + peerDependencies: + typescript: '>=4.7' + + '@tanstack/db@0.4.20': + resolution: {integrity: sha512-D5cCW9O+bjaIiLEY00djhEUnsdw7ARdezXr917jt95VNkQ8RX816oBoAsWAgphMcIoNvpB2nI1ccyLSCrztW6A==} + peerDependencies: + typescript: '>=4.7' + + '@tanstack/pacer@0.1.0': + resolution: {integrity: sha512-QVzkGO5clvGj/qdX8H2wUj0QCXCLZ/pwPMnfSqhoYfpzDRkRHDj+3D+VzdcehBIVnE+GCd1D/P1tGMzfjmfrzQ==} + engines: {node: '>=18'} + '@tanstack/query-core@5.79.0': resolution: {integrity: sha512-s+epTqqLM0/TbJzMAK7OEhZIzh63P9sWz5HEFc5XHL4FvKQXQkcjI8F3nee+H/xVVn7mrP610nVXwOytTSYd0w==} @@ -8968,6 +9004,9 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/store@0.8.0': + resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -13402,6 +13441,10 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fractional-indexing@3.2.0: + resolution: {integrity: sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==} + engines: {node: ^14.13.1 || >=16.0.0} + framer-motion@6.5.1: resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==} peerDependencies: @@ -19238,6 +19281,9 @@ packages: sorted-array-functions@1.3.0: resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} + sorted-btree@1.8.1: + resolution: {integrity: sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -30195,6 +30241,8 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 + '@standard-schema/spec@1.0.0': {} + '@stencil/core@4.36.3': optionalDependencies: '@rollup/rollup-darwin-arm64': 4.34.9 @@ -31386,6 +31434,21 @@ snapshots: '@tamagui/use-force-update': 1.79.6(react@18.3.1) react: 18.3.1 + '@tanstack/db-ivm@0.1.12(typescript@5.9.2)': + dependencies: + fractional-indexing: 3.2.0 + sorted-btree: 1.8.1 + typescript: 5.9.2 + + '@tanstack/db@0.4.20(typescript@5.9.2)': + dependencies: + '@standard-schema/spec': 1.0.0 + '@tanstack/db-ivm': 0.1.12(typescript@5.9.2) + '@tanstack/pacer': 0.1.0 + typescript: 5.9.2 + + '@tanstack/pacer@0.1.0': {} + '@tanstack/query-core@5.79.0': {} '@tanstack/react-query@5.79.0(react@18.3.1)': @@ -31393,6 +31456,8 @@ snapshots: '@tanstack/query-core': 5.79.0 react: 18.3.1 + '@tanstack/store@0.8.0': {} + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.27.1 @@ -37465,6 +37530,8 @@ snapshots: fraction.js@4.3.7: {} + fractional-indexing@3.2.0: {} + framer-motion@6.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@motionone/dom': 10.12.0 @@ -45687,6 +45754,8 @@ snapshots: sorted-array-functions@1.3.0: {} + sorted-btree@1.8.1: {} + source-map-js@1.2.1: {} source-map-loader@5.0.0(webpack@5.98.0(@swc/core@1.11.29)):