Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,27 @@ jobs:
limit-access-to-actor: true

# Save the build artifacts to be used in other jobs
- name: List APKs before upload
run: |
echo "Available APKs in build directory:"
find android/${{ env.main_project_module }}/build/outputs/apk/ -type f -name "*.apk" | sort

- name: Upload APK artifacts
uses: actions/upload-artifact@v4
with:
name: android-apk-artifacts-${{ matrix.arch }}
path: android/${{ env.main_project_module }}/build/outputs/apk/
path: |
android/${{ env.main_project_module }}/build/outputs/apk/${{ matrix.arch == 'arm64-v8a' && 'arm64v8a' || 'x8664' }}/debug/*-debug.apk
android/${{ env.main_project_module }}/build/outputs/apk/${{ matrix.arch == 'arm64-v8a' && 'arm64v8a' || 'x8664' }}/release/*-release-unsigned.apk
retention-days: 1
if-no-files-found: error

# Save the test APKs separately
- name: Upload Test APK artifacts
uses: actions/upload-artifact@v4
with:
name: android-test-apk-artifacts-${{ matrix.arch }}
path: android/${{ env.main_project_module }}/build/outputs/apk/androidTest/${{ matrix.arch == 'arm64-v8a' && 'arm64v8a' || 'x8664' }}/debug/
retention-days: 1
if-no-files-found: error

Expand Down Expand Up @@ -604,7 +620,13 @@ jobs:
with:
name: android-apk-artifacts-${{ matrix.arch }}
path: android/app/build/outputs/apk


- name: Download Test APK artifacts
uses: actions/download-artifact@v4
with:
name: android-test-apk-artifacts-${{ matrix.arch }}
path: android/app/build/outputs/apk/androidTest

- name: Download Coverage artifacts
uses: actions/download-artifact@v4
with:
Expand Down
6 changes: 4 additions & 2 deletions App/SetupSync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { open } from '@op-engineering/op-sqlite';
import { useQuery } from '@powersync/react';
import { PowerSyncContext } from "@powersync/react";
import { installCrsqliteOnTable } from '@lib/cr-sqlite/install';
import { psInsertDbTable, psClearTable } from '@lib/orm';
import { psInsertDbTable, supabaseClearTable } from '@lib/orm';
import { useNavigation } from '@react-navigation/native';
import { useSettings } from '@lib/hooks/SettingsContext';
import { useSupabase } from '@lib/hooks/SupabaseContext';
import { GITHUB_README_URL } from '@lib/constants';

// Split out type imports for better readability
Expand All @@ -20,6 +21,7 @@ type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
export const SetupSync = () => {
const navigation = useNavigation<NavigationProp>();
const { settings } = useSettings();
const { supabaseClient } = useSupabase();
const debugDisplayKeys = ['id', 'ttl', 'istart' ,'loc'];
const [showDangerZone, setShowDangerZone] = useState(false);
const [showDebugOutput, setShowDebugOutput] = useState(false);
Expand Down Expand Up @@ -236,7 +238,7 @@ export const SetupSync = () => {
style={[styles.syncButton, styles.deleteButton]}
onPress={async () => {
try {
await psClearTable('eventsV9', providerDb);
await supabaseClearTable('eventsV9', supabaseClient);
} catch (error) {
console.error('Failed to clear PowerSync events:', error);
}
Expand Down
15 changes: 12 additions & 3 deletions App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { StyleSheet, Text, View, Button, TouchableOpacity, BackHandler, Linking
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { db as psDb, setupPowerSync } from '@lib/powersync';
import { db as psDb } from '@lib/powersync';
import { setupRemoteDatabaseConnections } from '@lib/init_remote_db_connections';
import Logger from 'js-logger';
import { PowerSyncContext } from "@powersync/react";
import { SetupSync } from './SetupSync';
Expand All @@ -12,6 +13,8 @@ import { enableScreens } from 'react-native-screens';
import { useSettings } from '@lib/hooks/SettingsContext';
import { Ionicons } from '@expo/vector-icons';
import { SettingsProvider } from '@lib/hooks/SettingsContext';
import { SupabaseProvider } from '@lib/hooks/SupabaseContext';
import { SupabaseClient } from '@supabase/supabase-js';
import { GITHUB_README_URL } from '@lib/constants';

// Enable screens
Expand Down Expand Up @@ -47,11 +50,13 @@ const InitialSetupScreen = ({ navigation }: { navigation: NativeStackNavigationP
const HomeScreen = ({ navigation }: { navigation: NativeStackNavigationProp<RootStackParamList> }) => {
const { settings } = useSettings();
const [isReady, setIsReady] = useState(false);
const [initializedSupabaseClient, setInitializedSupabaseClient] = useState<SupabaseClient | null>(null);

useEffect(() => {
const init = async () => {
if (settings.syncEnabled) {
await setupPowerSync(settings);
const { supabaseClient } = await setupRemoteDatabaseConnections(settings, psDb);
setInitializedSupabaseClient(supabaseClient);
}
setIsReady(true);
};
Expand All @@ -66,7 +71,11 @@ const HomeScreen = ({ navigation }: { navigation: NativeStackNavigationProp<Root
);
}

return settings.syncEnabled ? <SetupSync /> : <InitialSetupScreen navigation={navigation} />;
return (
<SupabaseProvider client={initializedSupabaseClient}>
{settings.syncEnabled ? <SetupSync /> : <InitialSetupScreen navigation={navigation} />}
</SupabaseProvider>
);
};

export const App = () => {
Expand Down
29 changes: 29 additions & 0 deletions lib/hooks/SupabaseContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { createContext, useContext } from 'react';
import { SupabaseClient } from '@supabase/supabase-js';

interface SupabaseContextType {
supabaseClient: SupabaseClient | null;
}

const SupabaseContext = createContext<SupabaseContextType>({ supabaseClient: null });

export const useSupabase = () => {
const context = useContext(SupabaseContext);
if (!context) {
throw new Error('useSupabase must be used within a SupabaseProvider');
}
return context;
};

interface SupabaseProviderProps {
children: React.ReactNode;
client: SupabaseClient | null;
}

export const SupabaseProvider: React.FC<SupabaseProviderProps> = ({ children, client }) => {
return (
<SupabaseContext.Provider value={{ supabaseClient: client }}>
{children}
</SupabaseContext.Provider>
);
};
33 changes: 33 additions & 0 deletions lib/init_remote_db_connections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'react-native-url-polyfill/auto'
import { createClient, SupabaseClient } from '@supabase/supabase-js';
import { PowerSyncDatabase } from '@powersync/react-native';
import { Connector } from './powersync/Connector';
import { Settings } from './hooks/SettingsContext';

export async function setupRemoteDatabaseConnections(
settings: Settings,
powerSyncDb: PowerSyncDatabase
) {
// Initialize Supabase client
let supabaseClient: SupabaseClient | undefined = undefined;
try {
supabaseClient = createClient(settings.supabaseUrl, settings.supabaseAnonKey);
console.log('Supabase client initialized successfully');
} catch (e) {
console.error('Error initializing Supabase client:', e);
}

// Initialize PowerSync connector with Supabase client
const connector = new Connector(settings, supabaseClient!);
powerSyncDb.connect(connector);

try {
await powerSyncDb.init();
} catch (e) {
console.log('Error initializing PowerSync connection:', e);
}

return {
supabaseClient
};
}
31 changes: 31 additions & 0 deletions lib/orm/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { open } from '@op-engineering/op-sqlite';
import { AbstractPowerSyncDatabase } from '@powersync/react-native';
import { SupabaseClient } from '@supabase/supabase-js';

/**
* TODO: refactor the orm to be a hook that includes the supabase client and the powersync db
* without having to pass the supabase client and the powersync db to every function
*/

