Skip to content

Commit 8cfc680

Browse files
committed
feat: add vanilla react native storage adapter
1 parent ecd22e5 commit 8cfc680

File tree

6 files changed

+54
-47
lines changed

6 files changed

+54
-47
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from 'react';
44
import {
55
PowerSyncDatabase as PowerSyncDatabaseNative,
66
AbstractPowerSyncDatabase,
7-
ReactNativeFileSystemStorageAdapter
7+
ExpoFileSystemStorageAdapter
88
} from '@powersync/react-native';
99
import {
1010
PowerSyncDatabase as PowerSyncDatabaseWeb,
@@ -92,7 +92,7 @@ export class System {
9292

9393
if (AppConfig.supabaseBucket) {
9494
const isWeb = Platform.OS === 'web';
95-
const localStorage = isWeb ? new IndexDBFileSystemStorageAdapter() : new ReactNativeFileSystemStorageAdapter();
95+
const localStorage = isWeb ? new IndexDBFileSystemStorageAdapter() : new ExpoFileSystemStorageAdapter();
9696
const remoteStorage = new SupabaseRemoteStorageAdapter({
9797
client: this.supabaseConnector.client,
9898
bucket: AppConfig.supabaseBucket

packages/react-native/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,26 @@
3333
"homepage": "https://docs.powersync.com/",
3434
"peerDependencies": {
3535
"@journeyapps/react-native-quick-sqlite": "^2.4.9",
36+
"@dr.pogodin/react-native-fs": "^2.36.1",
37+
"expo-file-system": "*",
3638
"@powersync/common": "workspace:^1.43.1",
3739
"react": "*",
3840
"react-native": "*"
3941
},
4042
"peerDependenciesMeta": {
4143
"@journeyapps/react-native-quick-sqlite": {
4244
"optional": true
45+
},
46+
"@dr.pogodin/react-native-fs": {
47+
"optional": true
48+
},
49+
"expo-file-system": {
50+
"optional": true
4351
}
4452
},
4553
"dependencies": {
4654
"@powersync/common": "workspace:*",
4755
"@powersync/react": "workspace:*",
48-
"expo-file-system": "18.0.12",
4956
"async-mutex": "^0.5.0"
5057
},
5158
"devDependencies": {

packages/react-native/rollup.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default () => {
5858
'js-logger',
5959
'react-native',
6060
'react',
61-
'expo-file-system',
61+
'@dr.pogodin/react-native-fs',
6262
'async-mutex'
6363
]
6464
};

packages/react-native/src/attachments/ExpoFileSystemAdapter.ts renamed to packages/react-native/src/attachments/ExpoFileSystemStorageAdapter.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import { getInfoAsync, makeDirectoryAsync, deleteAsync, writeAsStringAsync, readAsStringAsync, documentDirectory } from 'expo-file-system';
21
import { decode as decodeBase64, encode as encodeBase64 } from 'base64-arraybuffer';
32
import { AttachmentData, EncodingType, LocalStorageAdapter } from '@powersync/common';
43

4+
try {
5+
var {
6+
getInfoAsync,
7+
makeDirectoryAsync,
8+
deleteAsync,
9+
writeAsStringAsync,
10+
readAsStringAsync,
11+
documentDirectory
12+
} = require('expo-file-system');
13+
} catch (e) {
14+
throw new Error(`Could not resolve expo-file-system.
15+
To use the Expo File System attachment adapter please install expo-file-system.`);
16+
}
17+
518
/**
6-
* ExpoFileSystemAdapter implements LocalStorageAdapter using Expo's
19+
* ExpoFileSystemAdapter implements LocalStorageAdapter using Expo's
720
* Suitable for React Native applications using Expo or Expo modules.
821
*/
9-
export class ExpoFileSystemAdapter implements LocalStorageAdapter {
22+
export class ExpoFileSystemStorageAdapter implements LocalStorageAdapter {
1023
private storageDirectory: string;
1124

1225
constructor(storageDirectory?: string) {
@@ -45,7 +58,7 @@ export class ExpoFileSystemAdapter implements LocalStorageAdapter {
4558
await writeAsStringAsync(filePath, data, {
4659
encoding: encoding === EncodingType.Base64 ? EncodingType.Base64 : EncodingType.UTF8
4760
});
48-
61+
4962
// Calculate size based on encoding
5063
if (encoding === EncodingType.Base64) {
5164
// Base64 string length / 4 * 3 gives approximate byte size
@@ -69,7 +82,7 @@ export class ExpoFileSystemAdapter implements LocalStorageAdapter {
6982

7083
async readFile(filePath: string, options?: { encoding?: EncodingType; mediaType?: string }): Promise<ArrayBuffer> {
7184
const encoding = options?.encoding ?? EncodingType.Base64;
72-
85+
7386
// Let the native function throw if file doesn't exist
7487
const content = await readAsStringAsync(filePath, {
7588
encoding: encoding === EncodingType.Base64 ? EncodingType.Base64 : EncodingType.UTF8

packages/react-native/src/sync/attachments/ExpoFileSystemAdapter.ts renamed to packages/react-native/src/attachments/ReactNativeFileSystemAdapter.ts

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
import * as FileSystem from 'expo-file-system';
21
import { decode as decodeBase64, encode as encodeBase64 } from 'base64-arraybuffer';
32
import { AttachmentData, EncodingType, LocalStorageAdapter } from '@powersync/common';
43

5-
/**
6-
* ExpoFileSystemAdapter implements LocalStorageAdapter using Expo's FileSystem.
7-
* Suitable for React Native applications using Expo or Expo modules.
8-
*/
9-
export class ExpoFileSystemAdapter implements LocalStorageAdapter {
4+
try {
5+
var rnfs = require('@dr.pogodin/react-native-fs');
6+
} catch (e) {
7+
throw new Error(`Could not resolve @dr.pogodin/react-native-fs.
8+
To use the React Native File System attachment adapter please install @dr.pogodin/react-native-fs.`);
9+
}
10+
11+
export class ReactNativeFileSystemStorageAdapter implements LocalStorageAdapter {
1012
private storageDirectory: string;
1113

1214
constructor(storageDirectory?: string) {
1315
// Default to a subdirectory in the document directory
14-
this.storageDirectory = storageDirectory ?? `${FileSystem.documentDirectory}attachments/`;
16+
this.storageDirectory = storageDirectory ?? `${rnfs.DocumentDirectoryPath}/attachments/`;
1517
}
1618

1719
async initialize(): Promise<void> {
18-
const dirInfo = await FileSystem.getInfoAsync(this.storageDirectory);
19-
if (!dirInfo.exists) {
20-
await FileSystem.makeDirectoryAsync(this.storageDirectory, { intermediates: true });
20+
const dirExists = await rnfs.exists(this.storageDirectory);
21+
if (!dirExists) {
22+
await rnfs.mkdir(this.storageDirectory);
2123
}
2224
}
2325

2426
async clear(): Promise<void> {
25-
const dirInfo = await FileSystem.getInfoAsync(this.storageDirectory);
26-
if (dirInfo.exists) {
27-
await FileSystem.deleteAsync(this.storageDirectory);
27+
const dirExists = await rnfs.exists(this.storageDirectory);
28+
if (dirExists) {
29+
await rnfs.unlink(this.storageDirectory);
2830
}
2931
}
3032

@@ -40,27 +42,19 @@ export class ExpoFileSystemAdapter implements LocalStorageAdapter {
4042
let size: number;
4143

4244
if (typeof data === 'string') {
43-
// Handle string data (typically base64 or UTF8)
4445
const encoding = options?.encoding ?? EncodingType.Base64;
45-
await FileSystem.writeAsStringAsync(filePath, data, {
46-
encoding: encoding === EncodingType.Base64 ? FileSystem.EncodingType.Base64 : FileSystem.EncodingType.UTF8
47-
});
48-
46+
await rnfs.writeFile(filePath, data, encoding === EncodingType.Base64 ? 'base64' : 'utf8');
47+
4948
// Calculate size based on encoding
5049
if (encoding === EncodingType.Base64) {
51-
// Base64 string length / 4 * 3 gives approximate byte size
5250
size = Math.ceil((data.length / 4) * 3);
5351
} else {
54-
// UTF8: Use TextEncoder to get accurate byte count
5552
const encoder = new TextEncoder();
5653
size = encoder.encode(data).byteLength;
5754
}
5855
} else {
59-
// Handle ArrayBuffer data
6056
const base64 = encodeBase64(data);
61-
await FileSystem.writeAsStringAsync(filePath, base64, {
62-
encoding: FileSystem.EncodingType.Base64
63-
});
57+
await rnfs.WriteFile(filePath, base64, 'base64');
6458
size = data.byteLength;
6559
}
6660

@@ -69,25 +63,19 @@ export class ExpoFileSystemAdapter implements LocalStorageAdapter {
6963

7064
async readFile(filePath: string, options?: { encoding?: EncodingType; mediaType?: string }): Promise<ArrayBuffer> {
7165
const encoding = options?.encoding ?? EncodingType.Base64;
72-
73-
// Let the native function throw if file doesn't exist
74-
const content = await FileSystem.readAsStringAsync(filePath, {
75-
encoding: encoding === EncodingType.Base64 ? FileSystem.EncodingType.Base64 : FileSystem.EncodingType.UTF8
76-
});
66+
67+
const content = await rnfs.readFile(filePath, encoding === EncodingType.Base64 ? 'base64' : 'utf8');
7768

7869
if (encoding === EncodingType.UTF8) {
79-
// Convert UTF8 string to ArrayBuffer
8070
const encoder = new TextEncoder();
8171
return encoder.encode(content).buffer;
8272
} else {
83-
// Convert base64 string to ArrayBuffer
8473
return decodeBase64(content);
8574
}
8675
}
8776

8877
async deleteFile(filePath: string, options?: { filename?: string }): Promise<void> {
89-
await FileSystem.deleteAsync(filePath).catch((error: any) => {
90-
// Gracefully ignore file not found errors, throw others
78+
await rnfs.unlink(filePath).catch((error: any) => {
9179
if (error?.message?.includes('not exist') || error?.message?.includes('ENOENT')) {
9280
return;
9381
}
@@ -97,19 +85,17 @@ export class ExpoFileSystemAdapter implements LocalStorageAdapter {
9785

9886
async fileExists(filePath: string): Promise<boolean> {
9987
try {
100-
const info = await FileSystem.getInfoAsync(filePath);
101-
return info.exists;
88+
return await rnfs.exists(filePath);
10289
} catch {
10390
return false;
10491
}
10592
}
10693

10794
async makeDir(path: string): Promise<void> {
108-
await FileSystem.makeDirectoryAsync(path, { intermediates: true });
95+
await rnfs.mkdir(path);
10996
}
11097

11198
async rmDir(path: string): Promise<void> {
112-
await FileSystem.deleteAsync(path);
99+
await rnfs.unlink(path);
113100
}
114101
}
115-

packages/react-native/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './sync/stream/ReactNativeRemote';
99
export * from './sync/stream/ReactNativeStreamingSyncImplementation';
1010
export * from './db/adapters/react-native-quick-sqlite/ReactNativeQuickSQLiteOpenFactory';
1111
export * from './attachments/ExpoFileSystemAdapter';
12+
export * from './attachments/ReactNativeFileSystemAdapter';

0 commit comments

Comments
 (0)