@powersync/react-native 0.0.0-dev-20260525085311 → 0.0.0-dev-20260630141119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +72 -6
  2. package/android/build.gradle +90 -0
  3. package/android/gradle.properties +4 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/AndroidManifestNew.xml +2 -0
  6. package/android/src/main/java/com/powersync/opsqlite/PowerSyncOpSqlitePackage.kt +46 -0
  7. package/ios/PowerSyncOpSqlite.h +5 -0
  8. package/ios/PowerSyncOpSqlite.mm +6 -0
  9. package/lib/db/PowerSyncDatabase.d.ts +9 -10
  10. package/lib/db/PowerSyncDatabase.js +33 -25
  11. package/lib/db/PowerSyncDatabase.js.map +1 -1
  12. package/lib/db/adapters/op-sqlite/OPSQLiteConnection.d.ts +25 -0
  13. package/lib/db/adapters/op-sqlite/OPSQLiteConnection.js +63 -0
  14. package/lib/db/adapters/op-sqlite/OPSQLiteConnection.js.map +1 -0
  15. package/lib/db/adapters/op-sqlite/OPSqliteAdapter.d.ts +32 -0
  16. package/lib/db/adapters/op-sqlite/OPSqliteAdapter.js +203 -0
  17. package/lib/db/adapters/op-sqlite/OPSqliteAdapter.js.map +1 -0
  18. package/lib/db/adapters/op-sqlite/OPSqliteDBOpenFactory.d.ts +11 -0
  19. package/lib/db/adapters/op-sqlite/OPSqliteDBOpenFactory.js +21 -0
  20. package/lib/db/adapters/op-sqlite/OPSqliteDBOpenFactory.js.map +1 -0
  21. package/lib/db/adapters/op-sqlite/SqliteOptions.d.ts +68 -0
  22. package/lib/db/adapters/op-sqlite/SqliteOptions.js +37 -0
  23. package/lib/db/adapters/op-sqlite/SqliteOptions.js.map +1 -0
  24. package/lib/index.d.ts +1 -3
  25. package/lib/index.js +0 -3
  26. package/lib/index.js.map +1 -1
  27. package/lib/sync/bucket/ReactNativeBucketStorageAdapter.d.ts +1 -1
  28. package/lib/sync/bucket/ReactNativeBucketStorageAdapter.js +1 -1
  29. package/lib/sync/bucket/ReactNativeBucketStorageAdapter.js.map +1 -1
  30. package/lib/sync/stream/ReactNativeRemote.d.ts +11 -11
  31. package/lib/sync/stream/ReactNativeRemote.js +42 -66
  32. package/lib/sync/stream/ReactNativeRemote.js.map +1 -1
  33. package/lib/sync/stream/ReactNativeStreamingSyncImplementation.d.ts +2 -1
  34. package/lib/sync/stream/ReactNativeStreamingSyncImplementation.js +2 -1
  35. package/lib/sync/stream/ReactNativeStreamingSyncImplementation.js.map +1 -1
  36. package/lib/sync/stream/fetch.d.ts +13 -0
  37. package/lib/sync/stream/fetch.js +31 -0
  38. package/lib/sync/stream/fetch.js.map +1 -0
  39. package/package.json +20 -33
  40. package/powersync-react-native.podspec +32 -0
  41. package/src/db/PowerSyncDatabase.ts +58 -31
  42. package/src/db/adapters/op-sqlite/OPSQLiteConnection.ts +95 -0
  43. package/src/db/adapters/op-sqlite/OPSqliteAdapter.ts +245 -0
  44. package/src/db/adapters/op-sqlite/OPSqliteDBOpenFactory.ts +25 -0
  45. package/src/db/adapters/op-sqlite/SqliteOptions.ts +93 -0
  46. package/src/index.ts +1 -3
  47. package/src/sync/bucket/ReactNativeBucketStorageAdapter.ts +1 -1
  48. package/src/sync/stream/ReactNativeRemote.ts +49 -86
  49. package/src/sync/stream/ReactNativeStreamingSyncImplementation.ts +4 -4
  50. package/src/sync/stream/fetch.ts +45 -0
  51. package/dist/index.js +0 -8741
  52. package/dist/index.js.map +0 -1
  53. package/lib/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.d.ts +0 -55
  54. package/lib/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.js +0 -66
  55. package/lib/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.js.map +0 -1
  56. package/lib/db/adapters/react-native-quick-sqlite/RNQSDBOpenFactory.d.ts +0 -19
  57. package/lib/db/adapters/react-native-quick-sqlite/RNQSDBOpenFactory.js +0 -34
  58. package/lib/db/adapters/react-native-quick-sqlite/RNQSDBOpenFactory.js.map +0 -1
  59. package/lib/db/adapters/react-native-quick-sqlite/ReactNativeQuickSQLiteOpenFactory.d.ts +0 -9
  60. package/lib/db/adapters/react-native-quick-sqlite/ReactNativeQuickSQLiteOpenFactory.js +0 -45
  61. package/lib/db/adapters/react-native-quick-sqlite/ReactNativeQuickSQLiteOpenFactory.js.map +0 -1
  62. package/src/db/adapters/react-native-quick-sqlite/RNQSDBAdapter.ts +0 -85
  63. package/src/db/adapters/react-native-quick-sqlite/RNQSDBOpenFactory.ts +0 -44
  64. package/src/db/adapters/react-native-quick-sqlite/ReactNativeQuickSQLiteOpenFactory.ts +0 -43