/**
* Inserts data from a regular SQLite table into a PowerSync table
Expand Down Expand Up @@ -56,6 +62,14 @@ export async function psClearTable(
tableName: string,
psDb: AbstractPowerSyncDatabase
) {
// TODO
// so sqlite doesn't support TRUNCATE
// and even if it did that isn't supported by powersync's protocol
// https://docs.powersync.com/architecture/powersync-protocol
// https://docs.powersync.com/architecture/client-architecture
// so I think we can just use the supabase client to TRUNCATE the table
// and still DELETE FROM to clear local
// honestly the sync server should just handle from the truncate
try {
const deleteResult = await psDb.execute(`DELETE FROM ${tableName}`);
console.log(`Successfully cleared all records from PowerSync table ${tableName}`);
Expand All @@ -66,3 +80,20 @@ export async function psClearTable(
}
}


export async function supabaseClearTable(
tableName: string,
supabaseClient: SupabaseClient | null
) {
if (!supabaseClient) {
throw new Error('Supabase client is not initialized');
}

try {
await supabaseClient.from(tableName).delete().neq('id', 0);
console.log(`Successfully cleared all records from Supabase table ${tableName}`);
} catch (error) {
console.error(`Failed to clear Supabase table ${tableName}:`, error);
throw error;
}
}
7 changes: 2 additions & 5 deletions lib/powersync/Connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@ export class Connector implements PowerSyncBackendConnector {
client: SupabaseClient;
private settings: Settings;

constructor(settings: Settings) {
constructor(settings: Settings, supabaseClient: SupabaseClient) {
this.settings = settings;
// TODO setup session storage to support supabase auth
// right now its not needed because will have people input
// there own powersync token an supabase links in the app to start
this.client = createClient(settings.supabaseUrl, settings.supabaseAnonKey);
this.client = supabaseClient;
}

async fetchCredentials() {
Expand Down
19 changes: 2 additions & 17 deletions lib/powersync/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { OPSqliteOpenFactory } from '@powersync/op-sqlite';
import { PowerSyncDatabase } from '@powersync/react-native';
import { Connector } from './Connector';
import { AppSchema } from './Schema';
import { Settings } from '../hooks/useStoredSettings';

const factory = new OPSqliteOpenFactory(
{
// Filename for the SQLite database — it's important to only instantiate one instance per file.
Expand All @@ -19,18 +18,4 @@ export const db = new PowerSyncDatabase({
// The schema you defined in the previous step
schema: AppSchema,
database: factory,
});

export const setupPowerSync = async (settings: Settings) => {
// Uses the backend connector that will be created in the next section
const connector = new Connector(settings);
db.connect(connector);

try {
await db.init();
} catch (e) {
console.log('Error initializing PowerSync connection:', e);
}

// console.log(db)
};
});
Loading