Skip to content

Commit

Permalink
feat(attachments): add error handlers (#73)
Browse files Browse the repository at this point in the history
Co-authored-by: DominicGBauer <[email protected]>
Co-authored-by: Steven Ontong <[email protected]>
  • Loading branch information
3 people authored Feb 21, 2024
1 parent 6802316 commit be450ff
Show file tree
Hide file tree
Showing 8 changed files with 1,924 additions and 543 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-cups-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@journeyapps/powersync-attachments": minor
---

Add ability to allow users to handle errors through onDownloadError and onUploadError when setting up the queue
254 changes: 127 additions & 127 deletions demos/react-native-supabase-todolist/ios/Podfile.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
};
Expand Down Expand Up @@ -535,7 +535,7 @@
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
Expand Down
12 changes: 11 additions & 1 deletion demos/react-native-supabase-todolist/library/powersync/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AppSchema } from './AppSchema';
import { SupabaseConnector } from '../supabase/SupabaseConnector';
import { KVStorage } from '../storage/KVStorage';
import { PhotoAttachmentQueue } from './PhotoAttachmentQueue';
import { type AttachmentRecord } from '@journeyapps/powersync-attachments';

export class System {
kvStorage: KVStorage;
Expand All @@ -30,7 +31,16 @@ export class System {

this.attachmentQueue = new PhotoAttachmentQueue({
powersync: this.powersync,
storage: this.storage
storage: this.storage,
// Use this to handle download errors where you can use the attachment
// and/or the exception to decide if you want to retry the download
onDownloadError: async (attachment: AttachmentRecord, exception: any) => {
if (exception.toString() === 'StorageApiError: Object not found') {
return { retry: false };
}

return { retry: true };
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CameraWidget } from './CameraWidget';
import { TodoRecord } from '../powersync/AppSchema';
import { AttachmentRecord } from '@journeyapps/powersync-attachments';
import { AppConfig } from '../supabase/AppConfig';
import { useSystem } from '../powersync/system';

export interface TodoItemWidgetProps {
record: TodoRecord;
Expand All @@ -19,6 +20,7 @@ export const TodoItemWidget: React.FC<TodoItemWidgetProps> = (props) => {
const { record, photoAttachment, onDelete, onToggleCompletion, onSavePhoto } = props;
const [loading, setLoading] = React.useState(false);
const [isCameraVisible, setCameraVisible] = React.useState(false);
const system = useSystem();

const handleCancel = React.useCallback(() => {
setCameraVisible(false);
Expand Down Expand Up @@ -49,8 +51,7 @@ export const TodoItemWidget: React.FC<TodoItemWidgetProps> = (props) => {
);
}}
/>
}
>
}>
{loading ? (
<ActivityIndicator />
) : (
Expand All @@ -74,7 +75,7 @@ export const TodoItemWidget: React.FC<TodoItemWidgetProps> = (props) => {
<Icon name={'camera'} type="font-awesome" onPress={() => setCameraVisible(true)} />
) : photoAttachment?.local_uri != null ? (
<Image
source={{ uri: photoAttachment.local_uri }}
source={{ uri: system.attachmentQueue.getLocalUri(photoAttachment.local_uri) }}
containerStyle={styles.item}
PlaceholderContent={<ActivityIndicator />}
/>
Expand Down
16 changes: 8 additions & 8 deletions demos/react-native-supabase-todolist/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,16 @@
"web-streams-polyfill": "^3.3.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/plugin-transform-async-generator-functions": "^7.22.15",
"@babel/preset-env": "^7.1.6",
"@types/base-64": "^1.0.0",
"@types/lodash": "^4.14.199",
"@types/react": "~18.2.14",
"@babel/core": "^7.23.9",
"@babel/plugin-transform-async-generator-functions": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@types/base-64": "^1.0.2",
"@types/lodash": "^4.14.202",
"@types/react": "~18.2.57",
"@types/uuid": "3.4.11",
"babel-preset-expo": "^10.0.1",
"prettier": "^3.0.3",
"typescript": "^5.1.3"
"prettier": "^3.2.5",
"typescript": "^5.3.3"
},
"private": true
}
65 changes: 45 additions & 20 deletions packages/powersync-attachments/src/AbstractAttachmentQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export interface AttachmentQueueOptions {
* Whether to mark the initial watched attachment IDs to be synced
*/
performInitialSync?: boolean;
/**
* How to handle download errors, return { retry: false } to ignore the download
*/
onDownloadError?: (attachment: AttachmentRecord, exception: any) => Promise<{ retry?: boolean }>;
/**
* How to handle upload errors, return { retry: false } to ignore the upload
*/
onUploadError?: (attachment: AttachmentRecord, exception: any) => Promise<{ retry?: boolean }>;
}

export const DEFAULT_ATTACHMENT_QUEUE_OPTIONS: Partial<AttachmentQueueOptions> = {
Expand Down Expand Up @@ -106,11 +114,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
this.initialSync = false;
// Mark AttachmentIds for sync
await this.powersync.execute(
`UPDATE
${this.table}
SET state = ${AttachmentState.QUEUED_SYNC}
WHERE
state < ${AttachmentState.SYNCED}
`UPDATE
${this.table}
SET state = ${AttachmentState.QUEUED_SYNC}
WHERE
state < ${AttachmentState.SYNCED}
AND
id IN (${_ids})`
);
Expand Down Expand Up @@ -142,9 +150,12 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =

// 3. Attachment in database and not in AttachmentIds, mark as archived
await this.powersync.execute(
`UPDATE ${this.table} SET state = ${AttachmentState.ARCHIVED} WHERE state < ${
AttachmentState.ARCHIVED
} AND id NOT IN (${ids.map((id) => `'${id}'`).join(',')})`
`UPDATE ${this.table}
SET state = ${AttachmentState.ARCHIVED}
WHERE
state < ${AttachmentState.ARCHIVED}
AND
id NOT IN (${ids.map((id) => `'${id}'`).join(',')})`
);
}
}
Expand Down Expand Up @@ -179,7 +190,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
const timestamp = new Date().getTime();
await this.powersync.execute(
`UPDATE ${this.table}
SET
SET
timestamp = ?,
filename = ?,
local_uri = ?,
Expand Down Expand Up @@ -267,6 +278,13 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
await this.update({ ...record, state: AttachmentState.SYNCED });
return false;
}
if (this.options.onUploadError) {
const { retry } = await this.options.onUploadError(record, e);
if (!retry) {
await this.update({ ...record, state: AttachmentState.ARCHIVED });
return true;
}
}
console.error(`UploadAttachment error for record ${JSON.stringify(record, null, 2)}`);
return false;
}
Expand Down Expand Up @@ -312,6 +330,13 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
console.debug(`Downloaded attachment "${record.id}"`);
return true;
} catch (e) {
if (this.options.onDownloadError) {
const { retry } = await this.options.onDownloadError(record, e);
if (!retry) {
await this.update({ ...record, state: AttachmentState.ARCHIVED });
return true;
}
}
console.error(`Download attachment error for record ${JSON.stringify(record, null, 2)}`, e);
}
return false;
Expand All @@ -320,11 +345,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
async *idsToUpload(): AsyncIterable<string[]> {
for await (const result of this.powersync.watch(
`SELECT id
FROM ${this.table}
FROM ${this.table}
WHERE
local_uri IS NOT NULL
AND
(state = ${AttachmentState.QUEUED_UPLOAD}
(state = ${AttachmentState.QUEUED_UPLOAD}
OR
state = ${AttachmentState.QUEUED_SYNC})`,
[]
Expand Down Expand Up @@ -374,9 +399,9 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
async getIdsToDownload(): Promise<string[]> {
const res = await this.powersync.getAll<{ id: string }>(
`SELECT id
FROM ${this.table}
FROM ${this.table}
WHERE
state = ${AttachmentState.QUEUED_DOWNLOAD}
state = ${AttachmentState.QUEUED_DOWNLOAD}
OR
state = ${AttachmentState.QUEUED_SYNC}
ORDER BY timestamp ASC`
Expand All @@ -387,10 +412,10 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
async *idsToDownload(): AsyncIterable<string[]> {
for await (const result of this.powersync.watch(
`SELECT id
FROM ${this.table}
WHERE
state = ${AttachmentState.QUEUED_DOWNLOAD}
OR
FROM ${this.table}
WHERE
state = ${AttachmentState.QUEUED_DOWNLOAD}
OR
state = ${AttachmentState.QUEUED_SYNC}`,
[]
)) {
Expand Down Expand Up @@ -460,10 +485,10 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}

async expireCache() {
const res = await this.powersync.getAll<AttachmentRecord>(`SELECT * FROM ${this.table}
WHERE
const res = await this.powersync.getAll<AttachmentRecord>(`SELECT * FROM ${this.table}
WHERE
state = ${AttachmentState.SYNCED} OR state = ${AttachmentState.ARCHIVED}
ORDER BY
ORDER BY
timestamp DESC
LIMIT 100 OFFSET ${this.options.cacheLimit}`);

Expand Down
Loading

0 comments on commit be450ff

Please sign in to comment.