@powersync/react-native 1.4.6 → 1.5.0
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.
package/README.md
CHANGED
|
@@ -30,32 +30,76 @@ npx expo install @journeyapps/react-native-quick-sqlite
|
|
|
30
30
|
|
|
31
31
|
## Install Polyfills
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
This package connects to a PowerSync instance via HTTP streams (enabled by default) or WebSockets.
|
|
34
|
+
* Both connection methods require the [React Native Common Polyfills](#react-native-common-polyfills), as detailed below.
|
|
35
|
+
* The WebSocket method requires an [additional polyfill](#web-sockets-buffer) for the `Buffer` interface.
|
|
36
|
+
* Other polyfills are required for [watched queries](#babel-plugins-watched-queries) using the Async Iterator response format.
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
### React Native Common Polyfills
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
- react-native-polyfill-globals
|
|
39
|
-
- react-native-url-polyfill
|
|
40
|
-
- text-encoding
|
|
41
|
-
- web-streams-polyfill@3.2.1
|
|
40
|
+
This package requires polyfills for HTTP streaming and other text encoding functions. These functions can be provided with [react-native-polyfill-globals](https://www.npmjs.com/package/react-native-polyfill-globals).
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
Install the collection of polyfills with:
|
|
44
43
|
|
|
45
44
|
```bash
|
|
46
|
-
npx expo install react-native-
|
|
45
|
+
npx expo install react-native-polyfill-globals
|
|
47
46
|
```
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
The `react-native-polyfill-globals` package uses peer dependencies for individual functions. Most modern package managers install peer dependencies by default, however currently the peer dependency version ranges are quite broad and might result in certain packages being incompatible. Currently an [issue](https://github.com/acostalima/react-native-polyfill-globals/issues/6) is open for a breaking change in one of the dependencies. The best practice is to currently add the packages as explicit dependencies with version ranges to your project with the command below.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx expo install react-native-fetch-api@^3.0.0 react-native-url-polyfill@^2.0.0 text-encoding@^0.7.0 web-streams-polyfill@3.2.1 base-64@^1.0.0
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Enable the polyfills in React Native app by adding the following in your top level entry point
|
|
50
55
|
|
|
51
56
|
```JavaScript
|
|
52
57
|
// App.js
|
|
53
58
|
import 'react-native-polyfill-globals/auto';
|
|
54
59
|
```
|
|
55
60
|
|
|
61
|
+
### HTTP Connections
|
|
62
|
+
|
|
63
|
+
HTTP connections require the HTTP streaming polyfills included in the [common section](#react-native-common-polyfills). See additional [setup](https://docs.powersync.com/client-sdk-references/react-native-and-expo#android-flipper-network-plugin) required for Android.
|
|
64
|
+
|
|
65
|
+
### WebSocket Connections: Buffer
|
|
66
|
+
|
|
67
|
+
Note: Beta Release - WebSockets are currently in a beta release. It should be safe to use in production if sufficient testing is done on the client side.
|
|
68
|
+
|
|
69
|
+
Our WebSocket implementation supports binary payloads which are encoded as BSON documents.
|
|
70
|
+
|
|
71
|
+
This requires support for the `Buffer` interface.
|
|
72
|
+
|
|
73
|
+
Apply the `Buffer` polyfill
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx expo install @craftzdog/react-native-buffer
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
import { Buffer } from '@craftzdog/react-native-buffer';
|
|
81
|
+
|
|
82
|
+
if (typeof global.Buffer == 'undefined') {
|
|
83
|
+
// @ts-ignore If using TypeScript
|
|
84
|
+
global.Buffer = Buffer;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This library uses `RSocket` for reactive WebSocket streams which requires `process.nextTick` to be available. Apply a polyfill if not available.
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
if (typeof process.nextTick == 'undefined') {
|
|
92
|
+
process.nextTick = setImmediate;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
56
96
|
### Babel Plugins: Watched Queries
|
|
57
97
|
|
|
58
|
-
Watched queries
|
|
98
|
+
Watched queries can be used with either a callback response or Async Iterator response.
|
|
99
|
+
|
|
100
|
+
Watched queries using the Async Iterator response format require support for Async Iterators.
|
|
101
|
+
|
|
102
|
+
Expo apps currently require polyfill and Babel plugins in order to use this functionality.
|
|
59
103
|
|
|
60
104
|
```bash
|
|
61
105
|
npx expo install @azure/core-asynciterator-polyfill
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { AbstractRemote } from '@powersync/common';
|
|
1
|
+
import { AbstractRemote, DataStream, StreamingSyncLine, SyncStreamOptions } from '@powersync/common';
|
|
2
2
|
export declare const STREAMING_POST_TIMEOUT_MS = 30000;
|
|
3
3
|
export declare class ReactNativeRemote extends AbstractRemote {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
postStreaming(path: string, data: any, headers?: Record<string, string>, signal?: AbortSignal): Promise<any>;
|
|
4
|
+
socketStream(options: SyncStreamOptions): Promise<DataStream<StreamingSyncLine>>;
|
|
5
|
+
postStream(options: SyncStreamOptions): Promise<DataStream<StreamingSyncLine>>;
|
|
7
6
|
}
|
|
@@ -1,152 +1,73 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractRemote } from '@powersync/common';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
3
|
export const STREAMING_POST_TIMEOUT_MS = 30_000;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
method: 'POST',
|
|
9
|
-
headers: {
|
|
10
|
-
...headers,
|
|
11
|
-
...request.headers
|
|
12
|
-
},
|
|
13
|
-
body: JSON.stringify(data)
|
|
14
|
-
});
|
|
15
|
-
if (!res.ok) {
|
|
16
|
-
throw new Error(`Received ${res.status} - ${res.statusText} when posting to ${path}: ${await res.text()}}`);
|
|
17
|
-
}
|
|
18
|
-
return res.json();
|
|
4
|
+
const CommonPolyfills = [
|
|
5
|
+
{
|
|
6
|
+
name: 'TextEncoder',
|
|
7
|
+
test: () => typeof TextEncoder == 'undefined'
|
|
19
8
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
throw new Error(`Received ${res.status} - ${res.statusText} when getting from ${path}: ${await res.text()}}`);
|
|
31
|
-
}
|
|
32
|
-
return res.json();
|
|
9
|
+
];
|
|
10
|
+
const SocketPolyfillTests = [
|
|
11
|
+
...CommonPolyfills,
|
|
12
|
+
{
|
|
13
|
+
name: 'nextTick',
|
|
14
|
+
test: () => typeof process.nextTick == 'undefined'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Buffer',
|
|
18
|
+
test: () => typeof global.Buffer == 'undefined'
|
|
33
19
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
20
|
+
];
|
|
21
|
+
const HttpPolyfillTests = [
|
|
22
|
+
...CommonPolyfills,
|
|
23
|
+
{
|
|
24
|
+
name: 'TextDecoder',
|
|
25
|
+
test: () => typeof TextDecoder == 'undefined'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'ReadableStream',
|
|
29
|
+
test: () => typeof ReadableStream == 'undefined'
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
const validatePolyfills = (tests) => {
|
|
33
|
+
const missingPolyfills = tests.filter((t) => t.test()).map((t) => t.name);
|
|
34
|
+
if (missingPolyfills.length) {
|
|
35
|
+
throw new Error(`
|
|
36
|
+
Polyfills are undefined. Please ensure React Native polyfills are installed and imported in the app entrypoint.
|
|
37
|
+
See package README for detailed instructions.
|
|
38
|
+
The following polyfills appear to be missing:
|
|
39
|
+
${missingPolyfills.join('\n')}`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export class ReactNativeRemote extends AbstractRemote {
|
|
43
|
+
async socketStream(options) {
|
|
44
|
+
validatePolyfills(SocketPolyfillTests);
|
|
45
|
+
return super.socketStream(options);
|
|
46
|
+
}
|
|
47
|
+
async postStream(options) {
|
|
48
|
+
validatePolyfills(HttpPolyfillTests);
|
|
46
49
|
const timeout = Platform.OS == 'android'
|
|
47
50
|
? setTimeout(() => {
|
|
48
51
|
this.logger.warn(`HTTP Streaming POST is taking longer than ${Math.ceil(STREAMING_POST_TIMEOUT_MS / 1000)} seconds to resolve. If using a debug build, please ensure Flipper Network plugin is disabled.`);
|
|
49
52
|
}, STREAMING_POST_TIMEOUT_MS)
|
|
50
53
|
: null;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
controller.abort(signal.reason ??
|
|
63
|
-
new AbortOperation('Cancelling network request before it resolves. Abort signal has been received.'));
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
const res = await fetch(request.url, {
|
|
67
|
-
method: 'POST',
|
|
68
|
-
headers: { ...headers, ...request.headers },
|
|
69
|
-
body: JSON.stringify(data),
|
|
70
|
-
signal: controller.signal,
|
|
71
|
-
cache: 'no-store',
|
|
72
|
-
/**
|
|
73
|
-
* The `react-native-fetch-api` polyfill provides streaming support via
|
|
74
|
-
* this non-standard flag
|
|
75
|
-
* https://github.com/react-native-community/fetch#enable-text-streaming
|
|
76
|
-
*/
|
|
77
|
-
// @ts-expect-error https://github.com/react-native-community/fetch#enable-text-streaming
|
|
78
|
-
reactNative: { textStreaming: true }
|
|
79
|
-
}).catch((ex) => {
|
|
80
|
-
if (ex.name == 'AbortError') {
|
|
81
|
-
throw new AbortOperation(`Pending fetch request to ${request.url} has been aborted.`);
|
|
54
|
+
const result = await super.postStream({
|
|
55
|
+
...options,
|
|
56
|
+
fetchOptions: {
|
|
57
|
+
...options.fetchOptions,
|
|
58
|
+
/**
|
|
59
|
+
* The `react-native-fetch-api` polyfill provides streaming support via
|
|
60
|
+
* this non-standard flag
|
|
61
|
+
* https://github.com/react-native-community/fetch#enable-text-streaming
|
|
62
|
+
*/
|
|
63
|
+
// @ts-expect-error https://github.com/react-native-community/fetch#enable-text-streaming
|
|
64
|
+
reactNative: { textStreaming: true }
|
|
82
65
|
}
|
|
83
|
-
throw ex;
|
|
84
66
|
});
|
|
85
|
-
if (timeout
|
|
67
|
+
if (timeout) {
|
|
86
68
|
clearTimeout(timeout);
|
|
87
69
|
}
|
|
88
|
-
|
|
89
|
-
throw new Error('Fetch request was aborted');
|
|
90
|
-
}
|
|
91
|
-
requestResolved = true;
|
|
92
|
-
if (!res.ok) {
|
|
93
|
-
const text = await res.text();
|
|
94
|
-
this.logger.error(`Could not POST streaming to ${path} - ${res.status} - ${res.statusText}: ${text}`);
|
|
95
|
-
const error = new Error(`HTTP ${res.statusText}: ${text}`);
|
|
96
|
-
error.status = res.status;
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* The can-ndjson-stream does not handle aborted streams well.
|
|
101
|
-
* This will intercept the readable stream and close the stream if
|
|
102
|
-
* aborted.
|
|
103
|
-
* TODO this function is duplicated in the Web SDK.
|
|
104
|
-
* The common SDK is a bit oblivious to `ReadableStream` classes.
|
|
105
|
-
* This should be improved when moving to Websockets
|
|
106
|
-
*/
|
|
107
|
-
const reader = res.body.getReader();
|
|
108
|
-
// This will close the network request and read stream
|
|
109
|
-
const closeReader = async () => {
|
|
110
|
-
try {
|
|
111
|
-
await reader.cancel();
|
|
112
|
-
}
|
|
113
|
-
catch (ex) {
|
|
114
|
-
// an error will throw if the reader hasn't been used yet
|
|
115
|
-
}
|
|
116
|
-
reader.releaseLock();
|
|
117
|
-
};
|
|
118
|
-
signal?.addEventListener('abort', () => {
|
|
119
|
-
closeReader();
|
|
120
|
-
});
|
|
121
|
-
const outputStream = new ReadableStream({
|
|
122
|
-
start: (controller) => {
|
|
123
|
-
const processStream = async () => {
|
|
124
|
-
while (!signal?.aborted) {
|
|
125
|
-
try {
|
|
126
|
-
const { done, value } = await reader.read();
|
|
127
|
-
// When no more data needs to be consumed, close the stream
|
|
128
|
-
if (done) {
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
// Enqueue the next data chunk into our target stream
|
|
132
|
-
controller.enqueue(value);
|
|
133
|
-
}
|
|
134
|
-
catch (ex) {
|
|
135
|
-
this.logger.error('Caught exception when reading sync stream', ex);
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (!signal?.aborted) {
|
|
140
|
-
// Close the downstream readable stream
|
|
141
|
-
await closeReader();
|
|
142
|
-
}
|
|
143
|
-
controller.close();
|
|
144
|
-
};
|
|
145
|
-
processStream();
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
// Create a new response out of the intercepted stream
|
|
149
|
-
return new Response(outputStream).body;
|
|
70
|
+
return result;
|
|
150
71
|
}
|
|
151
72
|
}
|
|
152
73
|
//# sourceMappingURL=ReactNativeRemote.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeRemote.js","sourceRoot":"","sources":["../../../src/sync/stream/ReactNativeRemote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"ReactNativeRemote.js","sourceRoot":"","sources":["../../../src/sync/stream/ReactNativeRemote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoD,MAAM,mBAAmB,CAAC;AACrG,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAOhD,MAAM,eAAe,GAAmB;IACtC;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,WAAW,IAAI,WAAW;KAC9C;CACF,CAAC;AAEF,MAAM,mBAAmB,GAAmB;IAC1C,GAAG,eAAe;IAClB;QACE,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,OAAO,CAAC,QAAQ,IAAI,WAAW;KACnD;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,WAAW;KAChD;CACF,CAAC;AAEF,MAAM,iBAAiB,GAAmB;IACxC,GAAG,eAAe;IAClB;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,WAAW,IAAI,WAAW;KAC9C;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,cAAc,IAAI,WAAW;KACjD;CACF,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,KAAqB,EAAE,EAAE;IAClD,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1E,IAAI,gBAAgB,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb;;;;EAIJ,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC1B,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IACnD,KAAK,CAAC,YAAY,CAAC,OAA0B;QAC3C,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;QACvC,OAAO,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAA0B;QACzC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;QAErC,MAAM,OAAO,GACX,QAAQ,CAAC,EAAE,IAAI,SAAS;YACtB,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,6CAA6C,IAAI,CAAC,IAAI,CACpD,yBAAyB,GAAG,IAAI,CACjC,gGAAgG,CAClG,CAAC;YACJ,CAAC,EAAE,yBAAyB,CAAC;YAC/B,CAAC,CAAC,IAAI,CAAC;QAEX,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC;YACpC,GAAG,OAAO;YACV,YAAY,EAAE;gBACZ,GAAG,OAAO,CAAC,YAAY;gBACvB;;;;mBAIG;gBACH,yFAAyF;gBACzF,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;aACrC;SACF,CAAC,CAAC;QAEH,IAAI,OAAO,EAAE,CAAC;YACZ,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -22,27 +22,21 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://docs.powersync.com/",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@journeyapps/react-native-quick-sqlite": "^1.1.
|
|
26
|
-
"base-64": "^1.0.0",
|
|
25
|
+
"@journeyapps/react-native-quick-sqlite": "^1.1.6",
|
|
27
26
|
"react": "*",
|
|
28
27
|
"react-native": "*",
|
|
29
|
-
"react-native-
|
|
30
|
-
"react-native-get-random-values": "^1.9.0",
|
|
31
|
-
"react-native-polyfill-globals": "^3.1.0",
|
|
32
|
-
"react-native-url-polyfill": "^2.0.0",
|
|
33
|
-
"text-encoding": "^0.7.0",
|
|
34
|
-
"web-streams-polyfill": "^3.2.1"
|
|
28
|
+
"react-native-polyfill-globals": "^3.1.0"
|
|
35
29
|
},
|
|
36
30
|
"dependencies": {
|
|
37
31
|
"async-lock": "^1.4.0",
|
|
38
|
-
"@powersync/react": "1.3.
|
|
39
|
-
"@powersync/common": "1.
|
|
32
|
+
"@powersync/react": "1.3.2",
|
|
33
|
+
"@powersync/common": "1.8.0"
|
|
40
34
|
},
|
|
41
35
|
"devDependencies": {
|
|
42
|
-
"@journeyapps/react-native-quick-sqlite": "^1.1.
|
|
36
|
+
"@journeyapps/react-native-quick-sqlite": "^1.1.6",
|
|
43
37
|
"@types/async-lock": "^1.4.0",
|
|
44
|
-
"react-native": "0.72.4",
|
|
45
38
|
"react": "18.2.0",
|
|
39
|
+
"react-native": "0.72.4",
|
|
46
40
|
"typescript": "^5.1.3"
|
|
47
41
|
},
|
|
48
42
|
"keywords": [
|