Skip to content

Commit 7e40d26

Browse files
author
Amine
committed
refactor: refactored react native demo to use new attachments API
1 parent 4cb5753 commit 7e40d26

File tree

7 files changed

+135
-214
lines changed

7 files changed

+135
-214
lines changed

demos/react-native-supabase-todolist/app/views/todos/edit/[id].tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,13 @@ const TodoView: React.FC = () => {
7777
};
7878

7979
const savePhoto = async (id: string, data: CameraCapturedPicture) => {
80-
if (system.attachmentQueue) {
80+
if (system.photoAttachmentQueue) {
8181
// We are sure the base64 is not null, as we are using the base64 option in the CameraWidget
82-
const { id: photoId } = await system.attachmentQueue.savePhoto(data.base64!);
82+
const { id: photoId } = await system.photoAttachmentQueue.saveFile({
83+
data: data.base64!,
84+
fileExtension: 'jpg',
85+
mediaType: 'image/jpeg'
86+
});
8387

8488
await system.powersync.execute(`UPDATE ${TODO_TABLE} SET photo_id = ? WHERE id = ?`, [photoId, id]);
8589
}
@@ -99,12 +103,16 @@ const TodoView: React.FC = () => {
99103
};
100104

101105
const deleteTodo = async (id: string, photoRecord?: AttachmentRecord) => {
102-
await system.powersync.writeTransaction(async (tx) => {
103-
if (system.attachmentQueue && photoRecord != null) {
104-
await system.attachmentQueue.delete(photoRecord, tx);
105-
}
106-
await tx.execute(`DELETE FROM ${TODO_TABLE} WHERE id = ?`, [id]);
107-
});
106+
if (system.photoAttachmentQueue && photoRecord != null) {
107+
await system.photoAttachmentQueue.deleteFile({
108+
id: photoRecord.id,
109+
updateHook: async (tx) => {
110+
await tx.execute(`DELETE FROM ${TODO_TABLE} WHERE id = ?`, [id]);
111+
}
112+
});
113+
} else {
114+
await system.powersync.execute(`DELETE FROM ${TODO_TABLE} WHERE id = ?`, [id]);
115+
}
108116
};
109117

110118
if (isLoading) {

demos/react-native-supabase-todolist/library/powersync/PhotoAttachmentQueue.ts

Lines changed: 0 additions & 50 deletions
This file was deleted.

demos/react-native-supabase-todolist/library/powersync/system.ts

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,38 @@ import '@azure/core-asynciterator-polyfill';
22

33
import { createBaseLogger, LogLevel, PowerSyncDatabase, SyncClientImplementation } from '@powersync/react-native';
44
import React from 'react';
5-
import { SupabaseStorageAdapter } from '../storage/SupabaseStorageAdapter';
65

7-
import { type AttachmentRecord } from '@powersync/attachments';
6+
import {
7+
AttachmentQueue,
8+
type AttachmentRecord,
9+
ExpoFileSystemAdapter,
10+
type WatchedAttachmentItem
11+
} from '@powersync/attachments';
812
import { configureFts } from '../fts/fts_setup';
913
import { KVStorage } from '../storage/KVStorage';
14+
import { SupabaseRemoteStorageAdapter } from '../storage/SupabaseRemoteStorageAdapter';
1015
import { AppConfig } from '../supabase/AppConfig';
1116
import { SupabaseConnector } from '../supabase/SupabaseConnector';
12-
import { AppSchema } from './AppSchema';
13-
import { PhotoAttachmentQueue } from './PhotoAttachmentQueue';
17+
import { AppSchema, TODO_TABLE } from './AppSchema';
1418

1519
const logger = createBaseLogger();
1620
logger.useDefaults();
1721
logger.setLevel(LogLevel.DEBUG);
1822

1923
export class System {
2024
kvStorage: KVStorage;
21-
storage: SupabaseStorageAdapter;
2225
supabaseConnector: SupabaseConnector;
2326
powersync: PowerSyncDatabase;
24-
attachmentQueue: PhotoAttachmentQueue | undefined = undefined;
27+
photoAttachmentQueue: AttachmentQueue | undefined = undefined;
2528

2629
constructor() {
2730
this.kvStorage = new KVStorage();
28-
this.supabaseConnector = new SupabaseConnector(this);
29-
this.storage = this.supabaseConnector.storage;
31+
this.supabaseConnector = new SupabaseConnector({
32+
kvStorage: this.kvStorage,
33+
supabaseUrl: AppConfig.supabaseUrl,
34+
supabaseAnonKey: AppConfig.supabaseAnonKey
35+
});
36+
3037
this.powersync = new PowerSyncDatabase({
3138
schema: AppSchema,
3239
database: {
@@ -50,17 +57,44 @@ export class System {
5057
*/
5158

5259
if (AppConfig.supabaseBucket) {
53-
this.attachmentQueue = new PhotoAttachmentQueue({
54-
powersync: this.powersync,
55-
storage: this.storage,
56-
// Use this to handle download errors where you can use the attachment
57-
// and/or the exception to decide if you want to retry the download
58-
onDownloadError: async (attachment: AttachmentRecord, exception: any) => {
59-
if (exception.toString() === 'StorageApiError: Object not found') {
60-
return { retry: false };
61-
}
60+
const localStorage = new ExpoFileSystemAdapter();
61+
const remoteStorage = new SupabaseRemoteStorageAdapter({
62+
client: this.supabaseConnector.client,
63+
bucket: AppConfig.supabaseBucket
64+
});
6265

63-
return { retry: true };
66+
this.photoAttachmentQueue = new AttachmentQueue({
67+
db: this.powersync,
68+
localStorage,
69+
remoteStorage,
70+
watchAttachments: (onUpdate) => {
71+
this.powersync.watch(
72+
`SELECT photo_id as id FROM ${TODO_TABLE} WHERE photo_id IS NOT NULL`,
73+
[],
74+
{
75+
onResult: (result: any) => {
76+
const attachments: WatchedAttachmentItem[] = (result.rows?._array ?? []).map((row: any) => ({
77+
id: row.id,
78+
fileExtension: 'jpg'
79+
}));
80+
onUpdate(attachments);
81+
}
82+
}
83+
);
84+
},
85+
errorHandler: {
86+
onDownloadError: async (attachment: AttachmentRecord, error: Error) => {
87+
if (error.toString() === 'StorageApiError: Object not found') {
88+
return false; // Don't retry
89+
}
90+
return true; // Retry
91+
},
92+
onUploadError: async (attachment: AttachmentRecord, error: Error) => {
93+
return true; // Retry uploads by default
94+
},
95+
onDeleteError: async (attachment: AttachmentRecord, error: Error) => {
96+
return true; // Retry deletes by default
97+
}
6498
}
6599
});
66100
}
@@ -70,8 +104,8 @@ export class System {
70104
await this.powersync.init();
71105
await this.powersync.connect(this.supabaseConnector, { clientImplementation: SyncClientImplementation.RUST });
72106

73-
if (this.attachmentQueue) {
74-
await this.attachmentQueue.init();
107+
if (this.photoAttachmentQueue) {
108+
await this.photoAttachmentQueue.startSync();
75109
}
76110

77111
// Demo using SQLite Full-Text Search with PowerSync.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { SupabaseClient } from '@supabase/supabase-js';
2+
import { AttachmentRecord, RemoteStorageAdapter } from '@powersync/attachments';
3+
4+
export interface SupabaseRemoteStorageAdapterOptions {
5+
client: SupabaseClient;
6+
bucket: string;
7+
}
8+
9+
/**
10+
* SupabaseRemoteStorageAdapter implements RemoteStorageAdapter for Supabase Storage.
11+
* Handles upload, download, and deletion of files from Supabase Storage buckets.
12+
*/
13+
export class SupabaseRemoteStorageAdapter implements RemoteStorageAdapter {
14+
constructor(private options: SupabaseRemoteStorageAdapterOptions) {}
15+
16+
async uploadFile(fileData: ArrayBuffer, attachment: AttachmentRecord): Promise<void> {
17+
const mediaType = attachment.mediaType ?? 'application/octet-stream';
18+
19+
const { error } = await this.options.client.storage
20+
.from(this.options.bucket)
21+
.upload(attachment.filename, fileData, { contentType: mediaType });
22+
23+
if (error) {
24+
throw error;
25+
}
26+
}
27+
28+
async downloadFile(attachment: AttachmentRecord): Promise<ArrayBuffer> {
29+
const { data, error } = await this.options.client.storage
30+
.from(this.options.bucket)
31+
.download(attachment.filename);
32+
33+
if (error) {
34+
throw error;
35+
}
36+
37+
// Convert Blob to ArrayBuffer
38+
return await data.arrayBuffer();
39+
}
40+
41+
async deleteFile(attachment: AttachmentRecord): Promise<void> {
42+
const { error } = await this.options.client.storage
43+
.from(this.options.bucket)
44+
.remove([attachment.filename]);
45+
46+
if (error) {
47+
console.debug('Failed to delete file from Supabase Storage', error);
48+
throw error;
49+
}
50+
}
51+
}
52+

demos/react-native-supabase-todolist/library/storage/SupabaseStorageAdapter.ts

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)