@@ -0,0 +1,93 @@
1
+ export interface SqliteOptions {
2
+ /**
3
+ * SQLite journal mode. Defaults to [SqliteJournalMode.wal].
4
+ */
5
+ journalMode?: SqliteJournalMode;
6
+
7
+ /**
8
+ * SQLite synchronous flag. Defaults to [SqliteSynchronous.normal], which
9
+ * is safe for WAL mode.
10
+ */
11
+ synchronous?: SqliteSynchronous;
12
+
13
+ /**
14
+ * Journal/WAL size limit. Defaults to 6MB.
15
+ * The WAL may grow larger than this limit during writes, but SQLite will
16
+ * attempt to truncate the file afterwards.
17
+ */
18
+ journalSizeLimit?: number;
19
+
20
+ /**
21
+ * Timeout in milliseconds waiting for locks to be released by other connections.
22
+ * Defaults to 30 seconds.
23
+ * Set to null or zero to fail immediately when the database is locked.
24
+ */
25
+ lockTimeoutMs?: number;
26
+
27
+ /**
28
+ * Encryption key for the database.
29
+ * If set, the database will be encrypted using SQLCipher.
30
+ */
31
+ encryptionKey?: string | null;
32
+
33
+ /**
34
+ * Where to store SQLite temporary files. Defaults to 'MEMORY'.
35
+ * Setting this to `FILESYSTEM` can cause issues with larger queries or datasets.
36
+ *
37
+ * For details, see: https://www.sqlite.org/pragma.html#pragma_temp_store
38
+ */
39
+ temporaryStorage?: TemporaryStorageOption;
40
+
41
+ /**
42
+ * Maximum SQLite cache size. Defaults to 50MB.
43
+ *
44
+ * For details, see: https://www.sqlite.org/pragma.html#pragma_cache_size
45
+ */
46
+ cacheSizeKb?: number;
47
+
48
+ /**
49
+ * Load extensions using the path and entryPoint.
50
+ * More info can be found here https://op-engineering.github.io/op-sqlite/docs/api#loading-extensions.
51
+ */
52
+ extensions?: Array<{
53
+ path: string;
54
+ entryPoint?: string;
55
+ }>;
56
+ }
57
+
58
+ export enum TemporaryStorageOption {
59
+ MEMORY = 'memory',
60
+ FILESYSTEM = 'file'
61
+ }
62
+
63
+ // SQLite journal mode. Set on the primary connection.
64
+ // This library is written with WAL mode in mind - other modes may cause
65
+ // unexpected locking behavior.
66
+ enum SqliteJournalMode {
67
+ // Use a write-ahead log instead of a rollback journal.
68
+ // This provides good performance and concurrency.
69
+ wal = 'WAL',
70
+ delete = 'DELETE',
71
+ truncate = 'TRUNCATE',
72
+ persist = 'PERSIST',
73
+ memory = 'MEMORY',
74
+ off = 'OFF'
75
+ }
76
+
77
+ // SQLite file commit mode.
78
+ enum SqliteSynchronous {
79
+ normal = 'NORMAL',
80
+ full = 'FULL',
81
+ off = 'OFF'
82
+ }
83
+
84
+ export const DEFAULT_SQLITE_OPTIONS: Required<SqliteOptions> = {
85
+ journalMode: SqliteJournalMode.wal,
86
+ synchronous: SqliteSynchronous.normal,
87
+ journalSizeLimit: 6 * 1024 * 1024,
88
+ cacheSizeKb: 50 * 1024,
89
+ temporaryStorage: TemporaryStorageOption.MEMORY,
90
+ lockTimeoutMs: 30000,
91
+ encryptionKey: null,
92
+ extensions: []
93
+ };
package/src/index.ts CHANGED
@@ -3,8 +3,6 @@ export * from '@powersync/common';
3
3
  export * from '@powersync/react';
