diff --git a/.changeset/little-bananas-fetch.md b/.changeset/little-bananas-fetch.md new file mode 100644 index 000000000..5cfc87b22 --- /dev/null +++ b/.changeset/little-bananas-fetch.md @@ -0,0 +1,7 @@ +--- +'@powersync/common': minor +--- + +- Added additional listeners for `closing` and `closed` events in `AbstractPowerSyncDatabase`. +- Added `query` and `customQuery` APIs for enhanced watched queries. +- Added `triggerImmediate` option to the `onChange` API. This allows emitting an initial event which can be useful for downstream use cases. diff --git a/.changeset/nine-pens-ring.md b/.changeset/nine-pens-ring.md new file mode 100644 index 000000000..4697d8141 --- /dev/null +++ b/.changeset/nine-pens-ring.md @@ -0,0 +1,43 @@ +--- +'@powersync/vue': minor +--- + +[Potentially breaking change] The `useQuery` hook results are now explicitly defined as readonly. These values should not be mutated. + +- Added the ability to limit re-renders by specifying a `rowComparator` for query results. The `useQuery` hook will only emit `data` changes when the data has changed. + +```javascript +// The data here will maintain previous object references for unchanged items. +const { data } = useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], { + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } +}); +``` + +- Added the ability to subscribe to an existing instance of a `WatchedQuery` + +```vue + + + +``` diff --git a/.changeset/plenty-rice-protect.md b/.changeset/plenty-rice-protect.md new file mode 100644 index 000000000..17c805e90 --- /dev/null +++ b/.changeset/plenty-rice-protect.md @@ -0,0 +1,6 @@ +--- +'@powersync/react': minor +'@powersync/vue': minor +--- + +- [Internal] Updated implementation to use shared `WatchedQuery` implementation. diff --git a/.changeset/stale-dots-jog.md b/.changeset/stale-dots-jog.md new file mode 100644 index 000000000..e1ed00c4b --- /dev/null +++ b/.changeset/stale-dots-jog.md @@ -0,0 +1,5 @@ +--- +'@powersync/web': minor +--- + +Improved query behaviour when client is closed. Pending requests will be aborted, future requests will be rejected with an Error. Fixed read and write lock requests not respecting timeout parameter. diff --git a/.changeset/swift-guests-explain.md b/.changeset/swift-guests-explain.md new file mode 100644 index 000000000..e1a1a8ad6 --- /dev/null +++ b/.changeset/swift-guests-explain.md @@ -0,0 +1,39 @@ +--- +'@powersync/react': minor +--- + +- Added the ability to limit re-renders by specifying a `rowComparator` for query results. The `useQuery` hook will only emit `data` changes when the data has changed. + +```javascript +// The data here will maintain previous object references for unchanged items. +const { data } = useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], { + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } +}); +``` + +- Added the ability to subscribe to an existing instance of a `WatchedQuery` + +```jsx +import { useWatchedQuerySubscription } from '@powersync/react'; + +const listsQuery = powerSync + .query({ + sql: `SELECT * FROM lists` + }) + .differentialWatch(); + +export const ListsWidget = (props) => { + const { data: lists } = useWatchedQuerySubscription(listsQuery); + + return ( +
+ {lists.map((list) => ( +
{list.name}
+ ))} +
+ ); +}; +``` diff --git a/.prettierrc b/.prettierrc index 85bab2c6a..b92dbc1be 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,6 @@ "jsxBracketSameLine": true, "useTabs": false, "printWidth": 120, - "trailingComma": "none" + "trailingComma": "none", + "plugins": ["prettier-plugin-embed", "prettier-plugin-sql"] } diff --git a/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx b/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx index 6b0a13e1d..4cb0e84fb 100644 --- a/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx +++ b/demos/react-supabase-todolist/src/app/views/sql-console/page.tsx @@ -1,24 +1,24 @@ -import React from 'react'; -import { useQuery } from '@powersync/react'; -import { Box, Button, Grid, TextField, styled } from '@mui/material'; -import { DataGrid } from '@mui/x-data-grid'; import { NavigationPage } from '@/components/navigation/NavigationPage'; +import { Alert, Box, Button, Grid, TextField, styled } from '@mui/material'; +import { DataGrid } from '@mui/x-data-grid'; +import { useQuery } from '@powersync/react'; +import React from 'react'; export type LoginFormParams = { email: string; password: string; }; -const DEFAULT_QUERY = 'SELECT * FROM lists'; - -export default function SQLConsolePage() { - const inputRef = React.useRef(); - const [query, setQuery] = React.useState(DEFAULT_QUERY); - const { data: querySQLResult } = useQuery(query); +const DEFAULT_QUERY = /* sql */ ` + SELECT + * + FROM + lists +`; +const TableDisplay = React.memo(({ data }: { data: ReadonlyArray }) => { const queryDataGridResult = React.useMemo(() => { - const firstItem = querySQLResult?.[0]; - + const firstItem = data?.[0]; return { columns: firstItem ? Object.keys(firstItem).map((field) => ({ @@ -26,9 +26,48 @@ export default function SQLConsolePage() { flex: 1 })) : [], - rows: querySQLResult + rows: data }; - }, [querySQLResult]); + }, [data]); + + return ( + + ({ ...r, id: r.id ?? index })) ?? []} + columns={queryDataGridResult.columns} + initialState={{ + pagination: { + paginationModel: { + pageSize: 20 + } + } + }} + pageSizeOptions={[20]} + disableRowSelectionOnClick + /> + + ); +}); + +export default function SQLConsolePage() { + const inputRef = React.useRef(); + const [query, setQuery] = React.useState(DEFAULT_QUERY); + + const { data, error } = useQuery(query, [], { + /** + * We don't use the isFetching status here, we can avoid re-renders if we don't report on it. + */ + reportFetching: false, + /** + * The query here will only emit results when the query data set changes. + * Result sets are compared by serializing each item to JSON and comparing the strings. + */ + rowComparator: { + keyBy: (item: any) => JSON.stringify(item), + compareBy: (item: any) => JSON.stringify(item) + } + }); return ( @@ -57,33 +96,13 @@ export default function SQLConsolePage() { if (queryInput) { setQuery(queryInput); } - }} - > + }}> Execute Query - - {queryDataGridResult ? ( - - {queryDataGridResult.columns ? ( - ({ ...r, id: r.id ?? index })) ?? []} - columns={queryDataGridResult.columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 20 - } - } - }} - pageSizeOptions={[20]} - disableRowSelectionOnClick - /> - ) : null} - - ) : null} + {error ? {error.message} : null} + ); diff --git a/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx b/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx index 0648a5ca5..7a488792f 100644 --- a/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx +++ b/demos/react-supabase-todolist/src/app/views/todo-lists/edit/page.tsx @@ -1,4 +1,7 @@ -import { usePowerSync, useQuery } from '@powersync/react'; +import { NavigationPage } from '@/components/navigation/NavigationPage'; +import { useSupabase } from '@/components/providers/SystemProvider'; +import { TodoItemWidget } from '@/components/widgets/TodoItemWidget'; +import { LISTS_TABLE, TODOS_TABLE, TodoRecord } from '@/library/powersync/AppSchema'; import AddIcon from '@mui/icons-material/Add'; import { Box, @@ -15,12 +18,9 @@ import { styled } from '@mui/material'; import Fab from '@mui/material/Fab'; +import { usePowerSync, useQuery } from '@powersync/react'; import React, { Suspense } from 'react'; import { useParams } from 'react-router-dom'; -import { useSupabase } from '@/components/providers/SystemProvider'; -import { LISTS_TABLE, TODOS_TABLE, TodoRecord } from '@/library/powersync/AppSchema'; -import { NavigationPage } from '@/components/navigation/NavigationPage'; -import { TodoItemWidget } from '@/components/widgets/TodoItemWidget'; /** * useSearchParams causes the entire element to fall back to client side rendering @@ -34,39 +34,36 @@ const TodoEditSection = () => { const { data: [listRecord] - } = useQuery<{ name: string }>(`SELECT name FROM ${LISTS_TABLE} WHERE id = ?`, [listID]); + } = useQuery<{ name: string }>( + /* sql */ ` + SELECT + name + FROM + ${LISTS_TABLE} + WHERE + id = ? + `, + [listID] + ); const { data: todos } = useQuery( - `SELECT * FROM ${TODOS_TABLE} WHERE list_id=? ORDER BY created_at DESC, id`, + /* sql */ ` + SELECT + * + FROM + ${TODOS_TABLE} + WHERE + list_id = ? + ORDER BY + created_at DESC, + id + `, [listID] ); const [showPrompt, setShowPrompt] = React.useState(false); const nameInputRef = React.createRef(); - const toggleCompletion = async (record: TodoRecord, completed: boolean) => { - const updatedRecord = { ...record, completed: completed }; - if (completed) { - const userID = supabase?.currentSession?.user.id; - if (!userID) { - throw new Error(`Could not get user ID.`); - } - updatedRecord.completed_at = new Date().toISOString(); - updatedRecord.completed_by = userID; - } else { - updatedRecord.completed_at = null; - updatedRecord.completed_by = null; - } - await powerSync.execute( - `UPDATE ${TODOS_TABLE} - SET completed = ?, - completed_at = ?, - completed_by = ? - WHERE id = ?`, - [completed, updatedRecord.completed_at, updatedRecord.completed_by, record.id] - ); - }; - const createNewTodo = async (description: string) => { const userID = supabase?.currentSession?.user.id; if (!userID) { @@ -74,21 +71,16 @@ const TodoEditSection = () => { } await powerSync.execute( - `INSERT INTO - ${TODOS_TABLE} - (id, created_at, created_by, description, list_id) - VALUES - (uuid(), datetime(), ?, ?, ?)`, + /* sql */ ` + INSERT INTO + ${TODOS_TABLE} (id, created_at, created_by, description, list_id) + VALUES + (uuid (), datetime (), ?, ?, ?) + `, [userID, description, listID!] ); }; - const deleteTodo = async (id: string) => { - await powerSync.writeTransaction(async (tx) => { - await tx.execute(`DELETE FROM ${TODOS_TABLE} WHERE id = ?`, [id]); - }); - }; - if (!listRecord) { return ( @@ -106,13 +98,7 @@ const TodoEditSection = () => { {todos.map((r) => ( - deleteTodo(r.id)} - isComplete={r.completed == 1} - toggleCompletion={() => toggleCompletion(r, !r.completed)} - /> + ))} @@ -129,8 +115,7 @@ const TodoEditSection = () => { await createNewTodo(nameInputRef.current!.value); setShowPrompt(false); } - }} - > + }}> {'Create Todo Item'} Enter a description for a new todo item diff --git a/demos/react-supabase-todolist/src/app/views/todo-lists/page.tsx b/demos/react-supabase-todolist/src/app/views/todo-lists/page.tsx index 1f1a686b7..edb168a2b 100644 --- a/demos/react-supabase-todolist/src/app/views/todo-lists/page.tsx +++ b/demos/react-supabase-todolist/src/app/views/todo-lists/page.tsx @@ -1,4 +1,9 @@ -import { usePowerSync, useStatus } from '@powersync/react'; +import { NavigationPage } from '@/components/navigation/NavigationPage'; +import { useSupabase } from '@/components/providers/SystemProvider'; +import { GuardBySync } from '@/components/widgets/GuardBySync'; +import { SearchBarWidget } from '@/components/widgets/SearchBarWidget'; +import { TodoListsWidget } from '@/components/widgets/TodoListsWidget'; +import { LISTS_TABLE } from '@/library/powersync/AppSchema'; import AddIcon from '@mui/icons-material/Add'; import { Box, @@ -12,18 +17,12 @@ import { styled } from '@mui/material'; import Fab from '@mui/material/Fab'; +import { usePowerSync } from '@powersync/react'; import React from 'react'; -import { useSupabase } from '@/components/providers/SystemProvider'; -import { LISTS_TABLE } from '@/library/powersync/AppSchema'; -import { NavigationPage } from '@/components/navigation/NavigationPage'; -import { SearchBarWidget } from '@/components/widgets/SearchBarWidget'; -import { TodoListsWidget } from '@/components/widgets/TodoListsWidget'; -import { GuardBySync } from '@/components/widgets/GuardBySync'; export default function TodoListsPage() { const powerSync = usePowerSync(); const supabase = useSupabase(); - const status = useStatus(); const [showPrompt, setShowPrompt] = React.useState(false); const nameInputRef = React.createRef(); @@ -36,7 +35,12 @@ export default function TodoListsPage() { } const res = await powerSync.execute( - `INSERT INTO ${LISTS_TABLE} (id, created_at, name, owner_id) VALUES (uuid(), datetime(), ?, ?) RETURNING *`, + /* sql */ ` + INSERT INTO + ${LISTS_TABLE} (id, created_at, name, owner_id) + VALUES + (uuid (), datetime (), ?, ?) RETURNING * + `, [name, userID] ); @@ -71,8 +75,7 @@ export default function TodoListsPage() { } }} aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > + aria-describedby="alert-dialog-description"> {'Create Todo List'} Enter a name for a new todo list diff --git a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx index 9bbbb4f11..8a3f3c209 100644 --- a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx +++ b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx @@ -1,9 +1,9 @@ import { configureFts } from '@/app/utils/fts_setup'; -import { AppSchema } from '@/library/powersync/AppSchema'; +import { AppSchema, ListRecord, LISTS_TABLE, TODOS_TABLE } from '@/library/powersync/AppSchema'; import { SupabaseConnector } from '@/library/powersync/SupabaseConnector'; import { CircularProgress } from '@mui/material'; import { PowerSyncContext } from '@powersync/react'; -import { createBaseLogger, LogLevel, PowerSyncDatabase } from '@powersync/web'; +import { createBaseLogger, DifferentialWatchedQuery, LogLevel, PowerSyncDatabase } from '@powersync/web'; import React, { Suspense } from 'react'; import { NavigationPanelContextProvider } from '../navigation/NavigationPanelContext'; @@ -17,10 +17,46 @@ export const db = new PowerSyncDatabase({ } }); +export type EnhancedListRecord = ListRecord & { total_tasks: number; completed_tasks: number }; + +export type QueryStore = { + lists: DifferentialWatchedQuery; +}; + +const QueryStore = React.createContext(null); +export const useQueryStore = () => React.useContext(QueryStore); + export const SystemProvider = ({ children }: { children: React.ReactNode }) => { - const [connector] = React.useState(new SupabaseConnector()); + const [connector] = React.useState(() => new SupabaseConnector()); const [powerSync] = React.useState(db); + const [queryStore] = React.useState(() => { + const listsQuery = db + .query({ + sql: /* sql */ ` + SELECT + ${LISTS_TABLE}.*, + COUNT(${TODOS_TABLE}.id) AS total_tasks, + SUM( + CASE + WHEN ${TODOS_TABLE}.completed = true THEN 1 + ELSE 0 + END + ) as completed_tasks + FROM + ${LISTS_TABLE} + LEFT JOIN ${TODOS_TABLE} ON ${LISTS_TABLE}.id = ${TODOS_TABLE}.list_id + GROUP BY + ${LISTS_TABLE}.id; + ` + }) + .differentialWatch(); + + return { + lists: listsQuery + }; + }); + React.useEffect(() => { const logger = createBaseLogger(); logger.useDefaults(); // eslint-disable-line @@ -30,7 +66,7 @@ export const SystemProvider = ({ children }: { children: React.ReactNode }) => { powerSync.init(); const l = connector.registerListener({ - initialized: () => { }, + initialized: () => {}, sessionStarted: () => { powerSync.connect(connector); } @@ -47,11 +83,13 @@ export const SystemProvider = ({ children }: { children: React.ReactNode }) => { return ( }> - - - {children} - - + + + + {children} + + + ); }; diff --git a/demos/react-supabase-todolist/src/components/widgets/ListItemWidget.tsx b/demos/react-supabase-todolist/src/components/widgets/ListItemWidget.tsx index bdb68b82d..e2ac183ef 100644 --- a/demos/react-supabase-todolist/src/components/widgets/ListItemWidget.tsx +++ b/demos/react-supabase-todolist/src/components/widgets/ListItemWidget.tsx @@ -1,73 +1,90 @@ -import React from 'react'; import { - ListItem, + Avatar, + Box, IconButton, + ListItem, ListItemAvatar, - Avatar, + ListItemButton, ListItemText, - Box, Paper, - styled, - ListItemButton + styled } from '@mui/material'; +import React from 'react'; -import DeleteIcon from '@mui/icons-material/DeleteOutline'; +import { TODO_LISTS_ROUTE } from '@/app/router'; +import { LISTS_TABLE, TODOS_TABLE } from '@/library/powersync/AppSchema'; import RightIcon from '@mui/icons-material/ArrowRightAlt'; +import DeleteIcon from '@mui/icons-material/DeleteOutline'; import ListIcon from '@mui/icons-material/ListAltOutlined'; +import { usePowerSync } from '@powersync/react'; +import { useNavigate } from 'react-router-dom'; export type ListItemWidgetProps = { + id: string; title: string; description: string; selected?: boolean; - onDelete: () => void; - onPress: () => void; }; -export const ListItemWidget: React.FC = (props) => { +export const ListItemWidget: React.FC = React.memo((props) => { + const { id, title, description, selected } = props; + + const powerSync = usePowerSync(); + const navigate = useNavigate(); + + const deleteList = React.useCallback(async () => { + await powerSync.writeTransaction(async (tx) => { + // Delete associated todos + await tx.execute( + /* sql */ ` + DELETE FROM ${TODOS_TABLE} + WHERE + list_id = ? + `, + [id] + ); + // Delete list record + await tx.execute( + /* sql */ ` + DELETE FROM ${LISTS_TABLE} + WHERE + id = ? + `, + [id] + ); + }); + }, [id]); + + const openList = React.useCallback(() => { + navigate(TODO_LISTS_ROUTE + '/' + id); + }, [id]); + return ( - { - props.onDelete(); - }} - > + - { - props.onPress(); - }} - > + - } - > - { - props.onPress(); - }} - selected={props.selected} - > + }> + - + ); -}; +}); export namespace S { export const MainPaper = styled(Paper)` diff --git a/demos/react-supabase-todolist/src/components/widgets/TodoItemWidget.tsx b/demos/react-supabase-todolist/src/components/widgets/TodoItemWidget.tsx index 8fac060de..d44418c4a 100644 --- a/demos/react-supabase-todolist/src/components/widgets/TodoItemWidget.tsx +++ b/demos/react-supabase-todolist/src/components/widgets/TodoItemWidget.tsx @@ -1,51 +1,88 @@ -import React from 'react'; -import { ListItem, IconButton, ListItemAvatar, ListItemText, Box, styled, Paper, ListItemButton } from '@mui/material'; -import DeleteIcon from '@mui/icons-material/DeleteOutline'; -import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import { TODOS_TABLE } from '@/library/powersync/AppSchema'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import DeleteIcon from '@mui/icons-material/DeleteOutline'; +import { Box, IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemText, Paper, styled } from '@mui/material'; +import { usePowerSync } from '@powersync/react'; +import React from 'react'; +import { useSupabase } from '../providers/SystemProvider'; export type TodoItemWidgetProps = { + id: string; description: string | null; isComplete: boolean; - onDelete: () => void; - toggleCompletion: () => void; }; -export const TodoItemWidget: React.FC = (props) => { +export const TodoItemWidget: React.FC = React.memo((props) => { + const { id, description, isComplete } = props; + + const powerSync = usePowerSync(); + const supabase = useSupabase(); + + const deleteTodo = React.useCallback(async () => { + await powerSync.writeTransaction(async (tx) => { + await tx.execute( + /* sql */ ` + DELETE FROM ${TODOS_TABLE} + WHERE + id = ? + `, + [id] + ); + }); + }, [id]); + + const toggleCompletion = React.useCallback(async () => { + let completedAt: String | null = null; + let completedBy: String | null = null; + + if (!isComplete) { + // Need to set to Completed. This requires a session. + const userID = supabase?.currentSession?.user.id; + if (!userID) { + throw new Error(`Could not get user ID.`); + } + completedAt = new Date().toISOString(); + completedBy = userID; + } + + await powerSync.execute( + /* sql */ ` + UPDATE ${TODOS_TABLE} + SET + completed = ?, + completed_at = ?, + completed_by = ? + WHERE + id = ? + `, + [!isComplete, completedAt, completedBy, id] + ); + }, [id, isComplete]); + return ( - { - props.onDelete(); - }} - > + - } - > - { - props.toggleCompletion(); - }} - > + }> + {props.isComplete ? : } - + ); -}; +}); namespace S { export const MainPaper = styled(Paper)` diff --git a/demos/react-supabase-todolist/src/components/widgets/TodoListsWidget.tsx b/demos/react-supabase-todolist/src/components/widgets/TodoListsWidget.tsx index 9352115dc..d2f12b9fa 100644 --- a/demos/react-supabase-todolist/src/components/widgets/TodoListsWidget.tsx +++ b/demos/react-supabase-todolist/src/components/widgets/TodoListsWidget.tsx @@ -1,9 +1,7 @@ -import { usePowerSync, useQuery } from '@powersync/react'; import { List } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; +import { useWatchedQuerySubscription } from '@powersync/react'; +import { useQueryStore } from '../providers/SystemProvider'; import { ListItemWidget } from './ListItemWidget'; -import { LISTS_TABLE, ListRecord, TODOS_TABLE } from '@/library/powersync/AppSchema'; -import { TODO_LISTS_ROUTE } from '@/app/router'; export type TodoListsWidgetProps = { selectedId?: string; @@ -14,30 +12,10 @@ const description = (total: number, completed: number = 0) => { }; export function TodoListsWidget(props: TodoListsWidgetProps) { - const powerSync = usePowerSync(); - const navigate = useNavigate(); + const queries = useQueryStore(); + const { data: listRecords, isLoading } = useWatchedQuerySubscription(queries!.lists); - const { data: listRecords, isLoading } = useQuery(` - SELECT - ${LISTS_TABLE}.*, COUNT(${TODOS_TABLE}.id) AS total_tasks, SUM(CASE WHEN ${TODOS_TABLE}.completed = true THEN 1 ELSE 0 END) as completed_tasks - FROM - ${LISTS_TABLE} - LEFT JOIN ${TODOS_TABLE} - ON ${LISTS_TABLE}.id = ${TODOS_TABLE}.list_id - GROUP BY - ${LISTS_TABLE}.id; - `); - - const deleteList = async (id: string) => { - await powerSync.writeTransaction(async (tx) => { - // Delete associated todos - await tx.execute(`DELETE FROM ${TODOS_TABLE} WHERE list_id = ?`, [id]); - // Delete list record - await tx.execute(`DELETE FROM ${LISTS_TABLE} WHERE id = ?`, [id]); - }); - }; - - if (isLoading) { + if (isLoading && listRecords.length == 0) { return
Loading...
; } @@ -46,13 +24,10 @@ export function TodoListsWidget(props: TodoListsWidgetProps) { {listRecords.map((r) => ( deleteList(r.id)} - onPress={() => { - navigate(TODO_LISTS_ROUTE + '/' + r.id); - }} /> ))} diff --git a/demos/yjs-react-supabase-text-collab/.env.local.template b/demos/yjs-react-supabase-text-collab/.env.local.template index 1282709c8..bcdc041a8 100644 --- a/demos/yjs-react-supabase-text-collab/.env.local.template +++ b/demos/yjs-react-supabase-text-collab/.env.local.template @@ -1,3 +1,5 @@ -VITE_SUPABASE_URL= +VITE_SUPABASE_URL=http://localhost:54321 VITE_SUPABASE_ANON_KEY= -VITE_POWERSYNC_URL= +VITE_POWERSYNC_URL=http://localhost:8080 +# Only required for development with a local Supabase instance +PS_SUPABASE_JWT_SECRET= \ No newline at end of file diff --git a/demos/yjs-react-supabase-text-collab/CHANGELOG.md b/demos/yjs-react-supabase-text-collab/CHANGELOG.md index 908563a2a..a4b16065c 100644 --- a/demos/yjs-react-supabase-text-collab/CHANGELOG.md +++ b/demos/yjs-react-supabase-text-collab/CHANGELOG.md @@ -1,5 +1,11 @@ # yjs-react-supabase-text-collab +## 0.2.0 + +- Added a local development option with local Supabase and PowerSync services. +- Updated Sync rules to use client parameters. Each client now only syncs `document` and `document_updates` for the document being edited. +- Updated `PowerSyncYjsProvider` to use an incremental watched query for `document_updates`. + ## 0.1.16 ### Patch Changes diff --git a/demos/yjs-react-supabase-text-collab/README.md b/demos/yjs-react-supabase-text-collab/README.md index 7d6e916b7..34810d4ae 100644 --- a/demos/yjs-react-supabase-text-collab/README.md +++ b/demos/yjs-react-supabase-text-collab/README.md @@ -17,12 +17,44 @@ pnpm install pnpm build:packages ``` +#### Quick Start: Local Development + +This demo can be started with local PowerSync and Supabase services. + +Follow the [instructions](https://supabase.com/docs/guides/cli/getting-started) for configuring Supabase locally. + +Copy the environment variables template file + +```bash +cp .env.template .env.local +``` + +Start the Supabase project + +```bash +supabase start +``` + +Copy the `anon key` and `JWT secret` into the `.env` file. + +Run the PowerSync service with + +```bash +docker run \ +-p 8080:8080 \ +-e POWERSYNC_CONFIG_B64=$(base64 -i ./powersync.yaml) \ +-e POWERSYNC_SYNC_RULES_B64=$(base64 -i ./sync-rules.yaml) \ +--env-file ./.env.local \ +--network supabase_network_yjs-react-supabase-text-collab \ +--name my-powersync journeyapps/powersync-service:latest +``` + ### 2. Create project on Supabase and set up Postgres This demo app uses Supabase as its Postgres database and backend: 1. [Create a new project on the Supabase dashboard](https://supabase.com/dashboard/projects). -2. Go to the Supabase SQL Editor for your new project and execute the SQL statements in [`database.sql`](database.sql) to create the database schema, database functions, and publication needed for PowerSync. +2. Go to the Supabase SQL Editor for your new project and execute the SQL statements in [`database.sql`](./supabase/migrations/20250618064101_configure_powersync.sql) to create the database schema, database functions, and publication needed for PowerSync. 3. Enable "anonymous sign-ins" for the project [here](https://supabase.com/dashboard/project/_/auth/providers). ### 3. Create new project on PowerSync and connect to Supabase/Postgres @@ -108,8 +140,8 @@ To-do - [ ] Add button to the UI allowing the user to merge the Yjs edits i.e. `document_update` rows. Invoke `merge-document-updates` edge function in Supabase. - [ ] Prepopulate sample text into newly created documents. - [ ] Improve performance / rework inefficient parts of implementation: - - [ ] Optimize the 'seen updates' approach to filter the `SELECT` query for updates that have not yet been seen — perhaps based on `created_at` timestamp generated on the Postgres side. For the watch query — watch for certain tables instead of watching a query. This will allow querying `document_updates` with a dynamic parameter. - - [ ] Flush 'seen updates' when `document_updates` are merged. + - [] Optimize the 'seen updates' approach to filter the `SELECT` query for updates that have not yet been seen — perhaps based on `created_at` timestamp generated on the Postgres side. For the watch query — watch for certain tables instead of watching a query. This will allow querying `document_updates` with a dynamic parameter. + - [x] Flush 'seen updates' when `document_updates` are merged. Done diff --git a/demos/yjs-react-supabase-text-collab/package.json b/demos/yjs-react-supabase-text-collab/package.json index 64605392c..1f8c469d1 100644 --- a/demos/yjs-react-supabase-text-collab/package.json +++ b/demos/yjs-react-supabase-text-collab/package.json @@ -1,6 +1,6 @@ { "name": "yjs-react-supabase-text-collab", - "version": "0.1.16", + "version": "0.2.0", "private": true, "scripts": { "dev": "vite", diff --git a/demos/yjs-react-supabase-text-collab/powersync.yaml b/demos/yjs-react-supabase-text-collab/powersync.yaml new file mode 100644 index 000000000..bedd899f4 --- /dev/null +++ b/demos/yjs-react-supabase-text-collab/powersync.yaml @@ -0,0 +1,47 @@ +# yaml-language-server: $schema=https://unpkg.com/@powersync/service-schema@latest/json-schema/powersync-config.json + +# This is a local development configuration file for PowerSync. + +# Note that this example uses YAML custom tags for environment variable substitution. +# Using `!env [variable name]` will substitute the value of the environment variable named +# [variable name]. +# +# Only environment variables with names starting with `PS_` can be substituted. +# +# If using VS Code see the `.vscode/settings.json` definitions which define custom tags. + +# Settings for telemetry reporting +# See https://docs.powersync.com/self-hosting/telemetry +telemetry: + # Opt out of reporting anonymized usage metrics to PowerSync telemetry service + disable_telemetry_sharing: false + +# Settings for source database replication +replication: + # Specify database connection details + # Note only 1 connection is currently supported + # Multiple connection support is on the roadmap + connections: + - type: postgresql + uri: postgresql://postgres:postgres@supabase_db_yjs-react-supabase-text-collab:5432/postgres + + # SSL settings + sslmode: disable # 'verify-full' (default) or 'verify-ca' or 'disable' + +# This is valid if using the `mongo` service defined in `ps-mongo.yaml` + +# Connection settings for sync bucket storage +storage: + # This uses Postgres bucket storage for simplicity + type: postgresql + uri: postgresql://postgres:postgres@supabase_db_yjs-react-supabase-text-collab:5432/postgres + # SSL settings + sslmode: disable # 'verify-full' (default) or 'verify-ca' or 'disable' + +# The port which the PowerSync API server will listen on +port: 8080 + +# Client (application end user) authentication settings +client_auth: + supabase: true + supabase_jwt_secret: !env PS_SUPABASE_JWT_SECRET diff --git a/demos/yjs-react-supabase-text-collab/src/app/editor/page.tsx b/demos/yjs-react-supabase-text-collab/src/app/editor/page.tsx index c7cf44920..c5b808608 100644 --- a/demos/yjs-react-supabase-text-collab/src/app/editor/page.tsx +++ b/demos/yjs-react-supabase-text-collab/src/app/editor/page.tsx @@ -1,22 +1,23 @@ -import { usePowerSync, useQuery, useStatus } from '@powersync/react'; -import { Box, Container, FormControlLabel, Switch, Typography } from '@mui/material'; -import { useEffect, useMemo, useState } from 'react'; +import { connector, useSupabase } from '@/components/providers/SystemProvider'; import MenuBar from '@/components/widgets/MenuBar'; import { PowerSyncYjsProvider } from '@/library/powersync/PowerSyncYjsProvider'; +import { Box, Container, FormControlLabel, Switch, Typography } from '@mui/material'; +import { usePowerSync, useQuery, useStatus } from '@powersync/react'; import Collaboration from '@tiptap/extension-collaboration'; import Highlight from '@tiptap/extension-highlight'; import TaskItem from '@tiptap/extension-task-item'; import TaskList from '@tiptap/extension-task-list'; import { EditorContent, useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; +import { useEffect, useMemo, useState } from 'react'; +import { useParams } from 'react-router-dom'; import * as Y from 'yjs'; import './tiptap-styles.scss'; -import { useParams } from 'react-router-dom'; -import { connector } from '@/components/providers/SystemProvider'; export default function EditorPage() { const powerSync = usePowerSync(); const status = useStatus(); + const supabase = useSupabase(); const { id: documentId } = useParams(); // cache the last edited document ID in local storage @@ -33,6 +34,12 @@ export default function EditorPage() { useEffect(() => { const provider = new PowerSyncYjsProvider(ydoc, powerSync, documentId!); + // Only sync changes for this document + powerSync.connect(supabase!, { + params: { + document_id: documentId! + } + }); return () => { provider.destroy(); }; diff --git a/demos/yjs-react-supabase-text-collab/src/components/providers/SystemProvider.tsx b/demos/yjs-react-supabase-text-collab/src/components/providers/SystemProvider.tsx index a939d8b40..7cbd439d8 100644 --- a/demos/yjs-react-supabase-text-collab/src/components/providers/SystemProvider.tsx +++ b/demos/yjs-react-supabase-text-collab/src/components/providers/SystemProvider.tsx @@ -1,8 +1,8 @@ import { AppSchema } from '@/library/powersync/AppSchema'; import { SupabaseConnector } from '@/library/powersync/SupabaseConnector'; +import { CircularProgress } from '@mui/material'; import { PowerSyncContext } from '@powersync/react'; import { createBaseLogger, LogLevel, PowerSyncDatabase } from '@powersync/web'; -import { CircularProgress } from '@mui/material'; import React, { Suspense } from 'react'; const SupabaseContext = React.createContext(null); @@ -13,7 +13,6 @@ export const powerSync = new PowerSyncDatabase({ schema: AppSchema }); export const connector = new SupabaseConnector(); -powerSync.connect(connector); const logger = createBaseLogger(); logger.useDefaults(); diff --git a/demos/yjs-react-supabase-text-collab/src/library/powersync/PowerSyncYjsProvider.ts b/demos/yjs-react-supabase-text-collab/src/library/powersync/PowerSyncYjsProvider.ts index 1b0e28447..4e41bc719 100644 --- a/demos/yjs-react-supabase-text-collab/src/library/powersync/PowerSyncYjsProvider.ts +++ b/demos/yjs-react-supabase-text-collab/src/library/powersync/PowerSyncYjsProvider.ts @@ -1,9 +1,9 @@ import * as Y from 'yjs'; import { b64ToUint8Array, Uint8ArrayTob64 } from '@/library/binary-utils'; -import { v4 as uuidv4 } from 'uuid'; import { AbstractPowerSyncDatabase } from '@powersync/web'; import { ObservableV2 } from 'lib0/observable'; +import { DocumentUpdates } from './AppSchema'; export interface PowerSyncYjsEvents { /** @@ -24,7 +24,6 @@ export interface PowerSyncYjsEvents { * @param documentId */ export class PowerSyncYjsProvider extends ObservableV2 { - private seenDocUpdates = new Set(); private abortController = new AbortController(); constructor( @@ -33,40 +32,61 @@ export class PowerSyncYjsProvider extends ObservableV2 { public readonly documentId: string ) { super(); + /** + * Watch for changes to the `document_updates` table for this document. + * This will be used to apply updates from other editors. + * When we received an added item we apply the update to the Yjs document. + */ + const updateQuery = db + .query({ + sql: /* sql */ ` + SELECT + * + FROM + document_updates + WHERE + document_id = ? + `, + parameters: [documentId] + }) + .differentialWatch(); - const updates = db.watch('SELECT * FROM document_updates WHERE document_id = ?', [documentId], { - signal: this.abortController.signal - }); + this.abortController.signal.addEventListener( + 'abort', + () => { + // Stop the watch query when the abort signal is triggered + updateQuery.close(); + }, + { once: true } + ); this._storeUpdate = this._storeUpdate.bind(this); this.destroy = this.destroy.bind(this); let synced = false; - - const watchLoop = async () => { - for await (const results of updates) { - if (this.abortController.signal.aborted) { - break; - } - - // New data detected in the database - for (const update of results.rows!._array) { - // Ignore any updates we've already seen - if (!this.seenDocUpdates.has(update.id)) { - this.seenDocUpdates.add(update.id); - // apply the update from the database to the doc - const origin = this; - Y.applyUpdateV2(doc, b64ToUint8Array(update.update_b64), origin); - } + const origin = this; + updateQuery.registerListener({ + onDiff: async (diff) => { + for (const added of diff.added) { + /** + * Local document updates get stored to the database and synced. + * + * These updates here originate from syncing remote updates. + * Applying these updates to YJS should not result in the `_storeUpdate` + * handler creating a new `document_update` record since we mark the `origin` + * here and check the `origin` in `_storeUpdate`. + */ + Y.applyUpdateV2(doc, b64ToUint8Array(added.update_b64), origin); } - if (!synced) { synced = true; this.emit('synced', []); } + }, + onError: (error) => { + console.error('Error in PowerSyncYjsProvider update query:', error); } - }; - watchLoop(); + }); doc.on('updateV2', this._storeUpdate); doc.on('destroy', this.destroy); @@ -77,14 +97,16 @@ export class PowerSyncYjsProvider extends ObservableV2 { // update originated from the database / PowerSync - ignore return; } - // update originated from elsewhere - save to the database - const docUpdateId = uuidv4(); - this.seenDocUpdates.add(docUpdateId); - await this.db.execute('INSERT INTO document_updates(id, document_id, update_b64) VALUES(?, ?, ?)', [ - docUpdateId, - this.documentId, - Uint8ArrayTob64(update) - ]); + + await this.db.execute( + /* sql */ ` + INSERT INTO + document_updates (id, document_id, update_b64) + VALUES + (uuid (), ?, ?) + `, + [this.documentId, Uint8ArrayTob64(update)] + ); } /** @@ -102,6 +124,13 @@ export class PowerSyncYjsProvider extends ObservableV2 { * Also call `destroy()` to remove any event listeners and prevent future updates to the database. */ async deleteData() { - await this.db.execute('DELETE FROM document_updates WHERE document_id = ?', [this.documentId]); + await this.db.execute( + /* sql */ ` + DELETE FROM document_updates + WHERE + document_id = ? + `, + [this.documentId] + ); } } diff --git a/demos/yjs-react-supabase-text-collab/supabase/config.toml b/demos/yjs-react-supabase-text-collab/supabase/config.toml index b3bdbbad6..62883d2db 100644 --- a/demos/yjs-react-supabase-text-collab/supabase/config.toml +++ b/demos/yjs-react-supabase-text-collab/supabase/config.toml @@ -79,6 +79,8 @@ enable_refresh_token_rotation = true refresh_token_reuse_interval = 10 # Allow/disallow new user signups to your project. enable_signup = true +enable_anonymous_sign_ins = true + [auth.email] # Allow/disallow new user signups via email to your project. diff --git a/demos/yjs-react-supabase-text-collab/database.sql b/demos/yjs-react-supabase-text-collab/supabase/migrations/20250618064101_configure_powersync.sql similarity index 100% rename from demos/yjs-react-supabase-text-collab/database.sql rename to demos/yjs-react-supabase-text-collab/supabase/migrations/20250618064101_configure_powersync.sql diff --git a/demos/yjs-react-supabase-text-collab/sync-rules.yaml b/demos/yjs-react-supabase-text-collab/sync-rules.yaml index d8b77a6e1..236b6bbd3 100644 --- a/demos/yjs-react-supabase-text-collab/sync-rules.yaml +++ b/demos/yjs-react-supabase-text-collab/sync-rules.yaml @@ -1,10 +1,11 @@ +# yaml-language-server: $schema=https://unpkg.com/@powersync/service-sync-rules@latest/schema/sync_rules.json + # Sync-rule docs: https://docs.powersync.com/usage/sync-rules bucket_definitions: documents: - data: - - SELECT * FROM documents - updates: # Allow remote changes to be synchronized even while there are local changes priority: 0 + parameters: SELECT (request.parameters() ->> 'document_id') as document_id data: - - SELECT id, document_id, base64(update_data) as update_b64 FROM document_updates + - SELECT * FROM documents WHERE id = bucket.document_id + - SELECT id, document_id, base64(update_data) as update_b64 FROM document_updates WHERE document_id = bucket.document_id diff --git a/docs/.gitignore b/docs/.gitignore index 0814a0d06..c32a3c8c3 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -19,6 +19,7 @@ npm-debug.log* docs/attachments-sdk/ docs/common-sdk/ +docs/node-sdk/ docs/react-native-sdk/ docs/react-sdk/ docs/vue-sdk/ diff --git a/package.json b/package.json index 5ae008c68..2e82c5855 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "format": "prettier --write .", "lint": "eslint .", "release": "pnpm build:packages:prod && pnpm changeset publish", - "test": "pnpm run -r test" + "test": "pnpm run -r --workspace-concurrency=0 test" }, "keywords": [], "type": "module", @@ -33,12 +33,14 @@ "@actions/core": "^1.10.1", "@changesets/cli": "2.27.2", "@pnpm/workspace.find-packages": "^4.0.2", - "@vitest/browser": "^3.0.8", + "@vitest/browser": "^3.2.4", "husky": "^9.0.11", "lint-staged": "^15.2.2", "playwright": "^1.51.0", "prettier": "^3.2.5", + "prettier-plugin-embed": "^0.4.15", + "prettier-plugin-sql": "^0.18.1", "typescript": "^5.7.2", - "vitest": "^3.0.8" + "vitest": "^3.2.4" } } diff --git a/packages/common/src/client/AbstractPowerSyncDatabase.ts b/packages/common/src/client/AbstractPowerSyncDatabase.ts index 90bd46010..490d56aa9 100644 --- a/packages/common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/common/src/client/AbstractPowerSyncDatabase.ts @@ -17,9 +17,10 @@ import { BaseObserver } from '../utils/BaseObserver.js'; import { ControlledExecutor } from '../utils/ControlledExecutor.js'; import { throttleTrailing } from '../utils/async.js'; import { ConnectionManager } from './ConnectionManager.js'; +import { CustomQuery } from './CustomQuery.js'; +import { ArrayQueryDefinition, Query } from './Query.js'; import { SQLOpenFactory, SQLOpenOptions, isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory.js'; import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector.js'; -import { runOnSchemaChange } from './runOnSchemaChange.js'; import { BucketStorageAdapter, PSInternalTable } from './sync/bucket/BucketStorageAdapter.js'; import { CrudBatch } from './sync/bucket/CrudBatch.js'; import { CrudEntry, CrudEntryJSON } from './sync/bucket/CrudEntry.js'; @@ -34,6 +35,9 @@ import { type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.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'; export interface DisconnectAndClearOptions { /** When set to false, data in local-only tables is preserved. */ @@ -71,7 +75,7 @@ export interface PowerSyncDatabaseOptionsWithSettings extends BasePowerSyncDatab database: SQLOpenOptions; } -export interface SQLWatchOptions { +export interface SQLOnChangeOptions { signal?: AbortSignal; tables?: string[]; /** The minimum interval between queries. */ @@ -83,6 +87,18 @@ export interface SQLWatchOptions { * by not removing PowerSync table name prefixes */ rawTableNames?: boolean; + /** + * Emits an empty result set immediately + */ + triggerImmediate?: boolean; +} + +export interface SQLWatchOptions extends SQLOnChangeOptions { + /** + * Optional comparator which will be used to compare the results of the query. + * The watched query will only yield results if the comparator returns false. + */ + comparator?: WatchedQueryComparator; } export interface WatchOnChangeEvent { @@ -102,6 +118,8 @@ export interface WatchOnChangeHandler { export interface PowerSyncDBListener extends StreamingSyncImplementationListener { initialized: () => void; schemaChanged: (schema: Schema) => void; + closing: () => Promise | void; + closed: () => Promise | void; } export interface PowerSyncCloseOptions { @@ -123,8 +141,6 @@ export const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions = { disconnect: true }; -export const DEFAULT_WATCH_THROTTLE_MS = 30; - export const DEFAULT_POWERSYNC_DB_OPTIONS = { retryDelayMs: 5000, crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS @@ -516,6 +532,8 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver cb.closing?.()); + const { disconnect } = options; if (disconnect) { await this.disconnect(); @@ -524,6 +542,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver cb.closed?.()); } /** @@ -862,6 +881,62 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver ({ + * ...row, + * created_at: new Date(row.created_at as string) + * }) + * }) + * .watch() + * // OR use .differentialWatch() for fine-grained watches. + * ``` + */ + query(query: ArrayQueryDefinition): Query { + const { sql, parameters = [], mapper } = query; + const compatibleQuery: WatchCompatibleQuery = { + compile: () => ({ + sql, + parameters + }), + execute: async ({ sql, parameters }) => { + const result = await this.getAll(sql, parameters); + return mapper ? result.map(mapper) : (result as RowType[]); + } + }; + return this.customQuery(compatibleQuery); + } + + /** + * Allows building a {@link WatchedQuery} using an existing {@link WatchCompatibleQuery}. + * The watched query will use the provided {@link WatchCompatibleQuery.execute} method to query results. + * + * @example + * ```javascript + * + * // Potentially a query from an ORM like Drizzle + * const query = db.select().from(lists); + * + * const watchedTodos = powersync.customQuery(query) + * .watch() + * // OR use .differentialWatch() for fine-grained watches. + * ``` + */ + customQuery(query: WatchCompatibleQuery): Query { + return new CustomQuery({ + db: this, + query + }); + } + /** * Execute a read query every time the source tables are modified. * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries. @@ -879,39 +954,45 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver ({ + sql: sql, + parameters: parameters ?? [] + }), + execute: () => this.executeReadOnly(sql, parameters) + }, + reportFetching: false, + throttleMs: options?.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS, + triggerOnTables: options?.tables + } + }); - const watchQuery = async (abortSignal: AbortSignal) => { - try { - const resolvedTables = await this.resolveTables(sql, parameters, options); - // Fetch initial data - const result = await this.executeReadOnly(sql, parameters); - onResult(result); - - this.onChangeWithCallback( - { - onChange: async () => { - try { - const result = await this.executeReadOnly(sql, parameters); - onResult(result); - } catch (error) { - onError?.(error); - } - }, - onError - }, - { - ...(options ?? {}), - tables: resolvedTables, - // Override the abort signal since we intercept it - signal: abortSignal - } - ); - } catch (error) { - onError?.(error); + const dispose = watchedQuery.registerListener({ + onData: (data) => { + if (!data) { + // This should not happen. We only use null for the initial data. + return; + } + onResult(data); + }, + onError: (error) => { + onError(error); } - }; + }); - runOnSchemaChange(watchQuery, this, options); + options?.signal?.addEventListener('abort', () => { + dispose(); + watchedQuery.close(); + }); } /** @@ -985,7 +1066,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver; + onChange(options?: SQLOnChangeOptions): AsyncIterable; /** * See {@link onChangeWithCallback}. * @@ -1000,11 +1081,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver void; + onChange(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void; onChange( - handlerOrOptions?: WatchOnChangeHandler | SQLWatchOptions, - maybeOptions?: SQLWatchOptions + handlerOrOptions?: WatchOnChangeHandler | SQLOnChangeOptions, + maybeOptions?: SQLOnChangeOptions ): (() => void) | AsyncIterable { if (handlerOrOptions && typeof handlerOrOptions === 'object' && 'onChange' in handlerOrOptions) { const handler = handlerOrOptions as WatchOnChangeHandler; @@ -1029,7 +1110,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver void { + onChangeWithCallback(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void { const { onChange, onError = (e: Error) => this.logger.error(e) } = handler ?? {}; if (!onChange) { throw new Error('onChange is required'); @@ -1056,6 +1137,10 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver { try { diff --git a/packages/common/src/client/CustomQuery.ts b/packages/common/src/client/CustomQuery.ts new file mode 100644 index 000000000..c7dfdd6bb --- /dev/null +++ b/packages/common/src/client/CustomQuery.ts @@ -0,0 +1,56 @@ +import { AbstractPowerSyncDatabase } from './AbstractPowerSyncDatabase.js'; +import { Query, StandardWatchedQueryOptions } from './Query.js'; +import { FalsyComparator } from './watched/processors/comparators.js'; +import { + DifferentialQueryProcessor, + DifferentialWatchedQueryOptions +} from './watched/processors/DifferentialQueryProcessor.js'; +import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js'; +import { DEFAULT_WATCH_QUERY_OPTIONS, WatchCompatibleQuery, WatchedQueryOptions } from './watched/WatchedQuery.js'; + +/** + * @internal + */ +export interface CustomQueryOptions { + db: AbstractPowerSyncDatabase; + query: WatchCompatibleQuery; +} + +/** + * @internal + */ +export class CustomQuery implements Query { + constructor(protected options: CustomQueryOptions) {} + + protected resolveOptions(options: WatchedQueryOptions): WatchedQueryOptions { + return { + reportFetching: options?.reportFetching ?? DEFAULT_WATCH_QUERY_OPTIONS.reportFetching, + throttleMs: options?.throttleMs ?? DEFAULT_WATCH_QUERY_OPTIONS.throttleMs, + triggerOnTables: options?.triggerOnTables + }; + } + + watch(watchOptions: StandardWatchedQueryOptions) { + return new OnChangeQueryProcessor({ + db: this.options.db, + comparator: watchOptions?.comparator ?? FalsyComparator, + placeholderData: watchOptions?.placeholderData ?? [], + watchOptions: { + ...this.resolveOptions(watchOptions), + query: this.options.query + } + }); + } + + differentialWatch(differentialWatchOptions: DifferentialWatchedQueryOptions) { + return new DifferentialQueryProcessor({ + db: this.options.db, + rowComparator: differentialWatchOptions?.rowComparator, + placeholderData: differentialWatchOptions?.placeholderData ?? [], + watchOptions: { + ...this.resolveOptions(differentialWatchOptions), + query: this.options.query + } + }); + } +} diff --git a/packages/common/src/client/Query.ts b/packages/common/src/client/Query.ts new file mode 100644 index 000000000..22ddc3278 --- /dev/null +++ b/packages/common/src/client/Query.ts @@ -0,0 +1,106 @@ +import { WatchedQueryComparator } from './watched/processors/comparators.js'; +import { + DifferentialWatchedQuery, + DifferentialWatchedQueryOptions +} from './watched/processors/DifferentialQueryProcessor.js'; +import { StandardWatchedQuery } from './watched/processors/OnChangeQueryProcessor.js'; +import { WatchedQueryOptions } from './watched/WatchedQuery.js'; + +/** + * Query parameters for {@link ArrayQueryDefinition#parameters} + */ +export type QueryParam = string | number | boolean | null | undefined | bigint | Uint8Array; + +/** + * Options for building a query with {@link AbstractPowerSyncDatabase#query}. + * This query will be executed with {@link AbstractPowerSyncDatabase#getAll}. + */ +export interface ArrayQueryDefinition { + sql: string; + parameters?: ReadonlyArray>; + /** + * Maps the raw SQLite row to a custom typed object. + * @example + * ```javascript + * mapper: (row) => ({ + * ...row, + * created_at: new Date(row.created_at as string), + * }) + * ``` + */ + mapper?: (row: Record) => RowType; +} + +/** + * Options for {@link Query#watch}. + */ +export interface StandardWatchedQueryOptions extends WatchedQueryOptions { + /** + * The underlying watched query implementation (re)evaluates the query on any SQLite table change. + * + * Providing this optional comparator can be used to filter duplicate result set emissions when the result set is unchanged. + * The comparator compares the previous and current result set. + * + * For an efficient comparator see {@link ArrayComparator}. + * + * @example + * ```javascript + * comparator: new ArrayComparator({ + * compareBy: (item) => JSON.stringify(item) + * }) + * ``` + */ + comparator?: WatchedQueryComparator; + + /** + * The initial data state reported while the query is loading for the first time. + * @default [] + */ + placeholderData?: RowType[]; +} + +export interface Query { + /** + * Creates a {@link WatchedQuery} which watches and emits results of the linked query. + * + * By default the returned watched query will emit changes whenever a change to the underlying SQLite tables is made. + * These changes might not be relevant to the query, but the query will emit a new result set. + * + * A {@link StandardWatchedQueryOptions#comparator} can be provided to limit the data emissions. The watched query will still + * query the underlying DB on underlying table changes, but the result will only be emitted if the comparator detects a change in the results. + * + * The comparator in this method is optimized and returns early as soon as it detects a change. Each data emission will correlate to a change in the result set, + * but note that the result set will not maintain internal object references to the previous result set. If internal object references are needed, + * consider using {@link Query#differentialWatch} instead. + */ + watch(options?: StandardWatchedQueryOptions): StandardWatchedQuery>>; + + /** + * Creates a {@link WatchedQuery} which watches and emits results of the linked query. + * + * This query method watches for changes in the underlying SQLite tables and runs the query on each table change. + * The difference between the current and previous result set is computed. + * The watched query will not emit changes if the result set is identical to the previous result set. + * + * If the result set is different, the watched query will emit the new result set and emit a detailed diff of the changes via the `onData` and `onDiff` listeners. + * + * The deep differentiation allows maintaining result set object references between result emissions. + * The {@link DifferentialWatchedQuery#state} `data` array will contain the previous row references for unchanged rows. + * + * @example + * ```javascript + * const watchedLists = powerSync.query({sql: 'SELECT * FROM lists'}) + * .differentialWatch(); + * + * const disposeListener = watchedLists.registerListener({ + * onData: (lists) => { + * console.log('The latest result set for the query is', lists); + * }, + * onDiff: (diff) => { + * console.log('The lists result set has changed since the last emission', diff.added, diff.removed, diff.updated, diff.all) + * } + * }) + * ``` + */ + differentialWatch(options?: DifferentialWatchedQueryOptions): DifferentialWatchedQuery; +} diff --git a/packages/common/src/client/sync/bucket/BucketStorageAdapter.ts b/packages/common/src/client/sync/bucket/BucketStorageAdapter.ts index ecd6c7ef4..3c24c059c 100644 --- a/packages/common/src/client/sync/bucket/BucketStorageAdapter.ts +++ b/packages/common/src/client/sync/bucket/BucketStorageAdapter.ts @@ -1,4 +1,4 @@ -import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObserver.js'; +import { BaseListener, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js'; import { CrudBatch } from './CrudBatch.js'; import { CrudEntry, OpId } from './CrudEntry.js'; import { SyncDataBatch } from './SyncDataBatch.js'; @@ -72,7 +72,7 @@ export interface BucketStorageListener extends BaseListener { crudUpdate: () => void; } -export interface BucketStorageAdapter extends BaseObserver, Disposable { +export interface BucketStorageAdapter extends BaseObserverInterface, Disposable { init(): Promise; saveSyncData(batch: SyncDataBatch, fixedKeyFormat?: boolean): Promise; removeBuckets(buckets: string[]): Promise; diff --git a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index 31aed88bc..669441df3 100644 --- a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -1,11 +1,11 @@ import Logger, { ILogger } from 'js-logger'; -import { DataStream } from '../../../utils/DataStream.js'; -import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js'; import { FULL_SYNC_PRIORITY, InternalProgressInformation } from '../../../db/crud/SyncProgress.js'; import * as sync_status from '../../../db/crud/SyncStatus.js'; +import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js'; import { AbortOperation } from '../../../utils/AbortOperation.js'; -import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObserver.js'; +import { BaseListener, BaseObserver, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js'; +import { DataStream } from '../../../utils/DataStream.js'; import { throttleLeadingTrailing } from '../../../utils/async.js'; import { BucketChecksum, @@ -17,6 +17,7 @@ import { import { CrudEntry } from '../bucket/CrudEntry.js'; import { SyncDataBucket } from '../bucket/SyncDataBucket.js'; import { AbstractRemote, FetchStrategy, SyncStreamOptions } from './AbstractRemote.js'; +import { EstablishSyncStream, Instruction, SyncPriorityStatus } from './core-instruction.js'; import { BucketRequest, CrudUploadNotification, @@ -30,7 +31,6 @@ import { isStreamingSyncCheckpointPartiallyComplete, isStreamingSyncData } from './streaming-sync-types.js'; -import { EstablishSyncStream, Instruction, SyncPriorityStatus } from './core-instruction.js'; export enum LockType { CRUD = 'crud', @@ -179,7 +179,9 @@ export interface AdditionalConnectionOptions { /** @internal */ export type RequiredAdditionalConnectionOptions = Required; -export interface StreamingSyncImplementation extends BaseObserver, Disposable { +export interface StreamingSyncImplementation + extends BaseObserverInterface, + Disposable { /** * Connects to the sync service */ @@ -231,6 +233,9 @@ export abstract class AbstractStreamingSyncImplementation protected _lastSyncedAt: Date | null; protected options: AbstractStreamingSyncImplementationOptions; protected abortController: AbortController | null; + // In rare cases, mostly for tests, uploads can be triggered without being properly connected. + // This allows ensuring that all upload processes can be aborted. + protected uploadAbortController: AbortController | null; protected crudUpdateListener?: () => void; protected streamingSyncPromise?: Promise; protected logger: ILogger; @@ -320,8 +325,10 @@ export abstract class AbstractStreamingSyncImplementation } async dispose() { + super.dispose(); this.crudUpdateListener?.(); this.crudUpdateListener = undefined; + this.uploadAbortController?.abort(); } abstract obtainLock(lockOptions: LockOptions): Promise; @@ -348,7 +355,17 @@ export abstract class AbstractStreamingSyncImplementation */ let checkedCrudItem: CrudEntry | undefined; - while (true) { + const controller = new AbortController(); + this.uploadAbortController = controller; + this.abortController?.signal.addEventListener( + 'abort', + () => { + controller.abort(); + }, + { once: true } + ); + + while (!controller.signal.aborted) { try { /** * This is the first item in the FIFO CRUD queue. @@ -393,7 +410,7 @@ The next upload iteration will be delayed.`); uploadError: ex } }); - await this.delayRetry(); + await this.delayRetry(controller.signal); if (!this.isConnected) { // Exit the upload loop if the sync stream is no longer connected break; @@ -409,6 +426,7 @@ The next upload iteration will be delayed.`); }); } } + this.uploadAbortController = null; } }); } diff --git a/packages/common/src/client/watched/GetAllQuery.ts b/packages/common/src/client/watched/GetAllQuery.ts new file mode 100644 index 000000000..116fe1fd4 --- /dev/null +++ b/packages/common/src/client/watched/GetAllQuery.ts @@ -0,0 +1,46 @@ +import { CompiledQuery } from '../../types/types.js'; +import { AbstractPowerSyncDatabase } from '../AbstractPowerSyncDatabase.js'; +import { WatchCompatibleQuery } from './WatchedQuery.js'; + +/** + * Options for {@link GetAllQuery}. + */ +export type GetAllQueryOptions = { + sql: string; + parameters?: ReadonlyArray; + /** + * Optional mapper function to convert raw rows into the desired RowType. + * @example + * ```javascript + * (rawRow) => ({ + * id: rawRow.id, + * created_at: new Date(rawRow.created_at), + * }) + * ``` + */ + mapper?: (rawRow: Record) => RowType; +}; + +/** + * Performs a {@link AbstractPowerSyncDatabase.getAll} operation for a watched query. + */ +export class GetAllQuery implements WatchCompatibleQuery { + constructor(protected options: GetAllQueryOptions) {} + + compile(): CompiledQuery { + return { + sql: this.options.sql, + parameters: this.options.parameters ?? [] + }; + } + + async execute(options: { db: AbstractPowerSyncDatabase }): Promise { + const { db } = options; + const { sql, parameters = [] } = this.compile(); + const rawResult = await db.getAll(sql, [...parameters]); + if (this.options.mapper) { + return rawResult.map(this.options.mapper); + } + return rawResult as RowType[]; + } +} diff --git a/packages/common/src/client/watched/WatchedQuery.ts b/packages/common/src/client/watched/WatchedQuery.ts new file mode 100644 index 000000000..ad0f0506d --- /dev/null +++ b/packages/common/src/client/watched/WatchedQuery.ts @@ -0,0 +1,119 @@ +import { CompiledQuery } from '../../types/types.js'; +import { BaseListener } from '../../utils/BaseObserver.js'; +import { MetaBaseObserverInterface } from '../../utils/MetaBaseObserver.js'; +import { AbstractPowerSyncDatabase } from '../AbstractPowerSyncDatabase.js'; + +/** + * State for {@link WatchedQuery} instances. + */ +export interface WatchedQueryState { + /** + * Indicates the initial loading state (hard loading). + * Loading becomes false once the first set of results from the watched query is available or an error occurs. + */ + readonly isLoading: boolean; + /** + * Indicates whether the query is currently fetching data, is true during the initial load + * and any time when the query is re-evaluating (useful for large queries). + */ + readonly isFetching: boolean; + /** + * The last error that occurred while executing the query. + */ + readonly error: Error | null; + /** + * The last time the query was updated. + */ + readonly lastUpdated: Date | null; + /** + * The last data returned by the query. + */ + readonly data: Data; +} + +/** + * Options provided to the `execute` method of a {@link WatchCompatibleQuery}. + */ +export interface WatchExecuteOptions { + sql: string; + parameters: any[]; + db: AbstractPowerSyncDatabase; +} + +/** + * Similar to {@link CompatibleQuery}, except the `execute` method + * does not enforce an Array result type. + */ +export interface WatchCompatibleQuery { + execute(options: WatchExecuteOptions): Promise; + compile(): CompiledQuery; +} + +export interface WatchedQueryOptions { + /** The minimum interval between queries. */ + throttleMs?: number; + /** + * If true (default) the watched query will update its state to report + * on the fetching state of the query. + * Setting to false reduces the number of state changes if the fetch status + * is not relevant to the consumer. + */ + reportFetching?: boolean; + + /** + * By default, watched queries requery the database on any change to any dependent table of the query. + * Supplying an override here can be used to limit the tables which trigger querying the database. + */ + triggerOnTables?: string[]; +} + +export enum WatchedQueryListenerEvent { + ON_DATA = 'onData', + ON_ERROR = 'onError', + ON_STATE_CHANGE = 'onStateChange', + CLOSED = 'closed' +} + +export interface WatchedQueryListener extends BaseListener { + [WatchedQueryListenerEvent.ON_DATA]?: (data: Data) => void | Promise; + [WatchedQueryListenerEvent.ON_ERROR]?: (error: Error) => void | Promise; + [WatchedQueryListenerEvent.ON_STATE_CHANGE]?: (state: WatchedQueryState) => void | Promise; + [WatchedQueryListenerEvent.CLOSED]?: () => void | Promise; +} + +export const DEFAULT_WATCH_THROTTLE_MS = 30; + +export const DEFAULT_WATCH_QUERY_OPTIONS: WatchedQueryOptions = { + throttleMs: DEFAULT_WATCH_THROTTLE_MS, + reportFetching: true +}; + +export interface WatchedQuery< + Data = unknown, + Settings extends WatchedQueryOptions = WatchedQueryOptions, + Listener extends WatchedQueryListener = WatchedQueryListener +> extends MetaBaseObserverInterface { + /** + * Current state of the watched query. + */ + readonly state: WatchedQueryState; + + readonly closed: boolean; + + /** + * Subscribe to watched query events. + * @returns A function to unsubscribe from the events. + */ + registerListener(listener: Listener): () => void; + + /** + * Updates the underlying query options. + * This will trigger a re-evaluation of the query and update the state. + */ + updateSettings(options: Settings): Promise; + + /** + * Close the watched query and end all subscriptions. + */ + close(): Promise; +} diff --git a/packages/common/src/client/watched/processors/AbstractQueryProcessor.ts b/packages/common/src/client/watched/processors/AbstractQueryProcessor.ts new file mode 100644 index 000000000..0e5c1ab8a --- /dev/null +++ b/packages/common/src/client/watched/processors/AbstractQueryProcessor.ts @@ -0,0 +1,201 @@ +import { AbstractPowerSyncDatabase } from '../../../client/AbstractPowerSyncDatabase.js'; +import { MetaBaseObserver } from '../../../utils/MetaBaseObserver.js'; +import { WatchedQuery, WatchedQueryListener, WatchedQueryOptions, WatchedQueryState } from '../WatchedQuery.js'; + +/** + * @internal + */ +export interface AbstractQueryProcessorOptions { + db: AbstractPowerSyncDatabase; + watchOptions: Settings; + placeholderData: Data; +} + +/** + * @internal + */ +export interface LinkQueryOptions { + abortSignal: AbortSignal; + settings: Settings; +} + +type MutableDeep = + T extends ReadonlyArray + ? U[] // convert readonly arrays to mutable arrays + : T; + +/** + * @internal Mutable version of {@link WatchedQueryState}. + * This is used internally to allow updates to the state. + */ +export type MutableWatchedQueryState = { + -readonly [P in keyof WatchedQueryState]: MutableDeep[P]>; +}; + +type WatchedQueryProcessorListener = WatchedQueryListener; + +/** + * Performs underlying watching and yields a stream of results. + * @internal + */ +export abstract class AbstractQueryProcessor< + Data = unknown[], + Settings extends WatchedQueryOptions = WatchedQueryOptions + > + extends MetaBaseObserver> + implements WatchedQuery +{ + readonly state: WatchedQueryState; + + protected abortController: AbortController; + protected initialized: Promise; + protected _closed: boolean; + protected disposeListeners: (() => void) | null; + + get closed() { + return this._closed; + } + + constructor(protected options: AbstractQueryProcessorOptions) { + super(); + this.abortController = new AbortController(); + this._closed = false; + this.state = this.constructInitialState(); + this.disposeListeners = null; + this.initialized = this.init(); + } + + protected constructInitialState(): WatchedQueryState { + return { + isLoading: true, + isFetching: this.reportFetching, // Only set to true if we will report updates in future + error: null, + lastUpdated: null, + data: this.options.placeholderData + }; + } + + protected get reportFetching() { + return this.options.watchOptions.reportFetching ?? true; + } + + /** + * Updates the underlying query. + */ + async updateSettings(settings: Settings) { + await this.initialized; + + if (!this.state.isFetching && this.reportFetching) { + await this.updateState({ + isFetching: true + }); + } + + this.options.watchOptions = settings; + this.abortController.abort(); + this.abortController = new AbortController(); + await this.runWithReporting(() => + this.linkQuery({ + abortSignal: this.abortController.signal, + settings + }) + ); + } + + /** + * This method is used to link a query to the subscribers of this listener class. + * This method should perform actual query watching and report results via {@link updateState} method. + */ + protected abstract linkQuery(options: LinkQueryOptions): Promise; + + protected async updateState(update: Partial>) { + if (typeof update.error !== 'undefined') { + await this.iterateAsyncListenersWithError(async (l) => l.onError?.(update.error!)); + // An error always stops for the current fetching state + update.isFetching = false; + update.isLoading = false; + } + + Object.assign(this.state, { lastUpdated: new Date() } satisfies Partial>, update); + + if (typeof update.data !== 'undefined') { + await this.iterateAsyncListenersWithError(async (l) => l.onData?.(this.state.data)); + } + + await this.iterateAsyncListenersWithError(async (l) => l.onStateChange?.(this.state)); + } + + /** + * Configures base DB listeners and links the query to listeners. + */ + protected async init() { + const { db } = this.options; + + const disposeCloseListener = db.registerListener({ + closing: async () => { + await this.close(); + } + }); + + // Wait for the schema to be set before listening to changes + await db.waitForReady(); + const disposeSchemaListener = db.registerListener({ + schemaChanged: async () => { + await this.runWithReporting(async () => { + await this.updateSettings(this.options.watchOptions); + }); + } + }); + + this.disposeListeners = () => { + disposeCloseListener(); + disposeSchemaListener(); + }; + + // Initial setup + this.runWithReporting(async () => { + await this.updateSettings(this.options.watchOptions); + }); + } + + async close() { + await this.initialized; + this.abortController.abort(); + this.disposeListeners?.(); + this.disposeListeners = null; + this._closed = true; + this.iterateListeners((l) => l.closed?.()); + this.listeners.clear(); + } + + /** + * Runs a callback and reports errors to the error listeners. + */ + protected async runWithReporting(callback: () => Promise): Promise { + try { + await callback(); + } catch (error) { + // This will update the error on the state and iterate error listeners + await this.updateState({ error }); + } + } + + /** + * Iterate listeners and reports errors to onError handlers. + */ + protected async iterateAsyncListenersWithError( + callback: (listener: Partial>) => Promise | void + ) { + try { + await this.iterateAsyncListeners(async (l) => callback(l)); + } catch (error) { + try { + await this.iterateAsyncListeners(async (l) => l.onError?.(error)); + } catch (error) { + // Errors here are ignored + // since we are already in an error state + this.options.db.logger.error('Watched query error handler threw an Error', error); + } + } + } +} diff --git a/packages/common/src/client/watched/processors/DifferentialQueryProcessor.ts b/packages/common/src/client/watched/processors/DifferentialQueryProcessor.ts new file mode 100644 index 000000000..48367e552 --- /dev/null +++ b/packages/common/src/client/watched/processors/DifferentialQueryProcessor.ts @@ -0,0 +1,297 @@ +import { WatchCompatibleQuery, WatchedQuery, WatchedQueryListener, WatchedQueryOptions } from '../WatchedQuery.js'; +import { + AbstractQueryProcessor, + AbstractQueryProcessorOptions, + LinkQueryOptions, + MutableWatchedQueryState +} from './AbstractQueryProcessor.js'; + +/** + * Represents an updated row in a differential watched query. + * It contains both the current and previous state of the row. + */ +export interface WatchedQueryRowDifferential { + readonly current: RowType; + readonly previous: RowType; +} + +/** + * Represents the result of a watched query that has been diffed. + * {@link DifferentialWatchedQueryState#diff} is of the {@link WatchedQueryDifferential} form. + */ +export interface WatchedQueryDifferential { + readonly added: ReadonlyArray>; + /** + * The entire current result set. + * Array item object references are preserved between updates if the item is unchanged. + * + * e.g. In the query + * ```sql + * SELECT name, make FROM assets ORDER BY make ASC; + * ``` + * + * If a previous result set contains an item (A) `{name: 'pc', make: 'Cool PC'}` and + * an update has been made which adds another item (B) to the result set (the item A is unchanged) - then + * the updated result set will be contain the same object reference, to item A, as the previous result set. + * This is regardless of the item A's position in the updated result set. + */ + readonly all: ReadonlyArray>; + readonly removed: ReadonlyArray>; + readonly updated: ReadonlyArray>>; + readonly unchanged: ReadonlyArray>; +} + +/** + * Row comparator for differentially watched queries which keys and compares items in the result set. + */ +export interface DifferentialWatchedQueryComparator { + /** + * Generates a unique key for the item. + */ + keyBy: (item: RowType) => string; + /** + * Generates a token for comparing items with matching keys. + */ + compareBy: (item: RowType) => string; +} + +/** + * Options for building a differential watched query with the {@link Query} builder. + */ +export interface DifferentialWatchedQueryOptions extends WatchedQueryOptions { + /** + * Initial result data which is presented while the initial loading is executing. + */ + placeholderData?: RowType[]; + + /** + * Row comparator used to identify and compare rows in the result set. + * If not provided, the default comparator will be used which keys items by their `id` property if available, + * otherwise it uses JSON stringification of the entire item for keying and comparison. + * @defaultValue {@link DEFAULT_ROW_COMPARATOR} + */ + rowComparator?: DifferentialWatchedQueryComparator; +} + +/** + * Settings for differential incremental watched queries using. + */ +export interface DifferentialWatchedQuerySettings extends DifferentialWatchedQueryOptions { + /** + * The query here must return an array of items that can be differentiated. + */ + query: WatchCompatibleQuery; +} + +export interface DifferentialWatchedQueryListener + extends WatchedQueryListener>> { + onDiff?: (diff: WatchedQueryDifferential) => void | Promise; +} + +export type DifferentialWatchedQuery = WatchedQuery< + ReadonlyArray>, + DifferentialWatchedQuerySettings, + DifferentialWatchedQueryListener +>; + +/** + * @internal + */ +export interface DifferentialQueryProcessorOptions + extends AbstractQueryProcessorOptions> { + rowComparator?: DifferentialWatchedQueryComparator; +} + +type DataHashMap = Map; + +/** + * An empty differential result set. + * This is used as the initial state for differential incrementally watched queries. + */ +export const EMPTY_DIFFERENTIAL = { + added: [], + all: [], + removed: [], + updated: [], + unchanged: [] +}; + +/** + * Default implementation of the {@link DifferentialWatchedQueryComparator} for watched queries. + * It keys items by their `id` property if available, alternatively it uses JSON stringification + * of the entire item for the key and comparison. + */ +export const DEFAULT_ROW_COMPARATOR: DifferentialWatchedQueryComparator = { + keyBy: (item) => { + if (item && typeof item == 'object' && typeof item['id'] == 'string') { + return item['id']; + } + return JSON.stringify(item); + }, + compareBy: (item) => JSON.stringify(item) +}; + +/** + * Uses the PowerSync onChange event to trigger watched queries. + * Results are emitted on every change of the relevant tables. + * @internal + */ +export class DifferentialQueryProcessor + extends AbstractQueryProcessor>, DifferentialWatchedQuerySettings> + implements DifferentialWatchedQuery +{ + protected comparator: DifferentialWatchedQueryComparator; + + constructor(protected options: DifferentialQueryProcessorOptions) { + super(options); + this.comparator = options.rowComparator ?? DEFAULT_ROW_COMPARATOR; + } + + /* + * @returns If the sets are equal + */ + protected differentiate( + current: RowType[], + previousMap: DataHashMap + ): { diff: WatchedQueryDifferential; map: DataHashMap; hasChanged: boolean } { + const { keyBy, compareBy } = this.comparator; + + let hasChanged = false; + const currentMap = new Map(); + const removedTracker = new Set(previousMap.keys()); + + // Allow mutating to populate the data temporarily. + const diff = { + all: [] as RowType[], + added: [] as RowType[], + removed: [] as RowType[], + updated: [] as WatchedQueryRowDifferential[], + unchanged: [] as RowType[] + }; + + /** + * Looping over the current result set array is important to preserve + * the ordering of the result set. + * We can replace items in the current array with previous object references if they are equal. + */ + for (const item of current) { + const key = keyBy(item); + const hash = compareBy(item); + currentMap.set(key, { hash, item }); + + const previousItem = previousMap.get(key); + if (!previousItem) { + // New item + hasChanged = true; + diff.added.push(item); + diff.all.push(item); + } else { + // Existing item + if (hash == previousItem.hash) { + diff.unchanged.push(previousItem.item); + // Use the previous object reference + diff.all.push(previousItem.item); + // update the map to preserve the reference + currentMap.set(key, previousItem); + } else { + hasChanged = true; + diff.updated.push({ current: item, previous: previousItem.item }); + // Use the new reference + diff.all.push(item); + } + } + // The item is present, we don't consider it removed + removedTracker.delete(key); + } + + diff.removed = Array.from(removedTracker).map((key) => previousMap.get(key)!.item); + hasChanged = hasChanged || diff.removed.length > 0; + + return { + diff, + hasChanged, + map: currentMap + }; + } + + protected async linkQuery(options: LinkQueryOptions>): Promise { + const { db, watchOptions } = this.options; + const { abortSignal } = options; + + const compiledQuery = watchOptions.query.compile(); + const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters as any[], { + tables: options.settings.triggerOnTables + }); + + let currentMap: DataHashMap = new Map(); + + // populate the currentMap from the placeholder data + this.state.data.forEach((item) => { + currentMap.set(this.comparator.keyBy(item), { + hash: this.comparator.compareBy(item), + item + }); + }); + + db.onChangeWithCallback( + { + onChange: async () => { + if (this.closed) { + return; + } + // This fires for each change of the relevant tables + try { + if (this.reportFetching && !this.state.isFetching) { + await this.updateState({ isFetching: true }); + } + + const partialStateUpdate: Partial> = {}; + + // Always run the query if an underlying table has changed + const result = await watchOptions.query.execute({ + sql: compiledQuery.sql, + // Allows casting from ReadOnlyArray[unknown] to Array + // This allows simpler compatibility with PowerSync queries + parameters: [...compiledQuery.parameters], + db: this.options.db + }); + + if (this.reportFetching) { + partialStateUpdate.isFetching = false; + } + + if (this.state.isLoading) { + partialStateUpdate.isLoading = false; + } + + const { diff, hasChanged, map } = this.differentiate(result, currentMap); + // Update for future comparisons + currentMap = map; + + if (hasChanged) { + await this.iterateAsyncListenersWithError((l) => l.onDiff?.(diff)); + Object.assign(partialStateUpdate, { + data: diff.all + }); + } + + if (Object.keys(partialStateUpdate).length > 0) { + await this.updateState(partialStateUpdate); + } + } catch (error) { + await this.updateState({ error }); + } + }, + onError: async (error) => { + await this.updateState({ error }); + } + }, + { + signal: abortSignal, + tables, + throttleMs: watchOptions.throttleMs, + triggerImmediate: true // used to emit the initial state + } + ); + } +} diff --git a/packages/common/src/client/watched/processors/OnChangeQueryProcessor.ts b/packages/common/src/client/watched/processors/OnChangeQueryProcessor.ts new file mode 100644 index 000000000..0763af4c6 --- /dev/null +++ b/packages/common/src/client/watched/processors/OnChangeQueryProcessor.ts @@ -0,0 +1,114 @@ +import { WatchCompatibleQuery, WatchedQuery, WatchedQueryOptions } from '../WatchedQuery.js'; +import { + AbstractQueryProcessor, + AbstractQueryProcessorOptions, + LinkQueryOptions, + MutableWatchedQueryState +} from './AbstractQueryProcessor.js'; +import { WatchedQueryComparator } from './comparators.js'; + +/** + * Settings for {@link WatchedQuery} instances created via {@link Query#watch}. + */ +export interface WatchedQuerySettings extends WatchedQueryOptions { + query: WatchCompatibleQuery; +} + +/** + * {@link WatchedQuery} returned from {@link Query#watch}. + */ +export type StandardWatchedQuery = WatchedQuery>; + +/** + * @internal + */ +export interface OnChangeQueryProcessorOptions + extends AbstractQueryProcessorOptions> { + comparator?: WatchedQueryComparator; +} + +/** + * Uses the PowerSync onChange event to trigger watched queries. + * Results are emitted on every change of the relevant tables. + * @internal + */ +export class OnChangeQueryProcessor extends AbstractQueryProcessor> { + constructor(protected options: OnChangeQueryProcessorOptions) { + super(options); + } + + /** + * @returns If the sets are equal + */ + protected checkEquality(current: Data, previous: Data): boolean { + // Use the provided comparator if available. Assume values are unique if not available. + return this.options.comparator?.checkEquality?.(current, previous) ?? false; + } + + protected async linkQuery(options: LinkQueryOptions): Promise { + const { db, watchOptions } = this.options; + const { abortSignal } = options; + + const compiledQuery = watchOptions.query.compile(); + const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters as any[], { + tables: options.settings.triggerOnTables + }); + + db.onChangeWithCallback( + { + onChange: async () => { + if (this.closed) { + return; + } + // This fires for each change of the relevant tables + try { + if (this.reportFetching && !this.state.isFetching) { + await this.updateState({ isFetching: true }); + } + + const partialStateUpdate: Partial> & { data?: Data } = {}; + + // Always run the query if an underlying table has changed + const result = await watchOptions.query.execute({ + sql: compiledQuery.sql, + // Allows casting from ReadOnlyArray[unknown] to Array + // This allows simpler compatibility with PowerSync queries + parameters: [...compiledQuery.parameters], + db: this.options.db + }); + + if (this.reportFetching) { + partialStateUpdate.isFetching = false; + } + + if (this.state.isLoading) { + partialStateUpdate.isLoading = false; + } + + // Check if the result has changed + if (!this.checkEquality(result, this.state.data)) { + Object.assign(partialStateUpdate, { + data: result + }); + } + + if (Object.keys(partialStateUpdate).length > 0) { + await this.updateState(partialStateUpdate); + } + } catch (error) { + await this.updateState({ error }); + } + }, + onError: async (error) => { + await this.updateState({ error }); + } + }, + { + signal: abortSignal, + tables, + throttleMs: watchOptions.throttleMs, + triggerImmediate: true // used to emit the initial state + } + ); + } +} diff --git a/packages/common/src/client/watched/processors/comparators.ts b/packages/common/src/client/watched/processors/comparators.ts new file mode 100644 index 000000000..038129d08 --- /dev/null +++ b/packages/common/src/client/watched/processors/comparators.ts @@ -0,0 +1,57 @@ +/** + * A basic comparator for incrementally watched queries. This performs a single comparison which + * determines if the result set has changed. The {@link WatchedQuery} will only emit the new result + * if a change has been detected. + */ +export interface WatchedQueryComparator { + checkEquality: (current: Data, previous: Data) => boolean; +} + +/** + * Options for {@link ArrayComparator} + */ +export type ArrayComparatorOptions = { + /** + * Returns a string to uniquely identify an item in the array. + */ + compareBy: (item: ItemType) => string; +}; + +/** + * An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query + * result has changes without necessarily processing all items in the result. + */ +export class ArrayComparator implements WatchedQueryComparator { + constructor(protected options: ArrayComparatorOptions) {} + + checkEquality(current: ItemType[], previous: ItemType[]) { + if (current.length === 0 && previous.length === 0) { + return true; + } + + if (current.length !== previous.length) { + return false; + } + + const { compareBy } = this.options; + + // At this point the lengths are equal + for (let i = 0; i < current.length; i++) { + const currentItem = compareBy(current[i]); + const previousItem = compareBy(previous[i]); + + if (currentItem !== previousItem) { + return false; + } + } + + return true; + } +} + +/** + * Watched query comparator that always reports changed result sets. + */ +export const FalsyComparator: WatchedQueryComparator = { + checkEquality: () => false // Default comparator that always returns false +}; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 44203a2b9..40d4f7330 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -31,6 +31,14 @@ export * from './db/schema/Schema.js'; export * from './db/schema/Table.js'; export * from './db/schema/TableV2.js'; +export * from './client/Query.js'; +export * from './client/watched/GetAllQuery.js'; +export * from './client/watched/processors/AbstractQueryProcessor.js'; +export * from './client/watched/processors/comparators.js'; +export * from './client/watched/processors/DifferentialQueryProcessor.js'; +export * from './client/watched/processors/OnChangeQueryProcessor.js'; +export * from './client/watched/WatchedQuery.js'; + export * from './utils/AbortOperation.js'; export * from './utils/BaseObserver.js'; export * from './utils/DataStream.js'; diff --git a/packages/common/src/utils/BaseObserver.ts b/packages/common/src/utils/BaseObserver.ts index df56e9e61..fa8067226 100644 --- a/packages/common/src/utils/BaseObserver.ts +++ b/packages/common/src/utils/BaseObserver.ts @@ -1,20 +1,22 @@ export interface Disposable { - dispose: () => Promise; + dispose: () => Promise | void; } +export type BaseListener = Record any) | undefined>; + export interface BaseObserverInterface { registerListener(listener: Partial): () => void; } -export type BaseListener = { - [key: string]: ((...event: any) => any) | undefined; -}; - export class BaseObserver implements BaseObserverInterface { protected listeners = new Set>(); constructor() {} + dispose(): void { + this.listeners.clear(); + } + /** * Register a listener for updates to the PowerSync client. */ diff --git a/packages/common/src/utils/MetaBaseObserver.ts b/packages/common/src/utils/MetaBaseObserver.ts new file mode 100644 index 000000000..73bb038f8 --- /dev/null +++ b/packages/common/src/utils/MetaBaseObserver.ts @@ -0,0 +1,81 @@ +import { BaseListener, BaseObserver, BaseObserverInterface } from './BaseObserver.js'; + +/** + * Represents the counts of listeners for each event type in a BaseListener. + */ +export type ListenerCounts = Partial> & { + total: number; +}; + +/** + * Meta listener which reports the counts of listeners for each event type. + */ +export interface MetaListener extends BaseListener { + listenersChanged?: (counts: ListenerCounts) => void; +} + +export interface ListenerMetaManager + extends BaseObserverInterface> { + counts: ListenerCounts; +} + +export interface MetaBaseObserverInterface extends BaseObserverInterface { + listenerMeta: ListenerMetaManager; +} + +/** + * A BaseObserver that tracks the counts of listeners for each event type. + */ +export class MetaBaseObserver + extends BaseObserver + implements MetaBaseObserverInterface +{ + protected get listenerCounts(): ListenerCounts { + const counts = {} as Partial>; + let total = 0; + for (const listener of this.listeners) { + for (const key in listener) { + if (listener[key]) { + counts[key] = (counts[key] ?? 0) + 1; + total++; + } + } + } + return { + ...counts, + total + }; + } + + get listenerMeta(): ListenerMetaManager { + return { + counts: this.listenerCounts, + // Allows registering a meta listener that will be notified of changes in listener counts + registerListener: (listener: Partial>) => { + return this.metaListener.registerListener(listener); + } + }; + } + + protected metaListener: BaseObserver>; + + constructor() { + super(); + this.metaListener = new BaseObserver>(); + } + + registerListener(listener: Partial): () => void { + const dispose = super.registerListener(listener); + const updatedCount = this.listenerCounts; + this.metaListener.iterateListeners((l) => { + l.listenersChanged?.(updatedCount); + }); + return () => { + dispose(); + const updatedCount = this.listenerCounts; + this.metaListener.iterateListeners((l) => { + l.listenersChanged?.(updatedCount); + }); + }; + } +} diff --git a/packages/kysely-driver/tests/sqlite/watch.test.ts b/packages/kysely-driver/tests/sqlite/watch.test.ts index 1c39a32e3..1a499a5fe 100644 --- a/packages/kysely-driver/tests/sqlite/watch.test.ts +++ b/packages/kysely-driver/tests/sqlite/watch.test.ts @@ -90,9 +90,9 @@ describe('Watch Tests', () => { await db .insertInto('assets') .values({ - id: sql`uuid()`, + id: sql`uuid ()`, make: 'test', - customer_id: sql`uuid()` + customer_id: sql`uuid ()` }) .execute(); @@ -126,9 +126,9 @@ describe('Watch Tests', () => { await db .insertInto('assets') .values({ - id: sql`uuid()`, + id: sql`uuid ()`, make: 'test', - customer_id: sql`uuid()` + customer_id: sql`uuid ()` }) .execute(); } @@ -180,9 +180,9 @@ describe('Watch Tests', () => { await db .insertInto('assets') .values({ - id: sql`uuid()`, + id: sql`uuid ()`, make: 'test', - customer_id: sql`uuid()` + customer_id: sql`uuid ()` }) .execute(); @@ -210,7 +210,7 @@ describe('Watch Tests', () => { const query = db.selectFrom('assets').select([ () => { - const fullName = sql`fakeFunction()`; // Simulate an error with invalid function + const fullName = sql`fakeFunction ()`; // Simulate an error with invalid function return fullName.as('full_name'); } ]); @@ -246,9 +246,9 @@ describe('Watch Tests', () => { for (let i = 0; i < updatesCount; i++) { db.insertInto('assets') .values({ - id: sql`uuid()`, + id: sql`uuid ()`, make: 'test', - customer_id: sql`uuid()` + customer_id: sql`uuid ()` }) .execute(); } @@ -261,4 +261,33 @@ describe('Watch Tests', () => { expect(receivedWithManagedOverflowCount).greaterThan(2); expect(receivedWithManagedOverflowCount).toBeLessThanOrEqual(4); }); + + it('incremental watch should accept queries', async () => { + const query = db.selectFrom('assets').select(db.fn.count('assets.id').as('count')); + + const watch = powerSyncDb.customQuery(query).watch(); + + const latestDataPromise = new Promise>>((resolve) => { + const dispose = watch.registerListener({ + onData: (data) => { + if (data.length > 0) { + resolve([...data]); + dispose(); + } + } + }); + }); + + await db + .insertInto('assets') + .values({ + id: sql`uuid ()`, + make: 'test', + customer_id: sql`uuid ()` + }) + .execute(); + + const data = await latestDataPromise; + expect(data.length).equals(1); + }); }); diff --git a/packages/node/package.json b/packages/node/package.json index 2997a5867..a697c7e46 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -61,7 +61,7 @@ "drizzle-orm": "^0.35.2", "rollup": "4.14.3", "typescript": "^5.5.3", - "vitest": "^3.0.5" + "vitest": "^3.2.4" }, "keywords": [ "data sync", diff --git a/packages/react-native/rollup.config.mjs b/packages/react-native/rollup.config.mjs index d2ee7e7fa..08699165b 100644 --- a/packages/react-native/rollup.config.mjs +++ b/packages/react-native/rollup.config.mjs @@ -12,7 +12,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export default (commandLineArgs) => { - const sourcemap = (commandLineArgs.sourceMap || 'true') == 'true'; + const sourceMap = (commandLineArgs.sourceMap || 'true') == 'true'; // Clears rollup CLI warning https://github.com/rollup/rollup/issues/2694 delete commandLineArgs.sourceMap; @@ -22,7 +22,7 @@ export default (commandLineArgs) => { output: { file: 'dist/index.js', format: 'cjs', - sourcemap: sourcemap + sourcemap: sourceMap }, plugins: [ // We do this so that we can inject on BSON's crypto usage. @@ -54,7 +54,7 @@ export default (commandLineArgs) => { } ] }), - terser({ sourceMap: sourcemap }) + terser({ sourceMap }) ], external: [ '@journeyapps/react-native-quick-sqlite', diff --git a/packages/react/README.md b/packages/react/README.md index e02344e50..ff10730e1 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -69,7 +69,7 @@ const Component = () => { ## Reactive Queries -The `useQuery` hook allows you to access the results of a watched query. Queries will automatically update when a dependant table is updated unless you set the `runQueryOnce` flag. You are also able to use a compilable query (e.g. [Kysely queries](https://github.com/powersync-ja/powersync-js/tree/main/packages/kysely-driver)) as a query argument in place of a SQL statement string. +The `useQuery` hook allows you to access the results of a watched query. Queries will automatically update when a dependent table is updated unless you set the `runQueryOnce` flag. You are also able to use a compilable query (e.g. [Kysely queries](https://github.com/powersync-ja/powersync-js/tree/main/packages/kysely-driver)) as a query argument in place of a SQL statement string. ```JSX // TodoListDisplay.jsx @@ -271,3 +271,148 @@ export const TodoListDisplaySuspense = () => { ); }; ``` + +## Preventing Unnecessary Renders + +The `useQuery` hook returns a stateful object which contains query fetching/loading state values and the query result set data. + +```tsx +function MyWidget() { + // ... Widget code + // result is an object which contains `isLoading`, `isFetching`, `data` members. + const result = useQuery(...) + + // ... Widget code +} +``` + +### High Order Components + +The returned object is a new JS object reference whenever the internal state changes e.g. if the query `isFetching` alternates in value. The parent component which calls `useQuery` will render each time the watched query state changes - this can result in other child widgets re-rendering if they are not memoized. Using the `result` object in child component props will cause those children to re-render on any state change of the watched query. The first step to avoid re-renders is to call `useQuery` in a Higher Order Component which passes query results to memoized children. + +```tsx +function MyWidget() { + // ... Widget code + // result is an object which contains `isLoading`, `isFetching`, `data` members. + const {data, error, isLoading} = useQuery(...) + + // ... Widget code + + return ( + // ... Other components + + // If MyWatchedWidget is not memoized + // - It will rerender on any state change of the watched query. E.g. if isFetching alternates + // If MyWatchedWidget is memoized + // - It will re-render if the data reference changes. By default the data reference changes after any + // change to the query's dependent tables. This can be optimized by using Incremental queries. + + ) +} +``` + +### Incremental Queries + +By default watched queries are queried whenever a change to the underlying tables has been detected. These changes might not be relevant to the actual query, but will still trigger a query and `data` update. + +```tsx +function MyWidget() { + // ... Widget code + // This query will update with a new data Array whenever any change is made to the `cats` table + // E.g. `INSERT INTO cats(name) VALUES ('silvester')` will return a new Array reference for `data` + const { data } = useQuery(`SELECT * FROM cats WHERE name = 'bob'`) + + // ... Widget code + + return ( + // Other components + // This will rerender for any change to the `cats` table + // Memoization cannot prevent this component from re-rendering since `data[0]` is always new object reference + // whenever a query has been triggered + + ) +} +``` + +Incremental watched queries ensure that the `data` member of the `useQuery` result maintains the same Array reference if the result set is unchanged. +Additionally, the internal array items maintain object references when unchanged. + +```tsx +function MyWidget() { + // ... Widget code + // This query will be fetched/queried whenever any change is made to the `cats` table. + // The `data` reference will only be changed if there have been changes since the previous value. + // This method performs a comparison in memory in order to determine changes. + // Note that isFetching is set (by default) whenever the query is being fetched/checked. + // This will result in `MyWidget` re-rendering for any change to the `cats` table. + const { data, isLoading, isFetching } = useQuery(`SELECT * FROM cats WHERE breed = 'tabby'`, [], { + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } + }) + + // ... Widget code + + return ( + // Other components + // The data array is the same reference if no changes have occurred between fetches + // Note: The array is a new reference is there are any changes in the result set (individual row object references are preserved for unchanged rows) + // Note: CatCollection requires memoization in order to prevent re-rendering (due to the parent re-rendering on fetch) + + ) +} +``` + +`useQuery` can be configured to disable reporting `isFetching` status. Disabling this setting reduces the number of events emitted from the hook, which can reduce renders in some circumstances. + +```tsx +function MyWidget() { + // ... Widget code + // This query will be fetched/queried whenever any change is made to the `cats` table. + // The `data` reference will only be changed if there have been changes since the previous value. + // When reportFetching == false the object returned from useQuery will only be changed when the data, isLoading or error state changes. + // This method performs a comparison in memory in order to determine changes. + const { data, isLoading } = useQuery(`SELECT * FROM cats WHERE breed = 'tabby'`, [], { + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } + reportFetching: false + }) + + // ... Widget code + + return ( + // Other components + // The data array is the same reference if no changes have occurred between fetches + // Note: The array is a new reference is there are any changes in the result set (individual row object references are not preserved) + + ) +} +``` + +## Query Subscriptions + +The `useWatchedQuerySubscription` hook lets you access the state of an externally managed `WatchedQuery` instance. Managing a query outside of a component enables in-memory caching and sharing of results between multiple subscribers. This reduces async loading time during component mount (thanks to in-memory caching) and minimizes the number of SQLite queries (by sharing results between multiple components). + +```jsx +// The lifecycle of this query is managed outside of any individual React component. +// The data is kept up-to-date in the background and can be shared by multiple subscribers. +const listsQuery = powerSync.query({ sql: 'SELECT * FROM lists' }).differentialWatch(); + +export const ContentComponent = () => { + // Subscribes to the `listsQuery` instance. The subscription is automatically + // cleaned up when the component unmounts. The `data` value always reflects + // the latest state of the query. + const { data: lists } = useWatchedQuerySubscription(listsQuery); + + return ( + + {lists.map((l) => ( + {JSON.stringify(l)} + ))} + + ); +}; +``` diff --git a/packages/react/package.json b/packages/react/package.json index 475b52933..2c7cfcdb8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -34,10 +34,13 @@ }, "devDependencies": { "@powersync/common": "workspace:*", + "@powersync/web": "workspace:*", "@testing-library/react": "^15.0.2", "@types/react": "^18.3.1", + "chart.js": "^4.5.0", "jsdom": "^24.0.0", "react": "18.3.1", + "react-dom": "18.3.1", "react-error-boundary": "^4.1.0" } } diff --git a/packages/react/src/QueryStore.ts b/packages/react/src/QueryStore.ts index a1954851e..36e709316 100644 --- a/packages/react/src/QueryStore.ts +++ b/packages/react/src/QueryStore.ts @@ -1,32 +1,69 @@ -import { AbstractPowerSyncDatabase } from '@powersync/common'; -import { Query, WatchedQuery } from './WatchedQuery'; -import { AdditionalOptions } from './hooks/useQuery'; +import { + AbstractPowerSyncDatabase, + WatchCompatibleQuery, + WatchedQuery, + WatchedQueryListenerEvent +} from '@powersync/common'; +import { DifferentialHookOptions } from './hooks/watched/watch-types'; -export function generateQueryKey(sqlStatement: string, parameters: any[], options: AdditionalOptions): string { +export function generateQueryKey( + sqlStatement: string, + parameters: ReadonlyArray, + options: DifferentialHookOptions +): string { return `${sqlStatement} -- ${JSON.stringify(parameters)} -- ${JSON.stringify(options)}`; } export class QueryStore { - cache = new Map(); + cache = new Map>(); constructor(private db: AbstractPowerSyncDatabase) {} - getQuery(key: string, query: Query, options: AdditionalOptions) { + getQuery(key: string, query: WatchCompatibleQuery, options: DifferentialHookOptions) { if (this.cache.has(key)) { - return this.cache.get(key); + return this.cache.get(key) as WatchedQuery; } - const q = new WatchedQuery(this.db, query, options); - const disposer = q.registerListener({ - disposed: () => { + const watch = options.rowComparator + ? this.db.customQuery(query).differentialWatch({ + rowComparator: options.rowComparator, + reportFetching: options.reportFetching, + throttleMs: options.throttleMs + }) + : this.db.customQuery(query).watch({ + reportFetching: options.reportFetching, + throttleMs: options.throttleMs + }); + + this.cache.set(key, watch); + + const disposer = watch.registerListener({ + closed: () => { this.cache.delete(key); disposer?.(); } }); - this.cache.set(key, q); + watch.listenerMeta.registerListener({ + listenersChanged: (counts) => { + // Dispose this query if there are no subscribers present + // We don't use the total here since we don't want to consider `onclose` listeners + const relevantCounts = [ + WatchedQueryListenerEvent.ON_DATA, + WatchedQueryListenerEvent.ON_STATE_CHANGE, + WatchedQueryListenerEvent.ON_ERROR + ].reduce((sum, event) => { + return sum + (counts[event] || 0); + }, 0); + + if (relevantCounts == 0) { + watch.close(); + this.cache.delete(key); + } + } + }); - return q; + return watch; } } diff --git a/packages/react/src/WatchedQuery.ts b/packages/react/src/WatchedQuery.ts deleted file mode 100644 index 250f91d6d..000000000 --- a/packages/react/src/WatchedQuery.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { - AbstractPowerSyncDatabase, - BaseListener, - BaseObserver, - CompilableQuery, - Disposable, - runOnSchemaChange -} from '@powersync/common'; -import { AdditionalOptions } from './hooks/useQuery'; - -export class Query { - rawQuery: string | CompilableQuery; - sqlStatement: string; - queryParameters: any[]; -} - -export interface WatchedQueryListener extends BaseListener { - onUpdate: () => void; - disposed: () => void; -} - -export class WatchedQuery extends BaseObserver implements Disposable { - readyPromise: Promise; - isReady: boolean = false; - currentData: any[] | undefined; - currentError: any; - tables: any[] | undefined; - - private temporaryHolds = new Set(); - private controller: AbortController | undefined; - private db: AbstractPowerSyncDatabase; - - private resolveReady: undefined | (() => void); - - readonly query: Query; - readonly options: AdditionalOptions; - - constructor(db: AbstractPowerSyncDatabase, query: Query, options: AdditionalOptions) { - super(); - this.db = db; - this.query = query; - this.options = options; - - this.readyPromise = new Promise((resolve) => { - this.resolveReady = resolve; - }); - } - - get logger() { - return this.db.logger ?? console; - } - - addTemporaryHold() { - const ref = new Object(); - this.temporaryHolds.add(ref); - this.maybeListen(); - - let timeout: any; - const release = () => { - this.temporaryHolds.delete(ref); - if (timeout) { - clearTimeout(timeout); - } - this.maybeDispose(); - }; - - const timeoutRelease = () => { - if (this.isReady || this.controller == null) { - release(); - } else { - // If the query is taking long, keep the temporary hold. - timeout = setTimeout(timeoutRelease, 5_000); - } - }; - - timeout = setTimeout(timeoutRelease, 5_000); - - return release; - } - - registerListener(listener: Partial): () => void { - const disposer = super.registerListener(listener); - - this.maybeListen(); - return () => { - disposer(); - this.maybeDispose(); - }; - } - - private async fetchTables() { - try { - this.tables = await this.db.resolveTables(this.query.sqlStatement, this.query.queryParameters, this.options); - } catch (e) { - this.logger.error('Failed to fetch tables:', e); - this.setError(e); - } - } - - async fetchData() { - try { - const result = - typeof this.query.rawQuery == 'string' - ? await this.db.getAll(this.query.sqlStatement, this.query.queryParameters) - : await this.query.rawQuery.execute(); - - const data = result ?? []; - this.setData(data); - } catch (e) { - this.logger.error('Failed to fetch data:', e); - this.setError(e); - } - } - - private maybeListen() { - if (this.controller != null) { - return; - } - - if (this.onUpdateListenersCount() == 0 && this.temporaryHolds.size == 0) { - return; - } - - const controller = new AbortController(); - this.controller = controller; - - const onError = (error: Error) => { - this.setError(error); - }; - - const watchQuery = async (abortSignal: AbortSignal) => { - await this.fetchTables(); - await this.fetchData(); - - if (!this.options.runQueryOnce) { - this.db.onChangeWithCallback( - { - onChange: async () => { - await this.fetchData(); - }, - onError - }, - { - ...this.options, - signal: abortSignal, - tables: this.tables - } - ); - } - }; - runOnSchemaChange(watchQuery, this.db, { signal: this.controller.signal }); - } - - private setData(results: any[]) { - this.isReady = true; - this.currentData = results; - this.currentError = undefined; - this.resolveReady?.(); - - this.iterateListeners((l) => l.onUpdate?.()); - } - - private setError(error: any) { - this.isReady = true; - this.currentData = undefined; - this.currentError = error; - this.resolveReady?.(); - - this.iterateListeners((l) => l.onUpdate?.()); - } - - private onUpdateListenersCount(): number { - return Array.from(this.listeners).filter((listener) => listener.onUpdate !== undefined).length; - } - - private maybeDispose() { - if (this.onUpdateListenersCount() == 0 && this.temporaryHolds.size == 0) { - this.controller?.abort(); - this.controller = undefined; - this.isReady = false; - this.currentData = undefined; - this.currentError = undefined; - this.dispose(); - - this.readyPromise = new Promise((resolve, reject) => { - this.resolveReady = resolve; - }); - } - } - - async dispose() { - this.iterateAsyncListeners(async (l) => l.disposed?.()); - } -} diff --git a/packages/react/src/hooks/usePowerSyncQuery.ts b/packages/react/src/hooks/deprecated/usePowerSyncQuery.ts similarity index 94% rename from packages/react/src/hooks/usePowerSyncQuery.ts rename to packages/react/src/hooks/deprecated/usePowerSyncQuery.ts index 59feb1d65..9f490274c 100644 --- a/packages/react/src/hooks/usePowerSyncQuery.ts +++ b/packages/react/src/hooks/deprecated/usePowerSyncQuery.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { usePowerSync } from './PowerSyncContext'; +import { usePowerSync } from '../PowerSyncContext'; /** * @deprecated use {@link useQuery} instead. diff --git a/packages/react/src/hooks/usePowerSyncStatus.ts b/packages/react/src/hooks/deprecated/usePowerSyncStatus.ts similarity index 93% rename from packages/react/src/hooks/usePowerSyncStatus.ts rename to packages/react/src/hooks/deprecated/usePowerSyncStatus.ts index 0798db66a..34a3ee91e 100644 --- a/packages/react/src/hooks/usePowerSyncStatus.ts +++ b/packages/react/src/hooks/deprecated/usePowerSyncStatus.ts @@ -1,5 +1,5 @@ import { useContext, useEffect, useState } from 'react'; -import { PowerSyncContext } from './PowerSyncContext'; +import { PowerSyncContext } from '../PowerSyncContext'; /** * @deprecated Use {@link useStatus} instead. diff --git a/packages/react/src/hooks/usePowerSyncWatchedQuery.ts b/packages/react/src/hooks/deprecated/usePowerSyncWatchedQuery.ts similarity index 96% rename from packages/react/src/hooks/usePowerSyncWatchedQuery.ts rename to packages/react/src/hooks/deprecated/usePowerSyncWatchedQuery.ts index 7521f6b8a..581823afc 100644 --- a/packages/react/src/hooks/usePowerSyncWatchedQuery.ts +++ b/packages/react/src/hooks/deprecated/usePowerSyncWatchedQuery.ts @@ -1,6 +1,6 @@ import { SQLWatchOptions } from '@powersync/common'; import React from 'react'; -import { usePowerSync } from './PowerSyncContext'; +import { usePowerSync } from '../PowerSyncContext'; /** * @deprecated use {@link useQuery} instead. diff --git a/packages/react/src/hooks/suspense/SuspenseQueryResult.ts b/packages/react/src/hooks/suspense/SuspenseQueryResult.ts new file mode 100644 index 000000000..d8b0f7b7e --- /dev/null +++ b/packages/react/src/hooks/suspense/SuspenseQueryResult.ts @@ -0,0 +1,4 @@ +import { QueryResult, ReadonlyQueryResult } from '../watched/watch-types'; + +export type SuspenseQueryResult = Pick, 'data' | 'refresh'>; +export type ReadonlySuspenseQueryResult = Pick, 'data' | 'refresh'>; diff --git a/packages/react/src/hooks/suspense/suspense-utils.ts b/packages/react/src/hooks/suspense/suspense-utils.ts new file mode 100644 index 000000000..f80621587 --- /dev/null +++ b/packages/react/src/hooks/suspense/suspense-utils.ts @@ -0,0 +1,90 @@ +import { WatchedQuery } from '@powersync/common'; +import React from 'react'; + +/** + * The store will dispose this query if it has no subscribers attached to it. + * The suspense promise adds a subscriber to the query, but the promise could resolve + * before this component is committed. The promise will release it's listener once the query is no longer loading. + * This temporary hold is used to ensure that the query is not disposed in the interim. + * Creates a subscription for state change which creates a temporary hold on the query + * @returns a function to release the hold + */ +export const useTemporaryHold = (watchedQuery?: WatchedQuery) => { + const releaseTemporaryHold = React.useRef<(() => void) | undefined>(undefined); + const addedHoldTo = React.useRef | undefined>(undefined); + + if (addedHoldTo.current !== watchedQuery) { + releaseTemporaryHold.current?.(); + addedHoldTo.current = watchedQuery; + + if (!watchedQuery || !watchedQuery.state.isLoading) { + // No query to hold or no reason to hold, return a no-op + return { + releaseHold: () => {} + }; + } + + const disposeSubscription = watchedQuery.registerListener({ + onStateChange: (state) => {} + }); + + let timeout: ReturnType; + + const disposeClosedListener = watchedQuery.registerListener({ + closed: () => { + if (timeout) { + clearTimeout(timeout); + } + disposeClosedListener(); + } + }); + + const releaseHold = () => { + disposeSubscription(); + disposeClosedListener(); + }; + releaseTemporaryHold.current = releaseHold; + + const timeoutPollMs = 5_000; + + const checkHold = () => { + if (watchedQuery.closed || !watchedQuery.state.isLoading || watchedQuery.state.error) { + // No need to keep a temporary hold on this query + releaseHold(); + } else { + // Need to keep the hold, check again after timeout + setTimeout(checkHold, timeoutPollMs); + } + }; + + // Set a timeout to conditionally remove the temporary hold + setTimeout(checkHold, timeoutPollMs); + } + + return { + releaseHold: releaseTemporaryHold.current + }; +}; + +/** + * React suspense relies on a promise that resolves once the initial data has loaded. + * This creates a promise which registers a listener on the watched query. + * Registering a listener on the watched query will ensure that the query is not disposed + * while the component is suspended. + */ +export const createSuspendingPromise = (query: WatchedQuery) => { + return new Promise((resolve) => { + // The listener here will dispose itself once the loading is done + // This decreases the number of listeners on the query + // even if the component is unmounted + const dispose = query.registerListener({ + onStateChange: (state) => { + // Returns to the hook if loading is completed or if loading resulted in an error + if (!state.isLoading || state.error) { + resolve(); + dispose(); + } + } + }); + }); +}; diff --git a/packages/react/src/hooks/suspense/useSingleSuspenseQuery.ts b/packages/react/src/hooks/suspense/useSingleSuspenseQuery.ts new file mode 100644 index 000000000..10adf3e4e --- /dev/null +++ b/packages/react/src/hooks/suspense/useSingleSuspenseQuery.ts @@ -0,0 +1,85 @@ +import { CompilableQuery, WatchedQuery } from '@powersync/common'; +import React from 'react'; +import { generateQueryKey, getQueryStore } from '../../QueryStore'; +import { usePowerSync } from '../PowerSyncContext'; +import { AdditionalOptions } from '../watched/watch-types'; +import { constructCompatibleQuery } from '../watched/watch-utils'; +import { createSuspendingPromise, useTemporaryHold } from './suspense-utils'; +import { SuspenseQueryResult } from './SuspenseQueryResult'; + +/** + * Use a query which is not watched, but suspends until the initial result has loaded. + * Internally this uses a WatchedQuery during suspense for state management. The watched + * query is potentially disposed, if there are no subscribers attached to it, after the initial load. + * The query can be refreshed by calling the `refresh` function after initial load. + */ +export const useSingleSuspenseQuery = ( + query: string | CompilableQuery, + parameters: any[] = [], + options: AdditionalOptions = {} +): SuspenseQueryResult => { + const powerSync = usePowerSync(); + if (!powerSync) { + throw new Error('PowerSync not configured.'); + } + + // Manually track data for single queries + const [data, setData] = React.useState(null); + const [error, setError] = React.useState(null); + + // Note, we don't need to check if the query changed since we fetch the WatchedQuery + // from the store given these query params + const { parsedQuery } = constructCompatibleQuery(query, parameters, options); + const { sql: parsedSql, parameters: parsedParameters } = parsedQuery.compile(); + + const key = generateQueryKey(parsedSql, parsedParameters, options); + const store = getQueryStore(powerSync); + + // Only use a temporary watched query if we don't have data yet. + const watchedQuery = data ? null : (store.getQuery(key, parsedQuery, options) as WatchedQuery); + const { releaseHold } = useTemporaryHold(watchedQuery); + React.useEffect(() => { + // Set the initial yielded data + // it should be available once we commit the component + if (watchedQuery?.state.error) { + setError(watchedQuery.state.error); + } else if (watchedQuery?.state.isLoading === false) { + setData(watchedQuery.state.data); + setError(null); + } + + if (!watchedQuery?.state.isLoading) { + releaseHold(); + } + }, []); + + if (error != null) { + // Report errors - this is caught by an error boundary + throw error; + } else if (data || watchedQuery?.state.isLoading === false) { + // Happy path data return + return { + data: data ?? watchedQuery?.state.data ?? [], + refresh: async (signal) => { + try { + const compiledQuery = parsedQuery.compile(); + const result = await parsedQuery.execute({ + sql: compiledQuery.sql, + parameters: [...compiledQuery.parameters], + db: powerSync + }); + if (signal.aborted) { + return; // Abort if the signal is already aborted + } + setData(result); + setError(null); + } catch (e) { + setError(e); + } + } + }; + } else { + // Notify suspense is required + throw createSuspendingPromise(watchedQuery!); + } +}; diff --git a/packages/react/src/hooks/suspense/useSuspenseQuery.ts b/packages/react/src/hooks/suspense/useSuspenseQuery.ts new file mode 100644 index 000000000..2a678735b --- /dev/null +++ b/packages/react/src/hooks/suspense/useSuspenseQuery.ts @@ -0,0 +1,76 @@ +import { CompilableQuery } from '@powersync/common'; +import { AdditionalOptions, DifferentialHookOptions } from '../watched/watch-types'; +import { ReadonlySuspenseQueryResult, SuspenseQueryResult } from './SuspenseQueryResult'; +import { useSingleSuspenseQuery } from './useSingleSuspenseQuery'; +import { useWatchedSuspenseQuery } from './useWatchedSuspenseQuery'; + +/** + * A hook to access the results of a watched query that suspends until the initial result has loaded. + * @example + * export const ContentComponent = () => { + * // The lists array here will be a new Array reference whenever a change to the + * // lists table is made. + * const { data: lists } = useSuspenseQuery('SELECT * from lists'); + * + * return + * {lists.map((l) => ( + * {JSON.stringify(l)} + * ))} + * ; + * } + * + * export const DisplayComponent = () => { + * return ( + * Loading content...}> + * + * + * ); + * } + * + * export const DiffContentComponent = () => { + * // A differential query will emit results when a change to the result set occurs. + * // The internal array object references are maintained for unchanged rows. + * // The returned lists array is read only when a `rowComparator` is provided. + * const { data: lists } = useSuspenseQuery('SELECT * from lists', [], { + * rowComparator: { + * keyBy: (item) => item.id, + * compareBy: (item) => JSON.stringify(item) + * } + * }); + * return + * {lists.map((l) => ( + * {JSON.stringify(l)} + * ))} + * ; + * } + * + * export const DisplayComponent = () => { + * return ( + * Loading content...}> + * + * + * ); + * } + */ +export function useSuspenseQuery( + query: string | CompilableQuery, + parameters?: any[], + options?: AdditionalOptions +): SuspenseQueryResult; +export function useSuspenseQuery( + query: string | CompilableQuery, + paramerers?: any[], + options?: DifferentialHookOptions +): ReadonlySuspenseQueryResult; +export function useSuspenseQuery( + query: string | CompilableQuery, + parameters: any[] = [], + options: AdditionalOptions & DifferentialHookOptions = {} +) { + switch (options?.runQueryOnce) { + case true: + return useSingleSuspenseQuery(query, parameters, options); + default: + return useWatchedSuspenseQuery(query, parameters, options); + } +} diff --git a/packages/react/src/hooks/suspense/useWatchedQuerySuspenseSubscription.ts b/packages/react/src/hooks/suspense/useWatchedQuerySuspenseSubscription.ts new file mode 100644 index 000000000..c56e79fc5 --- /dev/null +++ b/packages/react/src/hooks/suspense/useWatchedQuerySuspenseSubscription.ts @@ -0,0 +1,66 @@ +import { WatchedQuery } from '@powersync/common'; +import React from 'react'; +import { createSuspendingPromise, useTemporaryHold } from './suspense-utils'; + +/** + * A hook to access and subscribe to the results of an existing {@link WatchedQuery}. + * @example + * export const ContentComponent = () => { + * const { data: lists } = useWatchedQuerySuspenseSubscription(listsQuery); + * + * return + * {lists.map((l) => ( + * {JSON.stringify(l)} + * ))} + * ; + * } + * + * export const DisplayComponent = () => { + * return ( + * Loading content...}> + * + * + * ); + * } + */ +export const useWatchedQuerySuspenseSubscription = < + ResultType = unknown, + Query extends WatchedQuery = WatchedQuery +>( + query: Query +): Query['state'] => { + const { releaseHold } = useTemporaryHold(query); + + // Force update state function + const [, setUpdateCounter] = React.useState(0); + + React.useEffect(() => { + // This runs when the component came out of suspense + // This add a permanent hold since a listener has been added to the query + const dispose = query.registerListener({ + onStateChange() { + // Trigger rerender + setUpdateCounter((prev) => prev + 1); + } + }); + + // This runs on the first iteration before the component is suspended + // We should only release the hold once the component is no longer loading + if (!query.state.isLoading) { + releaseHold(); + } + + return dispose; + }, []); + + if (query.state.error != null) { + // Report errors - this is caught by an error boundary + throw query.state.error; + } else if (!query.state.isLoading) { + // Happy path data return + return query.state; + } else { + // Notify suspense is required + throw createSuspendingPromise(query); + } +}; diff --git a/packages/react/src/hooks/suspense/useWatchedSuspenseQuery.ts b/packages/react/src/hooks/suspense/useWatchedSuspenseQuery.ts new file mode 100644 index 000000000..c5171a01d --- /dev/null +++ b/packages/react/src/hooks/suspense/useWatchedSuspenseQuery.ts @@ -0,0 +1,36 @@ +import { CompilableQuery } from '@powersync/common'; +import { generateQueryKey, getQueryStore } from '../../QueryStore'; +import { usePowerSync } from '../PowerSyncContext'; +import { AdditionalOptions } from '../watched/watch-types'; +import { constructCompatibleQuery } from '../watched/watch-utils'; +import { useWatchedQuerySuspenseSubscription } from './useWatchedQuerySuspenseSubscription'; + +/** + * @internal This is not exported in the index.ts + */ +export const useWatchedSuspenseQuery = ( + query: string | CompilableQuery, + parameters: any[] = [], + options: AdditionalOptions = {} +) => { + const powerSync = usePowerSync(); + if (!powerSync) { + throw new Error('PowerSync not configured.'); + } + + // Note, we don't need to check if the query changed since we fetch the WatchedQuery + // from the store given these query params + const { parsedQuery } = constructCompatibleQuery(query, parameters, options); + const { sql: parsedSql, parameters: parsedParameters } = parsedQuery.compile(); + + const key = generateQueryKey(parsedSql, parsedParameters, options); + + // When the component is suspended, all state is discarded. We don't get + // any notification of that. So checkoutQuery reserves a temporary hold + // on the query. + // Once the component "commits", we exchange that for a permanent hold. + const store = getQueryStore(powerSync); + const watchedQuery = store.getQuery(key, parsedQuery, options); + + return useWatchedQuerySuspenseSubscription(watchedQuery); +}; diff --git a/packages/react/src/hooks/useQuery.ts b/packages/react/src/hooks/useQuery.ts deleted file mode 100644 index b7400eaea..000000000 --- a/packages/react/src/hooks/useQuery.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { parseQuery, type CompilableQuery, type ParsedQuery, type SQLWatchOptions } from '@powersync/common'; -import React from 'react'; -import { usePowerSync } from './PowerSyncContext'; - -export interface AdditionalOptions extends Omit { - runQueryOnce?: boolean; -} - -export type QueryResult = { - data: T[]; - /** - * Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs. - */ - isLoading: boolean; - /** - * Indicates whether the query is currently fetching data, is true during the initial load and any time when the query is re-evaluating (useful for large queries). - */ - isFetching: boolean; - error: Error | undefined; - /** - * Function used to run the query again. - */ - refresh?: (signal?: AbortSignal) => Promise; -}; - -/** - * A hook to access the results of a watched query. - * @example - * export const Component = () => { - * const { data: lists } = useQuery('SELECT * from lists'); - * - * return - * {lists.map((l) => ( - * {JSON.stringify(l)} - * ))} - * - * } - */ -export const useQuery = ( - query: string | CompilableQuery, - parameters: any[] = [], - options: AdditionalOptions = { runQueryOnce: false } -): QueryResult => { - const powerSync = usePowerSync(); - const logger = powerSync?.logger ?? console; - if (!powerSync) { - return { isLoading: false, isFetching: false, data: [], error: new Error('PowerSync not configured.') }; - } - - let parsedQuery: ParsedQuery; - try { - parsedQuery = parseQuery(query, parameters); - } catch (error) { - logger.error('Failed to parse query:', error); - return { isLoading: false, isFetching: false, data: [], error }; - } - - const { sqlStatement, parameters: queryParameters } = parsedQuery; - - const [data, setData] = React.useState([]); - const [error, setError] = React.useState(undefined); - const [isLoading, setIsLoading] = React.useState(true); - const [isFetching, setIsFetching] = React.useState(true); - const [tables, setTables] = React.useState([]); - - const memoizedParams = React.useMemo(() => queryParameters, [JSON.stringify(queryParameters)]); - const memoizedOptions = React.useMemo(() => options, [JSON.stringify(options)]); - const abortController = React.useRef(new AbortController()); - - const previousQueryRef = React.useRef({ sqlStatement, memoizedParams }); - - // Indicates that the query will be re-fetched due to a change in the query. - // Used when `isFetching` hasn't been set to true yet due to React execution. - const shouldFetch = React.useMemo( - () => - previousQueryRef.current.sqlStatement !== sqlStatement || - JSON.stringify(previousQueryRef.current.memoizedParams) != JSON.stringify(memoizedParams), - [powerSync, sqlStatement, memoizedParams, isFetching] - ); - - const handleResult = (result: T[]) => { - previousQueryRef.current = { sqlStatement, memoizedParams }; - setData(result); - setIsLoading(false); - setIsFetching(false); - setError(undefined); - }; - - const handleError = (e: Error) => { - previousQueryRef.current = { sqlStatement, memoizedParams }; - setData([]); - setIsLoading(false); - setIsFetching(false); - const wrappedError = new Error('PowerSync failed to fetch data: ' + e.message); - wrappedError.cause = e; - setError(wrappedError); - }; - - const fetchData = async (signal?: AbortSignal) => { - setIsFetching(true); - try { - const result = - typeof query == 'string' ? await powerSync.getAll(sqlStatement, queryParameters) : await query.execute(); - - if (signal?.aborted) { - return; - } - - handleResult(result); - } catch (e) { - logger.error('Failed to fetch data:', e); - handleError(e); - } - }; - - const fetchTables = async (signal?: AbortSignal) => { - try { - const tables = await powerSync.resolveTables(sqlStatement, memoizedParams, memoizedOptions); - - if (signal?.aborted) { - return; - } - - setTables(tables); - } catch (e) { - logger.error('Failed to fetch tables:', e); - handleError(e); - } - }; - - React.useEffect(() => { - const abortController = new AbortController(); - const updateData = async () => { - await fetchTables(abortController.signal); - await fetchData(abortController.signal); - }; - - updateData(); - - const l = powerSync.registerListener({ - schemaChanged: updateData - }); - - return () => { - abortController.abort(); - l?.(); - }; - }, [powerSync, memoizedParams, sqlStatement]); - - React.useEffect(() => { - // Abort any previous watches - abortController.current?.abort(); - abortController.current = new AbortController(); - - if (!options.runQueryOnce) { - powerSync.onChangeWithCallback( - { - onChange: async () => { - await fetchData(abortController.current.signal); - }, - onError(e) { - handleError(e); - } - }, - { - ...options, - signal: abortController.current.signal, - tables - } - ); - } - - return () => { - abortController.current?.abort(); - }; - }, [powerSync, sqlStatement, memoizedParams, memoizedOptions, tables]); - - return { isLoading, isFetching: isFetching || shouldFetch, data, error, refresh: fetchData }; -}; diff --git a/packages/react/src/hooks/useStatus.ts b/packages/react/src/hooks/useStatus.ts index 150767942..90624a173 100644 --- a/packages/react/src/hooks/useStatus.ts +++ b/packages/react/src/hooks/useStatus.ts @@ -1,4 +1,4 @@ -import { usePowerSyncStatus } from './usePowerSyncStatus'; +import { usePowerSyncStatus } from './deprecated/usePowerSyncStatus'; /** * Custom hook that provides access to the current status of PowerSync. @@ -14,4 +14,4 @@ import { usePowerSyncStatus } from './usePowerSyncStatus'; * * }; */ -export const useStatus = usePowerSyncStatus; +export const useStatus = () => usePowerSyncStatus(); diff --git a/packages/react/src/hooks/useSuspenseQuery.ts b/packages/react/src/hooks/useSuspenseQuery.ts deleted file mode 100644 index 87da1fbfa..000000000 --- a/packages/react/src/hooks/useSuspenseQuery.ts +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import { generateQueryKey, getQueryStore } from '../QueryStore'; -import { usePowerSync } from './PowerSyncContext'; -import { CompilableQuery, ParsedQuery, parseQuery } from '@powersync/common'; -import { WatchedQuery } from '../WatchedQuery'; -import { AdditionalOptions, QueryResult } from './useQuery'; - -export type SuspenseQueryResult = Pick, 'data' | 'refresh'>; - -/** - * A hook to access the results of a watched query that suspends until the initial result has loaded. - * @example - * export const ContentComponent = () => { - * const { data: lists } = useSuspenseQuery('SELECT * from lists'); - * - * return - * {lists.map((l) => ( - * {JSON.stringify(l)} - * ))} - * ; - * } - * - * export const DisplayComponent = () => { - * return ( - * Loading content...}> - * - * - * ); - * } - */ -export const useSuspenseQuery = ( - query: string | CompilableQuery, - parameters: any[] = [], - options: AdditionalOptions = {} -): SuspenseQueryResult => { - const powerSync = usePowerSync(); - if (!powerSync) { - throw new Error('PowerSync not configured.'); - } - - let parsedQuery: ParsedQuery; - try { - parsedQuery = parseQuery(query, parameters); - } catch (error) { - throw new Error('Failed to parse query: ' + error.message); - } - const key = generateQueryKey(parsedQuery.sqlStatement, parsedQuery.parameters, options); - - // When the component is suspended, all state is discarded. We don't get - // any notification of that. So checkoutQuery reserves a temporary hold - // on the query. - // Once the component "commits", we exchange that for a permanent hold. - const store = getQueryStore(powerSync); - const q = store.getQuery( - key, - { rawQuery: query, sqlStatement: parsedQuery.sqlStatement, queryParameters: parsedQuery.parameters }, - options - ); - - const addedHoldTo = React.useRef(undefined); - const releaseTemporaryHold = React.useRef<(() => void) | undefined>(undefined); - - if (addedHoldTo.current !== q) { - releaseTemporaryHold.current?.(); - releaseTemporaryHold.current = q.addTemporaryHold(); - addedHoldTo.current = q; - } - - const [_counter, setUpdateCounter] = React.useState(0); - - React.useEffect(() => { - const dispose = q.registerListener({ - onUpdate: () => { - setUpdateCounter((counter) => { - return counter + 1; - }); - } - }); - - releaseTemporaryHold.current?.(); - releaseTemporaryHold.current = undefined; - - return dispose; - }, []); - - if (q.currentError != null) { - throw q.currentError; - } else if (q.currentData != null) { - return { data: q.currentData, refresh: () => q.fetchData() }; - } else { - throw q.readyPromise; - } -}; diff --git a/packages/react/src/hooks/watched/useQuery.ts b/packages/react/src/hooks/watched/useQuery.ts new file mode 100644 index 000000000..48ed06647 --- /dev/null +++ b/packages/react/src/hooks/watched/useQuery.ts @@ -0,0 +1,86 @@ +import { type CompilableQuery } from '@powersync/common'; +import { usePowerSync } from '../PowerSyncContext'; +import { useSingleQuery } from './useSingleQuery'; +import { useWatchedQuery } from './useWatchedQuery'; +import { AdditionalOptions, DifferentialHookOptions, QueryResult, ReadonlyQueryResult } from './watch-types'; +import { constructCompatibleQuery } from './watch-utils'; + +/** + * A hook to access the results of a watched query. + * @example + * + * export const Component = () => { + * // The lists array here will be a new Array reference whenever a change to the + * // lists table is made. + * const { data: lists } = useQuery('SELECT * from lists'); + * + * return + * {lists.map((l) => ( + * {JSON.stringify(l)} + * ))} + * + * } + * + * export const DiffComponent = () => { + * // Providing a `rowComparator` results in the hook using an incremental query under the hood. + * // An incremental query will only emit results when a change to the result set occurs. + * // The internal array object references are maintained for unchanged rows. + * // The returned lists array is read only when a `rowComparator` is provided. + * const { data: lists } = useQuery('SELECT * from lists', [], { + * rowComparator: { + * keyBy: (item) => item.id, + * compareBy: (item) => JSON.stringify(item) + * } + * }); + * + * return + * {lists.map((l) => ( + * {JSON.stringify(l)} + * ))} + * + * } + */ + +export function useQuery( + query: string | CompilableQuery, + parameters?: any[], + options?: AdditionalOptions +): QueryResult; +export function useQuery( + query: string | CompilableQuery, + parameters?: any[], + options?: DifferentialHookOptions +): ReadonlyQueryResult; +export function useQuery( + query: string | CompilableQuery, + parameters: any[] = [], + options: AdditionalOptions & DifferentialHookOptions = {} +) { + const powerSync = usePowerSync(); + if (!powerSync) { + return { isLoading: false, isFetching: false, data: [], error: new Error('PowerSync not configured.') }; + } + const { parsedQuery, queryChanged } = constructCompatibleQuery(query, parameters, options); + + switch (options?.runQueryOnce) { + case true: + return useSingleQuery({ + query: parsedQuery, + powerSync, + queryChanged + }); + default: + return useWatchedQuery({ + query: parsedQuery, + powerSync, + queryChanged, + options: { + reportFetching: options.reportFetching, + // Maintains backwards compatibility with previous versions + // Differentiation is opt-in by default + // We emit new data for each table change by default. + rowComparator: options.rowComparator + } + }); + } +} diff --git a/packages/react/src/hooks/watched/useSingleQuery.ts b/packages/react/src/hooks/watched/useSingleQuery.ts new file mode 100644 index 000000000..f56ec8d15 --- /dev/null +++ b/packages/react/src/hooks/watched/useSingleQuery.ts @@ -0,0 +1,61 @@ +import React from 'react'; +import { QueryResult } from './watch-types'; +import { InternalHookOptions } from './watch-utils'; + +export const useSingleQuery = (options: InternalHookOptions): QueryResult => { + const { query, powerSync, queryChanged } = options; + + const [output, setOutputState] = React.useState>({ + isLoading: true, + isFetching: true, + data: [], + error: undefined + }); + + const runQuery = React.useCallback( + async (signal?: AbortSignal) => { + setOutputState((prev) => ({ ...prev, isLoading: true, isFetching: true, error: undefined })); + try { + const compiledQuery = query.compile(); + const result = await query.execute({ + sql: compiledQuery.sql, + parameters: [...compiledQuery.parameters], + db: powerSync + }); + if (signal.aborted) { + return; + } + setOutputState((prev) => ({ + ...prev, + isLoading: false, + isFetching: false, + data: result, + error: undefined + })); + } catch (error) { + setOutputState((prev) => ({ + ...prev, + isLoading: false, + isFetching: false, + data: [], + error + })); + } + }, + [queryChanged, query] + ); + + // Trigger initial query execution + React.useEffect(() => { + const abortController = new AbortController(); + runQuery(abortController.signal); + return () => { + abortController.abort(); + }; + }, [powerSync, queryChanged]); + + return { + ...output, + refresh: runQuery + }; +}; diff --git a/packages/react/src/hooks/watched/useWatchedQuery.ts b/packages/react/src/hooks/watched/useWatchedQuery.ts new file mode 100644 index 000000000..62a20dfa9 --- /dev/null +++ b/packages/react/src/hooks/watched/useWatchedQuery.ts @@ -0,0 +1,53 @@ +import React from 'react'; +import { useWatchedQuerySubscription } from './useWatchedQuerySubscription'; +import { DifferentialHookOptions, QueryResult, ReadonlyQueryResult } from './watch-types'; +import { InternalHookOptions } from './watch-utils'; + +/** + * @internal This is not exported from the index.ts + * + * When an incremental query is used the return type is readonly. This is required + * since the implementation requires a stable ref. + * For legacy compatibility we allow mutating when a standard query is used. Mutations should + * not affect the internal implementation in this case. + */ +export const useWatchedQuery = ( + options: InternalHookOptions & { options: DifferentialHookOptions } +): QueryResult | ReadonlyQueryResult => { + const { query, powerSync, queryChanged, options: hookOptions } = options; + + const createWatchedQuery = React.useCallback(() => { + const watch = hookOptions.rowComparator + ? powerSync.customQuery(query).differentialWatch({ + rowComparator: hookOptions.rowComparator, + reportFetching: hookOptions.reportFetching, + throttleMs: hookOptions.throttleMs + }) + : powerSync.customQuery(query).watch({ + reportFetching: hookOptions.reportFetching, + throttleMs: hookOptions.throttleMs + }); + return watch; + }, []); + + const [watchedQuery, setWatchedQuery] = React.useState(createWatchedQuery); + + React.useEffect(() => { + watchedQuery.close(); + setWatchedQuery(createWatchedQuery); + }, [powerSync]); + + // Indicates that the query will be re-fetched due to a change in the query. + // Used when `isFetching` hasn't been set to true yet due to React execution. + React.useEffect(() => { + if (queryChanged) { + watchedQuery.updateSettings({ + query, + throttleMs: hookOptions.throttleMs, + reportFetching: hookOptions.reportFetching + }); + } + }, [queryChanged]); + + return useWatchedQuerySubscription(watchedQuery); +}; diff --git a/packages/react/src/hooks/watched/useWatchedQuerySubscription.ts b/packages/react/src/hooks/watched/useWatchedQuerySubscription.ts new file mode 100644 index 000000000..c8cfa9252 --- /dev/null +++ b/packages/react/src/hooks/watched/useWatchedQuerySubscription.ts @@ -0,0 +1,39 @@ +import { WatchedQuery } from '@powersync/common'; +import React from 'react'; + +/** + * A hook to access and subscribe to the results of an existing {@link WatchedQuery} instance. + * @example + * export const ContentComponent = () => { + * const { data: lists } = useWatchedQuerySubscription(listsQuery); + * + * return + * {lists.map((l) => ( + * {JSON.stringify(l)} + * ))} + * ; + * } + * + */ +export const useWatchedQuerySubscription = < + ResultType = unknown, + Query extends WatchedQuery = WatchedQuery +>( + query: Query +): Query['state'] => { + const [output, setOutputState] = React.useState(query.state); + + React.useEffect(() => { + const dispose = query.registerListener({ + onStateChange: (state) => { + setOutputState({ ...state }); + } + }); + + return () => { + dispose(); + }; + }, [query]); + + return output; +}; diff --git a/packages/react/src/hooks/watched/watch-types.ts b/packages/react/src/hooks/watched/watch-types.ts new file mode 100644 index 000000000..d77e904a4 --- /dev/null +++ b/packages/react/src/hooks/watched/watch-types.ts @@ -0,0 +1,67 @@ +import { DifferentialWatchedQueryComparator, SQLOnChangeOptions } from '@powersync/common'; + +export interface HookWatchOptions extends Omit { + reportFetching?: boolean; +} + +export interface AdditionalOptions extends HookWatchOptions { + runQueryOnce?: boolean; +} + +export interface DifferentialHookOptions extends HookWatchOptions { + /** + * Used to compare result sets. + * + * By default the hook will requery on any dependent table change. This will + * emit a new hook result even if the result set has not changed. + * + * Specifying a {@link DifferentialWatchedQueryComparator} will remove emissions for + * unchanged result sets. + * Furthermore, emitted `data` arrays will preserve object references between result set emissions + * for unchanged rows. + * @example + * ```javascript + * { + * rowComparator: { + * keyBy: (item) => item.id, + * compareBy: (item) => JSON.stringify(item) + * } + * } + * ``` + */ + rowComparator?: DifferentialWatchedQueryComparator; +} + +export type ReadonlyQueryResult = { + readonly data: ReadonlyArray>; + /** + * Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs. + */ + readonly isLoading: boolean; + /** + * Indicates whether the query is currently fetching data, is true during the initial load and any time when the query is re-evaluating (useful for large queries). + */ + readonly isFetching: boolean; + readonly error: Error | undefined; + /** + * Function used to run the query again. + */ + refresh?: (signal?: AbortSignal) => Promise; +}; + +export type QueryResult = { + data: RowType[]; + /** + * Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs. + */ + isLoading: boolean; + /** + * Indicates whether the query is currently fetching data, is true during the initial load and any time when the query is re-evaluating (useful for large queries). + */ + isFetching: boolean; + error: Error | undefined; + /** + * Function used to run the query again. + */ + refresh?: (signal?: AbortSignal) => Promise; +}; diff --git a/packages/react/src/hooks/watched/watch-utils.ts b/packages/react/src/hooks/watched/watch-utils.ts new file mode 100644 index 000000000..fd72b2394 --- /dev/null +++ b/packages/react/src/hooks/watched/watch-utils.ts @@ -0,0 +1,78 @@ +import { AbstractPowerSyncDatabase, CompilableQuery, CompiledQuery, WatchCompatibleQuery } from '@powersync/common'; +import React from 'react'; +import { usePowerSync } from '../PowerSyncContext'; +import { AdditionalOptions } from './watch-types'; + +export type InternalHookOptions = { + query: WatchCompatibleQuery; + powerSync: AbstractPowerSyncDatabase; + queryChanged: boolean; +}; + +export const checkQueryChanged = (query: WatchCompatibleQuery, options: AdditionalOptions) => { + let _compiled: CompiledQuery; + try { + _compiled = query.compile(); + } catch (error) { + return false; // If compilation fails, we assume the query has changed + } + const compiled = _compiled!; + + const stringifiedParams = JSON.stringify(compiled.parameters); + const stringifiedOptions = JSON.stringify(options); + + const previousQueryRef = React.useRef({ sqlStatement: compiled.sql, stringifiedParams, stringifiedOptions }); + + if ( + previousQueryRef.current.sqlStatement !== compiled.sql || + previousQueryRef.current.stringifiedParams != stringifiedParams || + previousQueryRef.current.stringifiedOptions != stringifiedOptions + ) { + previousQueryRef.current.sqlStatement = compiled.sql; + previousQueryRef.current.stringifiedParams = stringifiedParams; + previousQueryRef.current.stringifiedOptions = stringifiedOptions; + + return true; + } + + return false; +}; + +export const constructCompatibleQuery = ( + query: string | CompilableQuery, + parameters: any[] = [], + options: AdditionalOptions +) => { + const powerSync = usePowerSync(); + + const parsedQuery = React.useMemo>(() => { + if (typeof query == 'string') { + return { + compile: () => ({ + sql: query, + parameters + }), + execute: () => powerSync.getAll(query, parameters) + }; + } else { + return { + // Generics differ a bit but holistically this is the same + compile: () => { + const compiled = query.compile(); + return { + sql: compiled.sql, + parameters: [...compiled.parameters] + }; + }, + execute: () => query.execute() + }; + } + }, [query, powerSync]); + + const queryChanged = checkQueryChanged(parsedQuery, options); + + return { + parsedQuery, + queryChanged + }; +}; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 6199b10ea..6bdc019aa 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,7 +1,11 @@ +export { usePowerSyncQuery } from './hooks/deprecated/usePowerSyncQuery'; +export { usePowerSyncStatus } from './hooks/deprecated/usePowerSyncStatus'; +export { usePowerSyncWatchedQuery } from './hooks/deprecated/usePowerSyncWatchedQuery'; export * from './hooks/PowerSyncContext'; -export { usePowerSyncQuery } from './hooks/usePowerSyncQuery'; +export { SuspenseQueryResult } from './hooks/suspense/SuspenseQueryResult'; +export { useSuspenseQuery } from './hooks/suspense/useSuspenseQuery'; +export { useWatchedQuerySuspenseSubscription } from './hooks/suspense/useWatchedQuerySuspenseSubscription'; export { useStatus } from './hooks/useStatus'; -export { useQuery } from './hooks/useQuery'; -export { useSuspenseQuery } from './hooks/useSuspenseQuery'; -export { usePowerSyncWatchedQuery } from './hooks/usePowerSyncWatchedQuery'; -export { usePowerSyncStatus } from './hooks/usePowerSyncStatus'; +export { useQuery } from './hooks/watched/useQuery'; +export { useWatchedQuerySubscription } from './hooks/watched/useWatchedQuerySubscription'; +export { AdditionalOptions } from './hooks/watched/watch-types'; diff --git a/packages/react/tests/QueryStore.test.tsx b/packages/react/tests/QueryStore.test.tsx index 201d458e1..b02e65e7e 100644 --- a/packages/react/tests/QueryStore.test.tsx +++ b/packages/react/tests/QueryStore.test.tsx @@ -1,6 +1,7 @@ +import { AbstractPowerSyncDatabase, SQLWatchOptions } from '@powersync/common'; import { beforeEach, describe, expect, it } from 'vitest'; import { generateQueryKey, getQueryStore, QueryStore } from '../src/QueryStore'; -import { AbstractPowerSyncDatabase, SQLWatchOptions } from '@powersync/common'; +import { openPowerSync } from './useQuery.test'; describe('QueryStore', () => { describe('generateQueryKey', () => { @@ -46,7 +47,7 @@ describe('QueryStore', () => { let options: SQLWatchOptions; beforeEach(() => { - db = createMockDatabase(); + db = openPowerSync(); store = new QueryStore(db); query = {}; options = {}; diff --git a/packages/react/tests/profile.test.tsx b/packages/react/tests/profile.test.tsx new file mode 100644 index 000000000..0c060751f --- /dev/null +++ b/packages/react/tests/profile.test.tsx @@ -0,0 +1,498 @@ +import * as commonSdk from '@powersync/common'; +import { PowerSyncDatabase } from '@powersync/web'; +import { Chart } from 'chart.js/auto'; +import React, { Profiler } from 'react'; +import ReactDOM from 'react-dom/client'; +import { beforeEach, describe, it, Mock, onTestFinished, vi } from 'vitest'; +import { PowerSyncContext } from '../src/hooks/PowerSyncContext'; +import { useQuery } from '../src/hooks/watched/useQuery'; +import { useWatchedQuerySubscription } from '../src/hooks/watched/useWatchedQuerySubscription'; + +let skipTests = true; +/** + * This does not run as part of all tests. Enable this suite manually to run performance tests. + * + * The tests here compare the time taken to render a list of items under different watched query modes. + * The Tests render a list of Items supplied from a Watched Query. + * + * Methodology: + * In all tests we start with an initial set of items then add a new item. + * The render time for the list widget is measured for each insert. + * + * Each watched query mode is tested with and without memoization of the components. + * + * The standard watch query mode returns new object references for each change. This means that the + * entire widget will render each time a new item is added - even if memoization is used. + * + * The differential watch mode will return previous object references for unchanged items. This can reduce the render time, + * but only if memoization is used. The time taken to process the differential changes is also measured, to make a fair comparison, + * the differential processing time is added to the render time for each insert. + * + * Initial data set volume is sweeped over a range of values. A memoized differential watch query should only render new items on insert. + * It is expected that render times will increase for regular watch queries as the initial data set volume increases. + */ +const AppSchema = new commonSdk.Schema({ + lists: new commonSdk.Table({ + name: commonSdk.column.text, + description: commonSdk.column.text, + items: commonSdk.column.integer + }) +}); + +type List = (typeof AppSchema)['types']['lists']; + +export const openPowerSync = () => { + const db = new PowerSyncDatabase({ + database: { dbFilename: 'test.db' }, + schema: AppSchema + }); + + onTestFinished(async () => { + await db.disconnectAndClear(); + await db.close(); + }); + + return db; +}; + +const TestWidget: React.FC<{ + getData: () => ReadonlyArray; + memoize: boolean; +}> = (props) => { + const data = props.getData(); + return ( +
+ {props.memoize + ? data.map((item) => ) + : data.map((item) => )} +
+ ); +}; + +const TestItemWidget: React.FC<{ item: List }> = (props) => { + const { item } = props; + return ( +
+
{item.id}
+
{item.name}
+
{item.description}
+
{item.items}
+
+ ); +}; + +const TestItemMemoized = React.memo(TestItemWidget); + +type InsertTestResult = { + initialRenderDuration: number; + renderDurations: number[]; + averageAdditionalRenderDuration: number; +}; + +/** + * Runs a single insert test for an amount of initial data and then inserts a number of items + * and measures the render time for each insert. + * Uses the data hook provided for rendering. + */ +const testInserts = async (options: { + db: commonSdk.AbstractPowerSyncDatabase; + getQueryData: () => ReadonlyArray; + useMemoize: boolean; + initialDataCount: number; + incrementalInsertsCount: number; +}): Promise => { + const { db, getQueryData, useMemoize, initialDataCount, incrementalInsertsCount } = options; + + const result: InsertTestResult = { + initialRenderDuration: 0, + renderDurations: [], + averageAdditionalRenderDuration: 0 + }; + + const container = document.createElement('div'); + document.body.appendChild(container); + const root = ReactDOM.createRoot(container); + let cleanupCompleted = false; + const cleanup = () => { + if (cleanupCompleted) return; + root.unmount(); + document.body.removeChild(container); + cleanupCompleted = true; + }; + onTestFinished(() => { + cleanup(); + }); + + /** + * The ordering of items by their numerically increasing name can cause items to be rendered in a different order + * React does not seem to efficiently handle rows being added in the middle of the list. + * This test tests if new items are sorted. + * We pad the name for correct sorting. + */ + const padWidth = Math.ceil((initialDataCount + incrementalInsertsCount) / 10); + const padName = (number: number) => number.toString().padStart(padWidth, '0'); + + const onRender: Mock = vi.fn(() => {}); + const getDataSpy = vi.fn(getQueryData); + const { benchmarkId } = await db.get<{ benchmarkId: string }>('select uuid() as benchmarkId'); + + root.render( + + + + + + ); + + // Create initial data + await db.writeTransaction(async (tx) => { + for (let i = 0; i < initialDataCount; i++) { + await tx.execute(/* sql */ ` + INSERT INTO + lists (id, name, description) + VALUES + ( + uuid (), + '${padName(i)}', + hex (randomblob (30)) + ) + `); + } + }); + + // The initial data should have been rendered after this returns correctly + await vi.waitFor( + () => { + expect(getDataSpy.mock.results.find((r) => r.value.length === initialDataCount)).toBeDefined(); + }, + { timeout: 100, interval: 10 } + ); + + // Get the last render time for update + const getLastUpdateProfile = () => [...onRender.mock.calls].reverse().find((call) => call[1] == 'update'); + const initialRenderProfile = getLastUpdateProfile(); + const initialRenderDuration = initialRenderProfile?.[2]; + + result.initialRenderDuration = initialRenderDuration ?? 0; + + const count = onRender.mock.calls.length; + for (let renderTestCount = 0; renderTestCount < incrementalInsertsCount; renderTestCount++) { + // Create a single item + await db.execute(/* sql */ ` + INSERT INTO + lists (id, name, description) + VALUES + ( + uuid (), + '${padName(initialDataCount + renderTestCount)}', + hex (randomblob (30)) + ) + `); + + // Wait for this change to be reflected in the UI + await vi.waitFor( + () => { + expect(getDataSpy.mock.results.find((r) => r.value.length == initialDataCount + renderTestCount)).toBeDefined(); + expect(onRender.mock.calls.length).toBe(count + 1 + renderTestCount); + }, + { + timeout: 1000, + interval: 10 + } + ); + const profile = getLastUpdateProfile(); + const duration = profile?.[2]; + if (duration != null) { + result.renderDurations.push(duration); + } else { + throw `No duration found for render ${renderTestCount + 1}`; + } + } + + cleanup(); + + result.averageAdditionalRenderDuration = + result.renderDurations.reduce((sum, duration) => sum + duration, 0) / result.renderDurations.length; + return result; +}; + +type DifferentialInsertTestResult = InsertTestResult & { + /** + * Represents the duration of the render, not including the differential processing. + * We add the differential processing time to the render time for comparison.s + */ + pureRenderDurations: number[]; +}; + +type TestsInsertsCompareResult = { + regular: InsertTestResult; + regularMemoized: InsertTestResult; + differential: DifferentialInsertTestResult; + differentialMemoized: DifferentialInsertTestResult; +}; + +const testsInsertsCompare = async (options: { + db: commonSdk.AbstractPowerSyncDatabase; + initialDataCount: number; + incrementalInsertsCount: number; +}) => { + const { db, incrementalInsertsCount, initialDataCount } = options; + const result: Partial = {}; + // Testing Regular Queries Without Memoization + result.regular = await testInserts({ + db, + incrementalInsertsCount, + initialDataCount, + useMemoize: false, + getQueryData: () => { + const { data } = useQuery('SELECT * FROM lists ORDER BY name ASC;', [], { + reportFetching: false + }); + return data; + } + }); + + // Testing Regular Queries Without Memoization + await db.execute('DELETE FROM lists;'); + result.regularMemoized = await testInserts({ + db, + incrementalInsertsCount, + initialDataCount, + useMemoize: true, + getQueryData: () => { + const { data } = useQuery('SELECT * FROM lists ORDER BY name ASC;', [], { + reportFetching: false + }); + return data; + } + }); + + // Testing Differential Updates + + const diffSpy = (query: commonSdk.WatchedQuery, outputTimes: number[]) => { + const base = (query as any).differentiate; + vi.spyOn(query as any, 'differentiate').mockImplementation((...params: any[]) => { + const start = performance.now(); + const result = base.apply(query, params); + const time = performance.now() - start; + outputTimes.push(time); + return result; + }); + }; + + const notMemoizedDifferentialTest = async () => { + await db.execute('DELETE FROM lists;'); + + const query = db + .query({ + sql: 'SELECT * FROM lists ORDER BY name ASC;' + }) + .differentialWatch({ + reportFetching: false + }); + + const times: number[] = []; + diffSpy(query, times); + + const baseResult = await testInserts({ + db, + incrementalInsertsCount, + initialDataCount, + useMemoize: false, + getQueryData: () => { + const { data } = useWatchedQuerySubscription(query); + return [...data]; + } + }); + + const renderDurations = baseResult.renderDurations.map((d, i) => d + (times[i] ?? 0)); + const averageAdditionalRenderDuration = + renderDurations.reduce((sum, duration) => sum + duration, 0) / renderDurations.length; + result.differential = { + ...baseResult, + pureRenderDurations: baseResult.renderDurations, + renderDurations, + averageAdditionalRenderDuration + }; + + await query.close(); + }; + await notMemoizedDifferentialTest(); + + // Testing Differential With Memoization + await db.execute('DELETE FROM lists;'); + + const query = db.query({ sql: 'SELECT * FROM lists ORDER BY name ASC;' }).differentialWatch({ + reportFetching: false + }); + + const times: number[] = []; + diffSpy(query, times); + + const baseResult = await testInserts({ + db, + incrementalInsertsCount, + initialDataCount, + useMemoize: true, + getQueryData: () => { + const { data } = useWatchedQuerySubscription(query); + return [...data]; + } + }); + + const renderDurations = baseResult.renderDurations.map((d, i) => d + (times[i] ?? 0)); + const averageAdditionalRenderDuration = + renderDurations.reduce((sum, duration) => sum + duration, 0) / renderDurations.length; + result.differentialMemoized = { + ...baseResult, + pureRenderDurations: baseResult.renderDurations, + renderDurations, + averageAdditionalRenderDuration + }; + + await query.close(); + + await db.execute('DELETE FROM lists;'); + + return result as TestsInsertsCompareResult; +}; + +describe.skipIf(skipTests)('Performance', { timeout: Infinity }, () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('Benchmark', async () => { + const db = openPowerSync(); + // const initialDataCount = 10; + const initialDataVolumeSteps = new Array(10).fill(0).map((_, i) => (i + 1) * 10); + const incrementalInsertsCount = 10; + const redoTestCount = 5; + + const totalResults: any[] = []; + + for (const initialDataCount of initialDataVolumeSteps) { + const results: TestsInsertsCompareResult[] = []; + for (let i = 0; i < redoTestCount; i++) { + console.log(`Running test for initial data count: ${initialDataCount}, iteration: ${i + 1} / ${redoTestCount}`); + // Run the test for the current initial data count + const result = await testsInsertsCompare({ + db, + initialDataCount, + incrementalInsertsCount + }); + results.push(result); + } + + // Average the individual averages over each iteration + const averageResult = { + initialDataCount, + regular: + results.reduce((acc, r) => { + return acc + r.regular.averageAdditionalRenderDuration; + }, 0) / redoTestCount, + regularMemoized: + results.reduce((acc, r) => { + return acc + r.regularMemoized.averageAdditionalRenderDuration; + }, 0) / redoTestCount, + differential: + results.reduce((acc, r) => { + return acc + r.differential.averageAdditionalRenderDuration; + }, 0) / redoTestCount, + differentialMemoized: + results.reduce((acc, r) => { + return acc + r.differentialMemoized.averageAdditionalRenderDuration; + }, 0) / redoTestCount, + differentialMemoImprovementPercentage: 0 + }; + + averageResult.differentialMemoImprovementPercentage = + ((averageResult.regular - averageResult.differentialMemoized) / averageResult.regular) * 100; + + totalResults.push(averageResult); + } + + // Unfortunately vitest browser mode does not support console.table + // This can be viewed if in the browser console. + console.table(totalResults); + + // CSV log + console.log(Object.keys(totalResults[0]).join(',')); + totalResults.forEach((r) => { + console.log(Object.values(r).join(',')); + }); + + // Make a nice chart, these are visible when running tests with a visible browser `headless: false` + const chartCanvas = document.createElement('canvas'); + document.body.appendChild(chartCanvas); + + // Chart the Average incremental render times + const testTypes = new Set(Object.keys(totalResults[0])); + // Don't show this on this chart + testTypes.delete('differentialMemoImprovementPercentage'); + testTypes.delete('initialDataCount'); + new Chart(chartCanvas, { + type: 'line', + data: { + labels: initialDataVolumeSteps, + datasets: Array.from(testTypes).map((resultType) => { + return { + label: resultType, + data: totalResults.map((r) => r[resultType]) + }; + }) + }, + options: { + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Average incremental render time (ms)' + } + }, + x: { + title: { + display: true, + text: 'Initial count of items' + } + } + } + } + }); + + const percentCanvas = document.createElement('canvas'); + document.body.appendChild(percentCanvas); + + // Chart the Average incremental render times + new Chart(percentCanvas, { + type: 'line', + data: { + labels: initialDataVolumeSteps, + datasets: [ + { + label: 'Percentage decrease of render time for Differential Memoized', + data: totalResults.map((r) => r.differentialMemoImprovementPercentage) + } + ] + }, + options: { + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Average incremental render time (ms)' + } + }, + x: { + title: { + display: true, + text: 'Initial count of items' + } + } + } + } + }); + }); +}); diff --git a/packages/react/tests/useQuery.test.tsx b/packages/react/tests/useQuery.test.tsx index b92bbaa38..297e1d21c 100644 --- a/packages/react/tests/useQuery.test.tsx +++ b/packages/react/tests/useQuery.test.tsx @@ -1,21 +1,30 @@ import * as commonSdk from '@powersync/common'; -import { cleanup, renderHook, waitFor } from '@testing-library/react'; +import { PowerSyncDatabase } from '@powersync/web'; +import { act, cleanup, renderHook, waitFor } from '@testing-library/react'; +import pDefer from 'p-defer'; import React from 'react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'; import { PowerSyncContext } from '../src/hooks/PowerSyncContext'; -import { useQuery } from '../src/hooks/useQuery'; - -const mockPowerSync = { - currentStatus: { status: 'initial' }, - registerListener: vi.fn(() => {}), - resolveTables: vi.fn(() => ['table1', 'table2']), - onChangeWithCallback: vi.fn(), - getAll: vi.fn(() => Promise.resolve(['list1', 'list2'])) -}; +import { useQuery } from '../src/hooks/watched/useQuery'; +import { useWatchedQuerySubscription } from '../src/hooks/watched/useWatchedQuerySubscription'; + +export const openPowerSync = () => { + const db = new PowerSyncDatabase({ + database: { dbFilename: 'test.db' }, + schema: new commonSdk.Schema({ + lists: new commonSdk.Table({ + name: commonSdk.column.text + }) + }) + }); -vi.mock('./PowerSyncContext', () => ({ - useContext: vi.fn(() => mockPowerSync) -})); + onTestFinished(async () => { + await db.disconnectAndClear(); + await db.close(); + }); + + return db; +}; describe('useQuery', () => { beforeEach(() => { @@ -33,7 +42,10 @@ describe('useQuery', () => { it('should set isLoading to true on initial load', async () => { const wrapper = ({ children }) => ( - {children} + // Placeholder use for `React` to prevent import cleanup from removing the React import + + {children} + ); const { result } = renderHook(() => useQuery('SELECT * from lists'), { wrapper }); @@ -42,30 +54,35 @@ describe('useQuery', () => { }); it('should run the query once if runQueryOnce flag is set', async () => { - const wrapper = ({ children }) => ( - {children} - ); + const db = openPowerSync(); + const onChangeSpy = vi.spyOn(db, 'onChangeWithCallback'); + const getAllSpy = vi.spyOn(db, 'getAll'); - const { result } = renderHook(() => useQuery('SELECT * from lists', [], { runQueryOnce: true }), { wrapper }); + const wrapper = ({ children }) => {children}; + + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['list1']); + + const { result } = renderHook(() => useQuery('SELECT name from lists', [], { runQueryOnce: true }), { wrapper }); expect(result.current.isLoading).toEqual(true); await waitFor( async () => { const currentResult = result.current; - expect(currentResult.data).toEqual(['list1', 'list2']); + expect(currentResult.data).toEqual([{ name: 'list1' }]); expect(currentResult.isLoading).toEqual(false); expect(currentResult.isFetching).toEqual(false); - expect(mockPowerSync.onChangeWithCallback).not.toHaveBeenCalled(); - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(1); + expect(onChangeSpy).not.toHaveBeenCalled(); + expect(getAllSpy).toHaveBeenCalledTimes(1); }, - { timeout: 100 } + { timeout: 1000 } ); }); it('should rerun the query when refresh is used', async () => { - const wrapper = ({ children }) => ( - {children} - ); + const db = openPowerSync(); + const getAllSpy = vi.spyOn(db, 'getAll'); + + const wrapper = ({ children }) => {children}; const { result } = renderHook(() => useQuery('SELECT * from lists', [], { runQueryOnce: true }), { wrapper }); @@ -74,73 +91,54 @@ describe('useQuery', () => { let refresh; await waitFor( - async () => { + () => { const currentResult = result.current; refresh = currentResult.refresh; expect(currentResult.isLoading).toEqual(false); - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(1); + expect(getAllSpy).toHaveBeenCalledTimes(1); }, - { timeout: 100 } + { timeout: 500, interval: 100 } ); - await refresh(); - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(2); + await act(() => refresh()); + + expect(getAllSpy).toHaveBeenCalledTimes(2); }); it('should set error when error occurs and runQueryOnce flag is set', async () => { - const mockPowerSyncError = { - currentStatus: { status: 'initial' }, - registerListener: vi.fn(() => {}), - onChangeWithCallback: vi.fn(), - resolveTables: vi.fn(() => ['table1', 'table2']), - getAll: vi.fn(() => { - throw new Error('some error'); - }) - }; + const db = openPowerSync(); - const wrapper = ({ children }) => ( - {children} - ); + const wrapper = ({ children }) => {children}; - const { result } = renderHook(() => useQuery('SELECT * from lists', [], { runQueryOnce: true }), { wrapper }); + const { result } = renderHook(() => useQuery('SELECT * from faketable', [], { runQueryOnce: true }), { wrapper }); await waitFor( async () => { - expect(result.current.error?.message).equal('PowerSync failed to fetch data: some error'); + expect(result.current.error?.message).equal('no such table: faketable'); }, - { timeout: 100 } + { timeout: 500, interval: 100 } ); }); - it('should set error when error occurs', async () => { - const mockPowerSyncError = { - currentStatus: { status: 'initial' }, - registerListener: vi.fn(() => {}), - onChangeWithCallback: vi.fn(), - resolveTables: vi.fn(() => ['table1', 'table2']), - getAll: vi.fn(() => { - throw new Error('some error'); - }) - }; + it('should set error when error occurs with watched query', async () => { + const db = openPowerSync(); - const wrapper = ({ children }) => ( - {children} - ); + const wrapper = ({ children }) => {children}; - const { result } = renderHook(() => useQuery('SELECT * from lists', []), { wrapper }); + const { result } = renderHook(() => useQuery('SELECT * from faketable', []), { wrapper }); await waitFor( async () => { - expect(result.current.error?.message).equals('PowerSync failed to fetch data: some error'); + expect(result.current.error?.message).equals('no such table: faketable'); }, - { timeout: 100 } + { timeout: 500, interval: 100 } ); }); it('should accept compilable queries', async () => { - const wrapper = ({ children }) => ( - {children} - ); + const db = openPowerSync(); + + const wrapper = ({ children }) => {children}; const { result } = renderHook( () => useQuery({ execute: () => [] as any, compile: () => ({ sql: 'SELECT * from lists', parameters: [] }) }), @@ -151,9 +149,9 @@ describe('useQuery', () => { }); it('should execute compatible queries', async () => { - const wrapper = ({ children }) => ( - {children} - ); + const db = openPowerSync(); + + const wrapper = ({ children }) => {children}; const query = () => useQuery({ @@ -162,24 +160,26 @@ describe('useQuery', () => { }); const { result } = renderHook(query, { wrapper }); - await vi.waitFor(() => { - expect(result.current.data[0]?.test).toEqual('custom'); - }); + await vi.waitFor( + () => { + expect(result.current.data[0]?.test).toEqual('custom'); + }, + { timeout: 500, interval: 100 } + ); }); it('should show an error if parsing the query results in an error', async () => { - const wrapper = ({ children }) => ( - {children} - ); - vi.spyOn(commonSdk, 'parseQuery').mockImplementation(() => { - throw new Error('error'); - }); + const db = openPowerSync(); + + const wrapper = ({ children }) => {children}; const { result } = renderHook( () => useQuery({ execute: () => [] as any, - compile: () => ({ sql: 'SELECT * from lists', parameters: ['param'] }) + compile: () => { + throw new Error('error'); + } }), { wrapper } ); @@ -192,9 +192,162 @@ describe('useQuery', () => { expect(currentResult.data).toEqual([]); expect(currentResult.error).toEqual(Error('error')); }, - { timeout: 100 } + { timeout: 500, interval: 100 } ); }); - // TODO: Add tests for powersync.onChangeWithCallback path + it('should emit result data when query changes', async () => { + const db = openPowerSync(); + const wrapper = ({ children }) => {children}; + const { result } = renderHook( + () => + useQuery('SELECT * FROM lists WHERE name = ?', ['aname'], { + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } + }), + { wrapper } + ); + + expect(result.current.isLoading).toEqual(true); + + await waitFor( + async () => { + const { current } = result; + expect(current.isLoading).toEqual(false); + }, + { timeout: 500, interval: 100 } + ); + + // This should trigger an update + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['aname']); + + await waitFor( + async () => { + const { current } = result; + expect(current.data.length).toEqual(1); + }, + { timeout: 500, interval: 100 } + ); + + const { + current: { data } + } = result; + + const deferred = pDefer(); + + const baseGetAll = db.getAll; + vi.spyOn(db, 'getAll').mockImplementation(async (sql, params) => { + // Allow pausing this call in order to test isFetching + await deferred.promise; + return baseGetAll.call(db, sql, params); + }); + + // The number of calls should be incremented after we make a change + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['anothername']); + + await waitFor( + () => { + expect(result.current.isFetching).toEqual(true); + }, + { timeout: 500, interval: 100 } + ); + + // Allow the result to be returned + deferred.resolve(); + + // We should still read the data from the DB + await waitFor( + () => { + expect(result.current.isFetching).toEqual(false); + }, + { timeout: 500, interval: 100 } + ); + + // The data reference should be the same as the previous time + expect(data == result.current.data).toEqual(true); + }); + + // Verifies backwards compatibility with the previous implementation (no comparison) + it('should emit result data when data changes when not using rowComparator', async () => { + const db = openPowerSync(); + const wrapper = ({ children }) => {children}; + const { result } = renderHook(() => useQuery('SELECT * FROM lists WHERE name = ?', ['aname']), { wrapper }); + + expect(result.current.isLoading).toEqual(true); + + await waitFor( + async () => { + const { current } = result; + expect(current.isLoading).toEqual(false); + }, + { timeout: 500, interval: 100 } + ); + + // This should trigger an update + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['aname']); + + // Keep track of the previous data reference + let previousData = result.current.data; + await waitFor( + async () => { + const { current } = result; + expect(current.data.length).toEqual(1); + previousData = current.data; + }, + { timeout: 500, interval: 100 } + ); + + // This should still trigger an update since the underlying tables changed. + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['noname']); + + // It's difficult to assert no update happened, but we can wait a bit + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // It should be the same data array reference, no update should have happened + expect(result.current.data == previousData).false; + }); + + it('should use an existing WatchedQuery instance', async () => { + const db = openPowerSync(); + + // This query can be instantiated once and reused. + // The query retains it's state and will not re-fetch the data unless the result changes. + // This is useful for queries that are used in multiple components. + const listsQuery = db + .query<{ id: string; name: string }>({ sql: `SELECT * FROM lists`, parameters: [] }) + .differentialWatch(); + + const wrapper = ({ children }) => {children}; + const { result } = renderHook(() => useWatchedQuerySubscription(listsQuery), { + wrapper + }); + + expect(result.current.isLoading).toEqual(true); + + await waitFor( + async () => { + const { current } = result; + expect(current.isLoading).toEqual(false); + }, + { timeout: 500, interval: 100 } + ); + + // This should trigger an update + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['aname']); + + await waitFor( + async () => { + const { current } = result; + expect(current.data.length).toEqual(1); + }, + { timeout: 500, interval: 100 } + ); + + // now use the same query again, the result should be available immediately + const { result: newResult } = renderHook(() => useWatchedQuerySubscription(listsQuery), { wrapper }); + expect(newResult.current.isLoading).toEqual(false); + expect(newResult.current.data.length).toEqual(1); + }); }); diff --git a/packages/react/tests/useSuspenseQuery.test.tsx b/packages/react/tests/useSuspenseQuery.test.tsx index f83c164d2..ecc5e28a3 100644 --- a/packages/react/tests/useSuspenseQuery.test.tsx +++ b/packages/react/tests/useSuspenseQuery.test.tsx @@ -1,37 +1,23 @@ -import * as commonSdk from '@powersync/common'; +import { AbstractPowerSyncDatabase, WatchedQuery, WatchedQueryListenerEvent } from '@powersync/common'; import { cleanup, renderHook, screen, waitFor } from '@testing-library/react'; -import React, { Suspense } from 'react'; +import React from 'react'; import { ErrorBoundary } from 'react-error-boundary'; -import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { PowerSyncContext } from '../src/hooks/PowerSyncContext'; -import { useSuspenseQuery } from '../src/hooks/useSuspenseQuery'; - -const defaultQueryResult = ['list1', 'list2']; - -const createMockPowerSync = () => { - return { - currentStatus: { status: 'initial' }, - registerListener: vi.fn(() => {}), - resolveTables: vi.fn(() => ['table1', 'table2']), - onChangeWithCallback: vi.fn(), - getAll: vi.fn(() => Promise.resolve(defaultQueryResult)) as Mock - }; -}; - -let mockPowerSync = createMockPowerSync(); - -vi.mock('./PowerSyncContext', () => ({ - useContext: vi.fn(() => mockPowerSync) -})); +import { useSuspenseQuery } from '../src/hooks/suspense/useSuspenseQuery'; +import { useWatchedQuerySuspenseSubscription } from '../src/hooks/suspense/useWatchedQuerySuspenseSubscription'; +import { openPowerSync } from './useQuery.test'; describe('useSuspenseQuery', () => { const loadingFallback = 'Loading'; const errorFallback = 'Error'; + let powersync: AbstractPowerSyncDatabase; + const wrapper = ({ children }) => ( - + - {children} + {children} ); @@ -66,7 +52,7 @@ describe('useSuspenseQuery', () => { beforeEach(() => { vi.clearAllMocks(); cleanup(); // Cleanup the DOM after each test - mockPowerSync = createMockPowerSync(); + powersync = openPowerSync(); }); it('should error when PowerSync is not set', async () => { @@ -76,50 +62,106 @@ describe('useSuspenseQuery', () => { }); it('should suspend on initial load', async () => { - mockPowerSync.getAll = vi.fn(() => { - return new Promise(() => {}); + // spy on watched query generation + const baseImplementation = powersync.customQuery; + let watch: WatchedQuery | null = null; + + const spy = vi.spyOn(powersync, 'customQuery').mockImplementation((options) => { + const builder = baseImplementation.call(powersync, options); + const baseBuild = builder.differentialWatch; + + // The hooks use the `watch` method if no comparator is set + vi.spyOn(builder, 'watch').mockImplementation((buildOptions) => { + watch = baseBuild.call(builder, buildOptions); + return watch!; + }); + + return builder!; }); const wrapper = ({ children }) => ( - - {children} + + + {children} +
Not suspending
+
); - renderHook(() => useSuspenseQuery('SELECT * from lists'), { wrapper }); + await powersync.execute("INSERT INTO lists (id, name) VALUES (uuid(), 'aname')"); + const { unmount } = renderHook(() => useSuspenseQuery('SELECT * from lists'), { wrapper }); + + expect(screen.queryByText('Not suspending')).toBeFalsy(); await waitForSuspend(); - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(1); - }); + // The component should render after suspending + await waitFor( + async () => { + expect(screen.queryByText('Not suspending')).toBeTruthy(); + }, + { timeout: 500, interval: 100 } + ); - it('should run the query once if runQueryOnce flag is set', async () => { - let resolvePromise: (_: string[]) => void = () => {}; + expect(watch).toBeDefined(); + expect(watch!.closed).false; + expect(watch!.state.data.length).eq(1); + expect(watch!.listenerMeta.counts[WatchedQueryListenerEvent.ON_STATE_CHANGE]).greaterThanOrEqual(2); // should have a temporary hold and state listener - mockPowerSync.getAll = vi.fn(() => { - return new Promise((resolve) => { - resolvePromise = resolve; - }); - }); + // wait for the temporary hold to elapse + await waitFor( + async () => { + expect(watch!.listenerMeta.counts[WatchedQueryListenerEvent.ON_STATE_CHANGE]).eq(1); + }, + { timeout: 10_000, interval: 500 } + ); - const { result } = renderHook(() => useSuspenseQuery('SELECT * from lists', [], { runQueryOnce: true }), { - wrapper - }); + // now unmount the hook, this should remove listeners from the watch and close the query + unmount(); - await waitForSuspend(); + // wait for the temporary hold to elapse + await waitFor( + async () => { + expect(watch!.listenerMeta.counts[WatchedQueryListenerEvent.ON_STATE_CHANGE]).undefined; + expect(watch?.closed).true; + }, + { timeout: 10_000, interval: 500 } + ); + }); - resolvePromise(defaultQueryResult); + it('should run the query once if runQueryOnce flag is set', async () => { + await powersync.execute("INSERT INTO lists (id, name) VALUES (uuid(), 'list1')"); - await waitForCompletedSuspend(); + const { result } = renderHook( + () => useSuspenseQuery<{ name: string }>('SELECT * from lists', [], { runQueryOnce: true }), + { + wrapper + } + ); + + // Wait for the data to be presented + let lastData; await waitFor( async () => { const currentResult = result.current; - expect(currentResult?.data).toEqual(['list1', 'list2']); - expect(mockPowerSync.onChangeWithCallback).not.toHaveBeenCalled(); - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(1); + lastData = currentResult?.data; + expect(lastData?.[0]).toBeDefined(); + expect(lastData?.[0].name).toBe('list1'); }, - { timeout: 100 } + { timeout: 1000 } ); + + await waitForCompletedSuspend(); + + // Do another insert, this should not trigger a re-render + await powersync.execute("INSERT INTO lists (id, name) VALUES (uuid(), 'list2')"); + + // Wait a bit, it's difficult to test that something did not happen, so we just wait a bit + await new Promise((r) => setTimeout(r, 1000)); + + expect(result.current.data).toEqual(lastData); + // sanity + expect(result.current.data?.length).toBe(1); }); it('should rerun the query when refresh is used', async () => { @@ -127,59 +169,41 @@ describe('useSuspenseQuery', () => { wrapper }); + // First ensure we do suspend, then wait for suspending to complete await waitForSuspend(); let refresh; - await waitFor( async () => { const currentResult = result.current; - refresh = currentResult.refresh; - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(1); + console.log(currentResult); + refresh = currentResult?.refresh; + expect(refresh).toBeDefined(); }, - { timeout: 100 } + { timeout: 1000 } ); await waitForCompletedSuspend(); + expect(refresh).toBeDefined(); + const spy = vi.spyOn(powersync, 'getAll'); + const callCount = spy.mock.calls.length; await refresh(); - expect(mockPowerSync.getAll).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledTimes(callCount + 1); }); it('should set error when error occurs', async () => { - let rejectPromise: (err: string) => void = () => {}; + renderHook(() => useSuspenseQuery('SELECT * from fakelists', []), { wrapper }); - mockPowerSync.getAll = vi.fn(() => { - return new Promise((_resolve, reject) => { - rejectPromise = reject; - }); - }); - - renderHook(() => useSuspenseQuery('SELECT * from lists', []), { wrapper }); - - await waitForSuspend(); - - rejectPromise('failure'); await waitForCompletedSuspend(); await waitForError(); }); it('should set error when error occurs and runQueryOnce flag is set', async () => { - let rejectPromise: (err: string) => void = () => {}; - - mockPowerSync.getAll = vi.fn(() => { - return new Promise((_resolve, reject) => { - rejectPromise = reject; - }); - }); - - renderHook(() => useSuspenseQuery('SELECT * from lists', [], { runQueryOnce: true }), { + renderHook(() => useSuspenseQuery('SELECT * from fakelists', [], { runQueryOnce: true }), { wrapper }); - await waitForSuspend(); - - rejectPromise('failure'); await waitForCompletedSuspend(); await waitForError(); }); @@ -214,15 +238,13 @@ describe('useSuspenseQuery', () => { }); it('should show an error if parsing the query results in an error', async () => { - vi.spyOn(commonSdk, 'parseQuery').mockImplementation(() => { - throw new Error('error'); - }); - const { result } = renderHook( () => useSuspenseQuery({ execute: () => [] as any, - compile: () => ({ sql: 'SELECT * from lists', parameters: ['param'] }) + compile: () => { + throw Error('error'); + } }), { wrapper } ); @@ -230,4 +252,51 @@ describe('useSuspenseQuery', () => { await waitForCompletedSuspend(); await waitForError(); }); + + it('should use an existing WatchedQuery instance', async () => { + const db = openPowerSync(); + + // This query can be instantiated once and reused. + // The query retains it's state and will not re-fetch the data unless the result changes. + // This is useful for queries that are used in multiple components. + const listsQuery = db + .query({ + sql: `SELECT * FROM lists`, + parameters: [] + }) + .watch(); + + const wrapper = ({ children }) => {children}; + const { result } = renderHook(() => useWatchedQuerySuspenseSubscription(listsQuery), { + wrapper + }); + + // Initially, the query should be loading/suspended + expect(result.current).toEqual(null); + + await waitFor( + async () => { + expect(result.current).not.null; + }, + { timeout: 500, interval: 100 } + ); + + expect(result.current.data.length).toEqual(0); + + // This should trigger an update + await db.execute('INSERT INTO lists(id, name) VALUES (uuid(), ?)', ['aname']); + + await waitFor( + async () => { + const { current } = result; + expect(current.data.length).toEqual(1); + }, + { timeout: 500, interval: 100 } + ); + + // now use the same query again, the result should be available immediately + const { result: newResult } = renderHook(() => useWatchedQuerySuspenseSubscription(listsQuery), { wrapper }); + expect(newResult.current).not.null; + expect(newResult.current.data.length).toEqual(1); + }); }); diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts index f96ac5630..75474d403 100644 --- a/packages/react/vitest.config.ts +++ b/packages/react/vitest.config.ts @@ -1,8 +1,44 @@ import { defineConfig, UserConfigExport } from 'vitest/config'; +import topLevelAwait from 'vite-plugin-top-level-await'; +import wasm from 'vite-plugin-wasm'; + const config: UserConfigExport = { + // This is only needed for local tests to resolve the package name correctly + worker: { + format: 'es', + plugins: () => [wasm(), topLevelAwait()] + }, + optimizeDeps: { + // Don't optimise these packages as they contain web workers and WASM files. + // https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673 + exclude: ['@journeyapps/wa-sqlite', '@powersync/web'], + include: ['bson', 'async-mutex', 'comlink'] + }, + plugins: [wasm(), topLevelAwait()], test: { - environment: 'jsdom' + globals: true, + include: ['tests/**/*.test.tsx'], + maxConcurrency: 1, + // This doesn't currently seem to work in browser mode, but setting this for one day when it does + sequence: { + shuffle: false, // Disable shuffling of test files + concurrent: false // Run test files sequentially + }, + browser: { + enabled: true, + /** + * Starts each test in a new iFrame + */ + isolate: true, + provider: 'playwright', + headless: true, + instances: [ + { + browser: 'chromium' + } + ] + } } }; diff --git a/packages/vue/README.md b/packages/vue/README.md index 200cac8fc..5998ea8d9 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -101,7 +101,78 @@ const addList = () => { ``` -### Static query +### Incremental Query + +By default, the `useQuery` composable emits a new `data` reference whenever any change occurs in the query's dependent tables. With incremental queries, updates are only emitted when relevant changes are detected, and the internal `data` array preserves object references for unchanged rows between emissions. This helps prevent unnecessary re-renders in your components. + +```Vue +// TodoListDisplayQuery.vue + + + +``` + +### Query Subscriptions + +The `useWatchedQuerySubscription` composable lets you access the state of an externally managed `WatchedQuery` instance. To enable in-memory caching and sharing of results between multiple subscribers, the query should be created outside of any component—such as in a module or singleton. This reduces async loading time when components mount (thanks to in-memory caching) and minimizes the number of SQLite queries (by sharing results between components). + +```js +// listsQuery.js +import { usePowerSync } from '@powersync/vue'; + +// This module creates and exports a single shared query instance. +// It should be imported wherever you want to subscribe to the query. +let listsQuery; +export function getListsQuery() { + if (!listsQuery) { + // Note: usePowerSync() must be called in a Vue setup context, so you may need to + // pass the PowerSync instance here if you are not using the default context. + const powerSync = usePowerSync(); + listsQuery = powerSync.value.query({ sql: 'SELECT * FROM lists' }).differentialWatch(); + } + return listsQuery; +} +``` + +```vue + + + + +``` + +### Static Query The `useQuery` composable can be configured to only execute initially and not every time changes to dependent tables are detected. The query can be manually re-executed by using the returned `refresh` function. diff --git a/packages/vue/package.json b/packages/vue/package.json index 207dc5e4e..9c08df473 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -34,6 +34,7 @@ }, "devDependencies": { "@powersync/common": "workspace:*", + "@powersync/web": "workspace:*", "flush-promises": "^1.0.2", "jsdom": "^24.0.0", "vue": "3.4.21" diff --git a/packages/vue/src/composables/useQuery.ts b/packages/vue/src/composables/useQuery.ts index 2a5d32f25..9768d40b6 100644 --- a/packages/vue/src/composables/useQuery.ts +++ b/packages/vue/src/composables/useQuery.ts @@ -1,28 +1,19 @@ -import { - type CompilableQuery, - ParsedQuery, - type SQLWatchOptions, - parseQuery, - runOnSchemaChange -} from '@powersync/common'; -import { type MaybeRef, type Ref, ref, toValue, watchEffect } from 'vue'; -import { usePowerSync } from './powerSync'; - -interface AdditionalOptions extends Omit { - runQueryOnce?: boolean; -} +import { type CompilableQuery } from '@powersync/common'; +import { type MaybeRef, type Ref } from 'vue'; +import { AdditionalOptions, useSingleQuery } from './useSingleQuery'; +import { useWatchedQuery } from './useWatchedQuery'; export type WatchedQueryResult = { - data: Ref; + readonly data: Ref>>; /** * Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs. */ - isLoading: Ref; + readonly isLoading: Ref; /** * Indicates whether the query is currently fetching data, is true during the initial load and any time when the query is re-evaluating (useful for large queries). */ - isFetching: Ref; - error: Ref; + readonly isFetching: Ref; + readonly error: Ref; /** * Function used to run the query again. */ @@ -54,115 +45,12 @@ export type WatchedQueryResult = { export const useQuery = ( query: MaybeRef>, sqlParameters: MaybeRef = [], - options: AdditionalOptions = {} + options: AdditionalOptions = {} ): WatchedQueryResult => { - const data = ref([]) as Ref; - const error = ref(undefined); - const isLoading = ref(true); - const isFetching = ref(true); - - // Only defined when the query and parameters are successfully parsed and tables are resolved - let fetchData: () => Promise | undefined; - - const powerSync = usePowerSync(); - const logger = powerSync?.value?.logger ?? console; - - const finishLoading = () => { - isLoading.value = false; - isFetching.value = false; - }; - - if (!powerSync) { - finishLoading(); - error.value = new Error('PowerSync not configured.'); - return { data, isLoading, isFetching, error }; + switch (true) { + case options.runQueryOnce: + return useSingleQuery(query, sqlParameters, options); + default: + return useWatchedQuery(query, sqlParameters, options); } - - const handleResult = (result: T[]) => { - finishLoading(); - data.value = result; - error.value = undefined; - }; - - const handleError = (e: Error) => { - fetchData = undefined; - finishLoading(); - data.value = []; - - const wrappedError = new Error('PowerSync failed to fetch data: ' + e.message); - wrappedError.cause = e; - error.value = wrappedError; - }; - - const _fetchData = async (executor: () => Promise) => { - isFetching.value = true; - try { - const result = await executor(); - handleResult(result); - } catch (e) { - logger.error('Failed to fetch data:', e); - handleError(e); - } - }; - - watchEffect(async (onCleanup) => { - const abortController = new AbortController(); - // Abort any previous watches when the effect triggers again, or when the component is unmounted - onCleanup(() => abortController.abort()); - - let parsedQuery: ParsedQuery; - const queryValue = toValue(query); - try { - parsedQuery = parseQuery(queryValue, toValue(sqlParameters).map(toValue)); - } catch (e) { - logger.error('Failed to parse query:', e); - handleError(e); - return; - } - - const { sqlStatement: sql, parameters } = parsedQuery; - const watchQuery = async (abortSignal: AbortSignal) => { - let resolvedTables = []; - try { - resolvedTables = await powerSync.value.resolveTables(sql, parameters, options); - } catch (e) { - logger.error('Failed to fetch tables:', e); - handleError(e); - return; - } - // Fetch initial data - const executor = - typeof queryValue == 'string' ? () => powerSync.value.getAll(sql, parameters) : () => queryValue.execute(); - fetchData = () => _fetchData(executor); - await fetchData(); - - if (options.runQueryOnce) { - return; - } - - powerSync.value.onChangeWithCallback( - { - onChange: async () => { - await fetchData(); - }, - onError: handleError - }, - { - ...options, - signal: abortSignal, - tables: resolvedTables - } - ); - }; - - runOnSchemaChange(watchQuery, powerSync.value, { signal: abortController.signal }); - }); - - return { - data, - isLoading, - isFetching, - error, - refresh: () => fetchData?.() - }; }; diff --git a/packages/vue/src/composables/useSingleQuery.ts b/packages/vue/src/composables/useSingleQuery.ts new file mode 100644 index 000000000..66968078a --- /dev/null +++ b/packages/vue/src/composables/useSingleQuery.ts @@ -0,0 +1,138 @@ +import { + type CompilableQuery, + DifferentialWatchedQueryComparator, + ParsedQuery, + parseQuery, + SQLOnChangeOptions +} from '@powersync/common'; +import { type MaybeRef, type Ref, ref, toValue, watchEffect } from 'vue'; +import { usePowerSync } from './powerSync'; + +export interface AdditionalOptions extends Omit { + runQueryOnce?: boolean; + /** + * Used to compare query result sets. + * + * By default the hook will requery on any dependent table change. This will + * emit a new hook result even if the result set has not changed. + * + * Specifying a {@link DifferentialWatchedQueryComparator} will remove emissions for + * unchanged result sets. + * Furthermore, emitted `data` arrays will preserve object references between result set emissions + * for unchanged rows. + * @example + * ```javascript + * { + * rowComparator: { + * keyBy: (item) => item.id, + * compareBy: (item) => JSON.stringify(item) + * } + * } + * ``` + */ + rowComparator?: DifferentialWatchedQueryComparator; +} + +export type WatchedQueryResult = { + readonly data: Ref>>; + /** + * Indicates the initial loading state (hard loading). Loading becomes false once the first set of results from the watched query is available or an error occurs. + */ + readonly isLoading: Ref; + /** + * Indicates whether the query is currently fetching data, is true during the initial load and any time when the query is re-evaluating (useful for large queries). + */ + readonly isFetching: Ref; + readonly error: Ref; + /** + * Function used to run the query again. + */ + refresh?: () => Promise; +}; + +export const useSingleQuery = ( + query: MaybeRef>, + sqlParameters: MaybeRef = [], + options: AdditionalOptions = {} +): WatchedQueryResult => { + const data = ref>>([]) as Ref>>; + const error = ref(undefined); + const isLoading = ref(true); + const isFetching = ref(true); + + // Only defined when the query and parameters are successfully parsed and tables are resolved + let fetchData: () => Promise | undefined; + + const powerSync = usePowerSync(); + const logger = powerSync?.value?.logger ?? console; + + const finishLoading = () => { + isLoading.value = false; + isFetching.value = false; + }; + + if (!powerSync || !powerSync.value) { + finishLoading(); + error.value = new Error('PowerSync not configured.'); + return { data, isLoading, isFetching, error }; + } + + const handleResult = (result: T[]) => { + finishLoading(); + data.value = result; + error.value = undefined; + }; + + const handleError = (e: Error) => { + fetchData = undefined; + finishLoading(); + data.value = []; + + const wrappedError = new Error('PowerSync failed to fetch data: ' + e.message); + wrappedError.cause = e; + error.value = wrappedError; + }; + + watchEffect(async (onCleanup) => { + const abortController = new AbortController(); + // Abort any previous watches when the effect triggers again, or when the component is unmounted + onCleanup(() => abortController.abort()); + + let parsedQuery: ParsedQuery; + const queryValue = toValue(query); + try { + parsedQuery = parseQuery(queryValue, toValue(sqlParameters).map(toValue)); + } catch (e) { + logger.error('Failed to parse query:', e); + handleError(e); + return; + } + + const { sqlStatement: sql, parameters } = parsedQuery; + // Fetch initial data + const executor = + typeof queryValue == 'string' ? () => powerSync.value.getAll(sql, parameters) : () => queryValue.execute(); + + fetchData = async () => { + isFetching.value = true; + try { + const result = await executor(); + handleResult(result); + } catch (e) { + logger.error('Failed to fetch data:', e); + handleError(e); + } + }; + + // fetch initial data + await fetchData(); + }); + + return { + data, + isLoading, + isFetching, + error, + refresh: () => fetchData?.() + }; +}; diff --git a/packages/vue/src/composables/useWatchedQuery.ts b/packages/vue/src/composables/useWatchedQuery.ts new file mode 100644 index 000000000..fa1cc29c3 --- /dev/null +++ b/packages/vue/src/composables/useWatchedQuery.ts @@ -0,0 +1,98 @@ +import { type CompilableQuery, ParsedQuery, parseQuery, WatchCompatibleQuery } from '@powersync/common'; +import { type MaybeRef, type Ref, ref, toValue, watchEffect } from 'vue'; +import { usePowerSync } from './powerSync'; +import { AdditionalOptions, WatchedQueryResult } from './useSingleQuery'; + +export const useWatchedQuery = ( + query: MaybeRef>, + sqlParameters: MaybeRef = [], + options: AdditionalOptions = {} +): WatchedQueryResult => { + const data = ref>>([]) as Ref>>; + const error = ref(undefined); + const isLoading = ref(true); + const isFetching = ref(true); + + const powerSync = usePowerSync(); + const logger = powerSync?.value?.logger ?? console; + + const finishLoading = () => { + isLoading.value = false; + isFetching.value = false; + }; + + if (!powerSync || !powerSync.value) { + finishLoading(); + error.value = new Error('PowerSync not configured.'); + return { data, isLoading, isFetching, error }; + } + + const handleError = (e: Error) => { + finishLoading(); + data.value = []; + + const wrappedError = new Error('PowerSync failed to fetch data: ' + e.message); + wrappedError.cause = e; + error.value = wrappedError; + }; + + watchEffect(async (onCleanup) => { + let parsedQuery: ParsedQuery; + const queryValue = toValue(query); + try { + parsedQuery = parseQuery(queryValue, toValue(sqlParameters).map(toValue)); + } catch (e) { + logger.error('Failed to parse query:', e); + handleError(e); + return; + } + + const { sqlStatement: sql, parameters } = parsedQuery; + + const compatibleQuery: WatchCompatibleQuery = { + compile: () => ({ sql, parameters }), + execute: async ({ db, sql, parameters }) => { + if (typeof queryValue === 'string') { + return db.getAll(sql, parameters); + } + return queryValue.execute(); + } + }; + + const watch = options.rowComparator + ? powerSync.value.customQuery(compatibleQuery).differentialWatch({ + rowComparator: options.rowComparator, + throttleMs: options.throttleMs + }) + : powerSync.value.customQuery(compatibleQuery).watch({ + throttleMs: options.throttleMs + }); + + const disposer = watch.registerListener({ + onStateChange: (state) => { + isLoading.value = state.isLoading; + isFetching.value = state.isFetching; + data.value = state.data; + if (state.error) { + const wrappedError = new Error('PowerSync failed to fetch data: ' + state.error.message); + wrappedError.cause = state.error; + error.value = wrappedError; + } else { + error.value = undefined; + } + } + }); + + onCleanup(() => { + disposer(); + watch.close(); + }); + }); + + return { + data, + isLoading, + isFetching, + error + }; +}; diff --git a/packages/vue/src/composables/useWatchedQuerySubscription.ts b/packages/vue/src/composables/useWatchedQuerySubscription.ts new file mode 100644 index 000000000..fd985f9d0 --- /dev/null +++ b/packages/vue/src/composables/useWatchedQuerySubscription.ts @@ -0,0 +1,60 @@ +import { WatchedQuery } from '@powersync/common'; +import { Ref, ref, watchEffect } from 'vue'; + +type RefState = WatchedQuery> = { + [K in keyof Query['state']]: Ref; +}; + +/** + * A composable to access and subscribe to the results of an existing {@link WatchedQuery} instance. + * @example + * ```vue + * + * + * + * ``` + */ +export const useWatchedQuerySubscription = < + ResultType = unknown, + Query extends WatchedQuery = WatchedQuery +>( + query: Query +): RefState => { + // This uses individual refs in order for the destructured use of this hook's return value to + // function correctly. + const data = ref(query.state.data) as Ref; + const error = ref(query.state.error); + const isLoading = ref(query.state.isLoading); + const isFetching = ref(query.state.isFetching); + + watchEffect((onCleanup) => { + const dispose = query.registerListener({ + onStateChange: (updatedState) => { + data.value = updatedState.data; + error.value = updatedState.error; + isFetching.value = updatedState.isFetching; + isLoading.value = updatedState.isLoading; + } + }); + onCleanup(() => dispose()); + }); + + return { + data, + error, + isFetching, + isLoading + } as RefState; +}; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 7a034c1e7..4fc5d571a 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -4,3 +4,4 @@ export { usePowerSyncStatus } from './composables/usePowerSyncStatus'; export { usePowerSyncWatchedQuery } from './composables/usePowerSyncWatchedQuery'; export { useQuery } from './composables/useQuery'; export { useStatus } from './composables/useStatus'; +export { useWatchedQuerySubscription } from './composables/useWatchedQuerySubscription'; diff --git a/packages/vue/tests/useQuery.test.ts b/packages/vue/tests/useQuery.test.ts index 2a33605c1..648ddb6e8 100644 --- a/packages/vue/tests/useQuery.test.ts +++ b/packages/vue/tests/useQuery.test.ts @@ -1,28 +1,52 @@ +import * as commonSdk from '@powersync/common'; +import { PowerSyncDatabase } from '@powersync/web'; import flushPromises from 'flush-promises'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, onTestFinished, vi } from 'vitest'; import { isProxy, isRef, ref } from 'vue'; -import * as PowerSync from '../src/composables/powerSync'; +import { createPowerSyncPlugin } from '../src/composables/powerSync'; import { useQuery } from '../src/composables/useQuery'; +import { useWatchedQuerySubscription } from '../src/composables/useWatchedQuerySubscription'; import { withSetup } from './utils'; -const mockPowerSync = { - currentStatus: { status: 'initial' }, - registerListener: vi.fn(() => {}), - resolveTables: vi.fn(), - watch: vi.fn(), - onChangeWithCallback: vi.fn(), - getAll: vi.fn(() => ['list1', 'list2']) +export const openPowerSync = () => { + const db = new PowerSyncDatabase({ + database: { dbFilename: 'test.db' }, + schema: new commonSdk.Schema({ + lists: new commonSdk.Table({ + name: commonSdk.column.text + }) + }) + }); + + onTestFinished(async () => { + await db.disconnectAndClear(); + await db.close(); + }); + + return db; }; describe('useQuery', () => { + let powersync: commonSdk.AbstractPowerSyncDatabase | null; + + beforeEach(() => { + powersync = openPowerSync(); + }); + afterEach(() => { - vi.clearAllMocks(); + vi.resetAllMocks(); }); - it('should error when PowerSync is not set', () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(undefined); + const withPowerSyncSetup = (callback: () => Result) => { + return withSetup(callback, (app) => { + const { install } = createPowerSyncPlugin({ database: powersync! }); + install(app); + }); + }; - const [{ data, isLoading, isFetching, error }] = withSetup(() => useQuery('SELECT * from lists')); + it('should error when PowerSync is not set', () => { + powersync = null; + const [{ data, isLoading, isFetching, error }] = withPowerSyncSetup(() => useQuery('SELECT * from lists')); expect(error.value?.message).toEqual('PowerSync not configured.'); expect(isFetching.value).toEqual(false); @@ -31,9 +55,9 @@ describe('useQuery', () => { }); it('should handle error in watchEffect', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(undefined); + powersync = null; - const [{ data, isLoading, isFetching, error }] = withSetup(() => useQuery('SELECT * from lists')); + const [{ data, isLoading, isFetching, error }] = withPowerSyncSetup(() => useQuery('SELECT * from lists')); expect(error.value).toEqual(Error('PowerSync not configured.')); expect(isFetching.value).toEqual(false); @@ -42,51 +66,93 @@ describe('useQuery', () => { }); it('should run the query once when runQueryOnce flag is set', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - const getAllSpy = mockPowerSync.getAll; - - const [{ data, isLoading, isFetching, error }] = withSetup(() => + await powersync!.execute(/* sql */ ` + INSERT INTO + lists (id, name) + VALUES + (uuid (), 'list1'); + `); + + const [{ data, isLoading, isFetching, error }] = withPowerSyncSetup(() => useQuery('SELECT * from lists', [], { runQueryOnce: true }) ); - await flushPromises(); - expect(getAllSpy).toHaveBeenCalledTimes(1); - expect(data.value).toEqual(['list1', 'list2']); - expect(isLoading.value).toEqual(false); - expect(isFetching.value).toEqual(false); - expect(error.value).toEqual(undefined); + await vi.waitFor( + () => { + expect(data.value.map((item) => item.name)).toEqual(['list1']); + expect(isLoading.value).toEqual(false); + expect(isFetching.value).toEqual(false); + expect(error.value).toEqual(undefined); + }, + { timeout: 1000 } + ); }); // ensure that Proxy wrapper object is stripped it('should propagate raw reactive sql parameters', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - const getAllSpy = mockPowerSync.getAll; + const getAllSpy = vi.spyOn(powersync!, 'getAll'); - const [{ data, isLoading, isFetching, error }] = withSetup(() => + const [{ data, isLoading, isFetching, error }] = withPowerSyncSetup(() => useQuery('SELECT * from lists where id = $1', ref([ref('test')])) ); - await flushPromises(); - expect(getAllSpy).toHaveBeenCalledTimes(1); - const sqlParam = (getAllSpy.mock.calls[0] as Array)[1]; - expect(isRef(sqlParam)).toEqual(false); - expect(isProxy(sqlParam)).toEqual(false); + + await vi.waitFor( + () => { + expect(getAllSpy).toHaveBeenCalledTimes(3); + const sqlParam = (getAllSpy.mock.calls[2] as Array)[1]; + expect(isRef(sqlParam)).toEqual(false); + expect(isProxy(sqlParam)).toEqual(false); + }, + { timeout: 1000 } + ); + }); + + it('should use an existing WatchedQuery instance', async () => { + // This query can be instantiated once and reused. + // The query retains it's state and will not re-fetch the data unless the result changes. + // This is useful for queries that are used in multiple components. + const listsQuery = powersync! + .query<{ id: string; name: string }>({ sql: `SELECT * FROM lists`, parameters: [] }) + .differentialWatch(); + + const [state] = withPowerSyncSetup(() => useWatchedQuerySubscription(listsQuery)); + + await powersync!.execute( + /* sql */ ` + INSERT INTO + lists (id, name) + VALUES + (uuid (), ?) + `, + ['test'] + ); + + await vi.waitFor( + () => { + expect(state.data.value.length).eq(1); + }, + { timeout: 1000 } + ); }); it('should rerun the query when refresh is used', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - const getAllSpy = mockPowerSync.getAll; + const getAllSpy = vi.spyOn(powersync!, 'getAll'); - const [{ isLoading, isFetching, refresh }] = withSetup(() => + const [{ isLoading, isFetching, refresh }] = withPowerSyncSetup(() => useQuery('SELECT * from lists', [], { runQueryOnce: true }) ); expect(isFetching.value).toEqual(true); expect(isLoading.value).toEqual(true); - await flushPromises(); - expect(isFetching.value).toEqual(false); - expect(isLoading.value).toEqual(false); + await vi.waitFor( + () => { + expect(isFetching.value).toEqual(false); + expect(isLoading.value).toEqual(false); + }, + { timeout: 1000 } + ); - expect(getAllSpy).toHaveBeenCalledTimes(1); + const callCount = getAllSpy.mock.calls.length; const refreshPromise = refresh?.(); expect(isFetching.value).toEqual(true); @@ -95,71 +161,71 @@ describe('useQuery', () => { await refreshPromise; expect(isFetching.value).toEqual(false); - expect(getAllSpy).toHaveBeenCalledTimes(2); + expect(getAllSpy).toHaveBeenCalledTimes(callCount + 1); }); it('should set error when error occurs and runQueryOnce flag is set', async () => { - const mockPowerSyncError = { - ...mockPowerSync, - getAll: vi.fn(() => { - throw new Error('some error'); - }) - }; - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSyncError) as any); + vi.spyOn(powersync!, 'getAll').mockImplementation(() => { + throw new Error('some error'); + }); - const [{ error }] = withSetup(() => useQuery('SELECT * from lists', [], { runQueryOnce: true })); + const [{ error }] = withPowerSyncSetup(() => useQuery('SELECT * from lists', [], { runQueryOnce: true })); await flushPromises(); expect(error.value?.message).toEqual('PowerSync failed to fetch data: some error'); }); it('should set error when error occurs', async () => { - const mockPowerSyncError = { - ...mockPowerSync, - getAll: vi.fn(() => { - throw new Error('some error'); - }) - }; - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSyncError) as any); - - const [{ error }] = withSetup(() => useQuery('SELECT * from lists', [])); - await flushPromises(); - - expect(error.value?.message).toEqual('PowerSync failed to fetch data: some error'); + vi.spyOn(powersync!, 'getAll').mockImplementation(() => { + throw new Error('some error'); + }); + + const [{ error }] = withPowerSyncSetup(() => useQuery('SELECT * from lists', [])); + await vi.waitFor( + () => { + expect(error.value?.message).toEqual('PowerSync failed to fetch data: some error'); + }, + { timeout: 1000 } + ); }); it('should accept compilable queries', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - - const [{ isLoading }] = withSetup(() => + const [{ isLoading }] = withPowerSyncSetup(() => useQuery({ execute: () => [] as any, compile: () => ({ sql: 'SELECT * from lists', parameters: [] }) }) ); expect(isLoading.value).toEqual(true); - await flushPromises(); - expect(isLoading.value).toEqual(false); + await vi.waitFor( + () => { + expect(isLoading.value).toEqual(false); + }, + { timeout: 1000 } + ); }); it('should execute compilable queries', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - - const [{ isLoading, data }] = withSetup(() => + const [result] = withPowerSyncSetup(() => useQuery({ execute: () => [{ test: 'custom' }] as any, compile: () => ({ sql: 'SELECT * from lists', parameters: [] }) }) ); + const { isLoading, data } = result; + expect(isLoading.value).toEqual(true); - await flushPromises(); - expect(isLoading.value).toEqual(false); - expect(data.value[0].test).toEqual('custom'); + + await vi.waitFor( + () => { + expect(isLoading.value).toEqual(false); + expect(data.value[0].test).toEqual('custom'); + }, + { timeout: 1000 } + ); }); it('should set error for compilable query on useQuery parameters', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - - const [{ error }] = withSetup(() => + const [{ error }] = withPowerSyncSetup(() => useQuery({ execute: () => [] as any, compile: () => ({ sql: 'SELECT * from lists', parameters: [] }) }, ['x']) ); diff --git a/packages/vue/tests/useStatus.test.ts b/packages/vue/tests/useStatus.test.ts index 8a0912da6..1516fc7fd 100644 --- a/packages/vue/tests/useStatus.test.ts +++ b/packages/vue/tests/useStatus.test.ts @@ -1,36 +1,48 @@ -import { describe, it, expect, vi, afterEach } from 'vitest'; +import * as commonSdk from '@powersync/common'; +import { PowerSyncDatabase } from '@powersync/web'; +import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest'; +import { createPowerSyncPlugin } from '../src/composables/powerSync'; import { useStatus } from '../src/composables/useStatus'; import { withSetup } from './utils'; -import * as PowerSync from '../src/composables/powerSync'; -import { ref } from 'vue'; -const cleanupListener = vi.fn(); +export const openPowerSync = () => { + const db = new PowerSyncDatabase({ + database: { dbFilename: 'test.db' }, + schema: new commonSdk.Schema({ + lists: new commonSdk.Table({ + name: commonSdk.column.text + }) + }) + }); + + onTestFinished(async () => { + await db.disconnectAndClear(); + await db.close(); + }); -const mockPowerSync = { - currentStatus: { connected: true }, - registerListener: () => cleanupListener + return db; }; describe('useStatus', () => { - afterEach(() => { - vi.resetAllMocks(); - }); + let powersync: commonSdk.AbstractPowerSyncDatabase | null; - it('should load the status of PowerSync', async () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any); - - const [status] = withSetup(() => useStatus()); - expect(status.value.connected).toBe(true); + beforeEach(() => { + powersync = openPowerSync(); }); - it('should run the listener cleanup on unmount', () => { - vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync as any)); - - const [, app] = withSetup(() => useStatus()); - const listenerUnsubscribe = cleanupListener; + afterEach(() => { + vi.resetAllMocks(); + }); - app.unmount(); + const withPowerSyncSetup = (callback: () => Result) => { + return withSetup(callback, (app) => { + const { install } = createPowerSyncPlugin({ database: powersync! }); + install(app); + }); + }; - expect(listenerUnsubscribe).toHaveBeenCalled(); + it('should load the status of PowerSync', async () => { + const [status] = withPowerSyncSetup(() => useStatus()); + expect(status.value.connected).toBe(false); }); }); diff --git a/packages/vue/tests/utils.ts b/packages/vue/tests/utils.ts index 5e3c20bf2..7761fc5b7 100644 --- a/packages/vue/tests/utils.ts +++ b/packages/vue/tests/utils.ts @@ -1,10 +1,11 @@ import type { App } from 'vue'; import { createApp } from 'vue'; -export function withSetup(composable: () => T): [T, App] { +export function withSetup(composable: () => T, provide?: (app: App) => void): [T, App] { let result: T; const app = createApp({ setup() { + provide?.(app); result = composable(); return () => {}; } diff --git a/packages/vue/vitest.config.ts b/packages/vue/vitest.config.ts index f96ac5630..b10729d9d 100644 --- a/packages/vue/vitest.config.ts +++ b/packages/vue/vitest.config.ts @@ -1,8 +1,44 @@ import { defineConfig, UserConfigExport } from 'vitest/config'; +import topLevelAwait from 'vite-plugin-top-level-await'; +import wasm from 'vite-plugin-wasm'; + const config: UserConfigExport = { + // This is only needed for local tests to resolve the package name correctly + worker: { + format: 'es', + plugins: () => [wasm(), topLevelAwait()] + }, + optimizeDeps: { + // Don't optimise these packages as they contain web workers and WASM files. + // https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673 + exclude: ['@journeyapps/wa-sqlite', '@powersync/web'], + include: ['bson', 'async-mutex', 'comlink'] + }, + plugins: [wasm(), topLevelAwait()], test: { - environment: 'jsdom' + globals: true, + include: ['tests/**/*.test.ts'], + maxConcurrency: 1, + // This doesn't currently seem to work in browser mode, but setting this for one day when it does + sequence: { + shuffle: false, // Disable shuffling of test files + concurrent: false // Run test files sequentially + }, + browser: { + enabled: true, + /** + * Starts each test in a new iFrame + */ + isolate: true, + provider: 'playwright', + headless: true, + instances: [ + { + browser: 'chromium' + } + ] + } } }; diff --git a/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts b/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts index 35c8dfa0d..25e0afa56 100644 --- a/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts +++ b/packages/web/src/db/adapters/LockedAsyncDatabaseAdapter.ts @@ -47,11 +47,18 @@ export class LockedAsyncDatabaseAdapter private _db: AsyncDatabaseConnection | null = null; protected _disposeTableChangeListener: (() => void) | null = null; private _config: ResolvedWebSQLOpenOptions | null = null; + protected pendingAbortControllers: Set; + + closing: boolean; + closed: boolean; constructor(protected options: LockedAsyncDatabaseAdapterOptions) { super(); this._dbIdentifier = options.name; this.logger = options.logger ?? createLogger(`LockedAsyncDatabaseAdapter - ${this._dbIdentifier}`); + this.pendingAbortControllers = new Set(); + this.closed = false; + this.closing = false; // Set the name if provided. We can query for the name if not available yet this.debugMode = options.debugMode ?? false; if (this.debugMode) { @@ -154,8 +161,11 @@ export class LockedAsyncDatabaseAdapter * tabs are still using it. */ async close() { + this.closing = true; this._disposeTableChangeListener?.(); + this.pendingAbortControllers.forEach((controller) => controller.abort('Closed')); await this.baseDB?.close?.(); + this.closed = true; } async getAll(sql: string, parameters?: any[] | undefined): Promise { @@ -175,20 +185,49 @@ export class LockedAsyncDatabaseAdapter async readLock(fn: (tx: LockContext) => Promise, options?: DBLockOptions | undefined): Promise { await this.waitForInitialized(); - return this.acquireLock(async () => - fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })) + return this.acquireLock( + async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), + { + timeoutMs: options?.timeoutMs + } ); } async writeLock(fn: (tx: LockContext) => Promise, options?: DBLockOptions | undefined): Promise { await this.waitForInitialized(); - return this.acquireLock(async () => - fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })) + return this.acquireLock( + async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), + { + timeoutMs: options?.timeoutMs + } ); } - protected acquireLock(callback: () => Promise): Promise { - return getNavigatorLocks().request(`db-lock-${this._dbIdentifier}`, callback); + protected async acquireLock(callback: () => Promise, options?: { timeoutMs?: number }): Promise { + await this.waitForInitialized(); + + if (this.closing) { + throw new Error(`Cannot acquire lock, the database is closing`); + } + + const abortController = new AbortController(); + this.pendingAbortControllers.add(abortController); + const { timeoutMs } = options ?? {}; + + const timoutId = timeoutMs + ? setTimeout(() => { + abortController.abort(`Timeout after ${timeoutMs}ms`); + this.pendingAbortControllers.delete(abortController); + }, timeoutMs) + : null; + + return getNavigatorLocks().request(`db-lock-${this._dbIdentifier}`, { signal: abortController.signal }, () => { + this.pendingAbortControllers.delete(abortController); + if (timoutId) { + clearTimeout(timoutId); + } + return callback(); + }); } async readTransaction(fn: (tx: Transaction) => Promise, options?: DBLockOptions | undefined): Promise { @@ -286,6 +325,7 @@ export class LockedAsyncDatabaseAdapter */ private _execute = async (sql: string, bindings?: any[]): Promise => { await this.waitForInitialized(); + const result = await this.baseDB.execute(sql, bindings); return { ...result, diff --git a/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts b/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts index dd059139b..1fe0177a7 100644 --- a/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts +++ b/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts @@ -207,6 +207,8 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem async dispose(): Promise { await this.waitForReady(); + await super.dispose(); + await new Promise((resolve) => { // Listen for the close acknowledgment from the worker this.messagePort.addEventListener('message', (event) => { diff --git a/packages/web/src/worker/sync/SharedSyncImplementation.ts b/packages/web/src/worker/sync/SharedSyncImplementation.ts index 84c1a9b52..c85947439 100644 --- a/packages/web/src/worker/sync/SharedSyncImplementation.ts +++ b/packages/web/src/worker/sync/SharedSyncImplementation.ts @@ -297,7 +297,6 @@ export class SharedSyncImplementation }); const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0; - return { shouldReconnect, trackedPort @@ -473,10 +472,8 @@ export class SharedSyncImplementation */ private async _testUpdateAllStatuses(status: SyncStatusOptions) { if (!this.connectionManager.syncStreamImplementation) { - // This is just for testing purposes - this.connectionManager.syncStreamImplementation = this.generateStreamingImplementation(); + throw new Error('Cannot update status without a sync stream implementation'); } - // Only assigning, don't call listeners for this test this.connectionManager.syncStreamImplementation!.syncStatus = new SyncStatus(status); this.updateAllStatuses(status); diff --git a/packages/web/tests/multiple_instances.test.ts b/packages/web/tests/multiple_instances.test.ts index f87851e15..739aaf4ca 100644 --- a/packages/web/tests/multiple_instances.test.ts +++ b/packages/web/tests/multiple_instances.test.ts @@ -131,11 +131,12 @@ describe('Multiple Instances', { sequential: true }, () => { await connector1.uploadData(db); }, identifier, + retryDelayMs: 90_000, // Large delay to allow for testing db: db.database as WebDBAdapter }; const stream1 = new SharedWebStreamingSyncImplementation(syncOptions1); - + await stream1.connect(); // Generate the second streaming sync implementation const connector2 = new TestConnector(); const syncOptions2: SharedWebStreamingSyncImplementationOptions = { @@ -187,20 +188,25 @@ describe('Multiple Instances', { sequential: true }, () => { triggerUpload1 = resolve; }); - // Create the first streaming client - const stream1 = new SharedWebStreamingSyncImplementation({ + const sharedSyncOptions = { adapter: new SqliteBucketStorage(db.database), remote: new WebRemote(connector1), - uploadCrud: async () => { - triggerUpload1(); - connector1.uploadData(db); - }, db: db.database as WebDBAdapter, identifier, - retryDelayMs: 100, + // The large delay here allows us to test between connection retries + retryDelayMs: 90_000, flags: { broadcastLogs: true } + }; + + // Create the first streaming client + const stream1 = new SharedWebStreamingSyncImplementation({ + ...sharedSyncOptions, + uploadCrud: async () => { + triggerUpload1(); + connector1.uploadData(db); + } }); // Generate the second streaming sync implementation @@ -215,18 +221,11 @@ describe('Multiple Instances', { sequential: true }, () => { }); const stream2 = new SharedWebStreamingSyncImplementation({ - adapter: new SqliteBucketStorage(db.database), - remote: new WebRemote(connector1), + ...sharedSyncOptions, uploadCrud: async () => { triggerUpload2(); connector2.uploadData(db); - }, - identifier, - retryDelayMs: 100, - flags: { - broadcastLogs: true - }, - db: db.database as WebDBAdapter + } }); // Waits for the stream to be marked as connected @@ -242,7 +241,9 @@ describe('Multiple Instances', { sequential: true }, () => { }); // hack to set the status to connected for tests - (stream1 as any)['_testUpdateStatus'](new SyncStatus({ connected: true })); + await stream1.connect(); + // Hack, set the status to connected in order to trigger the upload + await (stream1 as any)['_testUpdateStatus'](new SyncStatus({ connected: true })); // The status in the second stream client should be updated await stream2UpdatedPromise; @@ -255,7 +256,6 @@ describe('Multiple Instances', { sequential: true }, () => { 'steven@journeyapps.com' ]); - // Manual trigger since tests don't entirely configure watches for ps_crud stream1.triggerCrudUpload(); // The second connector should be called to upload await upload2TriggeredPromise; @@ -266,12 +266,13 @@ describe('Multiple Instances', { sequential: true }, () => { // Close the second client, leaving only the first one await stream2.dispose(); + // Hack, set the status to connected in order to trigger the upload + await (stream1 as any)['_testUpdateStatus'](new SyncStatus({ connected: true })); stream1.triggerCrudUpload(); // It should now upload from the first client await upload1TriggeredPromise; expect(spy1).toHaveBeenCalledOnce(); - await stream1.dispose(); }); }); diff --git a/packages/web/tests/watch.test.ts b/packages/web/tests/watch.test.ts index 6aea2211d..b9511e277 100644 --- a/packages/web/tests/watch.test.ts +++ b/packages/web/tests/watch.test.ts @@ -1,8 +1,15 @@ -import { AbstractPowerSyncDatabase } from '@powersync/common'; +import { + AbstractPowerSyncDatabase, + ArrayComparator, + GetAllQuery, + QueryResult, + WatchedQueryDifferential, + WatchedQueryState +} from '@powersync/common'; import { PowerSyncDatabase } from '@powersync/web'; import { v4 as uuid } from 'uuid'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { testSchema } from './utils/testDb'; +import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'; +import { TestDatabase, testSchema } from './utils/testDb'; vi.useRealTimers(); /** @@ -263,6 +270,204 @@ describe('Watch Tests', { sequential: true }, () => { expect(receivedCustomersUpdatesCount).equals(1); }); + it('should allow overriding table dependencies', async () => { + const assetsAbortController = new AbortController(); + + type CustomerAssetJoin = TestDatabase['assets'] & { customer_name: string; customer_email: string }; + const results: CustomerAssetJoin[][] = []; + + const onWatchAssets = (resultSet: CustomerAssetJoin[]) => { + results.push(resultSet); + }; + + const { id: customerId } = await powersync.get<{ id: string }>(`SELECT uuid() as id`); + + await powersync.execute( + /* sql */ ` + INSERT INTO + customers (id, name, email) + VALUES + (?, ?, ?) + `, + [customerId, 'bob', 'bob@powersync.com'] + ); + + await powersync.execute( + /* sql */ ` + INSERT into + assets (id, make, model, customer_id) + VALUES + (uuid (), 'sync_engine', 'powersync', ?) + `, + [customerId] + ); + + powersync.watch( + /* sql */ + ` + SELECT + assets.make, + assets.model, + assets.serial_number, + customers.name AS customer_name, + customers.email AS customer_email + FROM + assets + LEFT JOIN customers ON assets.customer_id = customers.id; + `, + [], + { onResult: (r) => onWatchAssets(r.rows?._array ?? []) }, + { + signal: assetsAbortController.signal, + // Only trigger on changes to the customers table + tables: ['customers'], + throttleMs: 0 + } + ); + + await vi.waitFor( + () => { + expect(results.length).eq(1); + expect(results[0].length).eq(1); + }, + { + timeout: 1000 + } + ); + + // Do an update on the assets table, this should not trigger the watched query + // due to the override + for (let attemptCount = 0; attemptCount < 5; attemptCount++) { + await powersync.execute( + /* sql */ ` + INSERT into + assets (id, make, model, customer_id) + VALUES + (uuid (), 'sync_engine', 'powersync_v2', ?) + `, + [customerId] + ); + // Give some time for watched queries to fire (if they need to) + await new Promise((r) => setTimeout(r, 100)); + } + + // now trigger an update on the customers table, this should update the watched query + await powersync.execute( + /* sql */ ` + INSERT INTO + customers (id, name, email) + VALUES + (uuid (), ?, ?) + `, + ['test', 'test@powersync.com'] + ); + + await vi.waitFor( + () => { + expect(results.length).eq(2); + }, + { timeout: 1000 } + ); + }); + + it('should allow overriding table dependencies (query api)', async () => { + const { id: customerId } = await powersync.get<{ id: string }>(`SELECT uuid() as id`); + + await powersync.execute( + /* sql */ ` + INSERT INTO + customers (id, name, email) + VALUES + (?, ?, ?) + `, + [customerId, 'bob', 'bob@powersync.com'] + ); + + await powersync.execute( + /* sql */ ` + INSERT into + assets (id, make, model, customer_id) + VALUES + (uuid (), 'sync_engine', 'powersync', ?) + `, + [customerId] + ); + + type CustomerAssetJoin = TestDatabase['assets'] & { customer_name: string; customer_email: string }; + const results: CustomerAssetJoin[][] = []; + + const query = powersync + .query({ + sql: + /* sql */ + ` + SELECT + assets.make, + assets.model, + assets.serial_number, + customers.name AS customer_name, + customers.email AS customer_email + FROM + assets + LEFT JOIN customers ON assets.customer_id = customers.id; + ` + }) + .watch({ + triggerOnTables: ['customers'], + throttleMs: 0 + }); + + query.registerListener({ + onData: (data) => { + results.push([...data]); + } + }); + + await vi.waitFor( + () => { + expect(results.length).eq(1); + expect(results[0].length).eq(1); + }, + { + timeout: 1000 + } + ); + + // Do an update on the assets table, this should not trigger the watched query + // due to the override + for (let attemptCount = 0; attemptCount < 5; attemptCount++) { + await powersync.execute( + /* sql */ ` + INSERT into + assets (id, make, model, customer_id) + VALUES + (uuid (), 'sync_engine', 'powersync_v2', ?) + `, + [customerId] + ); + // Give some time for watched queries to fire (if they need to) + await new Promise((r) => setTimeout(r, 100)); + } + + // now trigger an update on the customers table, this should update the watched query + await powersync.execute( + /* sql */ ` + INSERT INTO + customers (id, name, email) + VALUES + (uuid (), ?, ?) + `, + ['test', 'test@powersync.com'] + ); + + await vi.waitFor( + () => { + expect(results.length).eq(2); + }, + { timeout: 1000 } + ); + }); + it('should handle watch onError callback', async () => { const abortController = new AbortController(); const onResult = () => {}; // no-op @@ -281,9 +486,10 @@ describe('Watch Tests', { sequential: true }, () => { { signal: abortController.signal, throttleMs: throttleDuration } ); }); - abortController.abort(); await receivedError; + abortController.abort(); + expect(receivedErrorCount).equals(1); }); @@ -323,4 +529,620 @@ describe('Watch Tests', { sequential: true }, () => { expect(receivedWithManagedOverflowCount).greaterThan(2); expect(receivedWithManagedOverflowCount).toBeLessThanOrEqual(4); }); + + it('should stream watch results', async () => { + const watch = powersync + .query({ + sql: 'SELECT * FROM assets', + parameters: [] + }) + .watch(); + + const getNextState = () => + new Promise>((resolve) => { + const dispose = watch.registerListener({ + onStateChange: (state) => { + dispose(); + resolve(state); + } + }); + }); + + let state = watch.state; + expect(state.isFetching).true; + expect(state.isLoading).true; + + state = await getNextState(); + expect(state.isFetching).false; + expect(state.isLoading).false; + + const nextStatePromise = getNextState(); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + state = await nextStatePromise; + expect(state!.isFetching).true; + + state = await getNextState(); + expect(state.isFetching).false; + expect(state.data).toHaveLength(1); + }); + + it('should compare results with old watch method', async () => { + const controller = new AbortController(); + + const resultSets: QueryResult[] = []; + + // Wait for the first query load + await new Promise((resolve) => { + powersync.watch( + 'SELECT * FROM assets WHERE make = ?', + ['test'], + { + onResult: (result) => { + // Mark that we received the first result, this helps with counting events. + resolve(); + resultSets.push(result); + } + }, + { + signal: controller.signal, + comparator: { + checkEquality: (current, previous) => { + return JSON.stringify(current) === JSON.stringify(previous); + } + } + } + ); + }); + + onTestFinished(() => controller.abort()); + + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + + await vi.waitFor( + () => { + expect(resultSets[resultSets.length - 1]?.rows?._array?.map((r) => r.make)).deep.eq(['test', 'test']); + // We should only have updated less than or equal 3 times + expect(resultSets.length).lessThanOrEqual(3); + }, + { timeout: 1000, interval: 100 } + ); + }); + + it('should only report updates for relevant changes', async () => { + const watch = powersync + .query<{ make: string }>({ + sql: 'SELECT * FROM assets where make = ?', + parameters: ['test'] + }) + .watch({ + comparator: new ArrayComparator({ + compareBy: (item) => JSON.stringify(item) + }) + }); + + let notificationCount = 0; + const dispose = watch.registerListener({ + onData: () => { + notificationCount++; + } + }); + onTestFinished(dispose); + + // Should only trigger for this operation + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + + // Should not trigger for these operations + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make1', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make2', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make3', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make4', uuid()]); + + // The initial result with no data is equal to the default state/ + // We should only receive one notification when the data is updated + expect(notificationCount).equals(1); + expect(watch.state.data).toHaveLength(1); + }); + + it('should not report fetching status', async () => { + const watch = powersync + .query({ + sql: 'SELECT * FROM assets where make = ?', + parameters: ['test'] + }) + .watch({ + reportFetching: false + }); + + expect(watch.state.isFetching).false; + + let notificationCount = 0; + const dispose = watch.registerListener({ + onStateChange: () => { + notificationCount++; + } + }); + onTestFinished(dispose); + + // Should only a state change trigger for this operation + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + + // Should not trigger any state change for these operations + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make1', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make2', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make3', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['make4', uuid()]); + + // The initial result with no data is equal to the default state/ + // We should only receive one notification when the data is updated + expect(notificationCount).equals(1); + expect(watch.state.data).toHaveLength(1); + }); + + it('should allow updating queries', async () => { + // Create sample data + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]); + await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['nottest', uuid()]); + + const watch = powersync + .query<{ make: string }>({ + sql: 'SELECT * FROM assets where make = ?', + parameters: ['test'] + }) + .watch({ + reportFetching: false + }); + + expect(watch.state.isFetching).false; + + await vi.waitFor( + () => { + expect(watch.state.isLoading).false; + }, + { timeout: 1000 } + ); + + expect(watch.state.data).toHaveLength(1); + expect(watch.state.data[0].make).equals('test'); + + await watch.updateSettings({ + query: new GetAllQuery<{ make: string }>({ + sql: 'SELECT * FROM assets where make = ?', + parameters: ['nottest'] + }) + }); + + expect(watch.state.isLoading).false; + expect(watch.state.isFetching).true; + + await vi.waitFor( + () => { + expect(watch.state.isFetching).false; + }, + { timeout: 1000 } + ); + + expect(watch.state.data).toHaveLength(1); + expect(watch.state.data[0].make).equals('nottest'); + }); + + it('should report differential query results', async () => { + const watch = powersync + .query({ + sql: /* sql */ ` + SELECT + * + FROM + assets + `, + mapper: (raw) => { + return { + id: raw.id as string, + make: raw.make as string + }; + } + }) + .differentialWatch(); + + const diffs: WatchedQueryDifferential<{ id: string; make: string }>[] = []; + + watch.registerListener({ + onDiff: (diff) => { + diffs.push(diff); + } + }); + + // Create sample data + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test1', uuid()] + ); + + await vi.waitFor( + () => { + expect(diffs[0].added[0]?.make).equals('test1'); + }, + { timeout: 1000 } + ); + + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test2', uuid()] + ); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(2); + // This should now reflect that we had one change since the last event + expect(diffs[1].added).toHaveLength(1); + expect(diffs[1].added[0]?.make).equals('test2'); + + expect(diffs[1].removed).toHaveLength(0); + expect(diffs[1].all).toHaveLength(2); + }, + { timeout: 1000 } + ); + + await powersync.execute( + /* sql */ ` + DELETE FROM assets + WHERE + make = ? + `, + ['test2'] + ); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(3); + expect(diffs[2].added).toHaveLength(0); + expect(diffs[2].all).toHaveLength(1); + expect(diffs[2].unchanged).toHaveLength(1); + expect(diffs[2].unchanged[0]?.make).equals('test1'); + + expect(diffs[2].removed).toHaveLength(1); + expect(diffs[2].removed[0]?.make).equals('test2'); + }, + { timeout: 1000 } + ); + }); + + it('should report differential query results with a custom comparator', async () => { + const watch = powersync + .query({ + sql: /* sql */ ` + SELECT + * + FROM + assets + `, + mapper: (raw) => { + return { + id: raw.id as string, + make: raw.make as string + }; + } + }) + .differentialWatch({ + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } + }); + + // Create sample data + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test1', uuid()] + ); + + const diffs: WatchedQueryDifferential<{ id: string; make: string }>[] = []; + + watch.registerListener({ + onDiff: (diff) => { + diffs.push(diff); + } + }); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(1); + expect(diffs[0].added[0]?.make).equals('test1'); + }, + { timeout: 1000 } + ); + + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test2', uuid()] + ); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(2); + // This should now reflect that we had one change since the last event + expect(diffs[1].added).toHaveLength(1); + expect(diffs[1].added[0]?.make).equals('test2'); + + expect(diffs[1].removed).toHaveLength(0); + expect(diffs[1].all).toHaveLength(2); + }, + { timeout: 1000 } + ); + }); + + it('should preserve object references in result set', async () => { + // Sort the results by the `make` column in ascending order + const watch = powersync + .query({ + sql: /* sql */ ` + SELECT + * + FROM + assets + ORDER BY + make ASC; + `, + mapper: (raw) => { + return { + id: raw.id as string, + make: raw.make as string + }; + } + }) + .differentialWatch({ + rowComparator: { + keyBy: (item) => item.id, + compareBy: (item) => JSON.stringify(item) + } + }); + + // Create sample data + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, uuid ()), + (uuid (), ?, uuid ()), + (uuid (), ?, uuid ()) + `, + ['a', 'b', 'd'] + ); + + await vi.waitFor( + () => { + expect(watch.state.data.map((i) => i.make)).deep.equals(['a', 'b', 'd']); + }, + { timeout: 1000 } + ); + + const initialData = watch.state.data; + + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, uuid ()) + `, + ['c'] + ); + + await vi.waitFor( + () => { + expect(watch.state.data).toHaveLength(4); + }, + { timeout: 1000 } + ); + + console.log(JSON.stringify(watch.state.data)); + expect(initialData[0] == watch.state.data[0]).true; + expect(initialData[1] == watch.state.data[1]).true; + // The index after the insert should also still be the same ref as the previous item + expect(initialData[2] == watch.state.data[3]).true; + }); + + it('should report differential query results from initial state', async () => { + /** + * Differential queries start with a placeholder data. We run a watched query under the hood + * which triggers initially and for each change to underlying tables. + * Changes are calculated based on the initial state and the current state. + * The default empty differential state will result in the initial watch query reporting + * all results as added. + * We can perform relative differential queries by providing a placeholder data + * which is the initial state of the query. + */ + + // Create sample data + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test1', uuid()] + ); + + const watch = powersync + .query({ + sql: /* sql */ ` + SELECT + * + FROM + assets + `, + mapper: (raw) => { + return { + id: raw.id as string, + make: raw.make as string + }; + } + }) + .differentialWatch({ + placeholderData: + // Fetch the initial state as a baseline before creating the watch. + // Any changes after this state will be reported as changes. + await powersync.getAll(`SELECT * FROM assets`) + }); + + // It should have the initial value + expect(watch.state.data).toHaveLength(1); + + const diffs: WatchedQueryDifferential<{ id: string; make: string }>[] = []; + + watch.registerListener({ + onDiff: (diff) => { + diffs.push(diff); + } + }); + + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test2', uuid()] + ); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(1); + // This should now reflect that we had one change since the last event + expect(diffs[0].added).toHaveLength(1); + expect(diffs[0].added[0]?.make).equals('test2'); + + expect(diffs[0].removed).toHaveLength(0); + expect(diffs[0].all).toHaveLength(2); + }, + { timeout: 1000 } + ); + + await powersync.execute( + /* sql */ ` + DELETE FROM assets + WHERE + make = ? + `, + ['test2'] + ); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(2); + expect(diffs[1].added).toHaveLength(0); + expect(diffs[1].all).toHaveLength(1); + expect(diffs[1].unchanged).toHaveLength(1); + expect(diffs[1].unchanged[0]?.make).equals('test1'); + + expect(diffs[1].removed).toHaveLength(1); + expect(diffs[1].removed[0]?.make).equals('test2'); + }, + { timeout: 1000 } + ); + }); + + it('should report differential query results changed rows', async () => { + // Create sample data + await powersync.execute( + /* sql */ ` + INSERT INTO + assets (id, make, customer_id) + VALUES + (uuid (), ?, ?) + `, + ['test1', uuid()] + ); + + const watch = powersync + .query({ + sql: /* sql */ ` + SELECT + * + FROM + assets + `, + mapper: (raw) => { + return { + id: raw.id as string, + make: raw.make as string + }; + } + }) + .differentialWatch(); + + await vi.waitFor( + () => { + // Wait for the data to be loaded + expect(watch.state.data[0]?.make).equals('test1'); + }, + { timeout: 1000, interval: 100 } + ); + + const diffs: WatchedQueryDifferential<{ id: string; make: string }>[] = []; + + watch.registerListener({ + onDiff: (diff) => { + diffs.push(diff); + } + }); + + await powersync.execute( + /* sql */ ` + UPDATE assets + SET + make = ? + WHERE + make = ? + `, + ['test2', 'test1'] + ); + + await vi.waitFor( + () => { + expect(diffs).toHaveLength(1); + expect(diffs[0].added).toHaveLength(0); + const updated = diffs[0].updated[0]; + + // The update should contain previous and current values of changed rows + expect(updated).toBeDefined(); + expect(updated.previous.make).equals('test1'); + expect(updated.current.make).equals('test2'); + + expect(diffs[0].removed).toHaveLength(0); + expect(diffs[0].all).toHaveLength(1); + }, + { timeout: 1000 } + ); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53ef7e9e0..dee2c0bfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^4.0.2 version: 4.0.14(@pnpm/logger@5.2.0) '@vitest/browser': - specifier: ^3.0.8 - version: 3.2.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.15.29)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))(vitest@3.2.4) + specifier: ^3.2.4 + version: 3.2.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.15.29)(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 @@ -32,11 +32,17 @@ importers: prettier: specifier: ^3.2.5 version: 3.5.3 + prettier-plugin-embed: + specifier: ^0.4.15 + version: 0.4.15(babel-plugin-macros@3.1.0) + prettier-plugin-sql: + specifier: ^0.18.1 + version: 0.18.1(prettier@3.5.3) typescript: specifier: ^5.7.2 version: 5.8.3 vitest: - specifier: ^3.0.8 + specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@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/angular-supabase-todolist: @@ -89,10 +95,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@22.15.29)(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@22.15.29))(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@22.15.29)(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@22.15.29)(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@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.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@22.15.29)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(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@22.15.29)(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@22.15.29))(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@22.15.29)(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@22.15.29)(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@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.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@22.15.29)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(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@22.15.29)(chokidar@4.0.3) @@ -119,7 +125,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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/vector-icons': specifier: ^14.0.0 - version: 14.1.0(kpdfmw6ivudhnfw6o4uluiluqi) + version: 14.1.0(fdc5ce3de8aabd6226c79743411018d7) '@journeyapps/react-native-quick-sqlite': specifier: ^2.4.6 version: 2.4.6(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -140,7 +146,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.8.3))(@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(xwon3p5r2ryxkrzljplculi3hm) + version: 7.4.1(62bded36bd875de6ca3ee26c42c5ea03) '@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -164,7 +170,7 @@ importers: version: 2.1.10 expo-router: specifier: 4.0.21 - version: 4.0.21(7pkmiwofdx5cbd6rrboya7mm6y) + version: 4.0.21(e374635bcb9f01385d354b70deac59d5) 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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -212,7 +218,7 @@ importers: version: 10.2.0 react-navigation-stack: specifier: ^2.10.4 - version: 2.10.4(n5q7nzlozgkktehjjhku7iswqa) + version: 2.10.4(cc782526f6f527a9fd49628df4caf975) typed-async-storage: specifier: ^3.1.2 version: 3.1.2 @@ -522,7 +528,7 @@ importers: version: 0.15.0 next: specifier: 14.2.3 - version: 14.2.3(@babel/core@7.26.10)(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)(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 @@ -544,10 +550,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 @@ -562,13 +568,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.8.3)) + 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.8.3)) demos/example-node: dependencies: @@ -635,10 +641,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 @@ -781,7 +787,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@22.15.29))(prettier@3.5.3)(typescript@5.8.3) + version: 0.77.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.8.3)))(prettier@3.5.3)(typescript@5.8.3) '@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)) @@ -865,7 +871,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)) @@ -938,7 +944,7 @@ importers: version: 1.0.2 '@expo/vector-icons': specifier: ^14.0.3 - version: 14.1.0(kpdfmw6ivudhnfw6o4uluiluqi) + version: 14.1.0(fdc5ce3de8aabd6226c79743411018d7) '@journeyapps/react-native-quick-sqlite': specifier: ^2.4.6 version: 2.4.6(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -959,7 +965,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.8.3))(@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(xwon3p5r2ryxkrzljplculi3hm) + version: 7.4.1(62bded36bd875de6ca3ee26c42c5ea03) '@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -983,7 +989,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.8.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.8.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.8.3))(@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(iufejmpajqz4jjoldpycss6ycq) + version: 16.0.18(3cdcf7b8e47f65c9a4496cca30210857) 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -998,7 +1004,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.8.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.8.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.8.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.8.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(7pkmiwofdx5cbd6rrboya7mm6y) + version: 4.0.21(e374635bcb9f01385d354b70deac59d5) 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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -1040,7 +1046,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.8.3))(@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(n5q7nzlozgkktehjjhku7iswqa) + version: 2.10.4(cc782526f6f527a9fd49628df4caf975) devDependencies: '@babel/core': specifier: ^7.26.10 @@ -1077,7 +1083,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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/vector-icons': specifier: ^14.0.2 - version: 14.1.0(kpdfmw6ivudhnfw6o4uluiluqi) + version: 14.1.0(fdc5ce3de8aabd6226c79743411018d7) '@journeyapps/react-native-quick-sqlite': specifier: ^2.4.6 version: 2.4.6(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -1104,7 +1110,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.8.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.8.3))(@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.8.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.8.3))(@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(xwon3p5r2ryxkrzljplculi3hm) + version: 7.4.1(62bded36bd875de6ca3ee26c42c5ea03) '@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -1125,7 +1131,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.8.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.8.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.8.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.8.3))(@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(iufejmpajqz4jjoldpycss6ycq) + version: 16.0.18(3cdcf7b8e47f65c9a4496cca30210857) 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -1143,7 +1149,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.8.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.8.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.8.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.8.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(7pkmiwofdx5cbd6rrboya7mm6y) + version: 4.0.21(e374635bcb9f01385d354b70deac59d5) 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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)) @@ -1158,7 +1164,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.8.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.8.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.8.3))(@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(gkhgpojom75kfqjgntjbsh35pm) + version: 4.0.9(c0a3f55e662f74e948e2bd58fcbec8f1) 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -1216,10 +1222,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.8.3)) + 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.8.3)) jest-expo: specifier: ~52.0.3 - version: 52.0.6(ebvt7qw4la2zxeycgbhiqt2c3e) + version: 52.0.6(5ecd6a454ab2aef14ba34b6a53fe2748) react-test-renderer: specifier: 18.3.1 version: 18.3.1(react@18.3.1) @@ -1580,10 +1586,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.8.3) + 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.8.3) '@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.8.3) + 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.8.3) '@mdx-js/react': specifier: ^3.1.0 version: 3.1.0(@types/react@19.1.6)(react@18.3.1) @@ -1602,19 +1608,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.8.3) + 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.8.3) '@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 @@ -1721,7 +1727,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/react@19.1.6)(kysely@0.28.2)(react@19.0.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))(@types/react@19.1.6)(kysely@0.28.2)(react@19.0.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) @@ -1789,7 +1795,7 @@ importers: version: 1.4.2 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/react@19.1.6)(kysely@0.28.2)(react@19.0.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))(@types/react@19.1.6)(kysely@0.28.2)(react@19.0.0) rollup: specifier: 4.14.3 version: 4.14.3 @@ -1797,7 +1803,7 @@ importers: specifier: ^5.5.3 version: 5.8.3 vitest: - specifier: ^3.0.5 + specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@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) packages/powersync-op-sqlite: @@ -1854,18 +1860,27 @@ importers: '@powersync/common': specifier: workspace:* version: link:../common + '@powersync/web': + specifier: workspace:* + version: link:../web '@testing-library/react': specifier: ^15.0.2 version: 15.0.7(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react': specifier: ^18.3.1 version: 18.3.23 + chart.js: + specifier: ^4.5.0 + version: 4.5.0 jsdom: specifier: ^24.0.0 version: 24.1.3 react: specifier: 18.3.1 version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) react-error-boundary: specifier: ^4.1.0 version: 4.1.2(react@18.3.1) @@ -1964,6 +1979,9 @@ importers: '@powersync/common': specifier: workspace:* version: link:../common + '@powersync/web': + specifier: workspace:* + version: link:../web flush-promises: specifier: ^1.0.2 version: 1.0.2 @@ -2006,13 +2024,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 @@ -2194,7 +2212,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)) @@ -2236,7 +2254,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 @@ -2245,7 +2263,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 @@ -2254,7 +2272,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 @@ -5524,6 +5542,9 @@ packages: peerDependencies: tslib: '2' + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} @@ -10483,6 +10504,10 @@ packages: charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + chart.js@4.5.0: + resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==} + engines: {pnpm: '>=8'} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -11457,6 +11482,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + discontinuous-range@1.0.0: + resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -12662,6 +12690,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} @@ -14333,6 +14365,10 @@ packages: resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==} engines: {node: '>=12', npm: '>=6'} + jsox@1.2.123: + resolution: {integrity: sha512-LYordXJ/0Q4G8pUE1Pvh4fkfGvZY7lRe4WIJKl0wr0rtFDVw9lcdNW95GH0DceJ6E9xh41zJNW0vreEz7xOxCw==} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -15218,6 +15254,9 @@ packages: engines: {node: '>=18.18'} hasBin: true + micro-memoize@4.1.3: + resolution: {integrity: sha512-DzRMi8smUZXT7rCGikRwldEh6eO6qzKiPPopcr1+2EY3AYKpy5fu159PKWwIS9A6IWnrvPKDMcuFtyrroZa8Bw==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -15540,6 +15579,9 @@ packages: moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + moo@0.5.2: + resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + moti@0.25.4: resolution: {integrity: sha512-UiH0WcWzUYlUmo8Y1F+iyVW+qVVZ3YkXO3Q/gQUZzOhje6+Q0MdllYfqKW2m5IhFs+Vxt2i+osjvWBxXKK2yOw==} peerDependencies: @@ -15634,6 +15676,10 @@ packages: engines: {node: '>=10'} hasBin: true + nearley@2.20.1: + resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} + hasBin: true + needle@3.3.1: resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} engines: {node: '>= 4.4.x'} @@ -15787,6 +15833,10 @@ packages: resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} engines: {node: '>=6'} + node-sql-parser@4.18.0: + resolution: {integrity: sha512-2YEOR5qlI1zUFbGMLKNfsrR5JUvFg9LxIRVE+xJe962pfVLH0rnItqLzv96XVs1Y1UIR8FxsXAuvX/lYAWZ2BQ==} + engines: {node: '>=8'} + node-stream-zip@1.15.0: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} engines: {node: '>=0.12.0'} @@ -16177,6 +16227,10 @@ packages: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} + package-up@5.0.0: + resolution: {integrity: sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==} + engines: {node: '>=18'} + pacote@20.0.0: resolution: {integrity: sha512-pRjC5UFwZCgx9kUFDVM9YEahv4guZ1nSLqwmWiLUnDbGsjs+U5w7z6Uc8HNR1a6x8qnu5y9xtGE6D1uAuYz+0A==} engines: {node: ^18.17.0 || >=20.5.0} @@ -16901,6 +16955,15 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} + prettier-plugin-embed@0.4.15: + resolution: {integrity: sha512-9pZVIp3bw2jw+Ge+iAMZ4j+sIVC9cPruZ93H2tj5Wa/3YDFDJ/uYyVWdUGfcFUnv28drhW2Bmome9xSGXsPKOw==} + + prettier-plugin-sql@0.18.1: + resolution: {integrity: sha512-2+Nob2sg7hzLAKJoE6sfgtkhBZCqOzrWHZPvE4Kee/e80oOyI4qwy9vypeltqNBJwTtq3uiKPrCxlT03bBpOaw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + prettier: ^3.0.3 + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -17161,9 +17224,16 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + railroad-diagrams@1.0.0: + resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} + rambda@9.4.2: resolution: {integrity: sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==} + randexp@0.4.6: + resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} + engines: {node: '>=0.12'} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -17948,6 +18018,10 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -18558,6 +18632,10 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + sql-formatter@15.6.2: + resolution: {integrity: sha512-ZjqOfJGuB97UeHzTJoTbadlM0h9ynehtSTHNUbGfXR4HZ4rCIoD2oIW91W+A5oE76k8hl0Uz5GD8Sx3Pt9Xa3w==} + hasBin: true + srcset@4.0.0: resolution: {integrity: sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==} engines: {node: '>=12'} @@ -19056,6 +19134,9 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tiny-jsonc@1.0.2: + resolution: {integrity: sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw==} + tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} @@ -20815,11 +20896,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@22.15.29)(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@22.15.29))(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@22.15.29)(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@22.15.29)(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@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.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@22.15.29)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(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@22.15.29)(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@22.15.29)(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@22.15.29))(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@22.15.29)(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@22.15.29)(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@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.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@22.15.29)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(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 @@ -20868,13 +20949,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@22.15.29)(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@22.15.29))(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@22.15.29)(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@22.15.29)(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@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.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@22.15.29)(typescript@5.5.4)))(tsx@4.19.4)(typescript@5.5.4)(vite@6.2.7(@types/node@22.15.29)(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@22.15.29)(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@22.15.29)(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@22.15.29)(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 @@ -20886,14 +20967,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@22.15.29)(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 @@ -20901,38 +20982,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@22.15.29) + 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.5.4)) jest-environment-jsdom: 29.7.0 - tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.8.3)) + tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.5.4)) transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -20956,12 +21037,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 @@ -20992,7 +21073,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@22.15.29)(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@22.15.29)(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@22.15.29)(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) @@ -21028,7 +21109,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.8.3)) + tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.5.4)) transitivePeerDependencies: - '@types/node' - chokidar @@ -22773,7 +22854,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 @@ -22786,7 +22867,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 @@ -22800,34 +22881,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.8.3)': + '@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.8.3)': 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.8.3)(webpack@5.99.9(@swc/core@1.11.29)) + postcss-loader: 7.3.4(postcss@8.5.4)(typescript@5.8.3)(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' @@ -22844,15 +22925,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.8.3)': + '@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.8.3)': 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.8.3) + '@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.8.3) '@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 @@ -22868,7 +22949,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 @@ -22878,7 +22959,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) @@ -22887,9 +22968,9 @@ snapshots: tinypool: 1.1.0 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' @@ -22916,17 +22997,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 @@ -22938,16 +23019,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 @@ -22963,9 +23044,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 @@ -22974,9 +23055,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 @@ -22993,17 +23074,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.8.3))(@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.8.3)': + '@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.8.3))(@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.8.3)': 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.8.3) + '@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.8.3) '@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.8.3) - '@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.8.3))(@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.8.3) + '@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.8.3))(@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 @@ -23015,7 +23096,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' @@ -23035,17 +23116,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.8.3)': + '@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.8.3)': 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.8.3) + '@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.8.3) '@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.8.3))(@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.8.3))(@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 @@ -23056,7 +23137,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' @@ -23076,18 +23157,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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' @@ -23107,11 +23188,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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' @@ -23134,11 +23215,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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) @@ -23163,11 +23244,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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 @@ -23190,11 +23271,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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) @@ -23218,11 +23299,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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 @@ -23245,14 +23326,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.8.3)': + '@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.8.3)': 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.8.3) + '@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.8.3) '@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) @@ -23277,18 +23358,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.8.3)': + '@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.8.3)': 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.8.3) - '@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.8.3) + '@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.8.3) '@svgr/webpack': 8.1.0(typescript@5.8.3) 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' @@ -23308,23 +23389,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.8.3)': - 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.8.3) - '@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.8.3))(@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3))(@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.8.3) - '@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.8.3)': + 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.8.3) + '@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.8.3))(@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3))(@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.8.3) + '@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: @@ -23354,21 +23435,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.8.3)': + '@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.8.3)': 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.8.3) + '@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.8.3) '@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.8.3))(@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.8.3) - '@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.8.3) - '@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.8.3) - '@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.8.3))(@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.8.3))(@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.8.3) + '@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.8.3) + '@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.8.3) + '@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.8.3))(@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 @@ -23403,13 +23484,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.8.3))(@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.8.3))(@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.8.3) - '@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.8.3) + '@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 @@ -23428,16 +23509,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.8.3)': + '@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.8.3)': 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.8.3) + '@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.8.3) '@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.8.3) - '@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.8.3))(@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.8.3) + '@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.8.3))(@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 @@ -23477,7 +23558,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 @@ -23488,7 +23569,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' @@ -23498,7 +23579,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 @@ -23509,7 +23590,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' @@ -23519,9 +23600,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' @@ -23533,11 +23614,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 @@ -23553,14 +23634,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 @@ -23573,9 +23654,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 @@ -25027,13 +25108,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(kpdfmw6ivudhnfw6o4uluiluqi)': + '@expo/vector-icons@14.1.0(fdc5ce3de8aabd6226c79743411018d7)': 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.8.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.8.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.8.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 @@ -25383,7 +25464,77 @@ 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.8.3))': + '@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.8.3))': + 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.8.3)) + 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 + '@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@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 + 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.5.4))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -25397,7 +25548,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.8.3)) + 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.5.4)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -25417,8 +25568,9 @@ 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(@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@22.15.29)(typescript@5.8.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -25432,7 +25584,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@22.15.29)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -25452,6 +25604,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true '@jest/create-cache-key-function@29.7.0': dependencies: @@ -25653,6 +25806,8 @@ snapshots: dependencies: tslib: 2.8.1 + '@kurkle/color@0.3.4': {} + '@leichtgewicht/ip-codec@2.0.5': {} '@lexical/clipboard@0.15.0': @@ -26031,7 +26186,7 @@ snapshots: '@module-federation/manifest': 0.13.1(typescript@5.8.3)(vue-tsc@2.0.6(typescript@5.8.3)) '@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.8.3 @@ -26422,7 +26577,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 @@ -28175,7 +28330,7 @@ snapshots: - supports-color - typescript - '@react-native/eslint-config@0.77.0(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29))(prettier@3.5.3)(typescript@5.8.3)': + '@react-native/eslint-config@0.77.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.8.3)))(prettier@3.5.3)(typescript@5.8.3)': dependencies: '@babel/core': 7.26.10 '@babel/eslint-parser': 7.27.1(@babel/core@7.26.10)(eslint@8.57.1) @@ -28186,7 +28341,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.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29))(typescript@5.8.3) + 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.8.3))(eslint@8.57.1)(typescript@5.8.3))(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.8.3)))(typescript@5.8.3) 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) @@ -28196,7 +28351,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) @@ -28207,7 +28362,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) @@ -28292,9 +28447,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: @@ -28430,7 +28583,23 @@ 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(nyxmcqdttlojx3ihgax6eihdpu)': + '@react-navigation/drawer@7.4.1(62bded36bd875de6ca3ee26c42c5ea03)': + 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + color: 4.2.3 + 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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1) + react-native-drawer-layout: 4.1.10(react-native-gesture-handler@2.20.2(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + react-native-gesture-handler: 2.20.2(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + 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.8.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.8.3))(@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + use-latest-callback: 0.2.3(react@18.3.1) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@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) @@ -28447,22 +28616,6 @@ snapshots: - '@react-native-masked-view/masked-view' optional: true - '@react-navigation/drawer@7.4.1(xwon3p5r2ryxkrzljplculi3hm)': - 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) - color: 4.2.3 - 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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1) - react-native-drawer-layout: 4.1.10(react-native-gesture-handler@2.20.2(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) - react-native-gesture-handler: 2.20.2(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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) - 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.8.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.8.3))(@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) - use-latest-callback: 0.2.3(react@18.3.1) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - '@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)': dependencies: '@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) @@ -28893,11 +29046,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': {} @@ -29248,7 +29403,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 @@ -29263,6 +29418,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: @@ -30805,7 +30961,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 @@ -31342,11 +31498,11 @@ snapshots: - vite optional: true - '@vitest/browser@3.2.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.15.29)(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@22.15.29)(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@22.15.29)(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@22.15.29)(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 @@ -31377,7 +31533,7 @@ snapshots: optionalDependencies: vite: 5.4.19(@types/node@22.15.29)(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@22.15.29)(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@22.15.29)(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 @@ -31541,7 +31697,7 @@ snapshots: vue: 3.4.21(typescript@5.8.3) vue-demi: 0.13.11(vue@3.4.21(typescript@5.8.3)) - '@vuetify/loader-shared@2.1.0(vue@3.4.21(typescript@5.8.3))(vuetify@3.6.8(typescript@5.8.3)(vite-plugin-vuetify@2.1.1)(vue@3.4.21(typescript@5.8.3)))': + '@vuetify/loader-shared@2.1.0(vue@3.4.21(typescript@5.8.3))(vuetify@3.6.8)': dependencies: upath: 2.0.1 vue: 3.4.21(typescript@5.8.3) @@ -31672,17 +31828,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) @@ -31695,10 +31851,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': {} @@ -32196,19 +32352,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: @@ -32217,13 +32373,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 @@ -32929,6 +33078,10 @@ snapshots: charenc@0.0.2: {} + chart.js@4.5.0: + dependencies: + '@kurkle/color': 0.3.4 + check-error@2.1.1: {} cheerio-select@2.1.0: @@ -33301,7 +33454,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 @@ -33309,9 +33462,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 @@ -33432,13 +33585,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.8.3)): + 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.8.3)): 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.8.3)) + 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.8.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -33447,13 +33600,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: @@ -33462,6 +33615,38 @@ 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(@swc/core@1.11.29)(@types/node@22.15.29)(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@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.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@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.8.3)): + 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(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.8.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + create-require@1.1.1: {} crelt@1.0.6: {} @@ -33551,7 +33736,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 @@ -33562,10 +33747,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 @@ -33576,10 +33761,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 @@ -33590,7 +33775,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)): @@ -33604,10 +33789,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) @@ -33615,7 +33800,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 @@ -33967,10 +34152,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) @@ -33982,7 +34167,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 @@ -34007,7 +34192,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' @@ -34049,6 +34234,8 @@ snapshots: dependencies: path-type: 4.0.0 + discontinuous-range@1.0.0: {} + dlv@1.1.3: {} dns-packet@5.6.1: @@ -34138,7 +34325,7 @@ 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/react@19.1.6)(kysely@0.28.2)(react@19.0.0): + drizzle-orm@0.35.3(@libsql/client-wasm@0.15.8)(@op-engineering/op-sqlite@14.0.2(react-native@0.78.0(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli-server-api@15.1.3)(@types/react@19.1.6)(react@19.0.0))(react@19.0.0))(@types/react@19.1.6)(kysely@0.28.2)(react@19.0.0): dependencies: '@libsql/client-wasm': 0.15.8 optionalDependencies: @@ -34917,24 +35104,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.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29))(typescript@5.8.3): + 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.8.3))(eslint@8.57.1)(typescript@5.8.3))(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.8.3)))(typescript@5.8.3): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) - jest: 29.7.0(@types/node@22.15.29) + 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.8.3)) transitivePeerDependencies: - supports-color - typescript @@ -35362,7 +35549,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.8.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.8.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.8.3))(@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(iufejmpajqz4jjoldpycss6ycq): + expo-camera@16.0.18(3cdcf7b8e47f65c9a4496cca30210857): 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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) invariant: 2.2.4 @@ -35522,29 +35709,29 @@ snapshots: dependencies: invariant: 2.2.4 - expo-router@4.0.21(7pkmiwofdx5cbd6rrboya7mm6y): + 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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) + '@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 '@radix-ui/react-slot': 1.0.1(react@18.3.1) - '@react-navigation/bottom-tabs': 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.8.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.8.3))(@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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) - '@react-navigation/native-stack': 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.8.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.8.3))(@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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + '@react-navigation/bottom-tabs': 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.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-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.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) + '@react-navigation/native-stack': 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.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-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.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) client-only: 0.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.8.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.8.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.8.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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) - expo-linking: 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.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.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) + 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)) + expo-linking: 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) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-native-helmet-async: 2.0.4(react@18.3.1) - react-native-is-edge-to-edge: 1.1.7(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.8.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.8.3))(@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + react-native-is-edge-to-edge: 1.1.7(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-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.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) schema-utils: 4.3.2 semver: 7.6.3 server-only: 0.0.1 optionalDependencies: - '@react-navigation/drawer': 7.4.1(xwon3p5r2ryxkrzljplculi3hm) - 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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + '@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' - react @@ -35552,29 +35739,29 @@ snapshots: - react-native - supports-color - expo-router@4.0.21(cpo3xaw6yrjernjvkkkt7bisia): + expo-router@4.0.21(e374635bcb9f01385d354b70deac59d5): 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/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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) '@expo/server': 0.5.3 '@radix-ui/react-slot': 1.0.1(react@18.3.1) - '@react-navigation/bottom-tabs': 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.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-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.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) - '@react-navigation/native-stack': 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.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-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.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/bottom-tabs': 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.8.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.8.3))(@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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + '@react-navigation/native-stack': 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.8.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.8.3))(@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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) client-only: 0.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.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) - 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)) - expo-linking: 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: 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.8.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.8.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.8.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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) + expo-linking: 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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-native-helmet-async: 2.0.4(react@18.3.1) - react-native-is-edge-to-edge: 1.1.7(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-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.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + react-native-is-edge-to-edge: 1.1.7(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.8.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.8.3))(@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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) schema-utils: 4.3.2 semver: 7.6.3 server-only: 0.0.1 optionalDependencies: - '@react-navigation/drawer': 7.4.1(nyxmcqdttlojx3ihgax6eihdpu) - react-native-reanimated: 3.16.7(@babel/core@7.26.10)(react-native@0.76.9(@babel/core@7.26.10)(@babel/preset-env@7.27.2(@babel/core@7.26.10))(@react-native-community/cli@15.1.3(typescript@5.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + '@react-navigation/drawer': 7.4.1(62bded36bd875de6ca3ee26c42c5ea03) + 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.8.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' - react @@ -35615,7 +35802,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.8.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.8.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.8.3))(@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(gkhgpojom75kfqjgntjbsh35pm): + expo-system-ui@4.0.9(c0a3f55e662f74e948e2bd58fcbec8f1): dependencies: '@react-native/normalize-colors': 0.76.8 debug: 4.4.1(supports-color@8.1.1) @@ -35643,7 +35830,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)) @@ -35679,7 +35866,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(kpdfmw6ivudhnfw6o4uluiluqi) + '@expo/vector-icons': 14.1.0(fdc5ce3de8aabd6226c79743411018d7) 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.8.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.8.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.8.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.8.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.8.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.8.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.8.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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)) @@ -35880,11 +36067,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: {} @@ -35966,6 +36153,8 @@ snapshots: find-root@1.1.0: {} + find-up-simple@1.0.1: {} + find-up@2.1.0: dependencies: locate-path: 2.0.0 @@ -36756,7 +36945,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 @@ -36764,11 +36953,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 @@ -36776,10 +36964,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 @@ -36787,8 +36975,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: @@ -37492,16 +37692,16 @@ 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.8.3)): + 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.8.3)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.8.3)) + '@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.8.3)) '@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.8.3)) + 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.8.3)) 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.8.3)) + 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.8.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -37511,16 +37711,35 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.15.29): + 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.8.3)) + '@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@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)) 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@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 + 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.5.4)): + dependencies: + '@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.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(@swc/core@1.11.29)(@types/node@22.15.29)(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(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.5.4)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -37531,16 +37750,16 @@ snapshots: - ts-node optional: true - 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@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.8.3)): 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@22.15.29)(typescript@5.8.3)) '@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@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.8.3)) 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@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.8.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -37549,8 +37768,71 @@ snapshots: - babel-plugin-macros - supports-color - 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(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.8.3)): + 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.8.3) + 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 + '@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@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@20.17.57)(typescript@5.8.3)): + 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.5.4)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -37576,12 +37858,13 @@ 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.8.3) + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.5.4) transitivePeerDependencies: - babel-plugin-macros - supports-color + 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@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@22.15.29)(typescript@5.8.3)): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -37607,12 +37890,13 @@ 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@22.15.29)(typescript@5.8.3) 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@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 @@ -37638,11 +37922,75 @@ 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@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.5.4)): + 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': 22.15.29 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(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(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.8.3)): + 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': 22.15.29 + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -37662,7 +38010,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 @@ -37675,7 +38023,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: @@ -37705,7 +38053,7 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@52.0.6(ebvt7qw4la2zxeycgbhiqt2c3e): + jest-expo@52.0.6(5ecd6a454ab2aef14ba34b6a53fe2748): dependencies: '@expo/config': 10.0.11 '@expo/json-file': 9.1.4 @@ -37718,11 +38066,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.8.3))) + 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.8.3))) 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.8.3))(@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 @@ -37924,11 +38272,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.8.3))): + 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.8.3))): 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.8.3)) + 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.8.3)) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -37959,24 +38307,36 @@ 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.8.3)): + 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.8.3)): + 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.8.3)) + '@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.8.3)) + 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.8.3)) + '@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.8.3)) + 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): + 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.5.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.8.3)) + '@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.5.4)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.15.29) + 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.5.4)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -37984,17 +38344,18 @@ snapshots: - ts-node optional: true - 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.8.3)): 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@22.15.29)(typescript@5.8.3)) '@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@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.8.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node + optional: true jimp-compact@0.16.1: {} @@ -38223,6 +38584,8 @@ snapshots: ms: 2.1.3 semver: 7.5.4 + jsox@1.2.123: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -38323,11 +38686,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: @@ -38361,7 +38724,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: @@ -39725,6 +40088,8 @@ snapshots: - supports-color - utf-8-validate + micro-memoize@4.1.3: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.1.0 @@ -40068,17 +40433,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: @@ -40213,6 +40578,8 @@ snapshots: moment@2.30.1: optional: true + moo@0.5.2: {} + moti@0.25.4(react-dom@18.3.1(react@18.3.1))(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))(react@18.3.1): dependencies: framer-motion: 6.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -40319,6 +40686,13 @@ snapshots: split2: 3.2.2 through2: 4.0.2 + nearley@2.20.1: + dependencies: + commander: 2.20.3 + moo: 0.5.2 + railroad-diagrams: 1.0.0 + randexp: 0.4.6 + needle@3.3.1: dependencies: iconv-lite: 0.6.3 @@ -40335,7 +40709,7 @@ snapshots: nested-error-stacks@2.0.1: {} - next@14.2.3(@babel/core@7.26.10)(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)(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 @@ -40345,7 +40719,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.10)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.3 '@next/swc-darwin-x64': 14.2.3 @@ -40489,6 +40863,10 @@ snapshots: long-timeout: 0.1.1 sorted-array-functions: 1.3.0 + node-sql-parser@4.18.0: + dependencies: + big-integer: 1.6.52 + node-stream-zip@1.15.0: {} nopt@6.0.0: @@ -40605,11 +40983,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: {} @@ -40899,6 +41277,10 @@ snapshots: registry-url: 6.0.1 semver: 7.7.2 + package-up@5.0.0: + dependencies: + find-up-simple: 1.0.1 + pacote@20.0.0: dependencies: '@npmcli/git': 6.0.3 @@ -41303,32 +41685,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.8.3)): + 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.8.3)): 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.8.3) + ts-node: 10.9.2(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.8.3) - postcss-loader@7.3.4(postcss@8.5.4)(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.29)): + postcss-load-config@4.0.2(postcss@8.5.4)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.5.4)): + 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@22.15.29)(typescript@5.5.4) + optional: true + + postcss-loader@7.3.4(postcss@8.5.4)(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.29(@swc/helpers@0.5.13))): dependencies: cosmiconfig: 8.3.6(typescript@5.8.3) 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 @@ -41663,6 +42054,25 @@ snapshots: dependencies: fast-diff: 1.3.0 + prettier-plugin-embed@0.4.15(babel-plugin-macros@3.1.0): + dependencies: + '@types/estree': 1.0.6 + dedent: 1.6.0(babel-plugin-macros@3.1.0) + micro-memoize: 4.1.3 + package-up: 5.0.0 + tiny-jsonc: 1.0.2 + type-fest: 4.41.0 + transitivePeerDependencies: + - babel-plugin-macros + + prettier-plugin-sql@0.18.1(prettier@3.5.3): + dependencies: + jsox: 1.2.123 + node-sql-parser: 4.18.0 + prettier: 3.5.3 + sql-formatter: 15.6.2 + tslib: 2.8.1 + prettier@2.8.8: {} prettier@3.5.3: {} @@ -41950,8 +42360,15 @@ snapshots: quick-lru@5.1.1: {} + railroad-diagrams@1.0.0: {} + rambda@9.4.2: {} + randexp@0.4.6: + dependencies: + discontinuous-range: 1.0.0 + ret: 0.1.15 + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -42049,11 +42466,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.4 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.8.3): dependencies: @@ -42719,7 +43136,7 @@ snapshots: - supports-color - utf-8-validate - react-navigation-stack@2.10.4(n5q7nzlozgkktehjjhku7iswqa): + react-navigation-stack@2.10.4(cc782526f6f527a9fd49628df4caf975): 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.8.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) color: 3.2.1 @@ -42805,13 +43222,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: @@ -43311,6 +43728,8 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + ret@0.1.15: {} + retry@0.12.0: {} retry@0.13.1: {} @@ -43507,18 +43926,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) @@ -43946,13 +44365,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 @@ -44045,6 +44464,11 @@ snapshots: sprintf-js@1.1.3: {} + sql-formatter@15.6.2: + dependencies: + argparse: 2.0.1 + nearley: 2.20.1 + srcset@4.0.0: {} ssri@10.0.6: @@ -44288,6 +44712,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) @@ -44296,10 +44724,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 @@ -44313,12 +44737,13 @@ snapshots: hey-listen: 1.0.8 tslib: 2.8.1 - styled-jsx@5.1.1(@babel/core@7.26.10)(react@18.3.1): + styled-jsx@5.1.1(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: '@babel/core': 7.26.10 + babel-plugin-macros: 3.1.0 stylehacks@6.1.1(postcss@8.5.4): dependencies: @@ -44409,11 +44834,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)) symbol-observable@4.0.0: {} @@ -44425,7 +44850,7 @@ snapshots: tabbable@6.2.0: {} - tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.57)(typescript@5.8.3)): + 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.8.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -44444,7 +44869,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.8.3)) + 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.8.3)) postcss-nested: 6.2.0(postcss@8.5.4) postcss-selector-parser: 6.1.2 resolve: 1.22.10 @@ -44452,6 +44877,34 @@ snapshots: transitivePeerDependencies: - ts-node + tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(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@22.15.29)(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) @@ -44601,29 +45054,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: @@ -44634,7 +45086,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: @@ -44647,15 +45099,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 @@ -44663,7 +45106,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: @@ -44725,6 +45168,8 @@ snapshots: tiny-invariant@1.3.3: {} + tiny-jsonc@1.0.2: {} + tiny-warning@1.0.3: {} tinybench@2.9.0: {} @@ -44832,12 +45277,12 @@ snapshots: 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 @@ -44862,27 +45307,28 @@ snapshots: typescript: 5.8.3 webpack: 5.99.9(@swc/core@1.11.29) - ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.3.3): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@20.17.57)(typescript@5.8.3): 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.3.3 + typescript: 5.8.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) + optional: true - ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(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 @@ -44896,13 +45342,14 @@ snapshots: 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@22.15.29)(typescript@5.8.3): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -44916,40 +45363,40 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.8.3 + 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@22.15.29)(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': 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: 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.8.3): + ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.29)(typescript@5.8.3): 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': 22.15.29 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -44959,26 +45406,28 @@ snapshots: typescript: 5.8.3 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: {} @@ -45413,14 +45862,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: @@ -45594,7 +46043,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: @@ -45604,7 +46053,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: @@ -45614,7 +46063,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.41.1)(vite@5.4.19(@types/node@22.15.29)(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@22.15.29)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) transitivePeerDependencies: @@ -45624,7 +46073,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: @@ -45634,7 +46083,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.41.1)(vite@6.3.5(@types/node@22.15.29)(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@22.15.29)(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: @@ -45643,7 +46092,7 @@ snapshots: vite-plugin-vuetify@2.1.1(vite@5.4.19(@types/node@22.15.29)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0))(vue@3.4.21(typescript@5.8.3))(vuetify@3.6.8): dependencies: - '@vuetify/loader-shared': 2.1.0(vue@3.4.21(typescript@5.8.3))(vuetify@3.6.8(typescript@5.8.3)(vite-plugin-vuetify@2.1.1)(vue@3.4.21(typescript@5.8.3))) + '@vuetify/loader-shared': 2.1.0(vue@3.4.21(typescript@5.8.3))(vuetify@3.6.8) debug: 4.4.1(supports-color@8.1.1) upath: 2.0.1 vite: 5.4.19(@types/node@22.15.29)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) @@ -45930,9 +46379,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 @@ -45944,6 +46393,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 @@ -45953,7 +46411,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 @@ -46004,7 +46462,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 @@ -46031,7 +46529,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) @@ -46057,7 +46555,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) @@ -46066,37 +46564,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 @@ -46119,7 +46586,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: @@ -46127,7 +46594,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 @@ -46150,7 +46617,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: @@ -46243,7 +46710,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: @@ -46253,7 +46720,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 @@ -46262,7 +46729,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: diff --git a/tools/powersynctests/ios/Podfile.lock b/tools/powersynctests/ios/Podfile.lock index d6a91af9d..b198fb1a1 100644 --- a/tools/powersynctests/ios/Podfile.lock +++ b/tools/powersynctests/ios/Podfile.lock @@ -31,7 +31,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - powersync-op-sqlite (0.7.2): + - powersync-op-sqlite (0.7.3): - DoubleConversion - glog - hermes-engine @@ -1829,71 +1829,71 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 hermes-engine: b417d2b2aee3b89b58e63e23a51e02be91dc876d - op-sqlite: af963896bb0d5393f12e07189bb39b1eaa5f4ac3 - powersync-op-sqlite: a80d0cc9d4790470cea1752c83c0c9d1b6a30bb1 + op-sqlite: 27db77bc067129a35dccf00dad447e683318825d + powersync-op-sqlite: a2913a28ebedd7dccbaf2c7e330e03b25cb82611 powersync-sqlite-core: a58efd88833861f0a8bb636c171bdf0ed55c9801 - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 + RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35 RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd React: b229c49ed5898dab46d60f61ed5a0bfa2ee2fadb React-callinvoker: 2ac508e92c8bd9cf834cc7d7787d94352e4af58f - React-Core: 325b4f6d9162ae8b9a6ff42fe78e260eb124180d - React-CoreModules: 558041e5258f70cd1092f82778d07b8b2ff01897 - React-cxxreact: 8fff17cbe76e6a8f9991b59552e1235429f9c74b + React-Core: 13cdd1558d0b3f6d9d5a22e14d89150280e79f02 + React-CoreModules: b07a6744f48305405e67c845ebf481b6551b712a + React-cxxreact: 1055a86c66ac35b4e80bd5fb766aed5f494dfff4 React-debug: 0a5fcdbacc6becba0521e910c1bcfdb20f32a3f6 - React-defaultsnativemodule: 618dc50a0fad41b489997c3eb7aba3a74479fd14 - React-domnativemodule: 7ba599afb6c2a7ec3eb6450153e2efe0b8747e9a - React-Fabric: 252112089d2c63308f4cbfade4010b6606db67d1 - React-FabricComponents: 3c0f75321680d14d124438ab279c64ec2a3d13c4 - React-FabricImage: 728b8061cdec2857ca885fd605ee03ad43ffca98 + React-defaultsnativemodule: 4bb28fc97fee5be63a9ebf8f7a435cfe8ba69459 + React-domnativemodule: b36a11c2597243d7563985028c51ece988d8ae33 + React-Fabric: afc561718f25b2cd800b709d934101afe376a12c + React-FabricComponents: f4e0a4e18a27bf6d39cbf2a0b42f37a92fa4e37f + React-FabricImage: 37d8e8b672eda68a19d71143eb65148084efb325 React-featureflags: 19682e02ef5861d96b992af16a19109c3dfc1200 - React-featureflagsnativemodule: 23528c7e7d50782b7ef0804168ba40bbaf1e86ab - React-graphics: fefe48f71bfe6f48fd037f59e8277b12e91b6be1 - React-hermes: a9a0c8377627b5506ef9a7b6f60a805c306e3f51 - React-idlecallbacksnativemodule: 7e2b6a3b70e042f89cd91dbd73c479bb39a72a7e - React-ImageManager: e3300996ac2e2914bf821f71e2f2c92ae6e62ae2 - React-jserrorhandler: fa75876c662e5d7e79d6efc763fc9f4c88e26986 - React-jsi: f3f51595cc4c089037b536368f016d4742bf9cf7 - React-jsiexecutor: cca6c232db461e2fd213a11e9364cfa6fdaa20eb - React-jsinspector: 2bd4c9fddf189d6ec2abf4948461060502582bef - React-jsinspectortracing: a417d8a0ad481edaa415734b4dac81e3e5ee7dc6 - React-jsitracing: 1ff7172c5b0522cbf6c98d82bdbb160e49b5804e - React-logger: 018826bfd51b9f18e87f67db1590bc510ad20664 - React-Mapbuffer: 3c11cee7737609275c7b66bd0b1de475f094cedf - React-microtasksnativemodule: 843f352b32aacbe13a9c750190d34df44c3e6c2c - react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba - React-NativeModulesApple: 88433b6946778bea9c153e27b671de15411bf225 - React-perflogger: 9e8d3c0dc0194eb932162812a168aa5dc662f418 - React-performancetimeline: 5a2d6efef52bdcefac079c7baa30934978acd023 + React-featureflagsnativemodule: d7cddf6d907b4e5ab84f9e744b7e88461656e48c + React-graphics: b0f78580cdaf5800d25437e3d41cc6c3d83b7aea + React-hermes: 71186f872c932e4574d5feb3ed754dda63a0b3bd + React-idlecallbacksnativemodule: dd2af19cdd3bc55149d17a2409ed72b694dfbe9c + React-ImageManager: a77dde8d5aa6a2b6962c702bf3a47695ef0aa32b + React-jserrorhandler: 9c14e89f12d5904257a79aaf84a70cd2e5ac07ba + React-jsi: 0775a66820496769ad83e629f0f5cce621a57fc7 + React-jsiexecutor: 2cf5ba481386803f3c88b85c63fa102cba5d769e + React-jsinspector: 8052d532bb7a98b6e021755674659802fb140cc5 + React-jsinspectortracing: bdd8fd0adcb4813663562e7874c5842449df6d8a + React-jsitracing: 2bab3bf55de3d04baf205def375fa6643c47c794 + React-logger: 795cd5055782db394f187f9db0477d4b25b44291 + React-Mapbuffer: 0502faf46cab8fb89cfc7bf3e6c6109b6ef9b5de + React-microtasksnativemodule: 663bc64e3a96c5fc91081923ae7481adc1359a78 + react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 + React-NativeModulesApple: 16fbd5b040ff6c492dacc361d49e63cba7a6a7a1 + React-perflogger: ab51b7592532a0ea45bf6eed7e6cae14a368b678 + React-performancetimeline: bc2e48198ec814d578ac8401f65d78a574358203 React-RCTActionSheet: 592674cf61142497e0e820688f5a696e41bf16dd - React-RCTAnimation: e6d669872f9b3b4ab9527aab283b7c49283236b7 - React-RCTAppDelegate: de2343fe08be4c945d57e0ecce44afcc7dd8fc03 - React-RCTBlob: 3e2dce94c56218becc4b32b627fc2293149f798d - React-RCTFabric: cac2c033381d79a5956e08550b0220cb2d78ea93 - React-RCTFBReactNativeSpec: d10ca5e0ccbfeac8c047361fedf8e4ac653887b6 - React-RCTImage: dc04b176c022d12a8f55ae7a7279b1e091066ae0 - React-RCTLinking: 88f5e37fe4f26fbc80791aa2a5f01baf9b9a3fd5 - React-RCTNetwork: f213693565efbd698b8e9c18d700a514b49c0c8e - React-RCTSettings: a2d32a90c45a3575568cad850abc45924999b8a5 - React-RCTText: 54cdcd1cbf6f6a91dc6317f5d2c2b7fc3f6bf7a0 - React-RCTVibration: 11dae0e7f577b5807bb7d31e2e881eb46f854fd4 + React-RCTAnimation: 8fbb8dba757b49c78f4db403133ab6399a4ce952 + React-RCTAppDelegate: 7f88baa8cb4e5d6c38bb4d84339925c70c9ac864 + React-RCTBlob: f89b162d0fe6b570a18e755eb16cbe356d3c6d17 + React-RCTFabric: 8ad6d875abe6e87312cef90e4b15ef7f6bed72e6 + React-RCTFBReactNativeSpec: 8c29630c2f379c729300e4c1e540f3d1b78d1936 + React-RCTImage: ccac9969940f170503857733f9a5f63578e106e1 + React-RCTLinking: d82427bbf18415a3732105383dff119131cadd90 + React-RCTNetwork: 12ad4d0fbde939e00251ca5ca890da2e6825cc3c + React-RCTSettings: e7865bf9f455abf427da349c855f8644b5c39afa + React-RCTText: 2cdfd88745059ec3202a0842ea75a956c7d6f27d + React-RCTVibration: a3a1458e6230dfd64b3768ebc0a4aac430d9d508 React-rendererconsistency: 64e897e00d2568fd8dfe31e2496f80e85c0aaad1 - React-rendererdebug: 41ce452460c44bba715d9e41d5493a96de277764 + React-rendererdebug: a3f6d3ae7d2fa0035885026756281c07ee32479e React-rncore: 58748c2aa445f56b99e5118dad0aedb51c40ce9f - React-RuntimeApple: 7785ed0d8ae54da65a88736bb63ca97608a6d933 - React-RuntimeCore: 6029ea70bc77f98cfd43ebe69217f14e93ba1f12 + React-RuntimeApple: f0fda7bacabd32daa099cfda8f07466c30acd149 + React-RuntimeCore: 683ee0b6a76d4b4bf6fbf83a541895b4887cc636 React-runtimeexecutor: a188df372373baf5066e6e229177836488799f80 - React-RuntimeHermes: a264609c28b796edfffc8ae4cb8fad1773ab948b - React-runtimescheduler: 23ec3a1e0fb1ec752d1a9c1fb15258c30bfc7222 + React-RuntimeHermes: 907c8e9bec13ea6466b94828c088c24590d4d0b6 + React-runtimescheduler: a2e2a39125dd6426b5d8b773f689d660cd7c5f60 React-timing: bb220a53a795ed57976a4855c521f3de2f298fe5 - React-utils: 3b054aaebe658fc710a8d239d0e4b9fd3e0b78f9 - ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8 - ReactCodegen: 008c319179d681a6a00966edfc67fda68f9fbb2e - ReactCommon: 0c097b53f03d6bf166edbcd0915da32f3015dd90 - RNVectorIcons: bd818296a51dc2bb8c3bd97a3ca399df1afe216d + React-utils: 300d8bbb6555dcffaca71e7a0663201b5c7edbbc + ReactAppDependencyProvider: f2e81d80afd71a8058589e19d8a134243fa53f17 + ReactCodegen: a63a0ab6ae824aef2e8c744981edd718b16eb9f2 + ReactCommon: 3d39389f8e2a2157d5c999f8fba57bd1c8f226f0 + RNVectorIcons: 14a0c42f16a26bcc3e79a19bc1c5718284b1d469 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: afd04ff05ebe0121a00c468a8a3c8080221cb14c + Yoga: 9b7fb56e7b08cde60e2153344fa6afbd88e5d99f PODFILE CHECKSUM: a15b54e8d191759ce7fcccb262b8753851ec9fde diff --git a/tools/powersynctests/src/tests/queries.test.ts b/tools/powersynctests/src/tests/queries.test.ts index 63ebf96f0..e3a4d84f5 100644 --- a/tools/powersynctests/src/tests/queries.test.ts +++ b/tools/powersynctests/src/tests/queries.test.ts @@ -4,6 +4,7 @@ import { column, LockContext, PowerSyncDatabase, + QueryResult, Schema, Table } from '@powersync/react-native'; @@ -618,6 +619,55 @@ export function registerBaseTests() { expect(duration).lessThan(2000); }); + it('should compare results with old watch method', async () => { + const controller = new AbortController(); + + const resultSets: QueryResult[] = []; + + // Wait for the first query load + const donePromise = new Promise((resolve) => { + db.watch( + 'SELECT * FROM users WHERE name = ?', + ['test'], + { + onResult: (result) => { + // Mark that we received the first result, this helps with counting events. + if (result.rows?._array?.length == 2) { + resolve(); + } + resultSets.push(result); + } + }, + { + signal: controller.signal, + comparator: { + checkEquality: (current, previous) => { + return JSON.stringify(current) === JSON.stringify(previous); + } + } + } + ); + }); + + + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['test']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['nottest']); + await db.execute('INSERT INTO users(id, name) VALUES (uuid(), ?)', ['test']); + + + await donePromise; + + expect(resultSets[resultSets.length - 1]?.rows?._array?.map((r) => r.name)).deep.eq(['test', 'test']); + // We should only have updated less than or equal 3 times + expect(resultSets.length).lessThanOrEqual(3); + }); + it('Should handle multiple closes', async () => { // Bulk insert 10000 rows without using a transaction const bulkInsertCommands = []; @@ -653,5 +703,7 @@ export function registerBaseTests() { expect(results.map((r) => r.status)).deep.equal(Array(tests.length).fill('rejected')); } }); + + }); }