@electric-sql/client 0.7.3 → 0.8.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 +76 -1
- package/dist/cjs/index.cjs +230 -215
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +5 -4
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +19 -9
- package/dist/index.legacy-esm.js +226 -211
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +230 -215
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +71 -76
- package/src/error.ts +55 -0
- package/src/fetch.ts +1 -1
- package/src/parser.ts +2 -1
- package/src/shape.ts +4 -9
package/README.md
CHANGED
|
@@ -93,4 +93,79 @@ shape.subscribe(({ rows }) => {
|
|
|
93
93
|
}
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
### Error Handling
|
|
97
|
+
|
|
98
|
+
The ShapeStream provides two ways to handle errors:
|
|
99
|
+
|
|
100
|
+
1. Using the `onError` handler:
|
|
101
|
+
```typescript
|
|
102
|
+
const stream = new ShapeStream({
|
|
103
|
+
url: `${BASE_URL}/v1/shape`,
|
|
104
|
+
table: `foo`,
|
|
105
|
+
onError: (error) => {
|
|
106
|
+
// Handle all stream errors here
|
|
107
|
+
console.error('Stream error:', error)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If no `onError` handler is provided, the ShapeStream will throw errors that occur during streaming.
|
|
113
|
+
|
|
114
|
+
2. Individual subscribers can optionally handle errors specific to their subscription:
|
|
115
|
+
```typescript
|
|
116
|
+
stream.subscribe(
|
|
117
|
+
(messages) => {
|
|
118
|
+
// Handle messages
|
|
119
|
+
},
|
|
120
|
+
(error) => {
|
|
121
|
+
// Handle errors for this specific subscription
|
|
122
|
+
console.error('Subscription error:', error)
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Common error types include:
|
|
128
|
+
- `MissingShapeUrlError`: Missing required URL parameter
|
|
129
|
+
- `InvalidSignalError`: Invalid AbortSignal instance
|
|
130
|
+
- `ReservedParamError`: Using reserved parameter names
|
|
131
|
+
|
|
132
|
+
Runtime errors:
|
|
133
|
+
- `FetchError`: HTTP errors during shape fetching
|
|
134
|
+
- `FetchBackoffAbortError`: Fetch aborted using AbortSignal
|
|
135
|
+
- `MissingShapeHandleError`: Missing required shape handle
|
|
136
|
+
- `ParserNullValueError`: Parser encountered NULL value in a column that doesn't allow NULL values
|
|
137
|
+
|
|
138
|
+
See the [typescript client docs on the website](https://electric-sql.com/docs/api/clients/typescript#error-handling) for more details on error handling.
|
|
139
|
+
|
|
140
|
+
And in general, see the [docs website](https://electric-sql.com) and [examples folder](https://electric-sql.com/examples/basic) for more information.
|
|
141
|
+
|
|
142
|
+
## Develop
|
|
143
|
+
|
|
144
|
+
Install the pnpm workspace at the repo root:
|
|
145
|
+
|
|
146
|
+
```shell
|
|
147
|
+
pnpm install
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Build the package:
|
|
151
|
+
|
|
152
|
+
```shell
|
|
153
|
+
cd packages/typescript-client
|
|
154
|
+
pnpm build
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Test
|
|
158
|
+
|
|
159
|
+
In one terminal, start the backend running:
|
|
160
|
+
|
|
161
|
+
```shell
|
|
162
|
+
cd ../sync-service
|
|
163
|
+
mix deps.get
|
|
164
|
+
mix stop_dev && mix compile && mix start_dev && ies -S mix
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Then in this folder:
|
|
168
|
+
|
|
169
|
+
```shell
|
|
170
|
+
pnpm test
|
|
171
|
+
```
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -85,6 +85,91 @@ __export(src_exports, {
|
|
|
85
85
|
});
|
|
86
86
|
module.exports = __toCommonJS(src_exports);
|
|
87
87
|
|
|
88
|
+
// src/error.ts
|
|
89
|
+
var FetchError = class _FetchError extends Error {
|
|
90
|
+
constructor(status, text, json, headers, url, message) {
|
|
91
|
+
super(
|
|
92
|
+
message || `HTTP Error ${status} at ${url}: ${text != null ? text : JSON.stringify(json)}`
|
|
93
|
+
);
|
|
94
|
+
this.url = url;
|
|
95
|
+
this.name = `FetchError`;
|
|
96
|
+
this.status = status;
|
|
97
|
+
this.text = text;
|
|
98
|
+
this.json = json;
|
|
99
|
+
this.headers = headers;
|
|
100
|
+
}
|
|
101
|
+
static fromResponse(response, url) {
|
|
102
|
+
return __async(this, null, function* () {
|
|
103
|
+
const status = response.status;
|
|
104
|
+
const headers = Object.fromEntries([...response.headers.entries()]);
|
|
105
|
+
let text = void 0;
|
|
106
|
+
let json = void 0;
|
|
107
|
+
const contentType = response.headers.get(`content-type`);
|
|
108
|
+
if (contentType && contentType.includes(`application/json`)) {
|
|
109
|
+
json = yield response.json();
|
|
110
|
+
} else {
|
|
111
|
+
text = yield response.text();
|
|
112
|
+
}
|
|
113
|
+
return new _FetchError(status, text, json, headers, url);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var FetchBackoffAbortError = class extends Error {
|
|
118
|
+
constructor() {
|
|
119
|
+
super(`Fetch with backoff aborted`);
|
|
120
|
+
this.name = `FetchBackoffAbortError`;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var MissingShapeUrlError = class extends Error {
|
|
124
|
+
constructor() {
|
|
125
|
+
super(`Invalid shape options: missing required url parameter`);
|
|
126
|
+
this.name = `MissingShapeUrlError`;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var InvalidSignalError = class extends Error {
|
|
130
|
+
constructor() {
|
|
131
|
+
super(`Invalid signal option. It must be an instance of AbortSignal.`);
|
|
132
|
+
this.name = `InvalidSignalError`;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var MissingShapeHandleError = class extends Error {
|
|
136
|
+
constructor() {
|
|
137
|
+
super(
|
|
138
|
+
`shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)`
|
|
139
|
+
);
|
|
140
|
+
this.name = `MissingShapeHandleError`;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var ReservedParamError = class extends Error {
|
|
144
|
+
constructor(reservedParams) {
|
|
145
|
+
super(
|
|
146
|
+
`Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`
|
|
147
|
+
);
|
|
148
|
+
this.name = `ReservedParamError`;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var ParserNullValueError = class extends Error {
|
|
152
|
+
constructor(columnName) {
|
|
153
|
+
super(`Column "${columnName != null ? columnName : `unknown`}" does not allow NULL values`);
|
|
154
|
+
this.name = `ParserNullValueError`;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var MissingHeadersError = class extends Error {
|
|
158
|
+
constructor(url, missingHeaders) {
|
|
159
|
+
let msg = `The response for the shape request to ${url} didn't include the following required headers:
|
|
160
|
+
`;
|
|
161
|
+
missingHeaders.forEach((h) => {
|
|
162
|
+
msg += `- ${h}
|
|
163
|
+
`;
|
|
164
|
+
});
|
|
165
|
+
msg += `
|
|
166
|
+
This is often due to a proxy not setting CORS correctly so that all Electric headers can be read by the client.`;
|
|
167
|
+
msg += `
|
|
168
|
+
For more information visit the troubleshooting guide: /docs/guides/troubleshooting/missing-headers`;
|
|
169
|
+
super(msg);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
88
173
|
// src/parser.ts
|
|
89
174
|
var parseNumber = (value) => Number(value);
|
|
90
175
|
var parseBool = (value) => value === `true` || value === `t`;
|
|
@@ -186,7 +271,7 @@ function makeNullableParser(parser, columnInfo, columnName) {
|
|
|
186
271
|
return (value) => {
|
|
187
272
|
if (isPgNull(value)) {
|
|
188
273
|
if (!isNullable) {
|
|
189
|
-
throw new
|
|
274
|
+
throw new ParserNullValueError(columnName != null ? columnName : `unknown`);
|
|
190
275
|
}
|
|
191
276
|
return null;
|
|
192
277
|
}
|
|
@@ -208,54 +293,6 @@ function isUpToDateMessage(message) {
|
|
|
208
293
|
return isControlMessage(message) && message.headers.control === `up-to-date`;
|
|
209
294
|
}
|
|
210
295
|
|
|
211
|
-
// src/error.ts
|
|
212
|
-
var FetchError = class _FetchError extends Error {
|
|
213
|
-
constructor(status, text, json, headers, url, message) {
|
|
214
|
-
super(
|
|
215
|
-
message || `HTTP Error ${status} at ${url}: ${text != null ? text : JSON.stringify(json)}`
|
|
216
|
-
);
|
|
217
|
-
this.url = url;
|
|
218
|
-
this.name = `FetchError`;
|
|
219
|
-
this.status = status;
|
|
220
|
-
this.text = text;
|
|
221
|
-
this.json = json;
|
|
222
|
-
this.headers = headers;
|
|
223
|
-
}
|
|
224
|
-
static fromResponse(response, url) {
|
|
225
|
-
return __async(this, null, function* () {
|
|
226
|
-
const status = response.status;
|
|
227
|
-
const headers = Object.fromEntries([...response.headers.entries()]);
|
|
228
|
-
let text = void 0;
|
|
229
|
-
let json = void 0;
|
|
230
|
-
const contentType = response.headers.get(`content-type`);
|
|
231
|
-
if (contentType && contentType.includes(`application/json`)) {
|
|
232
|
-
json = yield response.json();
|
|
233
|
-
} else {
|
|
234
|
-
text = yield response.text();
|
|
235
|
-
}
|
|
236
|
-
return new _FetchError(status, text, json, headers, url);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
var FetchBackoffAbortError = class extends Error {
|
|
241
|
-
constructor() {
|
|
242
|
-
super(`Fetch with backoff aborted`);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
var MissingHeadersError = class extends Error {
|
|
246
|
-
constructor(url, missingHeaders) {
|
|
247
|
-
let msg = `The response for the shape request to ${url} didn't include the following required headers:
|
|
248
|
-
`;
|
|
249
|
-
missingHeaders.forEach((h) => {
|
|
250
|
-
msg += `- ${h}
|
|
251
|
-
`;
|
|
252
|
-
});
|
|
253
|
-
msg += `
|
|
254
|
-
This is often due to a proxy not setting CORS correctly so that all Electric headers can be read by the client.`;
|
|
255
|
-
super(msg);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
|
|
259
296
|
// src/constants.ts
|
|
260
297
|
var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
|
|
261
298
|
var SHAPE_HANDLE_HEADER = `electric-handle`;
|
|
@@ -355,7 +392,7 @@ function createFetchWithResponseHeadersCheck(fetchClient) {
|
|
|
355
392
|
if (response.ok) {
|
|
356
393
|
const headers = response.headers;
|
|
357
394
|
const missingHeaders = [];
|
|
358
|
-
const addMissingHeaders = (requiredHeaders) => requiredHeaders.filter((h) => !headers.has(h));
|
|
395
|
+
const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
|
|
359
396
|
addMissingHeaders(requiredElectricResponseHeaders);
|
|
360
397
|
const input = args[0];
|
|
361
398
|
const urlString = input.toString();
|
|
@@ -472,14 +509,14 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
472
509
|
WHERE_QUERY_PARAM,
|
|
473
510
|
REPLICA_PARAM
|
|
474
511
|
]);
|
|
475
|
-
var _fetchClient2, _messageParser, _subscribers,
|
|
512
|
+
var _error, _fetchClient2, _messageParser, _subscribers, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeHandle, _databaseId, _schema, _onError, _replica, _ShapeStream_instances, start_fn, publish_fn, sendErrorToSubscribers_fn, reset_fn;
|
|
476
513
|
var _ShapeStream = class _ShapeStream {
|
|
477
514
|
constructor(options) {
|
|
478
515
|
__privateAdd(this, _ShapeStream_instances);
|
|
516
|
+
__privateAdd(this, _error, null);
|
|
479
517
|
__privateAdd(this, _fetchClient2);
|
|
480
518
|
__privateAdd(this, _messageParser);
|
|
481
519
|
__privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
|
|
482
|
-
__privateAdd(this, _upToDateSubscribers, /* @__PURE__ */ new Map());
|
|
483
520
|
__privateAdd(this, _lastOffset);
|
|
484
521
|
__privateAdd(this, _liveCacheBuster);
|
|
485
522
|
// Seconds since our Electric Epoch 😎
|
|
@@ -490,17 +527,18 @@ var _ShapeStream = class _ShapeStream {
|
|
|
490
527
|
__privateAdd(this, _shapeHandle);
|
|
491
528
|
__privateAdd(this, _databaseId);
|
|
492
529
|
__privateAdd(this, _schema);
|
|
493
|
-
__privateAdd(this,
|
|
530
|
+
__privateAdd(this, _onError);
|
|
494
531
|
__privateAdd(this, _replica);
|
|
495
532
|
var _a, _b, _c;
|
|
496
|
-
validateOptions(options);
|
|
497
533
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
534
|
+
validateOptions(this.options);
|
|
498
535
|
__privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
|
|
499
536
|
__privateSet(this, _liveCacheBuster, ``);
|
|
500
|
-
__privateSet(this, _shapeHandle, this.options.
|
|
537
|
+
__privateSet(this, _shapeHandle, this.options.handle);
|
|
501
538
|
__privateSet(this, _databaseId, this.options.databaseId);
|
|
502
539
|
__privateSet(this, _messageParser, new MessageParser(options.parser));
|
|
503
540
|
__privateSet(this, _replica, this.options.replica);
|
|
541
|
+
__privateSet(this, _onError, this.options.onError);
|
|
504
542
|
const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
|
|
505
543
|
const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
|
|
506
544
|
onFailedAttempt: () => {
|
|
@@ -512,131 +550,22 @@ var _ShapeStream = class _ShapeStream {
|
|
|
512
550
|
__privateSet(this, _fetchClient2, createFetchWithResponseHeadersCheck(
|
|
513
551
|
createFetchWithChunkBuffer(fetchWithBackoffClient)
|
|
514
552
|
));
|
|
515
|
-
this.
|
|
553
|
+
__privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
516
554
|
}
|
|
517
555
|
get shapeHandle() {
|
|
518
556
|
return __privateGet(this, _shapeHandle);
|
|
519
557
|
}
|
|
558
|
+
get error() {
|
|
559
|
+
return __privateGet(this, _error);
|
|
560
|
+
}
|
|
520
561
|
get isUpToDate() {
|
|
521
562
|
return __privateGet(this, _isUpToDate);
|
|
522
563
|
}
|
|
523
564
|
get lastOffset() {
|
|
524
565
|
return __privateGet(this, _lastOffset);
|
|
525
566
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
start() {
|
|
530
|
-
return __async(this, null, function* () {
|
|
531
|
-
var _a, _b;
|
|
532
|
-
__privateSet(this, _isUpToDate, false);
|
|
533
|
-
const { url, table, where, columns, signal } = this.options;
|
|
534
|
-
try {
|
|
535
|
-
while (!(signal == null ? void 0 : signal.aborted) && !__privateGet(this, _isUpToDate) || this.options.subscribe) {
|
|
536
|
-
const fetchUrl = new URL(url);
|
|
537
|
-
if (this.options.params) {
|
|
538
|
-
const reservedParams = Object.keys(this.options.params).filter(
|
|
539
|
-
(key) => RESERVED_PARAMS.has(key)
|
|
540
|
-
);
|
|
541
|
-
if (reservedParams.length > 0) {
|
|
542
|
-
throw new Error(
|
|
543
|
-
`Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
for (const [key, value] of Object.entries(this.options.params)) {
|
|
547
|
-
fetchUrl.searchParams.set(key, value);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
if (table) fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table);
|
|
551
|
-
if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where);
|
|
552
|
-
if (columns && columns.length > 0)
|
|
553
|
-
fetchUrl.searchParams.set(COLUMNS_QUERY_PARAM, columns.join(`,`));
|
|
554
|
-
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
|
|
555
|
-
if (__privateGet(this, _isUpToDate)) {
|
|
556
|
-
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
|
|
557
|
-
fetchUrl.searchParams.set(
|
|
558
|
-
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
559
|
-
__privateGet(this, _liveCacheBuster)
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
if (__privateGet(this, _shapeHandle)) {
|
|
563
|
-
fetchUrl.searchParams.set(
|
|
564
|
-
SHAPE_HANDLE_QUERY_PARAM,
|
|
565
|
-
__privateGet(this, _shapeHandle)
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
if (__privateGet(this, _databaseId)) {
|
|
569
|
-
fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, __privateGet(this, _databaseId));
|
|
570
|
-
}
|
|
571
|
-
if (((_a = __privateGet(this, _replica)) != null ? _a : _ShapeStream.Replica.DEFAULT) != _ShapeStream.Replica.DEFAULT) {
|
|
572
|
-
fetchUrl.searchParams.set(REPLICA_PARAM, __privateGet(this, _replica));
|
|
573
|
-
}
|
|
574
|
-
let response;
|
|
575
|
-
try {
|
|
576
|
-
response = yield __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
|
|
577
|
-
signal,
|
|
578
|
-
headers: this.options.headers
|
|
579
|
-
});
|
|
580
|
-
__privateSet(this, _connected, true);
|
|
581
|
-
} catch (e) {
|
|
582
|
-
if (e instanceof FetchBackoffAbortError) break;
|
|
583
|
-
if (e instanceof MissingHeadersError) throw e;
|
|
584
|
-
if (!(e instanceof FetchError)) throw e;
|
|
585
|
-
if (e.status == 409) {
|
|
586
|
-
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
587
|
-
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
|
|
588
|
-
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
|
|
589
|
-
continue;
|
|
590
|
-
} else if (e.status >= 400 && e.status < 500) {
|
|
591
|
-
__privateMethod(this, _ShapeStream_instances, sendErrorToUpToDateSubscribers_fn).call(this, e);
|
|
592
|
-
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
|
|
593
|
-
throw e;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
const { headers, status } = response;
|
|
597
|
-
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
598
|
-
if (shapeHandle) {
|
|
599
|
-
__privateSet(this, _shapeHandle, shapeHandle);
|
|
600
|
-
}
|
|
601
|
-
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
|
|
602
|
-
if (lastOffset) {
|
|
603
|
-
__privateSet(this, _lastOffset, lastOffset);
|
|
604
|
-
}
|
|
605
|
-
const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
|
|
606
|
-
if (liveCacheBuster) {
|
|
607
|
-
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
608
|
-
}
|
|
609
|
-
const getSchema = () => {
|
|
610
|
-
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
611
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {};
|
|
612
|
-
};
|
|
613
|
-
__privateSet(this, _schema, (_b = __privateGet(this, _schema)) != null ? _b : getSchema());
|
|
614
|
-
const messages = status === 204 ? `[]` : yield response.text();
|
|
615
|
-
if (status === 204) {
|
|
616
|
-
__privateSet(this, _lastSyncedAt, Date.now());
|
|
617
|
-
}
|
|
618
|
-
const batch = __privateGet(this, _messageParser).parse(messages, __privateGet(this, _schema));
|
|
619
|
-
if (batch.length > 0) {
|
|
620
|
-
const prevUpToDate = __privateGet(this, _isUpToDate);
|
|
621
|
-
const lastMessage = batch[batch.length - 1];
|
|
622
|
-
if (isUpToDateMessage(lastMessage)) {
|
|
623
|
-
__privateSet(this, _lastSyncedAt, Date.now());
|
|
624
|
-
__privateSet(this, _isUpToDate, true);
|
|
625
|
-
}
|
|
626
|
-
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
|
|
627
|
-
if (!prevUpToDate && __privateGet(this, _isUpToDate)) {
|
|
628
|
-
__privateMethod(this, _ShapeStream_instances, notifyUpToDateSubscribers_fn).call(this);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
} catch (err) {
|
|
633
|
-
__privateSet(this, _error, err);
|
|
634
|
-
} finally {
|
|
635
|
-
__privateSet(this, _connected, false);
|
|
636
|
-
}
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
subscribe(callback, onError) {
|
|
567
|
+
subscribe(callback, onError = () => {
|
|
568
|
+
}) {
|
|
640
569
|
const subscriptionId = Math.random();
|
|
641
570
|
__privateGet(this, _subscribers).set(subscriptionId, [callback, onError]);
|
|
642
571
|
return () => {
|
|
@@ -646,16 +575,6 @@ var _ShapeStream = class _ShapeStream {
|
|
|
646
575
|
unsubscribeAll() {
|
|
647
576
|
__privateGet(this, _subscribers).clear();
|
|
648
577
|
}
|
|
649
|
-
subscribeOnceToUpToDate(callback, error) {
|
|
650
|
-
const subscriptionId = Math.random();
|
|
651
|
-
__privateGet(this, _upToDateSubscribers).set(subscriptionId, [callback, error]);
|
|
652
|
-
return () => {
|
|
653
|
-
__privateGet(this, _upToDateSubscribers).delete(subscriptionId);
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
unsubscribeAllUpToDateSubscribers() {
|
|
657
|
-
__privateGet(this, _upToDateSubscribers).clear();
|
|
658
|
-
}
|
|
659
578
|
/** Unix time at which we last synced. Undefined when `isLoading` is true. */
|
|
660
579
|
lastSyncedAt() {
|
|
661
580
|
return __privateGet(this, _lastSyncedAt);
|
|
@@ -671,13 +590,13 @@ var _ShapeStream = class _ShapeStream {
|
|
|
671
590
|
}
|
|
672
591
|
/** True during initial fetch. False afterwise. */
|
|
673
592
|
isLoading() {
|
|
674
|
-
return !this
|
|
593
|
+
return !__privateGet(this, _isUpToDate);
|
|
675
594
|
}
|
|
676
595
|
};
|
|
596
|
+
_error = new WeakMap();
|
|
677
597
|
_fetchClient2 = new WeakMap();
|
|
678
598
|
_messageParser = new WeakMap();
|
|
679
599
|
_subscribers = new WeakMap();
|
|
680
|
-
_upToDateSubscribers = new WeakMap();
|
|
681
600
|
_lastOffset = new WeakMap();
|
|
682
601
|
_liveCacheBuster = new WeakMap();
|
|
683
602
|
_lastSyncedAt = new WeakMap();
|
|
@@ -686,9 +605,127 @@ _connected = new WeakMap();
|
|
|
686
605
|
_shapeHandle = new WeakMap();
|
|
687
606
|
_databaseId = new WeakMap();
|
|
688
607
|
_schema = new WeakMap();
|
|
689
|
-
|
|
608
|
+
_onError = new WeakMap();
|
|
690
609
|
_replica = new WeakMap();
|
|
691
610
|
_ShapeStream_instances = new WeakSet();
|
|
611
|
+
start_fn = function() {
|
|
612
|
+
return __async(this, null, function* () {
|
|
613
|
+
var _a, _b, _c;
|
|
614
|
+
try {
|
|
615
|
+
while (!((_a = this.options.signal) == null ? void 0 : _a.aborted) && !__privateGet(this, _isUpToDate) || this.options.subscribe) {
|
|
616
|
+
const { url, table, where, columns, signal } = this.options;
|
|
617
|
+
const fetchUrl = new URL(url);
|
|
618
|
+
if (this.options.params) {
|
|
619
|
+
const reservedParams = Object.keys(this.options.params).filter(
|
|
620
|
+
(key) => RESERVED_PARAMS.has(key)
|
|
621
|
+
);
|
|
622
|
+
if (reservedParams.length > 0) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
for (const [key, value] of Object.entries(this.options.params)) {
|
|
628
|
+
fetchUrl.searchParams.set(key, value);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (table) fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table);
|
|
632
|
+
if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where);
|
|
633
|
+
if (columns && columns.length > 0)
|
|
634
|
+
fetchUrl.searchParams.set(COLUMNS_QUERY_PARAM, columns.join(`,`));
|
|
635
|
+
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
|
|
636
|
+
if (__privateGet(this, _isUpToDate)) {
|
|
637
|
+
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
|
|
638
|
+
fetchUrl.searchParams.set(
|
|
639
|
+
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
640
|
+
__privateGet(this, _liveCacheBuster)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (__privateGet(this, _shapeHandle)) {
|
|
644
|
+
fetchUrl.searchParams.set(
|
|
645
|
+
SHAPE_HANDLE_QUERY_PARAM,
|
|
646
|
+
__privateGet(this, _shapeHandle)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (__privateGet(this, _databaseId)) {
|
|
650
|
+
fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, __privateGet(this, _databaseId));
|
|
651
|
+
}
|
|
652
|
+
if (((_b = __privateGet(this, _replica)) != null ? _b : _ShapeStream.Replica.DEFAULT) != _ShapeStream.Replica.DEFAULT) {
|
|
653
|
+
fetchUrl.searchParams.set(REPLICA_PARAM, __privateGet(this, _replica));
|
|
654
|
+
}
|
|
655
|
+
let response;
|
|
656
|
+
try {
|
|
657
|
+
response = yield __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
|
|
658
|
+
signal,
|
|
659
|
+
headers: this.options.headers
|
|
660
|
+
});
|
|
661
|
+
__privateSet(this, _connected, true);
|
|
662
|
+
} catch (e) {
|
|
663
|
+
if (e instanceof FetchBackoffAbortError) break;
|
|
664
|
+
if (!(e instanceof FetchError)) throw e;
|
|
665
|
+
if (e.status == 409) {
|
|
666
|
+
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
667
|
+
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
|
|
668
|
+
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
|
|
669
|
+
continue;
|
|
670
|
+
} else if (e.status >= 400 && e.status < 500) {
|
|
671
|
+
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
|
|
672
|
+
throw e;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const { headers, status } = response;
|
|
676
|
+
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
677
|
+
if (shapeHandle) {
|
|
678
|
+
__privateSet(this, _shapeHandle, shapeHandle);
|
|
679
|
+
}
|
|
680
|
+
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
|
|
681
|
+
if (lastOffset) {
|
|
682
|
+
__privateSet(this, _lastOffset, lastOffset);
|
|
683
|
+
}
|
|
684
|
+
const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
|
|
685
|
+
if (liveCacheBuster) {
|
|
686
|
+
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
687
|
+
}
|
|
688
|
+
const getSchema = () => {
|
|
689
|
+
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
690
|
+
return schemaHeader ? JSON.parse(schemaHeader) : {};
|
|
691
|
+
};
|
|
692
|
+
__privateSet(this, _schema, (_c = __privateGet(this, _schema)) != null ? _c : getSchema());
|
|
693
|
+
const messages = status === 204 ? `[]` : yield response.text();
|
|
694
|
+
if (status === 204) {
|
|
695
|
+
__privateSet(this, _lastSyncedAt, Date.now());
|
|
696
|
+
}
|
|
697
|
+
const batch = __privateGet(this, _messageParser).parse(messages, __privateGet(this, _schema));
|
|
698
|
+
if (batch.length > 0) {
|
|
699
|
+
const lastMessage = batch[batch.length - 1];
|
|
700
|
+
if (isUpToDateMessage(lastMessage)) {
|
|
701
|
+
__privateSet(this, _lastSyncedAt, Date.now());
|
|
702
|
+
__privateSet(this, _isUpToDate, true);
|
|
703
|
+
}
|
|
704
|
+
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
} catch (err) {
|
|
708
|
+
__privateSet(this, _error, err);
|
|
709
|
+
if (__privateGet(this, _onError)) {
|
|
710
|
+
const retryOpts = yield __privateGet(this, _onError).call(this, err);
|
|
711
|
+
if (typeof retryOpts === `object`) {
|
|
712
|
+
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this);
|
|
713
|
+
if (`params` in retryOpts) {
|
|
714
|
+
this.options.params = retryOpts.params;
|
|
715
|
+
}
|
|
716
|
+
if (`headers` in retryOpts) {
|
|
717
|
+
this.options.headers = retryOpts.headers;
|
|
718
|
+
}
|
|
719
|
+
__privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
throw err;
|
|
724
|
+
} finally {
|
|
725
|
+
__privateSet(this, _connected, false);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
};
|
|
692
729
|
publish_fn = function(messages) {
|
|
693
730
|
return __async(this, null, function* () {
|
|
694
731
|
yield Promise.all(
|
|
@@ -709,24 +746,14 @@ sendErrorToSubscribers_fn = function(error) {
|
|
|
709
746
|
errorFn == null ? void 0 : errorFn(error);
|
|
710
747
|
});
|
|
711
748
|
};
|
|
712
|
-
notifyUpToDateSubscribers_fn = function() {
|
|
713
|
-
__privateGet(this, _upToDateSubscribers).forEach(([callback]) => {
|
|
714
|
-
callback();
|
|
715
|
-
});
|
|
716
|
-
};
|
|
717
|
-
sendErrorToUpToDateSubscribers_fn = function(error) {
|
|
718
|
-
__privateGet(this, _upToDateSubscribers).forEach(
|
|
719
|
-
([_, errorCallback]) => errorCallback(error)
|
|
720
|
-
);
|
|
721
|
-
};
|
|
722
749
|
/**
|
|
723
750
|
* Resets the state of the stream, optionally with a provided
|
|
724
751
|
* shape handle
|
|
725
752
|
*/
|
|
726
|
-
reset_fn = function(
|
|
753
|
+
reset_fn = function(handle) {
|
|
727
754
|
__privateSet(this, _lastOffset, `-1`);
|
|
728
755
|
__privateSet(this, _liveCacheBuster, ``);
|
|
729
|
-
__privateSet(this, _shapeHandle,
|
|
756
|
+
__privateSet(this, _shapeHandle, handle);
|
|
730
757
|
__privateSet(this, _isUpToDate, false);
|
|
731
758
|
__privateSet(this, _connected, false);
|
|
732
759
|
__privateSet(this, _schema, void 0);
|
|
@@ -738,26 +765,20 @@ _ShapeStream.Replica = {
|
|
|
738
765
|
var ShapeStream = _ShapeStream;
|
|
739
766
|
function validateOptions(options) {
|
|
740
767
|
if (!options.url) {
|
|
741
|
-
throw new
|
|
768
|
+
throw new MissingShapeUrlError();
|
|
742
769
|
}
|
|
743
770
|
if (options.signal && !(options.signal instanceof AbortSignal)) {
|
|
744
|
-
throw new
|
|
745
|
-
`Invalid signal option. It must be an instance of AbortSignal.`
|
|
746
|
-
);
|
|
771
|
+
throw new InvalidSignalError();
|
|
747
772
|
}
|
|
748
|
-
if (options.offset !== void 0 && options.offset !== `-1` && !options.
|
|
749
|
-
throw new
|
|
750
|
-
`shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)`
|
|
751
|
-
);
|
|
773
|
+
if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
|
|
774
|
+
throw new MissingShapeHandleError();
|
|
752
775
|
}
|
|
753
776
|
if (options.params) {
|
|
754
777
|
const reservedParams = Object.keys(options.params).filter(
|
|
755
778
|
(key) => RESERVED_PARAMS.has(key)
|
|
756
779
|
);
|
|
757
780
|
if (reservedParams.length > 0) {
|
|
758
|
-
throw new
|
|
759
|
-
`Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`
|
|
760
|
-
);
|
|
781
|
+
throw new ReservedParamError(reservedParams);
|
|
761
782
|
}
|
|
762
783
|
}
|
|
763
784
|
return;
|
|
@@ -778,15 +799,6 @@ var Shape = class {
|
|
|
778
799
|
__privateMethod(this, _Shape_instances, process_fn).bind(this),
|
|
779
800
|
__privateMethod(this, _Shape_instances, handleError_fn).bind(this)
|
|
780
801
|
);
|
|
781
|
-
const unsubscribe = __privateGet(this, _stream).subscribeOnceToUpToDate(
|
|
782
|
-
() => {
|
|
783
|
-
unsubscribe();
|
|
784
|
-
},
|
|
785
|
-
(e) => {
|
|
786
|
-
__privateMethod(this, _Shape_instances, handleError_fn).call(this, e);
|
|
787
|
-
throw e;
|
|
788
|
-
}
|
|
789
|
-
);
|
|
790
802
|
}
|
|
791
803
|
get isUpToDate() {
|
|
792
804
|
return __privateGet(this, _stream).isUpToDate;
|
|
@@ -794,6 +806,9 @@ var Shape = class {
|
|
|
794
806
|
get lastOffset() {
|
|
795
807
|
return __privateGet(this, _stream).lastOffset;
|
|
796
808
|
}
|
|
809
|
+
get handle() {
|
|
810
|
+
return __privateGet(this, _stream).shapeHandle;
|
|
811
|
+
}
|
|
797
812
|
get rows() {
|
|
798
813
|
return this.value.then((v) => Array.from(v.values()));
|
|
799
814
|
}
|