From dda87d9368745375d0017255a286df686a998b24 Mon Sep 17 00:00:00 2001 From: Mugi Khan Date: Tue, 17 Dec 2024 19:26:24 +0200 Subject: [PATCH 1/4] Setup fts5 and search screen --- .../app/_layout.tsx | 6 + .../app/search_modal.tsx | 23 ++++ .../library/fts/fts_helpers.ts | 39 ++++++ .../library/fts/fts_setup.ts | 64 +++++++++ .../library/fts/helpers.ts | 36 ++++++ .../library/powersync/system.ts | 5 + .../library/widgets/AutoCompleteWidget.tsx | 121 ++++++++++++++++++ .../library/widgets/HeaderWidget.tsx | 59 ++++++--- .../library/widgets/SearchBarWidget.tsx | 40 ++++++ 9 files changed, 373 insertions(+), 20 deletions(-) create mode 100644 demos/react-native-supabase-todolist/app/search_modal.tsx create mode 100644 demos/react-native-supabase-todolist/library/fts/fts_helpers.ts create mode 100644 demos/react-native-supabase-todolist/library/fts/fts_setup.ts create mode 100644 demos/react-native-supabase-todolist/library/fts/helpers.ts create mode 100644 demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx create mode 100644 demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx diff --git a/demos/react-native-supabase-todolist/app/_layout.tsx b/demos/react-native-supabase-todolist/app/_layout.tsx index 8efcf8ab0..dc3ce6841 100644 --- a/demos/react-native-supabase-todolist/app/_layout.tsx +++ b/demos/react-native-supabase-todolist/app/_layout.tsx @@ -30,6 +30,12 @@ const HomeLayout = () => { + ); diff --git a/demos/react-native-supabase-todolist/app/search_modal.tsx b/demos/react-native-supabase-todolist/app/search_modal.tsx new file mode 100644 index 000000000..9f065f31d --- /dev/null +++ b/demos/react-native-supabase-todolist/app/search_modal.tsx @@ -0,0 +1,23 @@ +import { Stack } from 'expo-router'; +import { StatusBar } from 'expo-status-bar'; +import { StyleSheet, Text, View } from 'react-native'; +import { SearchBarWidget } from '../library/widgets/SearchBarWidget'; + +export default function Modal() { + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexGrow: 1, + alignItems: 'center', + justifyContent: 'center' + } +}); diff --git a/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts b/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts new file mode 100644 index 000000000..1b9d0be56 --- /dev/null +++ b/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts @@ -0,0 +1,39 @@ +import { system } from '../powersync/system'; + +/** + * adding * to the end of the search term will match any word that starts with the search term + * e.g. searching bl will match blue, black, etc. + * consult FTS5 Full-text Query Syntax documentation for more options + * @param searchTerm + * @returns a modified search term with options. + */ +function createSearchTermWithOptions(searchTerm: string): string { + const searchTermWithOptions: string = `${searchTerm}*`; + return searchTermWithOptions; +} + +/** + * Search the FTS table for the given searchTerm + * @param searchTerm + * @param tableName + * @returns results from the FTS table + */ +export async function searchTable(searchTerm: string, tableName: string): Promise { + const searchTermWithOptions = createSearchTermWithOptions(searchTerm); + return await system.powersync.getAll(`SELECT * FROM fts_${tableName} WHERE fts_${tableName} MATCH ? ORDER BY rank`, [ + searchTermWithOptions + ]); +} + +//Used to display the search results in the autocomplete text field +export class SearchResult { + id: string; + todoName: string | null; + listName: string; + + constructor(id: string, listName: string, todoName: string | null = null) { + this.id = id; + this.listName = listName; + this.todoName = todoName; + } +} diff --git a/demos/react-native-supabase-todolist/library/fts/fts_setup.ts b/demos/react-native-supabase-todolist/library/fts/fts_setup.ts new file mode 100644 index 000000000..c2d16a012 --- /dev/null +++ b/demos/react-native-supabase-todolist/library/fts/fts_setup.ts @@ -0,0 +1,64 @@ +import { AppSchema } from '../powersync/AppSchema'; +import { ExtractType, generateJsonExtracts } from './helpers'; +import { system } from '../powersync/system'; + +/** + * Create a Full Text Search table for the given table and columns + * with an option to use a different tokenizer otherwise it defaults + * to unicode61. It also creates the triggers that keep the FTS table + * and the PowerSync table in sync. + * @param tableName + * @param columns + * @param tokenizationMethod + */ +async function createFtsTable(tableName: string, columns: string[], tokenizationMethod = 'unicode61'): Promise { + const internalName = AppSchema.tables.find((table) => table.name === tableName)?.internalName; + const stringColumns = columns.join(', '); + + return await system.powersync.writeTransaction(async (tx) => { + // Add FTS table + await tx.execute(` + CREATE VIRTUAL TABLE IF NOT EXISTS fts_${tableName} + USING fts5(id UNINDEXED, ${stringColumns}, tokenize='${tokenizationMethod}'); + `); + // Copy over records already in table + await tx.execute(` + INSERT OR REPLACE INTO fts_${tableName}(rowid, id, ${stringColumns}) + SELECT rowid, id, ${generateJsonExtracts(ExtractType.columnOnly, 'data', columns)} FROM ${internalName}; + `); + // Add INSERT, UPDATE and DELETE and triggers to keep fts table in sync with table + await tx.execute(` + CREATE TRIGGER IF NOT EXISTS fts_insert_trigger_${tableName} AFTER INSERT ON ${internalName} + BEGIN + INSERT INTO fts_${tableName}(rowid, id, ${stringColumns}) + VALUES ( + NEW.rowid, + NEW.id, + ${generateJsonExtracts(ExtractType.columnOnly, 'NEW.data', columns)} + ); + END; + `); + await tx.execute(` + CREATE TRIGGER IF NOT EXISTS fts_update_trigger_${tableName} AFTER UPDATE ON ${internalName} BEGIN + UPDATE fts_${tableName} + SET ${generateJsonExtracts(ExtractType.columnInOperation, 'NEW.data', columns)} + WHERE rowid = NEW.rowid; + END; + `); + await tx.execute(` + CREATE TRIGGER IF NOT EXISTS fts_delete_trigger_${tableName} AFTER DELETE ON ${internalName} BEGIN + DELETE FROM fts_${tableName} WHERE rowid = OLD.rowid; + END; + `); + }); +} + +/** + * This is where you can add more methods to generate FTS tables in this demo + * that correspond to the tables in your schema and populate them + * with the data you would like to search on + */ +export async function configureFts(): Promise { + await createFtsTable('lists', ['name'], 'porter unicode61'); + await createFtsTable('todos', ['description', 'list_id']); +} diff --git a/demos/react-native-supabase-todolist/library/fts/helpers.ts b/demos/react-native-supabase-todolist/library/fts/helpers.ts new file mode 100644 index 000000000..5a9054bd6 --- /dev/null +++ b/demos/react-native-supabase-todolist/library/fts/helpers.ts @@ -0,0 +1,36 @@ +type ExtractGenerator = (jsonColumnName: string, columnName: string) => string; + +export enum ExtractType { + columnOnly, + columnInOperation +} + +type ExtractGeneratorMap = Map; + +function _createExtract(jsonColumnName: string, columnName: string): string { + return `json_extract(${jsonColumnName}, '$.${columnName}')`; +} + +const extractGeneratorsMap: ExtractGeneratorMap = new Map([ + [ExtractType.columnOnly, (jsonColumnName: string, columnName: string) => _createExtract(jsonColumnName, columnName)], + [ + ExtractType.columnInOperation, + (jsonColumnName: string, columnName: string) => { + const extract = _createExtract(jsonColumnName, columnName); + return `${columnName} = ${extract}`; + } + ] +]); + +export const generateJsonExtracts = (type: ExtractType, jsonColumnName: string, columns: string[]): string => { + const generator = extractGeneratorsMap.get(type); + if (generator == null) { + throw new Error('Unexpected null generator for key: $type'); + } + + if (columns.length == 1) { + return generator(jsonColumnName, columns[0]); + } + + return columns.map((column) => generator(jsonColumnName, column)).join(', '); +}; diff --git a/demos/react-native-supabase-todolist/library/powersync/system.ts b/demos/react-native-supabase-todolist/library/powersync/system.ts index ca78d05c4..f25514465 100644 --- a/demos/react-native-supabase-todolist/library/powersync/system.ts +++ b/demos/react-native-supabase-todolist/library/powersync/system.ts @@ -11,6 +11,7 @@ import { AppConfig } from '../supabase/AppConfig'; import { SupabaseConnector } from '../supabase/SupabaseConnector'; import { AppSchema } from './AppSchema'; import { PhotoAttachmentQueue } from './PhotoAttachmentQueue'; +import { configureFts } from '../fts/fts_setup'; Logger.useDefaults(); @@ -68,6 +69,10 @@ export class System { if (this.attachmentQueue) { await this.attachmentQueue.init(); } + + // Demo using SQLite Full-Text Search with PowerSync. + // See https://docs.powersync.com/usage-examples/full-text-search for more details + configureFts(); } } diff --git a/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx new file mode 100644 index 000000000..203c9c1f9 --- /dev/null +++ b/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx @@ -0,0 +1,121 @@ +import { View, StyleSheet, TouchableOpacity } from 'react-native'; +import { Card, Input, ListItem, Text } from '@rneui/themed'; +import React, { useState } from 'react'; +import { router } from 'expo-router'; + +export interface AutocompleteWidgetProps { + origValue: string; + label?: string; + data: any[]; + onChange: (value: string) => void; + // origOnChange: (value: string) => void; + icon?: string; + style?: object; + menuStyle?: object; + right?: object; + left?: object; +} + +export const Autocomplete: React.FC = ({ + origValue, + label, + data, + onChange, + // origOnChange, + icon, + style, + menuStyle, + right, + left +}) => { + const [value, setValue] = useState(origValue); + const [menuVisible, setMenuVisible] = useState(false); + const [filteredData, setFilteredData] = useState([]); + + const filterData = (text: string) => { + return data.filter((val: any) => val?.toLowerCase()?.indexOf(text?.toLowerCase()) > -1); + }; + return ( + + + { + if (value.length === 0) { + setMenuVisible(true); + } + }} + onBlur={() => setMenuVisible(false)} + label={label} + // right={right} + // left={left} + // style={styles.input} + onChangeText={(text) => { + // origOnChange(text); + onChange(text); + // if (text && text.length > 0) { + // setFilteredData(filterData(text)); + // } else if (text && text.length === 0) { + // setFilteredData(data); + // } + setMenuVisible(true); + setValue(text); + }} + // value={value} + /> + + {menuVisible && ( + + {data.map((val, index) => ( + { + router.push({ + pathname: 'views/todos/edit/[id]', + params: { id: val.id } + }); + }}> + + {val.listName && {val.listName}} + {val.todoName && ( + + {'\u2022'} {val.todoName} + + )} + + + // { + // setValue(val); + // setMenuVisible(false); + // }} + // // title={datum} + // > + // + // {val} + // + // + ))} + + )} + + ); +}; + +const styles = StyleSheet.create({ + input: { + flexDirection: 'row', + flex: 1, + flexGrow: 1, + width: '100%', + alignItems: 'center', + justifyContent: 'flex-start' + } +}); diff --git a/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx index f8af67d3b..52354d665 100644 --- a/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx +++ b/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Alert, Text } from 'react-native'; -import { useNavigation } from 'expo-router'; +import { Alert, View, StyleSheet } from 'react-native'; +import { router, useNavigation } from 'expo-router'; import { Icon, Header } from '@rneui/themed'; import { useStatus } from '@powersync/react'; import { DrawerActions } from '@react-navigation/native'; @@ -29,27 +29,46 @@ export const HeaderWidget: React.FC<{ /> } rightComponent={ - { - if (system.attachmentQueue) { - system.attachmentQueue.trigger(); - } - Alert.alert( - 'Status', - `${status.connected ? 'Connected' : 'Disconnected'}. \nLast Synced at ${ - status?.lastSyncedAt?.toISOString() ?? '-' - }\nVersion: ${powersync.sdkVersion}` - ); - }} - /> + + { + router.push('search_modal'); + }} + /> + { + if (system.attachmentQueue) { + system.attachmentQueue.trigger(); + } + Alert.alert( + 'Status', + `${status.connected ? 'Connected' : 'Disconnected'}. \nLast Synced at ${ + status?.lastSyncedAt?.toISOString() ?? '-' + }\nVersion: ${powersync.sdkVersion}` + ); + }} + /> + } centerContainerStyle={{ justifyContent: 'center', alignItems: 'center' }} centerComponent={{ text: title, style: { color: '#fff' } }} /> ); }; + +const styles = StyleSheet.create({ + headerRight: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center' + } +}); diff --git a/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx new file mode 100644 index 000000000..6052210a3 --- /dev/null +++ b/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx @@ -0,0 +1,40 @@ +// import { Autocomplete, Box, Card, CardContent, FormControl, TextField, Typography } from '@mui/material'; +import React from 'react'; +import { usePowerSync } from '@powersync/react'; +import { Autocomplete } from './AutoCompleteWidget'; +import { SearchResult, searchTable } from '../fts/fts_helpers'; +import { LIST_TABLE, TODO_TABLE, ListRecord } from '../powersync/AppSchema'; + +// This is a simple search bar widget that allows users to search for lists and todo items +export const SearchBarWidget: React.FC = () => { + const [searchResults, setSearchResults] = React.useState([]); + const [value, setValue] = React.useState(null); + + // const navigate = useNavigate(); + const powersync = usePowerSync(); + + const handleInputChange = async (value: string) => { + if (value.length !== 0) { + let listsSearchResults: any[] = []; + const todoItemsSearchResults = await searchTable(value, 'todos'); + for (let i = 0; i < todoItemsSearchResults.length; i++) { + const res = await powersync.get(`SELECT * FROM ${LIST_TABLE} WHERE id = ?`, [ + todoItemsSearchResults[i]['list_id'] + ]); + todoItemsSearchResults[i]['list_name'] = res.name; + } + if (!todoItemsSearchResults.length) { + listsSearchResults = await searchTable(value, 'lists'); + } + const formattedListResults: SearchResult[] = listsSearchResults.map( + (result) => new SearchResult(result['id'], result['name']) + ); + const formattedTodoItemsResults: SearchResult[] = todoItemsSearchResults.map((result) => { + return new SearchResult(result['list_id'], result['list_name'] ?? '', result['description']); + }); + setSearchResults([...formattedTodoItemsResults, ...formattedListResults]); + } + }; + + return ; +}; From 75659dd72ee347bf8ab06e4c024b911019a10cf2 Mon Sep 17 00:00:00 2001 From: Mugi Khan Date: Tue, 17 Dec 2024 23:52:33 +0200 Subject: [PATCH 2/4] Cleanup --- .../app/_layout.tsx | 13 +- .../app/search_modal.tsx | 4 +- .../library/widgets/AutoCompleteWidget.tsx | 130 +++++++----------- .../library/widgets/HeaderWidget.tsx | 23 ++-- .../library/widgets/SearchBarWidget.tsx | 21 ++- 5 files changed, 95 insertions(+), 96 deletions(-) diff --git a/demos/react-native-supabase-todolist/app/_layout.tsx b/demos/react-native-supabase-todolist/app/_layout.tsx index dc3ce6841..941d4095e 100644 --- a/demos/react-native-supabase-todolist/app/_layout.tsx +++ b/demos/react-native-supabase-todolist/app/_layout.tsx @@ -1,7 +1,9 @@ -import { Stack } from 'expo-router'; +import { router, Stack } from 'expo-router'; import React, { useMemo } from 'react'; import { useSystem } from '../library/powersync/system'; import { PowerSyncContext } from '@powersync/react-native'; +import { Pressable } from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; /** * This App uses a nested navigation stack. @@ -33,6 +35,15 @@ const HomeLayout = () => { ( + { + router.back(); + }}> + + + ), presentation: 'fullScreenModal' }} /> diff --git a/demos/react-native-supabase-todolist/app/search_modal.tsx b/demos/react-native-supabase-todolist/app/search_modal.tsx index 9f065f31d..6024a6ae5 100644 --- a/demos/react-native-supabase-todolist/app/search_modal.tsx +++ b/demos/react-native-supabase-todolist/app/search_modal.tsx @@ -1,13 +1,11 @@ -import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, Text, View } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { SearchBarWidget } from '../library/widgets/SearchBarWidget'; export default function Modal() { return ( - ); diff --git a/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx index 203c9c1f9..bbc06444a 100644 --- a/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx +++ b/demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx @@ -1,107 +1,71 @@ -import { View, StyleSheet, TouchableOpacity } from 'react-native'; -import { Card, Input, ListItem, Text } from '@rneui/themed'; +import { View, StyleSheet } from 'react-native'; +import { Input, ListItem } from '@rneui/themed'; import React, { useState } from 'react'; -import { router } from 'expo-router'; +import { IconNode } from '@rneui/base'; export interface AutocompleteWidgetProps { - origValue: string; - label?: string; data: any[]; onChange: (value: string) => void; - // origOnChange: (value: string) => void; - icon?: string; - style?: object; - menuStyle?: object; - right?: object; - left?: object; + placeholder?: string; + onPress: (id: string) => void; + leftIcon?: IconNode; } -export const Autocomplete: React.FC = ({ - origValue, - label, - data, - onChange, - // origOnChange, - icon, - style, - menuStyle, - right, - left -}) => { - const [value, setValue] = useState(origValue); +export const Autocomplete: React.FC = ({ data, onChange, placeholder, onPress, leftIcon }) => { + const [value, setValue] = useState(''); const [menuVisible, setMenuVisible] = useState(false); - const [filteredData, setFilteredData] = useState([]); - const filterData = (text: string) => { - return data.filter((val: any) => val?.toLowerCase()?.indexOf(text?.toLowerCase()) > -1); - }; return ( - - + + { - if (value.length === 0) { + if (value?.length === 0) { setMenuVisible(true); } }} + leftIcon={leftIcon} + placeholder={placeholder} onBlur={() => setMenuVisible(false)} - label={label} - // right={right} - // left={left} - // style={styles.input} + underlineColorAndroid={'transparent'} + inputContainerStyle={{ borderBottomWidth: 0 }} onChangeText={(text) => { - // origOnChange(text); onChange(text); - // if (text && text.length > 0) { - // setFilteredData(filterData(text)); - // } else if (text && text.length === 0) { - // setFilteredData(data); - // } setMenuVisible(true); setValue(text); }} - // value={value} + containerStyle={{ + borderColor: 'black', + borderWidth: 1, + borderRadius: 4, + height: 48, + backgroundColor: 'white' + }} /> {menuVisible && ( - + {data.map((val, index) => ( - { - router.push({ - pathname: 'views/todos/edit/[id]', - params: { id: val.id } - }); - }}> - - {val.listName && {val.listName}} + setMenuVisible(false); + onPress(val.id); + }} + style={{ paddingBottom: 8 }}> + + {val.listName && ( + {val.listName} + )} {val.todoName && ( - + {'\u2022'} {val.todoName} - + )} - - - // { - // setValue(val); - // setMenuVisible(false); - // }} - // // title={datum} - // > - // - // {val} - // - // + + + ))} )} @@ -110,12 +74,20 @@ export const Autocomplete: React.FC = ({ }; const styles = StyleSheet.create({ - input: { - flexDirection: 'row', + container: { + flexDirection: 'column', flex: 1, flexGrow: 1, - width: '100%', - alignItems: 'center', - justifyContent: 'flex-start' + marginHorizontal: 8 + }, + inputContainer: { + flexDirection: 'row', + flex: 0, + marginVertical: 8 + }, + menuContainer: { + flex: 2, + flexGrow: 1, + flexDirection: 'column' } }); diff --git a/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx index 52354d665..47e9ea9dc 100644 --- a/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx +++ b/demos/react-native-supabase-todolist/library/widgets/HeaderWidget.tsx @@ -5,6 +5,7 @@ import { Icon, Header } from '@rneui/themed'; import { useStatus } from '@powersync/react'; import { DrawerActions } from '@react-navigation/native'; import { useSystem } from '../powersync/system'; +import { usePathname } from 'expo-router'; export const HeaderWidget: React.FC<{ title?: string; @@ -15,6 +16,8 @@ export const HeaderWidget: React.FC<{ const status = useStatus(); const { title } = props; + + const pathName = usePathname(); return (
- { - router.push('search_modal'); - }} - /> + {pathName.includes('lists') && ( + { + router.push('search_modal'); + }} + /> + )} = () => { const [searchResults, setSearchResults] = React.useState([]); - const [value, setValue] = React.useState(null); - // const navigate = useNavigate(); const powersync = usePowerSync(); const handleInputChange = async (value: string) => { @@ -36,5 +35,19 @@ export const SearchBarWidget: React.FC = () => { } }; - return ; + return ( + { + router.back(); + router.push({ + pathname: 'views/todos/edit/[id]', + params: { id: id } + }); + }} + /> + ); }; From 96622dde4946ccb6e1814ad032a55a3436c57004 Mon Sep 17 00:00:00 2001 From: Mugi Khan Date: Wed, 18 Dec 2024 10:38:07 +0200 Subject: [PATCH 3/4] Bump RNQS version and cleanup --- .../ios/Podfile.lock | 10 ++++---- .../library/fts/fts_setup.ts | 17 +++++++++----- .../library/powersync/system.ts | 2 +- .../library/widgets/SearchBarWidget.tsx | 1 - .../package.json | 2 +- pnpm-lock.yaml | 23 ++++++++++++++----- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/demos/react-native-supabase-todolist/ios/Podfile.lock b/demos/react-native-supabase-todolist/ios/Podfile.lock index bcfd74815..b589188c3 100644 --- a/demos/react-native-supabase-todolist/ios/Podfile.lock +++ b/demos/react-native-supabase-todolist/ios/Podfile.lock @@ -1005,7 +1005,7 @@ PODS: - React-debug - react-native-encrypted-storage (4.0.3): - React-Core - - react-native-quick-sqlite (2.2.0): + - react-native-quick-sqlite (2.2.1): - DoubleConversion - glog - hermes-engine @@ -1467,7 +1467,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - react-native-encrypted-storage (from `../../../node_modules/react-native-encrypted-storage`) - - "react-native-quick-sqlite (from `../../../node_modules/@journeyapps/react-native-quick-sqlite`)" + - "react-native-quick-sqlite (from `../node_modules/@journeyapps/react-native-quick-sqlite`)" - react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -1593,7 +1593,7 @@ EXTERNAL SOURCES: react-native-encrypted-storage: :path: "../../../node_modules/react-native-encrypted-storage" react-native-quick-sqlite: - :path: "../../../node_modules/@journeyapps/react-native-quick-sqlite" + :path: "../node_modules/@journeyapps/react-native-quick-sqlite" react-native-safe-area-context: :path: "../../../node_modules/react-native-safe-area-context" React-nativeconfig: @@ -1698,7 +1698,7 @@ SPEC CHECKSUMS: React-logger: 257858bd55f3a4e1bc0cf07ddc8fb9faba6f8c7c React-Mapbuffer: 6c1cacdbf40b531f549eba249e531a7d0bfd8e7f react-native-encrypted-storage: db300a3f2f0aba1e818417c1c0a6be549038deb7 - react-native-quick-sqlite: b4b34028dbe2d532beb2575f4b90ae58bec42260 + react-native-quick-sqlite: fa617eb5224e530a0cafe21f35dbff9d98b1a557 react-native-safe-area-context: afa5d614d6b1b73b743c9261985876606560d128 React-nativeconfig: ba9a2e54e2f0882cf7882698825052793ed4c851 React-NativeModulesApple: 8d11ff8955181540585c944cf48e9e7236952697 @@ -1733,4 +1733,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ad989b4e43152979093488e5c8b7457e401bf191 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/demos/react-native-supabase-todolist/library/fts/fts_setup.ts b/demos/react-native-supabase-todolist/library/fts/fts_setup.ts index c2d16a012..574eceaf2 100644 --- a/demos/react-native-supabase-todolist/library/fts/fts_setup.ts +++ b/demos/react-native-supabase-todolist/library/fts/fts_setup.ts @@ -1,6 +1,6 @@ import { AppSchema } from '../powersync/AppSchema'; import { ExtractType, generateJsonExtracts } from './helpers'; -import { system } from '../powersync/system'; +import { PowerSyncDatabase } from '@powersync/react-native'; /** * Create a Full Text Search table for the given table and columns @@ -11,11 +11,16 @@ import { system } from '../powersync/system'; * @param columns * @param tokenizationMethod */ -async function createFtsTable(tableName: string, columns: string[], tokenizationMethod = 'unicode61'): Promise { +async function createFtsTable( + db: PowerSyncDatabase, + tableName: string, + columns: string[], + tokenizationMethod = 'unicode61' +): Promise { const internalName = AppSchema.tables.find((table) => table.name === tableName)?.internalName; const stringColumns = columns.join(', '); - return await system.powersync.writeTransaction(async (tx) => { + return await db.writeTransaction(async (tx) => { // Add FTS table await tx.execute(` CREATE VIRTUAL TABLE IF NOT EXISTS fts_${tableName} @@ -58,7 +63,7 @@ async function createFtsTable(tableName: string, columns: string[], tokenization * that correspond to the tables in your schema and populate them * with the data you would like to search on */ -export async function configureFts(): Promise { - await createFtsTable('lists', ['name'], 'porter unicode61'); - await createFtsTable('todos', ['description', 'list_id']); +export async function configureFts(db: PowerSyncDatabase): Promise { + await createFtsTable(db, 'lists', ['name'], 'porter unicode61'); + await createFtsTable(db, 'todos', ['description', 'list_id']); } diff --git a/demos/react-native-supabase-todolist/library/powersync/system.ts b/demos/react-native-supabase-todolist/library/powersync/system.ts index f25514465..85d12364f 100644 --- a/demos/react-native-supabase-todolist/library/powersync/system.ts +++ b/demos/react-native-supabase-todolist/library/powersync/system.ts @@ -72,7 +72,7 @@ export class System { // Demo using SQLite Full-Text Search with PowerSync. // See https://docs.powersync.com/usage-examples/full-text-search for more details - configureFts(); + configureFts(this.powersync); } } diff --git a/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx index ff4259427..eb9b2e895 100644 --- a/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx +++ b/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx @@ -1,4 +1,3 @@ -// import { Autocomplete, Box, Card, CardContent, FormControl, TextField, Typography } from '@mui/material'; import React from 'react'; import { usePowerSync } from '@powersync/react'; import { Autocomplete } from './AutoCompleteWidget'; diff --git a/demos/react-native-supabase-todolist/package.json b/demos/react-native-supabase-todolist/package.json index 1aa52ff6b..9cbd43e65 100644 --- a/demos/react-native-supabase-todolist/package.json +++ b/demos/react-native-supabase-todolist/package.json @@ -10,7 +10,7 @@ "dependencies": { "@azure/core-asynciterator-polyfill": "^1.0.2", "@expo/vector-icons": "^14.0.3", - "@journeyapps/react-native-quick-sqlite": "^2.2.0", + "@journeyapps/react-native-quick-sqlite": "^2.2.1", "@powersync/attachments": "workspace:*", "@powersync/common": "workspace:*", "@powersync/react": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 329e1d59f..65c03465b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -768,8 +768,8 @@ importers: specifier: ^14.0.3 version: 14.0.4 '@journeyapps/react-native-quick-sqlite': - specifier: ^2.2.0 - version: 2.2.0(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) + specifier: ^2.2.1 + version: 2.2.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0) '@powersync/attachments': specifier: workspace:* version: link:../../packages/attachments @@ -4601,6 +4601,12 @@ packages: react: '*' react-native: '*' + '@journeyapps/react-native-quick-sqlite@2.2.1': + resolution: {integrity: sha512-imvd5P9ii5SmtPJed+R4Yn26QDV3ED/jx/3OuKMDcdjCFi/m5yCCrLz0ewgRwubHGkltNYy5d3tCYodmy4+1KA==} + peerDependencies: + react: '*' + react-native: '*' + '@journeyapps/wa-sqlite@0.4.2': resolution: {integrity: sha512-xdpDLbyC/DHkNcnXCfgBXUgfy+ff1w/sxVY6mjdGP8F4bgxnSQfUyN8+PNE2nTgYUx4y5ar57MEnSty4zjIm7Q==} @@ -19107,9 +19113,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/eslint-parser@7.25.8(@babel/core@7.24.5)(eslint@8.57.1)': + '@babel/eslint-parser@7.25.8(@babel/core@7.25.7)(eslint@8.57.1)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.7 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 eslint: 8.57.1 eslint-visitor-keys: 2.1.0 @@ -24549,6 +24555,11 @@ snapshots: react: 18.2.0 react-native: 0.74.5(@babel/core@7.25.7)(@babel/preset-env@7.25.7(@babel/core@7.25.7))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0) + '@journeyapps/react-native-quick-sqlite@2.2.1(react-native@0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': + dependencies: + react: 18.2.0 + react-native: 0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7(@babel/core@7.24.5))(@types/react@18.2.79)(encoding@0.1.13)(react@18.2.0) + '@journeyapps/wa-sqlite@0.4.2': {} '@journeyapps/wa-sqlite@1.0.0': {} @@ -26904,7 +26915,7 @@ snapshots: '@react-native/eslint-config@0.73.2(eslint@8.57.1)(prettier@3.3.3)(typescript@5.5.4)': dependencies: '@babel/core': 7.24.5 - '@babel/eslint-parser': 7.25.8(@babel/core@7.24.5)(eslint@8.57.1) + '@babel/eslint-parser': 7.25.8(@babel/core@7.25.7)(eslint@8.57.1) '@react-native/eslint-plugin': 0.73.1 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4) '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.5.4) @@ -33369,7 +33380,7 @@ snapshots: eslint-plugin-ft-flow@2.0.3(@babel/eslint-parser@7.25.8(@babel/core@7.24.5)(eslint@8.57.1))(eslint@8.57.1): dependencies: - '@babel/eslint-parser': 7.25.8(@babel/core@7.24.5)(eslint@8.57.1) + '@babel/eslint-parser': 7.25.8(@babel/core@7.25.7)(eslint@8.57.1) eslint: 8.57.1 lodash: 4.17.21 string-natural-compare: 3.0.1 From a6a9178c0173a109b227e4a350d7d72377bbae1d Mon Sep 17 00:00:00 2001 From: Mugi Khan Date: Wed, 18 Dec 2024 13:23:35 +0200 Subject: [PATCH 4/4] PR feedback --- .../library/fts/fts_helpers.ts | 10 ++-------- .../library/powersync/system.ts | 2 +- .../library/widgets/SearchBarWidget.tsx | 10 +++++----- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts b/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts index 1b9d0be56..5d2a396b4 100644 --- a/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts +++ b/demos/react-native-supabase-todolist/library/fts/fts_helpers.ts @@ -26,14 +26,8 @@ export async function searchTable(searchTerm: string, tableName: string): Promis } //Used to display the search results in the autocomplete text field -export class SearchResult { +export interface SearchResult { id: string; - todoName: string | null; listName: string; - - constructor(id: string, listName: string, todoName: string | null = null) { - this.id = id; - this.listName = listName; - this.todoName = todoName; - } + todoName: string | null; } diff --git a/demos/react-native-supabase-todolist/library/powersync/system.ts b/demos/react-native-supabase-todolist/library/powersync/system.ts index 85d12364f..d95650c4b 100644 --- a/demos/react-native-supabase-todolist/library/powersync/system.ts +++ b/demos/react-native-supabase-todolist/library/powersync/system.ts @@ -72,7 +72,7 @@ export class System { // Demo using SQLite Full-Text Search with PowerSync. // See https://docs.powersync.com/usage-examples/full-text-search for more details - configureFts(this.powersync); + await configureFts(this.powersync); } } diff --git a/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx b/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx index eb9b2e895..ce5e35fc5 100644 --- a/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx +++ b/demos/react-native-supabase-todolist/library/widgets/SearchBarWidget.tsx @@ -24,11 +24,11 @@ export const SearchBarWidget: React.FC = () => { if (!todoItemsSearchResults.length) { listsSearchResults = await searchTable(value, 'lists'); } - const formattedListResults: SearchResult[] = listsSearchResults.map( - (result) => new SearchResult(result['id'], result['name']) - ); - const formattedTodoItemsResults: SearchResult[] = todoItemsSearchResults.map((result) => { - return new SearchResult(result['list_id'], result['list_name'] ?? '', result['description']); + const formattedListResults: SearchResult[] = listsSearchResults.map((result): SearchResult => { + return { id: result['id'], listName: result['name'], todoName: null }; + }); + const formattedTodoItemsResults: SearchResult[] = todoItemsSearchResults.map((result): SearchResult => { + return { id: result['list_id'], listName: result['list_name'] ?? '', todoName: result['description'] }; }); setSearchResults([...formattedTodoItemsResults, ...formattedListResults]); }