From aa48f216fe4c58090f939c190a1f7206eed907bc Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 8 Aug 2025 15:55:26 +0200 Subject: [PATCH 1/8] update capacitor demo --- .../android/app/capacitor.build.gradle | 2 + .../android/capacitor.settings.gradle | 6 + .../ios/App/App.xcodeproj/project.pbxproj | 4 +- .../example-capacitor/ios/App/App/Info.plist | 96 ++++---- demos/example-capacitor/ios/App/Podfile | 2 + demos/example-capacitor/ios/App/Podfile.lock | 28 ++- demos/example-capacitor/package.json | 8 +- demos/example-capacitor/src/app/LogsPage.tsx | 216 ++++++++++++++++++ demos/example-capacitor/src/app/index.tsx | 27 ++- demos/example-capacitor/src/app/page.tsx | 106 ++++++--- .../src/components/LocalEditWidget.tsx | 61 +++++ .../src/components/providers/Logging.ts | 120 ++++++++++ .../components/providers/SystemProvider.tsx | 23 +- .../src/library/powersync/AppSchema.ts | 12 +- demos/example-capacitor/tsconfig.json | 2 +- demos/example-capacitor/vite.config.ts | 2 +- pnpm-lock.yaml | 98 +++++++- 17 files changed, 690 insertions(+), 123 deletions(-) create mode 100644 demos/example-capacitor/src/app/LogsPage.tsx create mode 100644 demos/example-capacitor/src/components/LocalEditWidget.tsx create mode 100644 demos/example-capacitor/src/components/providers/Logging.ts diff --git a/demos/example-capacitor/android/app/capacitor.build.gradle b/demos/example-capacitor/android/app/capacitor.build.gradle index 259821da2..d46a7a3d6 100644 --- a/demos/example-capacitor/android/app/capacitor.build.gradle +++ b/demos/example-capacitor/android/app/capacitor.build.gradle @@ -9,6 +9,8 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-filesystem') + implementation project(':capacitor-share') implementation project(':capacitor-splash-screen') } diff --git a/demos/example-capacitor/android/capacitor.settings.gradle b/demos/example-capacitor/android/capacitor.settings.gradle index 68ddb413e..a765310e6 100644 --- a/demos/example-capacitor/android/capacitor.settings.gradle +++ b/demos/example-capacitor/android/capacitor.settings.gradle @@ -2,5 +2,11 @@ include ':capacitor-android' project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor') +include ':capacitor-filesystem' +project(':capacitor-filesystem').projectDir = new File('../../../node_modules/@capacitor/filesystem/android') + +include ':capacitor-share' +project(':capacitor-share').projectDir = new File('../../../node_modules/@capacitor/share/android') + include ':capacitor-splash-screen' project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/@capacitor/splash-screen/android') diff --git a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj index e9273628f..ba5de84a2 100644 --- a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj +++ b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj @@ -350,7 +350,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 13; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; @@ -370,7 +370,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 13; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.powersync.example; diff --git a/demos/example-capacitor/ios/App/App/Info.plist b/demos/example-capacitor/ios/App/App/Info.plist index 966c88263..f7c470487 100644 --- a/demos/example-capacitor/ios/App/App/Info.plist +++ b/demos/example-capacitor/ios/App/App/Info.plist @@ -1,49 +1,53 @@ - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - powersync-capacitor - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + powersync-capacitor + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSDocumentsFolderUsageDescription + This app needs access to documents folder to save files + NSFileProviderDomainUsageDescription + This app needs access to manage files + + \ No newline at end of file diff --git a/demos/example-capacitor/ios/App/Podfile b/demos/example-capacitor/ios/App/Podfile index 70b65a21b..9e2944c4f 100644 --- a/demos/example-capacitor/ios/App/Podfile +++ b/demos/example-capacitor/ios/App/Podfile @@ -11,6 +11,8 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios' + pod 'CapacitorFilesystem', :path => '../../../../node_modules/@capacitor/filesystem' + pod 'CapacitorShare', :path => '../../../../node_modules/@capacitor/share' pod 'CapacitorSplashScreen', :path => '../../../../node_modules/@capacitor/splash-screen' end diff --git a/demos/example-capacitor/ios/App/Podfile.lock b/demos/example-capacitor/ios/App/Podfile.lock index 850243219..d8e4f0f29 100644 --- a/demos/example-capacitor/ios/App/Podfile.lock +++ b/demos/example-capacitor/ios/App/Podfile.lock @@ -1,13 +1,19 @@ PODS: - - Capacitor (6.0.0): + - Capacitor (6.2.1): - CapacitorCordova - - CapacitorCordova (6.0.0) - - CapacitorSplashScreen (6.0.0): + - CapacitorCordova (6.2.1) + - CapacitorFilesystem (6.0.3): + - Capacitor + - CapacitorShare (6.0.3): + - Capacitor + - CapacitorSplashScreen (7.0.2): - Capacitor DEPENDENCIES: - "Capacitor (from `../../../../node_modules/@capacitor/ios`)" - "CapacitorCordova (from `../../../../node_modules/@capacitor/ios`)" + - "CapacitorFilesystem (from `../../../../node_modules/@capacitor/filesystem`)" + - "CapacitorShare (from `../../../../node_modules/@capacitor/share`)" - "CapacitorSplashScreen (from `../../../../node_modules/@capacitor/splash-screen`)" EXTERNAL SOURCES: @@ -15,14 +21,20 @@ EXTERNAL SOURCES: :path: "../../../../node_modules/@capacitor/ios" CapacitorCordova: :path: "../../../../node_modules/@capacitor/ios" + CapacitorFilesystem: + :path: "../../../../node_modules/@capacitor/filesystem" + CapacitorShare: + :path: "../../../../node_modules/@capacitor/share" CapacitorSplashScreen: :path: "../../../../node_modules/@capacitor/splash-screen" SPEC CHECKSUMS: - Capacitor: 559d073c4ca6c27f8e7002c807eea94c3ba435a9 - CapacitorCordova: 8c4bfdf69368512e85b1d8b724dd7546abeb30af - CapacitorSplashScreen: 5431ab8d19c1c6e95777d53bfaa7a36a6c3d94c7 + Capacitor: 1e0d0e7330dea9f983b50da737d8918abcf273f8 + CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff + CapacitorFilesystem: fa3099b3c3aa43a1b51362d0c999301ab1a9a752 + CapacitorShare: 7af6ca761ce62030e8e9fbd2eb82416f5ceced38 + CapacitorSplashScreen: 8d6c8cb0542a8e81585c593815db8785ed8ce454 -PODFILE CHECKSUM: 30a5df536d5e7830e635f84e1fe35fa438802eaa +PODFILE CHECKSUM: c1703336f990a4728e25eafbdd9992a7afc2008e -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/demos/example-capacitor/package.json b/demos/example-capacitor/package.json index b9609d0e9..fa973a2fe 100644 --- a/demos/example-capacitor/package.json +++ b/demos/example-capacitor/package.json @@ -21,14 +21,19 @@ "dependencies": { "@capacitor/android": "^6.0.0", "@capacitor/core": "latest", + "@capacitor/filesystem": "^6.0.0", "@capacitor/ios": "^6.0.0", + "@capacitor/share": "^6.0.0", "@capacitor/splash-screen": "latest", "@journeyapps/wa-sqlite": "^1.2.0", + "@mui/icons-material": "^7.3.1", "@powersync/react": "workspace:*", "@powersync/web": "workspace:*", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.23.0" + "react-router-dom": "^6.30.1", + "react-window": "^1.8.11" }, "devDependencies": { "@capacitor/cli": "^6.0.0", @@ -36,6 +41,7 @@ "@types/node": "^20.12.12", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", + "@types/react-window": "^1.8.8", "vite": "^5.2.11", "vite-plugin-require": "^1.2.14", "vite-plugin-top-level-await": "^1.4.1", diff --git a/demos/example-capacitor/src/app/LogsPage.tsx b/demos/example-capacitor/src/app/LogsPage.tsx new file mode 100644 index 000000000..fadafdb3a --- /dev/null +++ b/demos/example-capacitor/src/app/LogsPage.tsx @@ -0,0 +1,216 @@ +import { Capacitor } from '@capacitor/core'; +import { Share } from '@capacitor/share'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { + Avatar, + Button, + ButtonGroup, + Checkbox, + Divider, + FormControlLabel, + FormGroup, + Grid, + IconButton, + ListItem, + ListItemAvatar, + ListItemText, + Paper, + styled, + Typography +} from '@mui/material'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; +import { LOG_STORAGE, LogRecord } from '../components/providers/Logging.js'; + +function downloadTextFile(filename: string, content: string) { + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); +} + +async function shareFile(content: string) { + await Share.share({ + title: 'logs', + text: content, + dialogTitle: 'Share your log file' + }); +} + +export const LogDisplay: React.FC<{ logs: ReadonlyArray> }> = React.memo((props) => { + const { logs } = props; + + const Row = ({ index, style }: ListChildComponentProps) => { + const log = logs[index]; + return ( +
+ + + + {log.level[0].toUpperCase()} + + + + + {log.timestamp} + + + {log.level.toUpperCase()} + + + } + secondary={ + + {log.message.slice(0, 100)} + {log.message.length > 100 ? '...' : ''} + + } + /> + + {index < logs.length - 1 && } +
+ ); + }; + + return ( + + + {logs.length === 0 ? ( + No logs + ) : ( + + {Row} + + )} + + + ); +}); + +const LOG_LEVELS = [ + { label: 'Error', value: 'ERROR', color: '#d32f2f' }, + { label: 'Warn', value: 'WARN', color: '#fbc02d' }, + { label: 'Debug', value: 'DEBUG', color: '#1976d2' }, + { label: 'Info', value: 'INFO', color: '#1976d2' } +]; + +const LogsPage = () => { + const [logs, setLogs] = React.useState(LOG_STORAGE.logs); + const [selectedLevels, setSelectedLevels] = React.useState(LOG_LEVELS.map((l) => l.value)); + const navigate = useNavigate(); + + React.useEffect(() => { + return LOG_STORAGE.registerListener({ + logsUpdated: (logs) => setLogs([...logs]) + }); + }, []); + + const handleLevelChange = (level: string) => { + setSelectedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level])); + }; + + const filteredLogs = logs.filter((log) => selectedLevels.includes(log.level)); + + return ( + + + navigate(-1)} aria-label="Back"> + + + + Logs + + + + + {LOG_LEVELS.map(({ label, value, color }) => ( + handleLevelChange(value)} + sx={{ color, '&.Mui-checked': { color } }} + /> + } + label={label} + /> + ))} + + + + + + + + + + + + + ); +}; + +namespace S { + export const MainGrid = styled(Grid)` + width: 100vw; + `; + + export const LogsListContainer = styled('div')` + width: 100%; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07); + overflow-x: auto; + overflow-y: auto; + max-height: 1000px; + padding: 0.5rem 0; + `; +} + +export default LogsPage; diff --git a/demos/example-capacitor/src/app/index.tsx b/demos/example-capacitor/src/app/index.tsx index c8a2e01f8..8bc064d98 100644 --- a/demos/example-capacitor/src/app/index.tsx +++ b/demos/example-capacitor/src/app/index.tsx @@ -1,15 +1,32 @@ -import React from 'react'; import { createRoot } from 'react-dom/client'; -import EntryPage from './page.jsx'; + +import { createTheme, CssBaseline, ThemeProvider } from '@mui/material'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; import SystemProvider from '../components/providers/SystemProvider.jsx'; +import LogsPage from './LogsPage.jsx'; +import EntryPage from './page.jsx'; + +const theme = createTheme({ + palette: { + mode: 'dark' + } +}); const root = createRoot(document.getElementById('app')!); root.render(); export function App() { return ( - - - + + + + + + } /> + } /> + + + + ); } diff --git a/demos/example-capacitor/src/app/page.tsx b/demos/example-capacitor/src/app/page.tsx index b5f0049f5..35e125025 100644 --- a/demos/example-capacitor/src/app/page.tsx +++ b/demos/example-capacitor/src/app/page.tsx @@ -1,34 +1,15 @@ +import { Button, ButtonGroup, CircularProgress, Grid, ListItem, Paper, styled } from '@mui/material'; +import { usePowerSync, useQuery, useStatus } from '@powersync/react'; import React from 'react'; -import { CircularProgress, Grid, ListItem, styled } from '@mui/material'; -import { useQuery, useStatus } from '@powersync/react'; - -const EntryPage = () => { - const status = useStatus(); - const { data: customers } = useQuery('SELECT id, name FROM customers'); - - const areVariablesSet = import.meta.env.VITE_POWERSYNC_URL && import.meta.env.VITE_PUBLIC_POWERSYNC_TOKEN; - - if (areVariablesSet && !status.hasSynced) { - return ( - -

- Syncing down from the backend. This will load indefinitely if you have not set the connection up correctly. -

- -
- ); - } - - if (!areVariablesSet) { - return ( - -

You have not set up a connection to the backend, please connect your backend.

-
- ); - } +import { useNavigate } from 'react-router-dom'; +import LocalEditWidget from '../components/LocalEditWidget.js'; +import { Customer } from '../library/powersync/AppSchema.js'; +import { BackendConnector } from '../library/powersync/BackendConnector.js'; +const CustomersWidget: React.FC<{ customers: Customer[] }> = (props) => { + const { customers } = props; return ( - +

Customers

@@ -47,21 +28,74 @@ const EntryPage = () => { )} -
+ + ); +}; + +const EntryPage = () => { + const status = useStatus(); + const powerSync = usePowerSync(); + + const { data: customers } = useQuery('SELECT id, name FROM customers'); + const navigate = useNavigate(); + + const areVariablesSet = import.meta.env.VITE_POWERSYNC_URL && import.meta.env.VITE_PUBLIC_POWERSYNC_TOKEN; + + return ( + + + + + + + + + + + + {areVariablesSet && !status.hasSynced && ( + <> +

+ Syncing down from the backend. This will load indefinitely if you have not set the connection up + correctly. +

+ + + )} + {!areVariablesSet && ( +

+ You have not set up a connection to the backend, please connect your backend. +

+ )} + {areVariablesSet && status.hasSynced && } +
+
+ + + + +
); }; namespace S { - export const CenteredGrid = styled(Grid)` + export const FlexGrid = styled(Grid)` display: flex; - justify-content: center; - align-items: center; + flex-direction: column; + margin: 10px; `; - export const MainGrid = styled(CenteredGrid)` - min-height: 100vh; - display: flex; - flex-direction: column; + export const CenteredGrid = styled(FlexGrid)` + justify-content: center; + align-items: center; `; } diff --git a/demos/example-capacitor/src/components/LocalEditWidget.tsx b/demos/example-capacitor/src/components/LocalEditWidget.tsx new file mode 100644 index 000000000..864aac62e --- /dev/null +++ b/demos/example-capacitor/src/components/LocalEditWidget.tsx @@ -0,0 +1,61 @@ +import DeleteIcon from '@mui/icons-material/Delete'; +import { Button, IconButton, List, ListItem, ListItemText, Paper, Typography } from '@mui/material'; +import { usePowerSync, useQuery } from '@powersync/react'; +import React from 'react'; +import { Product } from '../library/powersync/AppSchema.js'; + +function getRandomProductName() { + const adjectives = ['Cool', 'Amazing', 'Fresh', 'New', 'Shiny', 'Super', 'Eco', 'Smart']; + const nouns = ['Widget', 'Gadget', 'Device', 'Item', 'Thing', 'Product', 'Tool', 'Object']; + return ( + adjectives[Math.floor(Math.random() * adjectives.length)] + + ' ' + + nouns[Math.floor(Math.random() * nouns.length)] + + ' #' + + Math.floor(Math.random() * 10000) + ); +} + +export default function LocalEditWidget() { + const powerSync = usePowerSync(); + const { data: products } = useQuery('SELECT * FROM products'); + + const addProduct = React.useCallback(() => { + return powerSync.execute('INSERT INTO products (id, name) VALUES (uuid(), ?)', [getRandomProductName()]); + }, []); + + const deleteProduct = React.useCallback((product: Product) => { + return powerSync.execute('DELETE FROM products WHERE id = ?', [product.id]); + }, []); + + return ( + + + Products + + Perform local only edits to Products. These won't be synced. + + + {products.length === 0 ? ( + + + + ) : ( + products.map((product) => ( + deleteProduct(product)}> + + + }> + + + )) + )} + + + ); +} diff --git a/demos/example-capacitor/src/components/providers/Logging.ts b/demos/example-capacitor/src/components/providers/Logging.ts new file mode 100644 index 000000000..e31d97b71 --- /dev/null +++ b/demos/example-capacitor/src/components/providers/Logging.ts @@ -0,0 +1,120 @@ +import { BaseObserver, ControlledExecutor, createBaseLogger, LogLevel } from '@powersync/web'; + +export type LogRecord = { + level: string; + timestamp: string; + message: string; +}; + +export type LogListener = { + logsUpdated: (logs: ReadonlyArray>) => void; +}; + +export class LogStorage extends BaseObserver { + protected _logs: LogRecord[]; + protected writeExecutor: ControlledExecutor; + protected dbPromise: Promise; + + static readonly LOGS_DB_NAME = '_ps_logs_db'; + static readonly LOGS_STORE_NAME = 'logs'; + + get logs(): ReadonlyArray> { + return this._logs; + } + + constructor() { + super(); + this._logs = []; + this.writeExecutor = new ControlledExecutor(() => this.saveLogsToIndexedDB()); + this.dbPromise = this.openLogsDB(); + this.readLogsFromIndexedDB(); + } + + clearLogs() { + this._logs = []; + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + } + + writeLog(record: LogRecord) { + this._logs.unshift(record); + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + this.writeExecutor.schedule(); + } + + private async saveLogsToIndexedDB() { + const db = await this.dbPromise; + const tx = db.transaction(LogStorage.LOGS_STORE_NAME, 'readwrite'); + const store = tx.objectStore(LogStorage.LOGS_STORE_NAME); + await new Promise((resolve, reject) => { + const clearReq = store.clear(); + clearReq.onsuccess = () => { + this.logs.forEach((log) => store.add(log)); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }; + clearReq.onerror = () => reject(clearReq.error); + }); + } + + // Read all LogRecord entries from IndexedDB + private async readLogsFromIndexedDB(): Promise { + const db = await this.dbPromise; + const tx = db.transaction(LogStorage.LOGS_STORE_NAME, 'readwrite'); + const store = tx.objectStore(LogStorage.LOGS_STORE_NAME); + return new Promise((resolve, reject) => { + const req = store.getAll(); + req.onsuccess = () => { + this._logs = (req.result as any[]) + .map(({ id, ...rest }) => rest) + // sort in descending order + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + resolve(this._logs); + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + }; + req.onerror = () => reject(req.error); + }); + } + + // Helper to open the IndexedDB database + private async openLogsDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(LogStorage.LOGS_DB_NAME, 1); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(LogStorage.LOGS_STORE_NAME)) { + db.createObjectStore(LogStorage.LOGS_STORE_NAME, { keyPath: 'id', autoIncrement: true }); + } + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } +} + +export const LOG_STORAGE = new LogStorage(); + +const mapLogParam = (param: any) => { + if (param instanceof Error) { + return ` +Error +Name: ${param.name} +Message: ${param.message} +Cause: ${param.cause} +Stack: ${param.stack} + `.trim(); + } + return JSON.stringify(param); +}; + +// Configure base logger for global settings +const logger = createBaseLogger(); +logger.useDefaults({ + defaultLevel: LogLevel.DEBUG, + formatter: (messages, context) => { + LOG_STORAGE.writeLog({ + level: context.level.name, + message: messages.map(mapLogParam).join(' -- '), + timestamp: new Date().toISOString() + }); + } +}); diff --git a/demos/example-capacitor/src/components/providers/SystemProvider.tsx b/demos/example-capacitor/src/components/providers/SystemProvider.tsx index fc3b9e050..898311c25 100644 --- a/demos/example-capacitor/src/components/providers/SystemProvider.tsx +++ b/demos/example-capacitor/src/components/providers/SystemProvider.tsx @@ -1,30 +1,25 @@ -import { PowerSyncContext } from '@powersync/react'; -import { PowerSyncDatabase, createBaseLogger, LogLevel } from '@powersync/web'; +import { Capacitor } from '@capacitor/core'; import { CircularProgress } from '@mui/material'; +import { PowerSyncContext } from '@powersync/react'; +import { PowerSyncDatabase, WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web'; import React, { Suspense } from 'react'; import { AppSchema } from '../../library/powersync/AppSchema.js'; import { BackendConnector } from '../../library/powersync/BackendConnector.js'; -import { Capacitor } from '@capacitor/core'; - -const logger = createBaseLogger(); -logger.useDefaults(); -logger.setLevel(LogLevel.DEBUG); const platform = Capacitor.getPlatform(); -const isIOs = platform === 'ios'; -// Web worker implementation does not work on iOS -const useWebWorker = !isIOs; const powerSync = new PowerSyncDatabase({ - database: { dbFilename: 'powersync2.db' }, + database: new WASQLiteOpenFactory({ + dbFilename: 'ps.db', + vfs: WASQLiteVFS.AccessHandlePoolVFS, + debugMode: true + }), schema: AppSchema, flags: { - enableMultiTabs: false, - useWebWorker + enableMultiTabs: false } }); const connector = new BackendConnector(); - powerSync.connect(connector); export const SystemProvider = ({ children }: { children: React.ReactNode }) => { diff --git a/demos/example-capacitor/src/library/powersync/AppSchema.ts b/demos/example-capacitor/src/library/powersync/AppSchema.ts index 3569f78fc..f24887f76 100644 --- a/demos/example-capacitor/src/library/powersync/AppSchema.ts +++ b/demos/example-capacitor/src/library/powersync/AppSchema.ts @@ -5,8 +5,18 @@ const customers = new Table({ created_at: column.text }); +const products = new Table( + { + name: column.text + }, + { + localOnly: true + } +); + export const AppSchema = new Schema({ - customers + customers, + products }); export type Database = (typeof AppSchema)['types']; diff --git a/demos/example-capacitor/tsconfig.json b/demos/example-capacitor/tsconfig.json index 98800e2e9..59dfe0221 100644 --- a/demos/example-capacitor/tsconfig.json +++ b/demos/example-capacitor/tsconfig.json @@ -11,7 +11,7 @@ "outDir": "dist", "moduleResolution": "NodeNext", "resolveJsonModule": true, - "jsx": "preserve" + "jsx": "react-jsx" }, "references": [ { diff --git a/demos/example-capacitor/vite.config.ts b/demos/example-capacitor/vite.config.ts index e36be6a57..f0bcf31dc 100644 --- a/demos/example-capacitor/vite.config.ts +++ b/demos/example-capacitor/vite.config.ts @@ -1,5 +1,5 @@ -import wasm from 'vite-plugin-wasm'; import topLevelAwait from 'vite-plugin-top-level-await'; +import wasm from 'vite-plugin-wasm'; import { defineConfig } from 'vite'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 899edee2f..806d5d66f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,21 +271,33 @@ importers: '@capacitor/core': specifier: latest version: 7.4.2 + '@capacitor/filesystem': + specifier: ^6.0.0 + version: 6.0.3(@capacitor/core@7.4.2) '@capacitor/ios': specifier: ^6.0.0 version: 6.2.1(@capacitor/core@7.4.2) + '@capacitor/share': + specifier: ^6.0.0 + version: 6.0.3(@capacitor/core@7.4.2) '@capacitor/splash-screen': specifier: latest - version: 7.0.1(@capacitor/core@7.4.2) + version: 7.0.2(@capacitor/core@7.4.2) '@journeyapps/wa-sqlite': specifier: ^1.2.0 version: 1.2.6 + '@mui/icons-material': + specifier: ^7.3.1 + version: 7.3.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.23)(react@18.3.1) '@powersync/react': specifier: workspace:* version: link:../../packages/react '@powersync/web': specifier: workspace:* version: link:../../packages/web + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 react: specifier: ^18.2.0 version: 18.3.1 @@ -293,8 +305,11 @@ importers: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) react-router-dom: - specifier: ^6.23.0 + specifier: ^6.30.1 version: 6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-window: + specifier: ^1.8.11 + version: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@capacitor/cli': specifier: ^6.0.0 @@ -311,6 +326,9 @@ importers: '@types/react-dom': specifier: ^18.3.0 version: 18.3.6(@types/react@18.3.23) + '@types/react-window': + specifier: ^1.8.8 + version: 1.8.8 vite: specifier: ^5.2.11 version: 5.4.19(@types/node@20.17.57)(less@4.2.2)(lightningcss@1.30.1)(sass@1.89.1)(terser@5.40.0) @@ -847,7 +865,7 @@ importers: version: 1.23.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)) '@shopify/flash-list': specifier: 1.7.3 - version: 1.7.3(@babel/runtime@7.27.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.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + version: 1.7.3(@babel/runtime@7.28.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.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) '@supabase/supabase-js': specifier: 2.39.0 version: 2.39.0 @@ -3486,6 +3504,10 @@ packages: resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.2': + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -3522,13 +3544,23 @@ packages: '@capacitor/core@7.4.2': resolution: {integrity: sha512-akCf9A1FUR8AWTtmgGjHEq6LmGsjA2U7igaJ9PxiCBfyxKqlDbuGHrlNdpvHEjV5tUPH3KYtkze6gtFcNKPU9A==} + '@capacitor/filesystem@6.0.3': + resolution: {integrity: sha512-PdIP/yOGAbG1lq1wbFbSPhXQ9/5lpTpeiok2NneawJOk6UXvy9W7QZXRo7wXAP7J6FdzU7bKfOORRXpOJpgXyw==} + peerDependencies: + '@capacitor/core': ^6.0.0 + '@capacitor/ios@6.2.1': resolution: {integrity: sha512-tbMlQdQjxe1wyaBvYVU1yTojKJjgluZQsJkALuJxv/6F8QTw5b6vd7X785O/O7cMpIAZfUWo/vtAHzFkRV+kXw==} peerDependencies: '@capacitor/core': ^6.2.0 - '@capacitor/splash-screen@7.0.1': - resolution: {integrity: sha512-Nbqw9bEIe7uHj/HOT81mf4jT6uK1YykozpQw/uIKQDueMg6RJYaJK2/TMajIOohLk8fJF4TYIc1i9nGjNLnfGg==} + '@capacitor/share@6.0.3': + resolution: {integrity: sha512-BkNM73Ix+yxQ7fkni8CrrGcp1kSl7u+YNoPLwWKQ1MuQ5Uav0d+CT8M67ie+3dc4jASmegnzlC6tkTmFcPTLeA==} + peerDependencies: + '@capacitor/core': ^6.0.0 + + '@capacitor/splash-screen@7.0.2': + resolution: {integrity: sha512-bchh4F73CnVONm6XFEgXKEhbSEDQh2CQ0rNSoasIeJ5pf9JqHkkPS3t0Fnm33qHkLVFcaPoKPW69Y9zMpT5Vxg==} peerDependencies: '@capacitor/core': '>=7.0.0' @@ -5935,6 +5967,17 @@ packages: '@types/react': optional: true + '@mui/icons-material@7.3.1': + resolution: {integrity: sha512-upzCtG6awpL6noEZlJ5Z01khZ9VnLNLaj7tb6iPbN6G97eYfUTs8e9OyPKy3rEms3VQWmVBfri7jzeaRxdFIzA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^7.3.1 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/material@5.17.1': resolution: {integrity: sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==} engines: {node: '>=12.0.0'} @@ -9013,6 +9056,9 @@ packages: peerDependencies: '@types/react': '*' + '@types/react-window@1.8.8': + resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} + '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} @@ -17800,6 +17846,13 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' + react-window@1.8.11: + resolution: {integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==} + engines: {node: '>8.0.0'} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -22492,6 +22545,8 @@ snapshots: '@babel/runtime@7.27.6': {} + '@babel/runtime@7.28.2': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -22555,11 +22610,19 @@ snapshots: dependencies: tslib: 2.8.1 + '@capacitor/filesystem@6.0.3(@capacitor/core@7.4.2)': + dependencies: + '@capacitor/core': 7.4.2 + '@capacitor/ios@6.2.1(@capacitor/core@7.4.2)': dependencies: '@capacitor/core': 7.4.2 - '@capacitor/splash-screen@7.0.1(@capacitor/core@7.4.2)': + '@capacitor/share@6.0.3(@capacitor/core@7.4.2)': + dependencies: + '@capacitor/core': 7.4.2 + + '@capacitor/splash-screen@7.0.2(@capacitor/core@7.4.2)': dependencies: '@capacitor/core': 7.4.2 @@ -26489,6 +26552,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 + '@mui/icons-material@7.3.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.23)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.28.2 + '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.23 + '@mui/material@5.17.1(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.27.6 @@ -29273,9 +29344,9 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@shopify/flash-list@1.7.3(@babel/runtime@7.27.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.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)': + '@shopify/flash-list@1.7.3(@babel/runtime@7.28.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.3.3))(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 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) recyclerlistview: 4.2.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) @@ -31070,6 +31141,10 @@ snapshots: dependencies: '@types/react': 18.3.23 + '@types/react-window@1.8.8': + dependencies: + '@types/react': 18.3.23 + '@types/react@18.3.1': dependencies: '@types/prop-types': 15.7.14 @@ -43461,6 +43536,13 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + memoize-one: 5.2.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 From dfaa26c7f180a575a91baadcb464d3eb8b4fb94c Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 8 Aug 2025 15:56:33 +0200 Subject: [PATCH 2/8] Add ability to access DB worker logs in main context --- packages/common/src/client/SQLOpenFactory.ts | 3 + .../db/adapters/AsyncDatabaseConnection.ts | 17 +++- .../adapters/wa-sqlite/WASQLiteConnection.ts | 22 ++++- .../adapters/wa-sqlite/WASQLiteOpenFactory.ts | 29 +++--- packages/web/src/worker/db/LogHandler.ts | 17 ++++ .../web/src/worker/db/WASQLiteDB.worker.ts | 94 ++++++++++++++++--- 6 files changed, 153 insertions(+), 29 deletions(-) create mode 100644 packages/web/src/worker/db/LogHandler.ts diff --git a/packages/common/src/client/SQLOpenFactory.ts b/packages/common/src/client/SQLOpenFactory.ts index 44d5b21e4..3c3b34779 100644 --- a/packages/common/src/client/SQLOpenFactory.ts +++ b/packages/common/src/client/SQLOpenFactory.ts @@ -1,3 +1,4 @@ +import { ILogger } from 'js-logger'; import { DBAdapter } from '../db/DBAdapter.js'; export interface SQLOpenOptions { @@ -23,6 +24,8 @@ export interface SQLOpenOptions { * debugMode: process.env.NODE_ENV !== 'production' */ debugMode?: boolean; + + logger?: ILogger; } export interface SQLOpenFactory { diff --git a/packages/web/src/db/adapters/AsyncDatabaseConnection.ts b/packages/web/src/db/adapters/AsyncDatabaseConnection.ts index 183d5210b..f78d80096 100644 --- a/packages/web/src/db/adapters/AsyncDatabaseConnection.ts +++ b/packages/web/src/db/adapters/AsyncDatabaseConnection.ts @@ -32,9 +32,24 @@ export interface AsyncDatabaseConnection; } +/** + * @internal + */ +export interface DBWorkerLogEvent { + loggerName: string; + logLevel: string; + messages: string[]; +} + +/** + * @internal + */ +export type WorkerLogHandler = (event: DBWorkerLogEvent) => void; + /** * @internal */ export type OpenAsyncDatabaseConnection = ( - config: Config + config: Config, + logger?: WorkerLogHandler ) => AsyncDatabaseConnection; diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts index 091c85da4..a0d05064d 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts @@ -1,5 +1,5 @@ import * as SQLite from '@journeyapps/wa-sqlite'; -import { BaseObserver, BatchedUpdateNotification } from '@powersync/common'; +import { BaseObserver, BatchedUpdateNotification, ILogger } from '@powersync/common'; import { Mutex } from 'async-mutex'; import { AsyncDatabaseConnection, OnTableChangeCallback, ProxiedQueryResult } from '../AsyncDatabaseConnection'; import { ResolvedWASQLiteOpenFactoryOptions } from './WASQLiteOpenFactory'; @@ -36,7 +36,12 @@ export type SQLiteModule = Parameters[0]; /** * @internal */ -export type WASQLiteModuleFactoryOptions = { dbFileName: string; encryptionKey?: string }; +export type WASQLiteModuleFactoryOptions = { + dbFileName: string; + logger?: ILogger; + encryptionKey?: string; + debugMode?: boolean; +}; /** * @internal @@ -104,9 +109,16 @@ export const DEFAULT_MODULE_FACTORIES = { } // @ts-expect-error The types for this static method are missing upstream const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js'); + const vfs = await AccessHandlePoolVFS.create(options.dbFileName, module); + + if (options.debugMode && options.logger) { + // Enable VFS logs + vfs.log = (...params: any[]) => options.logger?.debug(...params); + } + return { module, - vfs: await AccessHandlePoolVFS.create(options.dbFileName, module) + vfs }; }, [WASQLiteVFS.OPFSCoopSyncVFS]: async (options: WASQLiteModuleFactoryOptions) => { @@ -187,7 +199,9 @@ export class WASqliteConnection protected async openSQLiteAPI(): Promise { const { module, vfs } = await this._moduleFactory({ dbFileName: this.options.dbFilename, - encryptionKey: this.options.encryptionKey + encryptionKey: this.options.encryptionKey, + debugMode: this.options.debugMode, + logger: this.options.logger }); const sqlite3 = SQLite.Factory(module); sqlite3.vfs_register(vfs, true); diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts index 129a3b130..45d7c4719 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts @@ -2,7 +2,7 @@ import { type ILogLevel, DBAdapter } from '@powersync/common'; import * as Comlink from 'comlink'; import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database'; import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory'; -import { AsyncDatabaseConnection, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection'; +import { AsyncDatabaseConnection, DBWorkerLogEvent, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection'; import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter'; import { DEFAULT_CACHE_SIZE_KB, @@ -21,7 +21,7 @@ export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOp vfs: WASQLiteVFS; } -export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions { +export interface WorkerDBOpenerOptions extends Omit { logLevel: ILogLevel; } @@ -82,15 +82,22 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory { return new WorkerWrappedAsyncDatabaseConnection({ remote: workerDBOpener, - baseConnection: await workerDBOpener({ - dbFilename: this.options.dbFilename, - vfs, - temporaryStorage, - cacheSizeKb, - flags: this.resolvedFlags, - encryptionKey: encryptionKey, - logLevel: this.logger.getLevel() - }), + baseConnection: await workerDBOpener( + { + dbFilename: this.options.dbFilename, + vfs, + temporaryStorage, + cacheSizeKb, + flags: this.resolvedFlags, + encryptionKey: encryptionKey, + logLevel: this.logger.getLevel(), + debugMode: this.options.debugMode + }, + Comlink.proxy((event: DBWorkerLogEvent) => { + // TODO + (this.logger as any)[event.logLevel.toLocaleLowerCase()]('[DB Worker] ', ...event.messages); + }) + ), identifier: this.options.dbFilename, onClose: () => { if (workerPort instanceof Worker) { diff --git a/packages/web/src/worker/db/LogHandler.ts b/packages/web/src/worker/db/LogHandler.ts new file mode 100644 index 000000000..990500b84 --- /dev/null +++ b/packages/web/src/worker/db/LogHandler.ts @@ -0,0 +1,17 @@ +import { BaseListener, BaseObserver } from '@powersync/common'; + +export type LogEvent = { + loggerName: string; + logLevel: string; + messages: string[]; +}; + +export interface LogHandlerListener extends BaseListener { + onLog: (event: LogEvent) => void; +} + +export class LogHandler extends BaseObserver { + pushLog(entry: LogEvent) { + this.iterateListeners((l) => l.onLog?.(entry)); + } +} diff --git a/packages/web/src/worker/db/WASQLiteDB.worker.ts b/packages/web/src/worker/db/WASQLiteDB.worker.ts index 3db4bdc49..6d1309ce9 100644 --- a/packages/web/src/worker/db/WASQLiteDB.worker.ts +++ b/packages/web/src/worker/db/WASQLiteDB.worker.ts @@ -3,19 +3,16 @@ */ import '@journeyapps/wa-sqlite'; -import { createBaseLogger, createLogger } from '@powersync/common'; +import { createBaseLogger, createLogger, ILogHandler, LogLevel } from '@powersync/common'; import * as Comlink from 'comlink'; -import { AsyncDatabaseConnection } from '../../db/adapters/AsyncDatabaseConnection'; +import { AsyncDatabaseConnection, WorkerLogHandler } from '../../db/adapters/AsyncDatabaseConnection'; import { WASqliteConnection } from '../../db/adapters/wa-sqlite/WASQLiteConnection'; import { ResolvedWASQLiteOpenFactoryOptions, WorkerDBOpenerOptions } from '../../db/adapters/wa-sqlite/WASQLiteOpenFactory'; import { getNavigatorLocks } from '../../shared/navigator'; - -const baseLogger = createBaseLogger(); -baseLogger.useDefaults(); -const logger = createLogger('db-worker'); +import { LogHandler } from './LogHandler'; /** * Keeps track of open DB connections and the clients which @@ -31,11 +28,60 @@ const OPEN_DB_LOCK = 'open-wasqlite-db'; let nextClientId = 1; +function logUnhandledException(error: Error) { + const errorMessage = ` + Name: ${error.name} + Cause: ${error.cause} + Message: ${error.message} + Stack: ${error.stack}`.trim(); + + for (const dbFilename of DBMap.keys()) { + logBroadcaster.pushLog({ + loggerName: dbFilename, + logLevel: LogLevel.ERROR.name, + messages: ['Uncaught Exception in DB worker', errorMessage] + }); + } +} + +// Report unhandled exceptions to all loggers +addEventListener('unhandledrejection', (event) => { + logUnhandledException(event.reason); +}); + +addEventListener('error', (event) => { + logUnhandledException(event.error); +}); + +const baseLogger = createBaseLogger(); + +const logBroadcaster = new LogHandler(); + +const defaultHandler = baseLogger.createDefaultHandler(); +const logHandler: ILogHandler = (messages, context) => { + logBroadcaster.pushLog({ + loggerName: context.name ?? 'unknown', + logLevel: context.level.name, + messages: messages.map((m) => String(m)) + }); + defaultHandler(messages, context); +}; + +baseLogger.useDefaults({ + formatter: logHandler +}); + +const workerLogger = createLogger('db-worker'); + const openWorkerConnection = async (options: ResolvedWASQLiteOpenFactoryOptions): Promise => { const connection = new WASqliteConnection(options); return { init: Comlink.proxy(() => connection.init()), - getConfig: Comlink.proxy(() => connection.getConfig()), + getConfig: Comlink.proxy(() => { + const config = connection.getConfig(); + // TODO logger is not transferable + return Object.fromEntries(Object.entries(config).filter(([key]) => key !== 'logger')) as any; + }), close: Comlink.proxy(() => connection.close()), execute: Comlink.proxy(async (sql: string, params?: any[]) => connection.execute(sql, params)), executeRaw: Comlink.proxy(async (sql: string, params?: any[]) => connection.executeRaw(sql, params)), @@ -47,17 +93,38 @@ const openWorkerConnection = async (options: ResolvedWASQLiteOpenFactoryOptions) }; }; -const openDBShared = async (options: WorkerDBOpenerOptions): Promise => { +const openDBShared = async ( + options: WorkerDBOpenerOptions, + logHandler?: WorkerLogHandler +): Promise => { // Prevent multiple simultaneous opens from causing race conditions return getNavigatorLocks().request(OPEN_DB_LOCK, async () => { const clientId = nextClientId++; const { dbFilename, logLevel } = options; - logger.setLevel(logLevel); + // This updates the log level for the worker-level logger + // The DB connection logger will automatically track the main context logger + // since it passes logs to it. + workerLogger.setLevel(logLevel); + + let disposeLogListener = logHandler + ? logBroadcaster.registerListener({ + onLog: (event) => { + if (event.loggerName !== dbFilename) { + return; + } + logHandler(event); + } + }) + : null; if (!DBMap.has(dbFilename)) { const clientIds = new Set(); - const connection = await openWorkerConnection(options); + const logger = createLogger(dbFilename); + const connection = await openWorkerConnection({ + ...options, + logger + }); await connection.init(); DBMap.set(dbFilename, { clientIds, @@ -76,14 +143,15 @@ const openDBShared = async (options: WorkerDBOpenerOptions): Promise { const { clientIds } = dbEntry; - logger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`); + disposeLogListener?.(); + workerLogger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`); clientIds.delete(clientId); if (clientIds.size == 0) { - logger.debug(`Closing connection to ${dbFilename}.`); + workerLogger.debug(`Closing connection to ${dbFilename}.`); DBMap.delete(dbFilename); return db.close?.(); } - logger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`); + workerLogger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`); return; }) }; From ec901c6c102f9b24e462af38e11c660a6cc0114a Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 8 Aug 2025 16:40:49 +0200 Subject: [PATCH 3/8] make capacitor demo more responsive --- .../components/providers/SystemProvider.tsx | 2 +- demos/example-capacitor/src/index.css | 7 ++++-- demos/example-capacitor/src/index.html | 24 +++++++++++-------- .../adapters/wa-sqlite/WASQLiteConnection.ts | 1 + 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/demos/example-capacitor/src/components/providers/SystemProvider.tsx b/demos/example-capacitor/src/components/providers/SystemProvider.tsx index 898311c25..e4a2ad124 100644 --- a/demos/example-capacitor/src/components/providers/SystemProvider.tsx +++ b/demos/example-capacitor/src/components/providers/SystemProvider.tsx @@ -11,7 +11,7 @@ const platform = Capacitor.getPlatform(); const powerSync = new PowerSyncDatabase({ database: new WASQLiteOpenFactory({ dbFilename: 'ps.db', - vfs: WASQLiteVFS.AccessHandlePoolVFS, + vfs: platform == 'ios' ? WASQLiteVFS.AccessHandlePoolVFS : WASQLiteVFS.OPFSCoopSyncVFS, debugMode: true }), schema: AppSchema, diff --git a/demos/example-capacitor/src/index.css b/demos/example-capacitor/src/index.css index 8856f90b3..a89143ed2 100644 --- a/demos/example-capacitor/src/index.css +++ b/demos/example-capacitor/src/index.css @@ -1,7 +1,10 @@ body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; margin: auto; max-width: 38rem; padding: 2rem; + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); } diff --git a/demos/example-capacitor/src/index.html b/demos/example-capacitor/src/index.html index de7a97c29..27e149188 100644 --- a/demos/example-capacitor/src/index.html +++ b/demos/example-capacitor/src/index.html @@ -1,11 +1,15 @@ - - - - - - - -
- - + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts index a0d05064d..57b16ddc0 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts @@ -111,6 +111,7 @@ export const DEFAULT_MODULE_FACTORIES = { const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js'); const vfs = await AccessHandlePoolVFS.create(options.dbFileName, module); + // TODO, maybe use a different flag for this (if we want to actually expose this) if (options.debugMode && options.logger) { // Enable VFS logs vfs.log = (...params: any[]) => options.logger?.debug(...params); From 9f366671211478d3ab312d6082ae6310787bcefb Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 8 Aug 2025 16:55:17 +0200 Subject: [PATCH 4/8] fix regression --- packages/web/src/worker/db/WASQLiteDB.worker.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/web/src/worker/db/WASQLiteDB.worker.ts b/packages/web/src/worker/db/WASQLiteDB.worker.ts index 6d1309ce9..ed5dfb817 100644 --- a/packages/web/src/worker/db/WASQLiteDB.worker.ts +++ b/packages/web/src/worker/db/WASQLiteDB.worker.ts @@ -77,10 +77,10 @@ const openWorkerConnection = async (options: ResolvedWASQLiteOpenFactoryOptions) const connection = new WASqliteConnection(options); return { init: Comlink.proxy(() => connection.init()), - getConfig: Comlink.proxy(() => { - const config = connection.getConfig(); - // TODO logger is not transferable - return Object.fromEntries(Object.entries(config).filter(([key]) => key !== 'logger')) as any; + getConfig: Comlink.proxy(async () => { + // Can't send the logger over + const { logger, ...rest } = await connection.getConfig(); + return rest; }), close: Comlink.proxy(() => connection.close()), execute: Comlink.proxy(async (sql: string, params?: any[]) => connection.execute(sql, params)), From 7037846ea124ea90d823218f22d666284a4910ca Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Fri, 8 Aug 2025 16:55:42 +0200 Subject: [PATCH 5/8] add changeset --- .changeset/neat-crabs-lie.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-crabs-lie.md diff --git a/.changeset/neat-crabs-lie.md b/.changeset/neat-crabs-lie.md new file mode 100644 index 000000000..c848811a2 --- /dev/null +++ b/.changeset/neat-crabs-lie.md @@ -0,0 +1,5 @@ +--- +'@powersync/web': patch +--- + +Testing From de3c4bdb0a8a5e8a83b0e8f3c42969167fe1bbf5 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 12 Aug 2025 09:40:27 +0200 Subject: [PATCH 6/8] Add even more logging --- packages/web/src/db/PowerSyncDatabase.ts | 1 + .../adapters/wa-sqlite/WASQLiteConnection.ts | 37 +++++++++++++++---- .../web/src/worker/db/WASQLiteDB.worker.ts | 37 ++++++++++++------- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/packages/web/src/db/PowerSyncDatabase.ts b/packages/web/src/db/PowerSyncDatabase.ts index f55346808..1c599c1b2 100644 --- a/packages/web/src/db/PowerSyncDatabase.ts +++ b/packages/web/src/db/PowerSyncDatabase.ts @@ -164,6 +164,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase { if (this.unloadListener) { window.removeEventListener('unload', this.unloadListener); } + this.logger.debug(`Closing PowerSync instance for ${this.database.name}`); return super.close({ // Don't disconnect by default if multiple tabs are enabled diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts index 57b16ddc0..372c05f9c 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts @@ -102,6 +102,7 @@ export const DEFAULT_MODULE_FACTORIES = { }, [WASQLiteVFS.AccessHandlePoolVFS]: async (options: WASQLiteModuleFactoryOptions) => { let module; + options.logger?.debug(`Opening VFS with options`, JSON.stringify(options)); if (options.encryptionKey) { module = await MultiCipherSyncWASQLiteModuleFactory(); } else { @@ -109,17 +110,32 @@ export const DEFAULT_MODULE_FACTORIES = { } // @ts-expect-error The types for this static method are missing upstream const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js'); - const vfs = await AccessHandlePoolVFS.create(options.dbFileName, module); + const vfs = new AccessHandlePoolVFS(options.dbFileName, module); + + // Allow extra logs + const proxiedVFS = new Proxy(vfs, { + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + + // Only wrap methods, not properties + if (typeof value === 'function') { + return function (...args: any[]) { + options.logger?.debug(`VFS: Called ${String(prop)} with:`, args); + return value.apply(vfs, args); + }; + } - // TODO, maybe use a different flag for this (if we want to actually expose this) - if (options.debugMode && options.logger) { - // Enable VFS logs - vfs.log = (...params: any[]) => options.logger?.debug(...params); - } + return value; + } + }); + + await vfs.isReady(); + + // const vfs = await AccessHandlePoolVFS.create(options.dbFileName, module); return { module, - vfs + vfs: proxiedVFS }; }, [WASQLiteVFS.OPFSCoopSyncVFS]: async (options: WASQLiteModuleFactoryOptions) => { @@ -246,6 +262,7 @@ export class WASqliteConnection } async init() { + this.options.logger?.debug('Opening WASQLite connection for', this.options.dbFilename); this._sqliteAPI = await this.openSQLiteAPI(); await this.openDB(); this.registerBroadcastListeners(); @@ -356,6 +373,7 @@ export class WASqliteConnection } async close() { + this.options.logger?.debug('Closing WASQLite connection', this.options.dbFilename); this.broadcastChannel?.close(); await this.sqliteAPI.close(this.dbP); } @@ -419,6 +437,11 @@ export class WASqliteConnection sql: string | TemplateStringsArray, bindings?: any[] ): Promise<{ columns: string[]; rows: SQLiteCompatibleType[][] }[]> { + // TODO, this might be redundant + if (this.options.debugMode) { + this.options.logger?.debug(`Executing SQL: ${sql}`); + } + const results = []; for await (const stmt of this.sqliteAPI.statements(this.dbP, sql as string)) { let columns; diff --git a/packages/web/src/worker/db/WASQLiteDB.worker.ts b/packages/web/src/worker/db/WASQLiteDB.worker.ts index ed5dfb817..9c854a237 100644 --- a/packages/web/src/worker/db/WASQLiteDB.worker.ts +++ b/packages/web/src/worker/db/WASQLiteDB.worker.ts @@ -26,6 +26,16 @@ type SharedDBWorkerConnection = { const DBMap = new Map(); const OPEN_DB_LOCK = 'open-wasqlite-db'; +function logToAll(logLevel: string, ...messages: string[]) { + for (const dbFilename of DBMap.keys()) { + logBroadcaster.pushLog({ + loggerName: dbFilename, + logLevel, + messages + }); + } +} + let nextClientId = 1; function logUnhandledException(error: Error) { @@ -35,13 +45,7 @@ function logUnhandledException(error: Error) { Message: ${error.message} Stack: ${error.stack}`.trim(); - for (const dbFilename of DBMap.keys()) { - logBroadcaster.pushLog({ - loggerName: dbFilename, - logLevel: LogLevel.ERROR.name, - messages: ['Uncaught Exception in DB worker', errorMessage] - }); - } + logToAll(LogLevel.ERROR.name, 'Uncaught Exception in DB worker', errorMessage); } // Report unhandled exceptions to all loggers @@ -53,6 +57,15 @@ addEventListener('error', (event) => { logUnhandledException(event.error); }); +addEventListener('unload', () => { + logToAll(LogLevel.INFO.name, 'DB worker is unloading'); + Array.from(DBMap.values()).forEach(async (dbConnection) => { + const { db, clientIds } = dbConnection; + logToAll(LogLevel.INFO.name, `closing db with ids ${clientIds}`); + db.close(); + }); +}); + const baseLogger = createBaseLogger(); const logBroadcaster = new LogHandler(); @@ -102,6 +115,8 @@ const openDBShared = async ( const clientId = nextClientId++; const { dbFilename, logLevel } = options; + logToAll(`Worker opening DB connection for`, JSON.stringify(options)); + // This updates the log level for the worker-level logger // The DB connection logger will automatically track the main context logger // since it passes logs to it. @@ -121,6 +136,7 @@ const openDBShared = async ( if (!DBMap.has(dbFilename)) { const clientIds = new Set(); const logger = createLogger(dbFilename); + logger.debug(`Worker opened a new connection`); const connection = await openWorkerConnection({ ...options, logger @@ -171,10 +187,3 @@ if (typeof SharedWorkerGlobalScope !== 'undefined') { // A dedicated worker can be shared externally Comlink.expose(openDBShared); } - -addEventListener('unload', () => { - Array.from(DBMap.values()).forEach(async (dbConnection) => { - const { db } = dbConnection; - db.close?.(); - }); -}); From 0f43f9058e05e21f22a7550a0aa7664390b4a2d1 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 18 Aug 2025 12:17:19 +0200 Subject: [PATCH 7/8] more logs --- .../example-capacitor/src/components/providers/Logging.ts | 8 +++++--- .../src/components/providers/SystemProvider.tsx | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/demos/example-capacitor/src/components/providers/Logging.ts b/demos/example-capacitor/src/components/providers/Logging.ts index e31d97b71..470efc352 100644 --- a/demos/example-capacitor/src/components/providers/Logging.ts +++ b/demos/example-capacitor/src/components/providers/Logging.ts @@ -1,4 +1,4 @@ -import { BaseObserver, ControlledExecutor, createBaseLogger, LogLevel } from '@powersync/web'; +import { BaseObserver, ControlledExecutor, createBaseLogger, createLogger, LogLevel } from '@powersync/web'; export type LogRecord = { level: string; @@ -107,8 +107,8 @@ Stack: ${param.stack} }; // Configure base logger for global settings -const logger = createBaseLogger(); -logger.useDefaults({ +export const BASE_LOGGER = createBaseLogger(); +BASE_LOGGER.useDefaults({ defaultLevel: LogLevel.DEBUG, formatter: (messages, context) => { LOG_STORAGE.writeLog({ @@ -118,3 +118,5 @@ logger.useDefaults({ }); } }); + +export const LOGGER = createLogger('CapacitorPS'); diff --git a/demos/example-capacitor/src/components/providers/SystemProvider.tsx b/demos/example-capacitor/src/components/providers/SystemProvider.tsx index e4a2ad124..f618bcfd5 100644 --- a/demos/example-capacitor/src/components/providers/SystemProvider.tsx +++ b/demos/example-capacitor/src/components/providers/SystemProvider.tsx @@ -5,9 +5,11 @@ import { PowerSyncDatabase, WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/ import React, { Suspense } from 'react'; import { AppSchema } from '../../library/powersync/AppSchema.js'; import { BackendConnector } from '../../library/powersync/BackendConnector.js'; +import { LOGGER } from './Logging.js'; const platform = Capacitor.getPlatform(); +LOGGER.debug('Initing PowerSync globally '); const powerSync = new PowerSyncDatabase({ database: new WASQLiteOpenFactory({ dbFilename: 'ps.db', From 6aa288ffa8eb83827d4458cbb95569c3aaaaaf74 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 26 Aug 2025 16:40:16 +0200 Subject: [PATCH 8/8] update capacitor --- .../android/app/capacitor.build.gradle | 4 +- .../ios/App/App.xcodeproj/project.pbxproj | 14 +- .../example-capacitor/ios/App/App/Info.plist | 96 ++++--- demos/example-capacitor/ios/App/Podfile | 2 +- demos/example-capacitor/ios/App/Podfile.lock | 25 +- demos/example-capacitor/package.json | 10 +- pnpm-lock.yaml | 235 ++++++++++-------- 7 files changed, 210 insertions(+), 176 deletions(-) diff --git a/demos/example-capacitor/android/app/capacitor.build.gradle b/demos/example-capacitor/android/app/capacitor.build.gradle index d46a7a3d6..6e9cddfc6 100644 --- a/demos/example-capacitor/android/app/capacitor.build.gradle +++ b/demos/example-capacitor/android/app/capacitor.build.gradle @@ -2,8 +2,8 @@ android { compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } diff --git a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj index ba5de84a2..578b355d4 100644 --- a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj +++ b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj @@ -283,7 +283,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -334,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -349,12 +349,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZGT7463CVJ; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; - PRODUCT_BUNDLE_IDENTIFIER = com.powersync.example; + PRODUCT_BUNDLE_IDENTIFIER = com.powersync.capacitor; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; @@ -369,11 +370,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZGT7463CVJ; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.powersync.example; + PRODUCT_BUNDLE_IDENTIFIER = com.powersync.capacitor; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_VERSION = 5.0; diff --git a/demos/example-capacitor/ios/App/App/Info.plist b/demos/example-capacitor/ios/App/App/Info.plist index f7c470487..91d4e771a 100644 --- a/demos/example-capacitor/ios/App/App/Info.plist +++ b/demos/example-capacitor/ios/App/App/Info.plist @@ -1,53 +1,49 @@ - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - powersync-capacitor - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - NSDocumentsFolderUsageDescription - This app needs access to documents folder to save files - NSFileProviderDomainUsageDescription - This app needs access to manage files - - \ No newline at end of file + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + powersync-capacitor + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/demos/example-capacitor/ios/App/Podfile b/demos/example-capacitor/ios/App/Podfile index 9e2944c4f..660c5bef9 100644 --- a/demos/example-capacitor/ios/App/Podfile +++ b/demos/example-capacitor/ios/App/Podfile @@ -1,6 +1,6 @@ require_relative '../../../../node_modules/@capacitor/ios/scripts/pods_helpers' -platform :ios, '13.0' +platform :ios, '14.0' use_frameworks! # workaround to avoid Xcode caching of Pods that requires diff --git a/demos/example-capacitor/ios/App/Podfile.lock b/demos/example-capacitor/ios/App/Podfile.lock index d8e4f0f29..098acc197 100644 --- a/demos/example-capacitor/ios/App/Podfile.lock +++ b/demos/example-capacitor/ios/App/Podfile.lock @@ -1,13 +1,15 @@ PODS: - - Capacitor (6.2.1): + - Capacitor (7.4.3): - CapacitorCordova - - CapacitorCordova (6.2.1) - - CapacitorFilesystem (6.0.3): + - CapacitorCordova (7.4.3) + - CapacitorFilesystem (7.1.4): - Capacitor - - CapacitorShare (6.0.3): + - IONFilesystemLib (~> 1.0.1) + - CapacitorShare (7.0.2): - Capacitor - CapacitorSplashScreen (7.0.2): - Capacitor + - IONFilesystemLib (1.0.1) DEPENDENCIES: - "Capacitor (from `../../../../node_modules/@capacitor/ios`)" @@ -16,6 +18,10 @@ DEPENDENCIES: - "CapacitorShare (from `../../../../node_modules/@capacitor/share`)" - "CapacitorSplashScreen (from `../../../../node_modules/@capacitor/splash-screen`)" +SPEC REPOS: + trunk: + - IONFilesystemLib + EXTERNAL SOURCES: Capacitor: :path: "../../../../node_modules/@capacitor/ios" @@ -29,12 +35,13 @@ EXTERNAL SOURCES: :path: "../../../../node_modules/@capacitor/splash-screen" SPEC CHECKSUMS: - Capacitor: 1e0d0e7330dea9f983b50da737d8918abcf273f8 - CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff - CapacitorFilesystem: fa3099b3c3aa43a1b51362d0c999301ab1a9a752 - CapacitorShare: 7af6ca761ce62030e8e9fbd2eb82416f5ceced38 + Capacitor: b4741ca7affb32c1b70debd03df92cbf522d8a80 + CapacitorCordova: 435121e81a2df4d0034f0fb11fcefab5104cfdb5 + CapacitorFilesystem: ac0a386949ed6c295a3dffa129ecd26b881ae5be + CapacitorShare: 051c3ceee0ddf3bb54574851a622bbb317eed5bd CapacitorSplashScreen: 8d6c8cb0542a8e81585c593815db8785ed8ce454 + IONFilesystemLib: 89258b8e3e85465da93127d25d7ce37f977e8a6f -PODFILE CHECKSUM: c1703336f990a4728e25eafbdd9992a7afc2008e +PODFILE CHECKSUM: fbd8888dbdcba79e24ab532c719332d64cd3a89f COCOAPODS: 1.16.2 diff --git a/demos/example-capacitor/package.json b/demos/example-capacitor/package.json index fa973a2fe..ae8d6f8c6 100644 --- a/demos/example-capacitor/package.json +++ b/demos/example-capacitor/package.json @@ -19,11 +19,11 @@ "preview": "vite preview" }, "dependencies": { - "@capacitor/android": "^6.0.0", + "@capacitor/android": "^7.4.3", "@capacitor/core": "latest", - "@capacitor/filesystem": "^6.0.0", - "@capacitor/ios": "^6.0.0", - "@capacitor/share": "^6.0.0", + "@capacitor/filesystem": "^7.1.4", + "@capacitor/ios": "^7.4.3", + "@capacitor/share": "^7.0.2", "@capacitor/splash-screen": "latest", "@journeyapps/wa-sqlite": "^1.2.0", "@mui/icons-material": "^7.3.1", @@ -36,7 +36,7 @@ "react-window": "^1.8.11" }, "devDependencies": { - "@capacitor/cli": "^6.0.0", + "@capacitor/cli": "^7.4.3", "@swc/core": "~1.6.0", "@types/node": "^20.12.12", "@types/react": "^18.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a7dd7db7..f560afdb9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -266,20 +266,20 @@ importers: demos/example-capacitor: dependencies: '@capacitor/android': - specifier: ^6.0.0 - version: 6.2.1(@capacitor/core@7.4.3) + specifier: ^7.4.3 + version: 7.4.3(@capacitor/core@7.4.3) '@capacitor/core': specifier: latest version: 7.4.3 '@capacitor/filesystem': - specifier: ^6.0.0 - version: 6.0.3(@capacitor/core@7.4.3) + specifier: ^7.1.4 + version: 7.1.4(@capacitor/core@7.4.3) '@capacitor/ios': - specifier: ^6.0.0 - version: 6.2.1(@capacitor/core@7.4.3) + specifier: ^7.4.3 + version: 7.4.3(@capacitor/core@7.4.3) '@capacitor/share': - specifier: ^6.0.0 - version: 6.0.3(@capacitor/core@7.4.3) + specifier: ^7.0.2 + version: 7.0.2(@capacitor/core@7.4.3) '@capacitor/splash-screen': specifier: latest version: 7.0.2(@capacitor/core@7.4.3) @@ -312,8 +312,8 @@ importers: version: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@capacitor/cli': - specifier: ^6.0.0 - version: 6.2.1 + specifier: ^7.4.3 + version: 7.4.3 '@swc/core': specifier: ~1.6.0 version: 1.6.13 @@ -3531,39 +3531,42 @@ packages: peerDependencies: react: '>=16.3.0' - '@capacitor/android@6.2.1': - resolution: {integrity: sha512-8gd4CIiQO5LAIlPIfd5mCuodBRxMMdZZEdj8qG8m+dQ1sQ2xyemVpzHmRK8qSCHorsBUCg3D62j2cp6bEBAkdw==} + '@capacitor/android@7.4.3': + resolution: {integrity: sha512-VpjvnOcmYGPLgvXRhe3CGLs62Cg7sxOyp77NddCr+Y06qqgnoaj6OGeBVTc2DZlqZ6bSmh15JvFu82pkvmdgfQ==} peerDependencies: - '@capacitor/core': ^6.2.0 + '@capacitor/core': ^7.4.0 - '@capacitor/cli@6.2.1': - resolution: {integrity: sha512-JKl0FpFge8PgQNInw12kcKieQ4BmOyazQ4JGJOfEpVXlgrX1yPhSZTPjngupzTCiK3I7q7iGG5kjun0fDqgSCA==} - engines: {node: '>=18.0.0'} + '@capacitor/cli@7.4.3': + resolution: {integrity: sha512-SWozpdDgrbQ/ry1nIapugDFvE9z+l22BmU/+fpgL2Zv5487hGdXvCX5+1SluuFBP3IPpx6b4LjsKnBigyJoUWg==} + engines: {node: '>=20.0.0'} hasBin: true '@capacitor/core@7.4.3': resolution: {integrity: sha512-wCWr8fQ9Wxn0466vPg7nMn0tivbNVjNy1yL4GvDSIZuZx7UpU2HeVGNe9QjN/quEd+YLRFeKEBLBw619VqUiNg==} - '@capacitor/filesystem@6.0.3': - resolution: {integrity: sha512-PdIP/yOGAbG1lq1wbFbSPhXQ9/5lpTpeiok2NneawJOk6UXvy9W7QZXRo7wXAP7J6FdzU7bKfOORRXpOJpgXyw==} + '@capacitor/filesystem@7.1.4': + resolution: {integrity: sha512-BdUnOVulHAtruW2GeC9o7e9LO5aFcVYqNn3dLypJSOck/WipUFNfI0QWoUS0FVGeqBbDJgFGi3zjXJx0lzbDkA==} peerDependencies: - '@capacitor/core': ^6.0.0 + '@capacitor/core': '>=7.0.0' - '@capacitor/ios@6.2.1': - resolution: {integrity: sha512-tbMlQdQjxe1wyaBvYVU1yTojKJjgluZQsJkALuJxv/6F8QTw5b6vd7X785O/O7cMpIAZfUWo/vtAHzFkRV+kXw==} + '@capacitor/ios@7.4.3': + resolution: {integrity: sha512-VNm7cHODgh3KK/4ZC2rXU9gBlvHii/mYFLI+XMXwq24nhB679QxHhz+pUuI7PatYoM2q4MAL0NR/dRgehKCaSA==} peerDependencies: - '@capacitor/core': ^6.2.0 + '@capacitor/core': ^7.4.0 - '@capacitor/share@6.0.3': - resolution: {integrity: sha512-BkNM73Ix+yxQ7fkni8CrrGcp1kSl7u+YNoPLwWKQ1MuQ5Uav0d+CT8M67ie+3dc4jASmegnzlC6tkTmFcPTLeA==} + '@capacitor/share@7.0.2': + resolution: {integrity: sha512-VyNPo/9831xnL17IMDeft5yNdBjoKNb451P95sRcr69hulRDqHc+kndqOVaMXnaA6IyBdWnnFv/n1HUf4cXpGw==} peerDependencies: - '@capacitor/core': ^6.0.0 + '@capacitor/core': '>=7.0.0' '@capacitor/splash-screen@7.0.2': resolution: {integrity: sha512-bchh4F73CnVONm6XFEgXKEhbSEDQh2CQ0rNSoasIeJ5pf9JqHkkPS3t0Fnm33qHkLVFcaPoKPW69Y9zMpT5Vxg==} peerDependencies: '@capacitor/core': '>=7.0.0' + '@capacitor/synapse@1.0.4': + resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==} + '@changesets/apply-release-plan@7.0.12': resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} @@ -5464,42 +5467,42 @@ packages: resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} engines: {node: '>=16.0.0'} - '@ionic/utils-array@2.1.5': - resolution: {integrity: sha512-HD72a71IQVBmQckDwmA8RxNVMTbxnaLbgFOl+dO5tbvW9CkkSFCv41h6fUuNsSEVgngfkn0i98HDuZC8mk+lTA==} - engines: {node: '>=10.3.0'} - - '@ionic/utils-fs@3.1.6': - resolution: {integrity: sha512-eikrNkK89CfGPmexjTfSWl4EYqsPSBh0Ka7by4F0PLc1hJZYtJxUZV3X4r5ecA8ikjicUmcbU7zJmAjmqutG/w==} - engines: {node: '>=10.3.0'} + '@ionic/utils-array@2.1.6': + resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==} + engines: {node: '>=16.0.0'} '@ionic/utils-fs@3.1.7': resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==} engines: {node: '>=16.0.0'} - '@ionic/utils-object@2.1.5': - resolution: {integrity: sha512-XnYNSwfewUqxq+yjER1hxTKggftpNjFLJH0s37jcrNDwbzmbpFTQTVAp4ikNK4rd9DOebX/jbeZb8jfD86IYxw==} - engines: {node: '>=10.3.0'} - - '@ionic/utils-process@2.1.10': - resolution: {integrity: sha512-mZ7JEowcuGQK+SKsJXi0liYTcXd2bNMR3nE0CyTROpMECUpJeAvvaBaPGZf5ERQUPeWBVuwqAqjUmIdxhz5bxw==} - engines: {node: '>=10.3.0'} + '@ionic/utils-object@2.1.6': + resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==} + engines: {node: '>=16.0.0'} - '@ionic/utils-stream@3.1.5': - resolution: {integrity: sha512-hkm46uHvEC05X/8PHgdJi4l4zv9VQDELZTM+Kz69odtO9zZYfnt8DkfXHJqJ+PxmtiE5mk/ehJWLnn/XAczTUw==} - engines: {node: '>=10.3.0'} + '@ionic/utils-process@2.1.12': + resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==} + engines: {node: '>=16.0.0'} - '@ionic/utils-subprocess@2.1.11': - resolution: {integrity: sha512-6zCDixNmZCbMCy5np8klSxOZF85kuDyzZSTTQKQP90ZtYNCcPYmuFSzaqDwApJT4r5L3MY3JrqK1gLkc6xiUPw==} - engines: {node: '>=10.3.0'} + '@ionic/utils-stream@3.1.7': + resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==} + engines: {node: '>=16.0.0'} - '@ionic/utils-terminal@2.3.3': - resolution: {integrity: sha512-RnuSfNZ5fLEyX3R5mtcMY97cGD1A0NVBbarsSQ6yMMfRJ5YHU7hHVyUfvZeClbqkBC/pAqI/rYJuXKCT9YeMCQ==} - engines: {node: '>=10.3.0'} + '@ionic/utils-subprocess@3.0.1': + resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==} + engines: {node: '>=16.0.0'} '@ionic/utils-terminal@2.3.5': resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==} engines: {node: '>=16.0.0'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -13162,6 +13165,11 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + glob@6.0.4: resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} deprecated: Glob versions prior to v9 are no longer supported @@ -14157,6 +14165,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + jake@10.9.2: resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} engines: {node: '>=10'} @@ -15636,6 +15648,10 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -16529,6 +16545,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + path-temp@2.1.0: resolution: {integrity: sha512-cMMJTAZlion/RWRRC48UbrDymEIt+/YSD/l8NqjneyDw2rDOBQcP5yRkMB4CYGn47KMhZvbblBP7Z79OsMw72w==} engines: {node: '>=8.15'} @@ -18237,9 +18257,9 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rimraf@4.4.1: - resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} - engines: {node: '>=14'} + rimraf@6.0.1: + resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} + engines: {node: 20 || >=22} hasBin: true ripemd160@2.0.2: @@ -20775,14 +20795,14 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} - xml2js@0.6.0: resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} engines: {node: '>=4.0.0'} + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + xmlbuilder@11.0.1: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} @@ -22581,29 +22601,29 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.0.0 - '@capacitor/android@6.2.1(@capacitor/core@7.4.3)': + '@capacitor/android@7.4.3(@capacitor/core@7.4.3)': dependencies: '@capacitor/core': 7.4.3 - '@capacitor/cli@6.2.1': + '@capacitor/cli@7.4.3': dependencies: '@ionic/cli-framework-output': 2.2.8 - '@ionic/utils-fs': 3.1.7 - '@ionic/utils-subprocess': 2.1.11 + '@ionic/utils-subprocess': 3.0.1 '@ionic/utils-terminal': 2.3.5 - commander: 9.5.0 + commander: 12.1.0 debug: 4.4.1(supports-color@8.1.1) env-paths: 2.2.1 + fs-extra: 11.3.0 kleur: 4.1.5 native-run: 2.0.1 open: 8.4.2 plist: 3.1.0 prompts: 2.4.2 - rimraf: 4.4.1 + rimraf: 6.0.1 semver: 7.7.2 tar: 6.2.1 tslib: 2.8.1 - xml2js: 0.5.0 + xml2js: 0.6.2 transitivePeerDependencies: - supports-color @@ -22611,15 +22631,16 @@ snapshots: dependencies: tslib: 2.8.1 - '@capacitor/filesystem@6.0.3(@capacitor/core@7.4.3)': + '@capacitor/filesystem@7.1.4(@capacitor/core@7.4.3)': dependencies: '@capacitor/core': 7.4.3 + '@capacitor/synapse': 1.0.4 - '@capacitor/ios@6.2.1(@capacitor/core@7.4.3)': + '@capacitor/ios@7.4.3(@capacitor/core@7.4.3)': dependencies: '@capacitor/core': 7.4.3 - '@capacitor/share@6.0.3(@capacitor/core@7.4.3)': + '@capacitor/share@7.0.2(@capacitor/core@7.4.3)': dependencies: '@capacitor/core': 7.4.3 @@ -22627,6 +22648,8 @@ snapshots: dependencies: '@capacitor/core': 7.4.3 + '@capacitor/synapse@1.0.4': {} + '@changesets/apply-release-plan@7.0.12': dependencies: '@changesets/config': 3.1.1 @@ -25570,18 +25593,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@ionic/utils-array@2.1.5': - dependencies: - debug: 4.4.1(supports-color@8.1.1) - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - - '@ionic/utils-fs@3.1.6': + '@ionic/utils-array@2.1.6': dependencies: - '@types/fs-extra': 8.1.5 debug: 4.4.1(supports-color@8.1.1) - fs-extra: 9.1.0 tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -25595,17 +25609,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@ionic/utils-object@2.1.5': + '@ionic/utils-object@2.1.6': dependencies: debug: 4.4.1(supports-color@8.1.1) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@ionic/utils-process@2.1.10': + '@ionic/utils-process@2.1.12': dependencies: - '@ionic/utils-object': 2.1.5 - '@ionic/utils-terminal': 2.3.3 + '@ionic/utils-object': 2.1.6 + '@ionic/utils-terminal': 2.3.5 debug: 4.4.1(supports-color@8.1.1) signal-exit: 3.0.7 tree-kill: 1.2.2 @@ -25613,27 +25627,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@ionic/utils-stream@3.1.5': + '@ionic/utils-stream@3.1.7': dependencies: debug: 4.4.1(supports-color@8.1.1) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@ionic/utils-subprocess@2.1.11': + '@ionic/utils-subprocess@3.0.1': dependencies: - '@ionic/utils-array': 2.1.5 - '@ionic/utils-fs': 3.1.6 - '@ionic/utils-process': 2.1.10 - '@ionic/utils-stream': 3.1.5 - '@ionic/utils-terminal': 2.3.3 + '@ionic/utils-array': 2.1.6 + '@ionic/utils-fs': 3.1.7 + '@ionic/utils-process': 2.1.12 + '@ionic/utils-stream': 3.1.7 + '@ionic/utils-terminal': 2.3.5 cross-spawn: 7.0.6 debug: 4.4.1(supports-color@8.1.1) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@ionic/utils-terminal@2.3.3': + '@ionic/utils-terminal@2.3.5': dependencies: '@types/slice-ansi': 4.0.0 debug: 4.4.1(supports-color@8.1.1) @@ -25647,19 +25661,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@ionic/utils-terminal@2.3.5': + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': dependencies: - '@types/slice-ansi': 4.0.0 - debug: 4.4.1(supports-color@8.1.1) - signal-exit: 3.0.7 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - tslib: 2.8.1 - untildify: 4.0.0 - wrap-ansi: 7.0.0 - transitivePeerDependencies: - - supports-color + '@isaacs/balanced-match': 4.0.1 '@isaacs/cliui@8.0.2': dependencies: @@ -36787,6 +36793,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + glob@6.0.4: dependencies: inflight: 1.0.6 @@ -37931,6 +37946,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + jake@10.9.2: dependencies: async: 3.2.6 @@ -40742,6 +40761,10 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -41718,6 +41741,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + path-temp@2.1.0: dependencies: unique-string: 2.0.0 @@ -44055,9 +44083,10 @@ snapshots: dependencies: glob: 7.2.3 - rimraf@4.4.1: + rimraf@6.0.1: dependencies: - glob: 9.3.5 + glob: 11.0.3 + package-json-from-dist: 1.0.1 ripemd160@2.0.2: dependencies: @@ -47403,12 +47432,12 @@ snapshots: xml-name-validator@5.0.0: {} - xml2js@0.5.0: + xml2js@0.6.0: dependencies: sax: 1.4.1 xmlbuilder: 11.0.1 - xml2js@0.6.0: + xml2js@0.6.2: dependencies: sax: 1.4.1 xmlbuilder: 11.0.1