4
4
 
5
5
  export * from './db/PowerSyncDatabase';
6
- export * from './db/adapters/react-native-quick-sqlite/RNQSDBAdapter';
7
- export * from './db/adapters/react-native-quick-sqlite/RNQSDBOpenFactory';
6
+ export { PowerSyncFetchImplementation } from './sync/stream/fetch';
8
7
  export * from './sync/stream/ReactNativeRemote';
9
8
  export * from './sync/stream/ReactNativeStreamingSyncImplementation';
10
- export * from './db/adapters/react-native-quick-sqlite/ReactNativeQuickSQLiteOpenFactory';
@@ -1,4 +1,4 @@
1
- import { PowerSyncControlCommand, SqliteBucketStorage } from '@powersync/common';
1
+ import { PowerSyncControlCommand, SqliteBucketStorage } from '@powersync/shared-internals';
2
2
 
3
3
  export class ReactNativeBucketStorageAdapter extends SqliteBucketStorage {
4
4
  control(op: PowerSyncControlCommand, payload: string | Uint8Array | ArrayBuffer | null): Promise<string> {
@@ -1,60 +1,63 @@
1
- import {
2
- AbstractRemote,
3
- AbstractRemoteOptions,
4
- DEFAULT_REMOTE_LOGGER,
5
- FetchImplementation,
6
- FetchImplementationProvider,
7
- ILogger,
8
- RemoteConnector,
9
- SyncStreamOptions
10
- } from '@powersync/common';
1
+ import { LogLevels, PowerSyncLogger } from '@powersync/common';
2
+ import { AbstractRemote, FetchOptions, RemoteConnector } from '@powersync/shared-internals';
11
3
  import { Platform } from 'react-native';
12
- import { TextDecoder } from 'text-encoding';
13
-
14
- import { fetch } from 'react-native-fetch-api';
4
+ import { WebSocketSupport, WebSocketSyncStreamPlatform } from '@powersync/shared-internals/websockets';
5
+ import { defaultFetchImplementation, PowerSyncFetchImplementation } from './fetch';
15
6
 
16
7
  export const STREAMING_POST_TIMEOUT_MS = 30_000;
17
8
 
18
- type ReactNativeFetchOptions = RequestInit & {
19
- reactNative?: Record<string, unknown>;
20
- };
21
-
22
- /**
23
- * Directly imports fetch implementation from react-native-fetch-api.
24
- * This removes the requirement for the global `fetch` to be overridden by
25
- * a polyfill.
26
- */
27
- class ReactNativeFetchProvider extends FetchImplementationProvider {
28
- getFetch(): FetchImplementation {
29
- return fetch.bind(globalThis);
30
- }
9
+ export interface ReactNativeRemoteOptions {
10
+ fetchImplementation?: PowerSyncFetchImplementation;
31
11
  }
32
12
 
33
13
  export class ReactNativeRemote extends AbstractRemote {
34
14
  constructor(
35
15
  protected connector: RemoteConnector,
36
- protected logger: ILogger = DEFAULT_REMOTE_LOGGER,
37
- options?: Partial<AbstractRemoteOptions>
16
+ logger: PowerSyncLogger,
17
+ readonly options?: ReactNativeRemoteOptions
38
18
  ) {
39
- super(connector, logger, {
40
- ...(options ?? {}),
41
- fetchImplementation: options?.fetchImplementation ?? new ReactNativeFetchProvider()
42
- });
19
+ super(connector, logger);
43
20
  }
44
21
 
45
- get fetch(): FetchImplementation {
46
- const fetchImplementation = super.fetch;
47
- return ((input, init) => {
48
- const options = (init ?? {}) as ReactNativeFetchOptions;
22
+ protected async fetch(options: FetchOptions): Promise<Response> {
23
+ let timeout: ReturnType<typeof setTimeout> | null = null;
24
+ const { expectStreamingResponse } = options;
25
+ if (expectStreamingResponse) {
26
+ timeout =
27
+ Platform.OS == 'android'
28
+ ? setTimeout(() => {
29
+ this.logger.log({
30
+ level: LogLevels.warn,
31
+ message: `HTTP Streaming POST is taking longer than ${Math.ceil(
32
+ STREAMING_POST_TIMEOUT_MS / 1000
33
+ )} seconds to resolve. If using a debug build, please ensure Flipper Network plugin is disabled.`
34
+ });
35
+ }, STREAMING_POST_TIMEOUT_MS)
36
+ : null;
37
+ }
38
+
39
+ try {
40
+ const fetchImpl = this.options?.fetchImplementation ?? defaultFetchImplementation(this.logger);
41
+
42
+ if (expectStreamingResponse && !fetchImpl.supportsStreams) {
43
+ // We can't fall back to the default fetch() implementation since we need a response stream.
44
+ const errorMessage =
45
+ 'The PowerSync SDK requires a fetch() implementation capable of streaming responses, which React Native ' +
46
+ 'does not support natively. The SDK was unable to import `expo/fetch`. ' +
47
+ "If you're not using expo, consider passing a custom fetchImplementation via the remote option on " +
48
+ 'the PowerSyncDatabase constructor, or use `SyncStreamConnectionMethod.WEB_SOCKET` on your connect() call.';
49
+
50
+ throw new Error(errorMessage);
51
+ }
52
+
53
+ return fetchImpl.run(options);
54
+ } finally {
55
+ if (timeout != null) clearTimeout(timeout);
56
+ }
57
+ }
49
58
 
50
- return fetchImplementation(input, {
51
- ...options,
52
- reactNative: {
53
- ...(options.reactNative ?? {}),
54
- textStreaming: true
55
- }
56
- } as RequestInit);
57
- }) as FetchImplementation;
59
+ protected loadWebSocketSupport(platform: WebSocketSyncStreamPlatform): Promise<WebSocketSupport> {
60
+ return Promise.resolve((websockets ??= new WebSocketSupport(platform)));
58
61
  }
59
62
 
60
63
  getUserAgent(): string {
@@ -65,46 +68,6 @@ export class ReactNativeRemote extends AbstractRemote {
65
68
  `${Platform.OS}/${Platform.Version}`
66
69
  ].join(' ');
67
70
  }
68
-
69
- createTextDecoder(): TextDecoder {
70
- return new TextDecoder();
71
- }
72
-
73
- protected get supportsStreamingBinaryResponses(): boolean {
74
- // We have to pass textStreaming: true to get streamed responses at all, and those don't support binary data.
75
- return false;
76
- }
77
-
78
- protected async fetchStreamRaw(options: SyncStreamOptions) {
79
- const timeout =
80
- Platform.OS == 'android'
81
- ? setTimeout(() => {
82
- this.logger.warn(
83
- `HTTP Streaming POST is taking longer than ${Math.ceil(
84
- STREAMING_POST_TIMEOUT_MS / 1000
85
- )} seconds to resolve. If using a debug build, please ensure Flipper Network plugin is disabled.`
86
- );
87
- }, STREAMING_POST_TIMEOUT_MS)
88
- : null;
89
-
90
- try {
91
- return await super.fetchStreamRaw({
92
- ...options,
93
- fetchOptions: {
94
- ...options.fetchOptions,
95
- /**
96
- * The `react-native-fetch-api` polyfill provides streaming support via
97
- * this non-standard flag
98
- * https://github.com/react-native-community/fetch#enable-text-streaming
99
- */
100
- // @ts-expect-error https://github.com/react-native-community/fetch#enable-text-streaming
101
- reactNative: { textStreaming: true }
102
- }
103
- });
104
- } finally {
105
- if (timeout) {
106
- clearTimeout(timeout);
107
- }
108
- }
109
- }
110
71
  }
72
+
73
+ let websockets: WebSocketSupport | undefined;
@@ -2,9 +2,9 @@ import {
2
2
  AbstractStreamingSyncImplementation,
3
3
  AbstractStreamingSyncImplementationOptions,
4
4
  LockOptions,
5
- LockType,
6
- Mutex
7
- } from '@powersync/common';
5
+ LockType
6
+ } from '@powersync/shared-internals';
7
+ import { Mutex } from '@powersync/shared-internals';
8
8
 
9
9
  /**
10
10
  * Global locks which prevent multiple instances from syncing
@@ -13,7 +13,7 @@ import {
13
13
  const LOCKS = new Map<string, Map<LockType, Mutex>>();
14
14
 
15
15
  export class ReactNativeStreamingSyncImplementation extends AbstractStreamingSyncImplementation {
16
- locks: Map<LockType, Mutex>;
16
+ locks!: Map<LockType, Mutex>;
17
17
 
18
18
  constructor(options: AbstractStreamingSyncImplementationOptions) {
19
19
  super(options);
@@ -0,0 +1,45 @@
1
+ import { LogLevels, PowerSyncLogger } from '@powersync/common';
2
+ import { FetchOptions } from '@powersync/shared-internals';
3
+
4
+ export interface PowerSyncFetchImplementation {
5
+ /**
6
+ * Whether this fetch implementation supports streaming responses.
7
+ *
8
+ * The PowerSync service delivers sync data through a streaming (`Transfer-Encoding: chunked`) response, which is not
9
+ * supported by the default `fetch` polyfill on React Native.
10
+ */
11
+ readonly supportsStreams: boolean;
12
+ run(options: FetchOptions): Promise<Response>;
13
+ }
14
+
15
+ export function defaultFetchImplementation(logger: PowerSyncLogger): PowerSyncFetchImplementation {
16
+ return (resolvedDefault ??= resolveDefaultFetchImplementation(logger));
17
+ }
18
+
19
+ let resolvedDefault: PowerSyncFetchImplementation | undefined;
20
+
21
+ function resolveDefaultFetchImplementation(logger: PowerSyncLogger): PowerSyncFetchImplementation {
22
+ try {
23
+ const { fetch } = require('expo/fetch');
24
+ return {
25
+ supportsStreams: true,
26
+ run({ resource, request }) {
27
+ return fetch(resource, request);
28
+ }
29
+ };
30
+ } catch (expoNotFound) {
31
+ logger.log({
32
+ level: LogLevels.debug,
33
+ message: 'Could not resolve expo/fetch, HTTP streams are unavailable.',
34
+ error: expoNotFound
35
+ });
36
+
37
+ // Fetch polyfill built in to React Native. This one doesn't support streaming responses.
38
+ return {
39
+ supportsStreams: false,
40
+ run({ resource, request }) {
41
+ return fetch(resource, request);
42
+ }
43
+ };
44
+ }
45
+ }