diff --git a/demos/react-supabase-todolist-tanstackdb/e2e/mocks/GuardBySync.tsx b/demos/react-supabase-todolist-tanstackdb/e2e/mocks/GuardBySync.tsx new file mode 100644 index 000000000..da638ca83 --- /dev/null +++ b/demos/react-supabase-todolist-tanstackdb/e2e/mocks/GuardBySync.tsx @@ -0,0 +1,10 @@ +import { FC, ReactNode } from 'react'; + +/** + * Mock GuardBySync that always renders children immediately. + * Bypasses the sync check for testing purposes. + */ +export const GuardBySync: FC<{ children: ReactNode; priority?: number }> = ({ children }) => { + // Always render children - skip the sync check + return <>{children}; +}; diff --git a/demos/react-supabase-todolist-tanstackdb/e2e/mocks/SupabaseConnector.ts b/demos/react-supabase-todolist-tanstackdb/e2e/mocks/SupabaseConnector.ts new file mode 100644 index 000000000..2d6d2259b --- /dev/null +++ b/demos/react-supabase-todolist-tanstackdb/e2e/mocks/SupabaseConnector.ts @@ -0,0 +1,155 @@ +import { + AbstractPowerSyncDatabase, + BaseObserver, + PowerSyncBackendConnector, + type PowerSyncCredentials +} from '@powersync/web'; + +// Mock user ID for testing +export const MOCK_USER_ID = 'test-user-123'; + +export type SupabaseConfig = { + supabaseUrl: string; + supabaseAnonKey: string; + powersyncUrl: string; +}; + +// Mock Session type matching Supabase's Session +type MockSession = { + access_token: string; + refresh_token: string; + expires_in: number; + expires_at: number; + token_type: string; + user: { + id: string; + email: string; + aud: string; + role: string; + created_at: string; + }; +}; + +export type SupabaseConnectorListener = { + initialized: () => void; + sessionStarted: (session: MockSession) => void; +}; + +/** + * Mock SupabaseConnector for testing. + * Simulates an authenticated session without requiring actual Supabase credentials. + */ +export class SupabaseConnector extends BaseObserver implements PowerSyncBackendConnector { + readonly client: MockSupabaseClient; + readonly config: SupabaseConfig; + + ready: boolean; + currentSession: MockSession | null; + + constructor() { + super(); + this.config = { + supabaseUrl: 'https://mock.supabase.test', + powersyncUrl: 'https://mock.powersync.test', + supabaseAnonKey: 'mock-anon-key' + }; + + this.client = new MockSupabaseClient(); + + // Pre-authenticated session + this.currentSession = createMockSession(); + this.ready = false; + } + + async init() { + if (this.ready) { + return; + } + + // Simulate session being loaded + this.ready = true; + this.iterateListeners((cb) => cb.initialized?.()); + + // Trigger session started since we're pre-authenticated + if (this.currentSession) { + this.iterateListeners((cb) => cb.sessionStarted?.(this.currentSession!)); + } + } + + async login(_username: string, _password: string) { + // Mock login - always succeeds + this.currentSession = createMockSession(); + this.updateSession(this.currentSession); + } + + async fetchCredentials(): Promise { + // Return mock credentials + return { + endpoint: this.config.powersyncUrl, + token: this.currentSession?.access_token ?? 'mock-token' + }; + } + + async uploadData(_database: AbstractPowerSyncDatabase): Promise { + // Mock upload - do nothing + } + + updateSession(session: MockSession | null) { + this.currentSession = session; + if (session) { + this.iterateListeners((cb) => cb.sessionStarted?.(session)); + } + } +} + +/** + * Creates a mock authenticated session + */ +function createMockSession(): MockSession { + return { + access_token: 'mock-access-token', + refresh_token: 'mock-refresh-token', + expires_in: 3600, + expires_at: Math.floor(Date.now() / 1000) + 3600, + token_type: 'bearer', + user: { + id: MOCK_USER_ID, + email: 'test@example.com', + aud: 'authenticated', + role: 'authenticated', + created_at: new Date().toISOString() + } + }; +} + +/** + * Mock Supabase client for testing + */ +class MockSupabaseClient { + auth = { + getSession: async () => ({ + data: { session: createMockSession() }, + error: null + }), + signInWithPassword: async (_credentials: { email: string; password: string }) => ({ + data: { session: createMockSession() }, + error: null + }), + signOut: async () => ({ error: null }), + onAuthStateChange: (_callback: (event: string, session: MockSession | null) => void) => { + return { data: { subscription: { unsubscribe: () => {} } } }; + } + }; + + from(_table: string) { + return { + upsert: async (_record: unknown) => ({ error: null }), + update: (_data: unknown) => ({ + eq: async (_column: string, _value: unknown) => ({ error: null }) + }), + delete: () => ({ + eq: async (_column: string, _value: unknown) => ({ error: null }) + }) + }; + } +} diff --git a/demos/react-supabase-todolist-tanstackdb/e2e/test-setup.tsx b/demos/react-supabase-todolist-tanstackdb/e2e/test-setup.tsx new file mode 100644 index 000000000..c50086592 --- /dev/null +++ b/demos/react-supabase-todolist-tanstackdb/e2e/test-setup.tsx @@ -0,0 +1,12 @@ +// Re-export MOCK_USER_ID from the mock +export { MOCK_USER_ID } from './mocks/SupabaseConnector'; + +/** + * Sets up the test DOM structure matching the app's index.html + */ +export function setupTestDOM() { + document.body.innerHTML = ` +
+ `; + return document.getElementById('app')!; +} diff --git a/demos/react-supabase-todolist-tanstackdb/e2e/todo-lists.test.tsx b/demos/react-supabase-todolist-tanstackdb/e2e/todo-lists.test.tsx new file mode 100644 index 000000000..b56326615 --- /dev/null +++ b/demos/react-supabase-todolist-tanstackdb/e2e/todo-lists.test.tsx @@ -0,0 +1,195 @@ +import { screen, waitFor } from '@testing-library/react'; +import { act } from 'react'; +import { Root, createRoot } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { App } from '../src/app/App'; +import { MOCK_USER_ID, setupTestDOM } from './test-setup'; + +// Get access to the PowerSync database instance exposed on window +declare global { + interface Window { + _powersync: import('@powersync/web').PowerSyncDatabase; + } +} + +describe('TodoLists E2E', () => { + let root: Root | null = null; + let container: HTMLElement; + + beforeEach(async () => { + // Set up DOM + container = setupTestDOM(); + await renderAppAndWaitForTodoListsScreen(); + }); + + afterEach(async () => { + // Cleanup + if (root) { + root.unmount(); + root = null; + } + + // Clean up the PowerSync database if it exists + if (window._powersync) { + try { + await window._powersync.disconnectAndClear(); + } catch (e) { + // Ignore errors during cleanup + } + } + }); + + /** + * Helper to render app and wait for the Todo Lists screen to appear + */ + async function renderAppAndWaitForTodoListsScreen() { + await act(async () => { + root = createRoot(container); + root.render(); + }); + + // Wait for PowerSync to be initialized + await waitFor( + () => { + expect(window._powersync).toBeDefined(); + }, + { timeout: 10000 } + ); + + // Wait for the Todo Lists screen to appear (app auto-navigates when authenticated) + await waitFor( + () => { + expect(screen.getByText('Todo Lists')).toBeTruthy(); + }, + { timeout: 10000 } + ); + } + + /** + * Helper to insert a list and wait for it to appear + */ + async function insertList(name: string) { + const listId = crypto.randomUUID(); + + await act(async () => { + await window._powersync.execute(`INSERT INTO lists (id, name, owner_id, created_at) VALUES (?, ?, ?, ?)`, [ + listId, + name, + MOCK_USER_ID, + new Date().toISOString() + ]); + }); + + return listId; + } + + /** + * Helper to insert a todo + */ + async function insertTodo(listId: string, description: string, completed: boolean = false) { + const todoId = crypto.randomUUID(); + + await act(async () => { + await window._powersync.execute( + `INSERT INTO todos (id, list_id, description, created_by, created_at, completed, completed_at, completed_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ + todoId, + listId, + description, + MOCK_USER_ID, + new Date().toISOString(), + completed ? 1 : 0, + completed ? new Date().toISOString() : null, + completed ? MOCK_USER_ID : null + ] + ); + }); + + return todoId; + } + + it('should load the app and show the Todo Lists screen', async () => { + // Verify we're on the Todo Lists page + expect(screen.getByText('Todo Lists')).toBeTruthy(); + }); + + it('should display a list widget after inserting a list via PowerSync SQL', async () => { + const listName = 'My Shopping List'; + await insertList(listName); + + // Wait for the list widget to render with our list name + await waitFor( + () => { + expect(screen.getByText(listName)).toBeTruthy(); + }, + { timeout: 10000 } + ); + + // Verify action buttons are present + expect(screen.getByRole('button', { name: /delete/i })).toBeTruthy(); + expect(screen.getByRole('button', { name: /proceed/i })).toBeTruthy(); + }); + + it('should display multiple list widgets after inserting multiple lists', async () => { + // Insert multiple lists + await insertList('Groceries'); + await insertList('Work Tasks'); + await insertList('Personal Goals'); + + // Wait for all list widgets to render + await waitFor( + () => { + expect(screen.getByText('Groceries')).toBeTruthy(); + expect(screen.getByText('Work Tasks')).toBeTruthy(); + expect(screen.getByText('Personal Goals')).toBeTruthy(); + }, + { timeout: 10000 } + ); + + // Verify we have 3 delete buttons (one per list) + const deleteButtons = screen.getAllByRole('button', { name: /delete/i }); + expect(deleteButtons).toHaveLength(3); + }); + + it('should display list with correct todo counts (pending and completed)', async () => { + const listName = 'My Task List'; + const listId = await insertList(listName); + + // Insert todos - 2 incomplete, 1 completed + await insertTodo(listId, 'Buy groceries', false); + await insertTodo(listId, 'Call mom', false); + await insertTodo(listId, 'Finish report', true); + + // Wait for the list widget to render with correct stats + await waitFor( + () => { + expect(screen.getByText(listName)).toBeTruthy(); + // Should show "2 pending, 1 completed" in the description + expect(screen.getByText(/2 pending/i)).toBeTruthy(); + expect(screen.getByText(/1 completed/i)).toBeTruthy(); + }, + { timeout: 10000 } + ); + }); + + it('should render list widgets with delete and navigate action buttons', async () => { + const listName = 'Test List'; + await insertList(listName); + + // Wait for the list widget with action buttons + await waitFor( + () => { + expect(screen.getByText(listName)).toBeTruthy(); + expect(screen.getByRole('button', { name: /delete/i })).toBeTruthy(); + expect(screen.getByRole('button', { name: /proceed/i })).toBeTruthy(); + }, + { timeout: 10000 } + ); + }); + + it('should display the floating action button to add new lists', async () => { + // The FAB should be present - find by class + const fab = document.querySelector('.MuiFab-root'); + expect(fab).not.toBeNull(); + }); +}); diff --git a/demos/react-supabase-todolist-tanstackdb/package.json b/demos/react-supabase-todolist-tanstackdb/package.json index ce552097e..5d31af475 100644 --- a/demos/react-supabase-todolist-tanstackdb/package.json +++ b/demos/react-supabase-todolist-tanstackdb/package.json @@ -6,7 +6,9 @@ "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", - "start": "pnpm build && pnpm preview" + "start": "pnpm build && pnpm preview", + "prepare:isolated:test": "pnpm exec playwright install", + "test:build": "vitest run" }, "dependencies": { "@emotion/react": "11.11.4", @@ -15,13 +17,12 @@ "@mui/icons-material": "^5.15.12", "@mui/material": "^5.15.12", "@mui/x-data-grid": "^6.19.6", - "@powersync/common": "workspace:*", "@powersync/react": "workspace:*", "@powersync/web": "workspace:*", "@supabase/supabase-js": "^2.39.7", - "@tanstack/db": "^0.4.17", - "@tanstack/powersync-db-collection": "^0.1.0", - "@tanstack/react-db": "^0.1.39", + "@tanstack/db": "^0.5.11", + "@tanstack/powersync-db-collection": "^0.1.15", + "@tanstack/react-db": "^0.1.55", "formik": "^2.4.6", "lodash": "^4.17.21", "react": "^18.2.0", @@ -31,17 +32,21 @@ }, "devDependencies": { "@swc/core": "~1.6.0", + "@testing-library/react": "^16.3.0", "@types/lodash": "^4.14.202", "@types/node": "^20.11.25", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.2.1", + "@vitest/browser": "^3.2.4", "autoprefixer": "^10.4.18", "babel-loader": "^9.1.3", + "playwright": "^1.51.0", "typescript": "^5.4.2", "vite": "^5.1.5", "vite-plugin-pwa": "^0.19.2", "vite-plugin-top-level-await": "^1.4.1", - "vite-plugin-wasm": "^3.3.0" + "vite-plugin-wasm": "^3.3.0", + "vitest": "^3.2.4" } } diff --git a/demos/react-supabase-todolist-tanstackdb/src/app/App.tsx b/demos/react-supabase-todolist-tanstackdb/src/app/App.tsx new file mode 100644 index 000000000..a04352f70 --- /dev/null +++ b/demos/react-supabase-todolist-tanstackdb/src/app/App.tsx @@ -0,0 +1,15 @@ +import { RouterProvider } from 'react-router-dom'; +import { SystemProvider } from '@/components/providers/SystemProvider'; +import { ThemeProviderContainer } from '@/components/providers/ThemeProviderContainer'; +import { router } from '@/app/router'; + +export function App() { + return ( + + + + + + ); +} + diff --git a/demos/react-supabase-todolist-tanstackdb/src/app/index.tsx b/demos/react-supabase-todolist-tanstackdb/src/app/index.tsx index e6aa8fae0..8f1e8b10b 100644 --- a/demos/react-supabase-todolist-tanstackdb/src/app/index.tsx +++ b/demos/react-supabase-todolist-tanstackdb/src/app/index.tsx @@ -1,18 +1,5 @@ import { createRoot } from 'react-dom/client'; -import { RouterProvider } from 'react-router-dom'; -import { SystemProvider } from '@/components/providers/SystemProvider'; -import { ThemeProviderContainer } from '@/components/providers/ThemeProviderContainer'; -import { router } from '@/app/router'; +import { App } from './App'; const root = createRoot(document.getElementById('app')!); root.render(); - -export function App() { - return ( - - - - - - ); -} diff --git a/demos/react-supabase-todolist-tanstackdb/tsconfig.json b/demos/react-supabase-todolist-tanstackdb/tsconfig.json index 744ba768e..bf1ed4d45 100644 --- a/demos/react-supabase-todolist-tanstackdb/tsconfig.json +++ b/demos/react-supabase-todolist-tanstackdb/tsconfig.json @@ -13,14 +13,12 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "types": ["vitest/globals"], "paths": { "@/*": ["./src/*"] } }, + "include": ["src", "e2e"], "exclude": ["node_modules"], - "references": [ - { - "path": "../../packages/web" - } - ] + "references": [] } diff --git a/demos/react-supabase-todolist-tanstackdb/vite.config.mts b/demos/react-supabase-todolist-tanstackdb/vite.config.mts index ab0b7d202..1acf8fce7 100644 --- a/demos/react-supabase-todolist-tanstackdb/vite.config.mts +++ b/demos/react-supabase-todolist-tanstackdb/vite.config.mts @@ -1,9 +1,10 @@ -import wasm from 'vite-plugin-wasm'; -import topLevelAwait from 'vite-plugin-top-level-await'; +/// import { fileURLToPath, URL } from 'url'; +import topLevelAwait from 'vite-plugin-top-level-await'; +import wasm from 'vite-plugin-wasm'; -import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; // https://vitejs.dev/config/ @@ -70,5 +71,29 @@ export default defineConfig({ worker: { format: 'es', plugins: () => [wasm(), topLevelAwait()] + }, + test: { + globals: true, + include: ['../e2e/**/*.test.{ts,tsx}'], + maxConcurrency: 1, + alias: { + // Mock SupabaseConnector with an authenticated session for tests + '@/library/powersync/SupabaseConnector': fileURLToPath( + new URL('./e2e/mocks/SupabaseConnector.ts', import.meta.url) + ), + // Mock GuardBySync to bypass the sync check for tests + '@/components/widgets/GuardBySync': fileURLToPath(new URL('./e2e/mocks/GuardBySync.tsx', import.meta.url)) + }, + browser: { + enabled: true, + isolate: true, + provider: 'playwright', + headless: true, + instances: [ + { + browser: 'chromium' + } + ] + } } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d693149a1..f8c991158 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1565,9 +1565,6 @@ importers: '@mui/x-data-grid': specifier: ^6.19.6 version: 6.20.4(@mui/material@5.17.1(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.17.1(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@powersync/common': - specifier: workspace:* - version: link:../../packages/common '@powersync/react': specifier: workspace:* version: link:../../packages/react @@ -1578,14 +1575,14 @@ importers: specifier: ^2.39.7 version: 2.49.9 '@tanstack/db': - specifier: ^0.4.17 - version: 0.4.17(typescript@5.9.2) + specifier: ^0.5.11 + version: 0.5.11(typescript@5.9.2) '@tanstack/powersync-db-collection': - specifier: ^0.1.0 - version: 0.1.0(@powersync/common@packages+common)(typescript@5.9.2) + specifier: ^0.1.15 + version: 0.1.15(@powersync/common@1.43.1)(typescript@5.9.2) '@tanstack/react-db': - specifier: ^0.1.39 - version: 0.1.39(react@18.3.1)(typescript@5.9.2) + specifier: ^0.1.55 + version: 0.1.55(react@18.3.1)(typescript@5.9.2) formik: specifier: ^2.4.6 version: 2.4.6(react@18.3.1) @@ -1608,6 +1605,9 @@ importers: '@swc/core': specifier: ~1.6.0 version: 1.6.13 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.6(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/lodash': specifier: ^4.14.202 version: 4.17.17 @@ -1623,12 +1623,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.5.0(vite@5.4.19(@types/node@20.17.57)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0)) + '@vitest/browser': + specifier: ^3.2.4 + version: 3.2.4(playwright@1.52.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))(vitest@3.2.4) autoprefixer: specifier: ^10.4.18 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(@swc/core@1.6.13)) + playwright: + specifier: ^1.51.0 + version: 1.52.0 typescript: specifier: ^5.4.2 version: 5.9.2 @@ -1644,6 +1650,9 @@ importers: vite-plugin-wasm: specifier: ^3.3.0 version: 3.4.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)) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/browser@3.2.4)(jsdom@24.1.3)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) demos/vue-supabase-todolist: dependencies: @@ -7044,6 +7053,9 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@powersync/common@1.43.1': + resolution: {integrity: sha512-Or4riEfRPazEvcO7yvyNymhGnRguDJJRwKPsgkCle4Y13YMEvhdLY3Gfevk5lxhTsqlpIIR5J2/MfJLYo9zDnQ==} + '@powersync/sql-js@0.0.7': resolution: {integrity: sha512-6EuViqqAVnCz33zbvPWzPtHykoFaZTfUz6VHprRLWP/PybO/U2NREIH9CTzCrZGHkWnLEIT21pz6BhdrPAJT0g==} @@ -9189,30 +9201,30 @@ packages: peerDependencies: react: '*' - '@tanstack/db-ivm@0.1.12': - resolution: {integrity: sha512-QTs2dgrjTCkATZh12nb854K/Of80QyyR6URYyxnGPnJgXFchvw25Ve3TtFW51fHR5hheQltkbtpPgBjSZvoMVA==} + '@tanstack/db-ivm@0.1.13': + resolution: {integrity: sha512-sBOWGY4tqMEym2ewjdWrDb5c5c8akvgnEbGVPAtkfFS3QVV0zfVb5RJAkAc8GSxb3ByVfYjyaShVr0kMJhMuow==} peerDependencies: typescript: '>=4.7' - '@tanstack/db@0.4.17': - resolution: {integrity: sha512-/Pjt7agChx5WuEs8vE6QdFnZIyNV6Q+n5wp8Lvf7gYyBZGvtXGoRHTKZeZjsSXgDm8tdLXkEXu1VNpE8F7d2iw==} + '@tanstack/db@0.5.11': + resolution: {integrity: sha512-D/JA4FWqTl4AeSZmjJNA5qOcT5cRBoTlSZjIdvZkbQu/mfphK+/fsICBVNxhLKq4mg+Q3EibIZO7VJwJiJ/cqw==} peerDependencies: typescript: '>=4.7' - '@tanstack/pacer@0.1.0': - resolution: {integrity: sha512-QVzkGO5clvGj/qdX8H2wUj0QCXCLZ/pwPMnfSqhoYfpzDRkRHDj+3D+VzdcehBIVnE+GCd1D/P1tGMzfjmfrzQ==} + '@tanstack/pacer-lite@0.1.1': + resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} engines: {node: '>=18'} - '@tanstack/powersync-db-collection@0.1.0': - resolution: {integrity: sha512-/U+rabWe7hwHJBv5KOyagPUmRjujA9B12+Lx4Vd5k5MN+YwU8hh32JMQ60Ljoo/MGBcDs3J9iecGncPnVQteNQ==} + '@tanstack/powersync-db-collection@0.1.15': + resolution: {integrity: sha512-TG5hMDT5jh/L8J/cM1xjACfnE4gvjyrQ+3ezPTlEFRBoG6sKr2f7mA9puSGYYmT5Qy+hyztnn5pFANqN5Asyiw==} peerDependencies: '@powersync/common': ^1.41.0 '@tanstack/query-core@5.79.0': resolution: {integrity: sha512-s+epTqqLM0/TbJzMAK7OEhZIzh63P9sWz5HEFc5XHL4FvKQXQkcjI8F3nee+H/xVVn7mrP610nVXwOytTSYd0w==} - '@tanstack/react-db@0.1.39': - resolution: {integrity: sha512-OznmgEvh4zoLPbuMKoLeaE+FtY748QhC3OegUbr/bExKNdRQ4Ui+HZw673/nvjlR8v+nn32rwzScRfL8StVcVA==} + '@tanstack/react-db@0.1.55': + resolution: {integrity: sha512-59fNWy8GMP4li+jm34dNDYsj75qV84TjbbylheDK/erYae2ZEUx0W9JrHU2zLqjPK1OAbuIiQriLdBy2RTyjhQ==} peerDependencies: react: '>=16.8.0' @@ -9239,6 +9251,21 @@ packages: '@types/react': optional: true + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@testing-library/user-event@14.6.1': resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} engines: {node: '>=12', npm: '>=6'} @@ -10879,8 +10906,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.2: - resolution: {integrity: sha512-PxSsosKQjI38iXkmb3d0Y32efqyA0uW4s41u4IVBsLlWLhCiYNpH/AfNOVWRqCQBlD8TFJTz6OUWNd4DFJCnmw==} + baseline-browser-mapping@2.9.6: + resolution: {integrity: sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==} hasBin: true basic-auth@2.0.1: @@ -11247,8 +11274,8 @@ packages: caniuse-lite@1.0.30001720: resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==} - caniuse-lite@1.0.30001759: - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} @@ -12568,8 +12595,8 @@ packages: electron-to-chromium@1.5.161: resolution: {integrity: sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==} - electron-to-chromium@1.5.264: - resolution: {integrity: sha512-1tEf0nLgltC3iy9wtlYDlQDc5Rg9lEKVjEmIHJ21rI9OcqkvD45K1oyNIRA4rR1z3LgJ7KeGzEBojVcV6m4qjA==} + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} electron-winstaller@5.4.0: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} @@ -24220,7 +24247,7 @@ snapshots: '@babel/preset-env': 7.27.2(@babel/core@7.26.10) '@babel/preset-react': 7.27.1(@babel/core@7.26.10) '@babel/preset-typescript': 7.27.1(@babel/core@7.26.10) - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 '@babel/runtime-corejs3': 7.27.4 '@babel/traverse': 7.27.4 '@docusaurus/logger': 3.8.0 @@ -27323,7 +27350,7 @@ snapshots: '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -27682,7 +27709,7 @@ snapshots: '@mui/private-theming@5.17.1(@types/react@18.3.23)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 '@mui/utils': 5.17.1(@types/react@18.3.23)(react@18.3.1) prop-types: 15.8.1 react: 18.3.1 @@ -27691,7 +27718,7 @@ snapshots: '@mui/styled-engine@5.16.14(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 '@emotion/cache': 11.14.0 csstype: 3.1.3 prop-types: 15.8.1 @@ -27702,7 +27729,7 @@ snapshots: '@mui/styled-engine@5.16.14(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 '@emotion/cache': 11.14.0 csstype: 3.1.3 prop-types: 15.8.1 @@ -28454,11 +28481,16 @@ snapshots: '@popperjs/core@2.11.8': {} + '@powersync/common@1.43.1': + dependencies: + async-mutex: 0.5.0 + event-iterator: 2.0.0 + '@powersync/sql-js@0.0.7': {} '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 react: 18.3.1 '@radix-ui/react-slot@1.0.1(react@18.3.1)': @@ -31980,26 +32012,26 @@ 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)': + '@tanstack/db-ivm@0.1.13(typescript@5.9.2)': dependencies: fractional-indexing: 3.2.0 sorted-btree: 1.8.1 typescript: 5.9.2 - '@tanstack/db@0.4.17(typescript@5.9.2)': + '@tanstack/db@0.5.11(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 + '@tanstack/db-ivm': 0.1.13(typescript@5.9.2) + '@tanstack/pacer-lite': 0.1.1 typescript: 5.9.2 - '@tanstack/pacer@0.1.0': {} + '@tanstack/pacer-lite@0.1.1': {} - '@tanstack/powersync-db-collection@0.1.0(@powersync/common@packages+common)(typescript@5.9.2)': + '@tanstack/powersync-db-collection@0.1.15(@powersync/common@1.43.1)(typescript@5.9.2)': dependencies: - '@powersync/common': link:packages/common + '@powersync/common': 1.43.1 '@standard-schema/spec': 1.0.0 - '@tanstack/db': 0.4.17(typescript@5.9.2) + '@tanstack/db': 0.5.11(typescript@5.9.2) '@tanstack/store': 0.8.0 debug: 4.4.3 p-defer: 4.0.1 @@ -32009,9 +32041,9 @@ snapshots: '@tanstack/query-core@5.79.0': {} - '@tanstack/react-db@0.1.39(react@18.3.1)(typescript@5.9.2)': + '@tanstack/react-db@0.1.55(react@18.3.1)(typescript@5.9.2)': dependencies: - '@tanstack/db': 0.4.17(typescript@5.9.2) + '@tanstack/db': 0.5.11(typescript@5.9.2) react: 18.3.1 use-sync-external-store: 1.6.0(react@18.3.1) transitivePeerDependencies: @@ -32055,6 +32087,16 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.6(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.23 + '@types/react-dom': 18.3.6(@types/react@18.3.23) + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 @@ -33169,6 +33211,25 @@ snapshots: 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) + '@vitest/browser@3.2.4(playwright@1.52.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))(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@5.4.19(@types/node@20.17.57)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0)) + '@vitest/utils': 3.2.4 + magic-string: 0.30.17 + sirv: 3.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/browser@3.2.4)(jsdom@24.1.3)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) + ws: 8.18.2 + optionalDependencies: + playwright: 1.52.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + '@vitest/browser@3.2.4(playwright@1.52.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))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.0 @@ -33215,6 +33276,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 + '@vitest/mocker@3.2.4(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: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + 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) + '@vitest/mocker@3.2.4(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: '@vitest/spy': 3.2.4 @@ -34089,7 +34158,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -34273,7 +34342,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.2: {} + baseline-browser-mapping@2.9.6: {} basic-auth@2.0.1: dependencies: @@ -34514,9 +34583,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.2 - caniuse-lite: 1.0.30001759 - electron-to-chromium: 1.5.264 + baseline-browser-mapping: 2.9.6 + caniuse-lite: 1.0.30001760 + electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.2(browserslist@4.28.1) @@ -34771,7 +34840,7 @@ snapshots: caniuse-lite@1.0.30001720: {} - caniuse-lite@1.0.30001759: {} + caniuse-lite@1.0.30001760: {} cardinal@2.1.1: dependencies: @@ -36037,7 +36106,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 csstype: 3.1.3 dom-serializer@1.4.1: @@ -36299,7 +36368,7 @@ snapshots: electron-to-chromium@1.5.161: {} - electron-to-chromium@1.5.264: {} + electron-to-chromium@1.5.267: {} electron-winstaller@5.4.0: dependencies: @@ -41657,12 +41726,12 @@ snapshots: metro-runtime@0.76.8: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 react-refresh: 0.4.3 metro-runtime@0.80.12: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 flow-enums-runtime: 0.0.6 metro-runtime@0.81.5: @@ -44430,7 +44499,7 @@ snapshots: 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)): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' webpack: 5.99.9(@swc/core@1.11.29) @@ -45283,13 +45352,13 @@ snapshots: react-router-config@5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 react: 18.3.1 react-router: 5.3.4(react@18.3.1) react-router-dom@5.3.4(react@18.3.1): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -45307,7 +45376,7 @@ snapshots: react-router@5.3.4(react@18.3.1): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -48151,6 +48220,24 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite-node@3.2.4(@types/node@20.17.57)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1(supports-color@8.1.1) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + 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: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@3.2.4(@types/node@24.2.0)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0): dependencies: cac: 6.7.14 @@ -48366,6 +48453,47 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/browser@3.2.4)(jsdom@24.1.3)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(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)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1(supports-color@8.1.1) + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.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) + vite-node: 3.2.4(@types/node@20.17.57)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 20.17.57 + '@vitest/browser': 3.2.4(playwright@1.52.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))(vitest@3.2.4) + jsdom: 24.1.3 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.2.0)(@vitest/browser@3.2.4)(jsdom@24.1.3)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0): dependencies: '@types/chai': 5.2.2