@electric-sql/client 0.7.2 → 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 +273 -196
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +5 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +37 -10
- package/dist/index.legacy-esm.js +269 -192
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +273 -196
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +106 -71
- package/src/error.ts +66 -0
- package/src/fetch.ts +52 -1
- package/src/parser.ts +2 -1
- package/src/shape.ts +9 -10
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,41 +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
|
-
|
|
246
296
|
// src/constants.ts
|
|
247
297
|
var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
|
|
248
298
|
var SHAPE_HANDLE_HEADER = `electric-handle`;
|
|
@@ -330,6 +380,36 @@ function createFetchWithChunkBuffer(fetchClient, prefetchOptions = ChunkPrefetch
|
|
|
330
380
|
});
|
|
331
381
|
return prefetchClient;
|
|
332
382
|
}
|
|
383
|
+
var requiredElectricResponseHeaders = [
|
|
384
|
+
`electric-offset`,
|
|
385
|
+
`electric-handle`
|
|
386
|
+
];
|
|
387
|
+
var requiredLiveResponseHeaders = [`electric-cursor`];
|
|
388
|
+
var requiredNonLiveResponseHeaders = [`electric-schema`];
|
|
389
|
+
function createFetchWithResponseHeadersCheck(fetchClient) {
|
|
390
|
+
return (...args) => __async(this, null, function* () {
|
|
391
|
+
const response = yield fetchClient(...args);
|
|
392
|
+
if (response.ok) {
|
|
393
|
+
const headers = response.headers;
|
|
394
|
+
const missingHeaders = [];
|
|
395
|
+
const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
|
|
396
|
+
addMissingHeaders(requiredElectricResponseHeaders);
|
|
397
|
+
const input = args[0];
|
|
398
|
+
const urlString = input.toString();
|
|
399
|
+
const url = new URL(urlString);
|
|
400
|
+
if (url.searchParams.has(LIVE_QUERY_PARAM, `true`)) {
|
|
401
|
+
addMissingHeaders(requiredLiveResponseHeaders);
|
|
402
|
+
}
|
|
403
|
+
if (!url.searchParams.has(LIVE_QUERY_PARAM) || url.searchParams.has(LIVE_QUERY_PARAM, `false`)) {
|
|
404
|
+
addMissingHeaders(requiredNonLiveResponseHeaders);
|
|
405
|
+
}
|
|
406
|
+
if (missingHeaders.length > 0) {
|
|
407
|
+
throw new MissingHeadersError(urlString, missingHeaders);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return response;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
333
413
|
var _fetchClient, _maxPrefetchedRequests, _prefetchQueue, _queueHeadUrl, _queueTailUrl, _PrefetchQueue_instances, prefetch_fn;
|
|
334
414
|
var PrefetchQueue = class {
|
|
335
415
|
constructor(options) {
|
|
@@ -429,14 +509,14 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
429
509
|
WHERE_QUERY_PARAM,
|
|
430
510
|
REPLICA_PARAM
|
|
431
511
|
]);
|
|
432
|
-
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;
|
|
433
513
|
var _ShapeStream = class _ShapeStream {
|
|
434
514
|
constructor(options) {
|
|
435
515
|
__privateAdd(this, _ShapeStream_instances);
|
|
516
|
+
__privateAdd(this, _error, null);
|
|
436
517
|
__privateAdd(this, _fetchClient2);
|
|
437
518
|
__privateAdd(this, _messageParser);
|
|
438
519
|
__privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
|
|
439
|
-
__privateAdd(this, _upToDateSubscribers, /* @__PURE__ */ new Map());
|
|
440
520
|
__privateAdd(this, _lastOffset);
|
|
441
521
|
__privateAdd(this, _liveCacheBuster);
|
|
442
522
|
// Seconds since our Electric Epoch 😎
|
|
@@ -447,17 +527,18 @@ var _ShapeStream = class _ShapeStream {
|
|
|
447
527
|
__privateAdd(this, _shapeHandle);
|
|
448
528
|
__privateAdd(this, _databaseId);
|
|
449
529
|
__privateAdd(this, _schema);
|
|
450
|
-
__privateAdd(this,
|
|
530
|
+
__privateAdd(this, _onError);
|
|
451
531
|
__privateAdd(this, _replica);
|
|
452
532
|
var _a, _b, _c;
|
|
453
|
-
validateOptions(options);
|
|
454
533
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
534
|
+
validateOptions(this.options);
|
|
455
535
|
__privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
|
|
456
536
|
__privateSet(this, _liveCacheBuster, ``);
|
|
457
|
-
__privateSet(this, _shapeHandle, this.options.
|
|
537
|
+
__privateSet(this, _shapeHandle, this.options.handle);
|
|
458
538
|
__privateSet(this, _databaseId, this.options.databaseId);
|
|
459
539
|
__privateSet(this, _messageParser, new MessageParser(options.parser));
|
|
460
540
|
__privateSet(this, _replica, this.options.replica);
|
|
541
|
+
__privateSet(this, _onError, this.options.onError);
|
|
461
542
|
const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
|
|
462
543
|
const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
|
|
463
544
|
onFailedAttempt: () => {
|
|
@@ -466,128 +547,25 @@ var _ShapeStream = class _ShapeStream {
|
|
|
466
547
|
(_b2 = (_a2 = options.backoffOptions) == null ? void 0 : _a2.onFailedAttempt) == null ? void 0 : _b2.call(_a2);
|
|
467
548
|
}
|
|
468
549
|
}));
|
|
469
|
-
__privateSet(this, _fetchClient2,
|
|
470
|
-
|
|
550
|
+
__privateSet(this, _fetchClient2, createFetchWithResponseHeadersCheck(
|
|
551
|
+
createFetchWithChunkBuffer(fetchWithBackoffClient)
|
|
552
|
+
));
|
|
553
|
+
__privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
471
554
|
}
|
|
472
555
|
get shapeHandle() {
|
|
473
556
|
return __privateGet(this, _shapeHandle);
|
|
474
557
|
}
|
|
475
|
-
get isUpToDate() {
|
|
476
|
-
return __privateGet(this, _isUpToDate);
|
|
477
|
-
}
|
|
478
558
|
get error() {
|
|
479
559
|
return __privateGet(this, _error);
|
|
480
560
|
}
|
|
481
|
-
|
|
482
|
-
return
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
try {
|
|
487
|
-
while (!(signal == null ? void 0 : signal.aborted) && !__privateGet(this, _isUpToDate) || this.options.subscribe) {
|
|
488
|
-
const fetchUrl = new URL(url);
|
|
489
|
-
if (this.options.params) {
|
|
490
|
-
const reservedParams = Object.keys(this.options.params).filter(
|
|
491
|
-
(key) => RESERVED_PARAMS.has(key)
|
|
492
|
-
);
|
|
493
|
-
if (reservedParams.length > 0) {
|
|
494
|
-
throw new Error(
|
|
495
|
-
`Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
for (const [key, value] of Object.entries(this.options.params)) {
|
|
499
|
-
fetchUrl.searchParams.set(key, value);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (table) fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table);
|
|
503
|
-
if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where);
|
|
504
|
-
if (columns && columns.length > 0)
|
|
505
|
-
fetchUrl.searchParams.set(COLUMNS_QUERY_PARAM, columns.join(`,`));
|
|
506
|
-
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
|
|
507
|
-
if (__privateGet(this, _isUpToDate)) {
|
|
508
|
-
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
|
|
509
|
-
fetchUrl.searchParams.set(
|
|
510
|
-
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
511
|
-
__privateGet(this, _liveCacheBuster)
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
if (__privateGet(this, _shapeHandle)) {
|
|
515
|
-
fetchUrl.searchParams.set(
|
|
516
|
-
SHAPE_HANDLE_QUERY_PARAM,
|
|
517
|
-
__privateGet(this, _shapeHandle)
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
if (__privateGet(this, _databaseId)) {
|
|
521
|
-
fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, __privateGet(this, _databaseId));
|
|
522
|
-
}
|
|
523
|
-
if (((_a = __privateGet(this, _replica)) != null ? _a : _ShapeStream.Replica.DEFAULT) != _ShapeStream.Replica.DEFAULT) {
|
|
524
|
-
fetchUrl.searchParams.set(REPLICA_PARAM, __privateGet(this, _replica));
|
|
525
|
-
}
|
|
526
|
-
let response;
|
|
527
|
-
try {
|
|
528
|
-
response = yield __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
|
|
529
|
-
signal,
|
|
530
|
-
headers: this.options.headers
|
|
531
|
-
});
|
|
532
|
-
__privateSet(this, _connected, true);
|
|
533
|
-
} catch (e) {
|
|
534
|
-
if (e instanceof FetchBackoffAbortError) break;
|
|
535
|
-
if (!(e instanceof FetchError)) throw e;
|
|
536
|
-
if (e.status == 409) {
|
|
537
|
-
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
538
|
-
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
|
|
539
|
-
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
|
|
540
|
-
continue;
|
|
541
|
-
} else if (e.status >= 400 && e.status < 500) {
|
|
542
|
-
__privateMethod(this, _ShapeStream_instances, sendErrorToUpToDateSubscribers_fn).call(this, e);
|
|
543
|
-
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
|
|
544
|
-
throw e;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
const { headers, status } = response;
|
|
548
|
-
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
549
|
-
if (shapeHandle) {
|
|
550
|
-
__privateSet(this, _shapeHandle, shapeHandle);
|
|
551
|
-
}
|
|
552
|
-
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
|
|
553
|
-
if (lastOffset) {
|
|
554
|
-
__privateSet(this, _lastOffset, lastOffset);
|
|
555
|
-
}
|
|
556
|
-
const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
|
|
557
|
-
if (liveCacheBuster) {
|
|
558
|
-
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
559
|
-
}
|
|
560
|
-
const getSchema = () => {
|
|
561
|
-
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
|
|
562
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {};
|
|
563
|
-
};
|
|
564
|
-
__privateSet(this, _schema, (_b = __privateGet(this, _schema)) != null ? _b : getSchema());
|
|
565
|
-
const messages = status === 204 ? `[]` : yield response.text();
|
|
566
|
-
if (status === 204) {
|
|
567
|
-
__privateSet(this, _lastSyncedAt, Date.now());
|
|
568
|
-
}
|
|
569
|
-
const batch = __privateGet(this, _messageParser).parse(messages, __privateGet(this, _schema));
|
|
570
|
-
if (batch.length > 0) {
|
|
571
|
-
const prevUpToDate = __privateGet(this, _isUpToDate);
|
|
572
|
-
const lastMessage = batch[batch.length - 1];
|
|
573
|
-
if (isUpToDateMessage(lastMessage)) {
|
|
574
|
-
__privateSet(this, _lastSyncedAt, Date.now());
|
|
575
|
-
__privateSet(this, _isUpToDate, true);
|
|
576
|
-
}
|
|
577
|
-
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
|
|
578
|
-
if (!prevUpToDate && __privateGet(this, _isUpToDate)) {
|
|
579
|
-
__privateMethod(this, _ShapeStream_instances, notifyUpToDateSubscribers_fn).call(this);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
} catch (err) {
|
|
584
|
-
__privateSet(this, _error, err);
|
|
585
|
-
} finally {
|
|
586
|
-
__privateSet(this, _connected, false);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
561
|
+
get isUpToDate() {
|
|
562
|
+
return __privateGet(this, _isUpToDate);
|
|
563
|
+
}
|
|
564
|
+
get lastOffset() {
|
|
565
|
+
return __privateGet(this, _lastOffset);
|
|
589
566
|
}
|
|
590
|
-
subscribe(callback, onError) {
|
|
567
|
+
subscribe(callback, onError = () => {
|
|
568
|
+
}) {
|
|
591
569
|
const subscriptionId = Math.random();
|
|
592
570
|
__privateGet(this, _subscribers).set(subscriptionId, [callback, onError]);
|
|
593
571
|
return () => {
|
|
@@ -597,16 +575,6 @@ var _ShapeStream = class _ShapeStream {
|
|
|
597
575
|
unsubscribeAll() {
|
|
598
576
|
__privateGet(this, _subscribers).clear();
|
|
599
577
|
}
|
|
600
|
-
subscribeOnceToUpToDate(callback, error) {
|
|
601
|
-
const subscriptionId = Math.random();
|
|
602
|
-
__privateGet(this, _upToDateSubscribers).set(subscriptionId, [callback, error]);
|
|
603
|
-
return () => {
|
|
604
|
-
__privateGet(this, _upToDateSubscribers).delete(subscriptionId);
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
unsubscribeAllUpToDateSubscribers() {
|
|
608
|
-
__privateGet(this, _upToDateSubscribers).clear();
|
|
609
|
-
}
|
|
610
578
|
/** Unix time at which we last synced. Undefined when `isLoading` is true. */
|
|
611
579
|
lastSyncedAt() {
|
|
612
580
|
return __privateGet(this, _lastSyncedAt);
|
|
@@ -622,13 +590,13 @@ var _ShapeStream = class _ShapeStream {
|
|
|
622
590
|
}
|
|
623
591
|
/** True during initial fetch. False afterwise. */
|
|
624
592
|
isLoading() {
|
|
625
|
-
return !this
|
|
593
|
+
return !__privateGet(this, _isUpToDate);
|
|
626
594
|
}
|
|
627
595
|
};
|
|
596
|
+
_error = new WeakMap();
|
|
628
597
|
_fetchClient2 = new WeakMap();
|
|
629
598
|
_messageParser = new WeakMap();
|
|
630
599
|
_subscribers = new WeakMap();
|
|
631
|
-
_upToDateSubscribers = new WeakMap();
|
|
632
600
|
_lastOffset = new WeakMap();
|
|
633
601
|
_liveCacheBuster = new WeakMap();
|
|
634
602
|
_lastSyncedAt = new WeakMap();
|
|
@@ -637,9 +605,127 @@ _connected = new WeakMap();
|
|
|
637
605
|
_shapeHandle = new WeakMap();
|
|
638
606
|
_databaseId = new WeakMap();
|
|
639
607
|
_schema = new WeakMap();
|
|
640
|
-
|
|
608
|
+
_onError = new WeakMap();
|
|
641
609
|
_replica = new WeakMap();
|
|
642
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
|
+
};
|
|
643
729
|
publish_fn = function(messages) {
|
|
644
730
|
return __async(this, null, function* () {
|
|
645
731
|
yield Promise.all(
|
|
@@ -660,24 +746,14 @@ sendErrorToSubscribers_fn = function(error) {
|
|
|
660
746
|
errorFn == null ? void 0 : errorFn(error);
|
|
661
747
|
});
|
|
662
748
|
};
|
|
663
|
-
notifyUpToDateSubscribers_fn = function() {
|
|
664
|
-
__privateGet(this, _upToDateSubscribers).forEach(([callback]) => {
|
|
665
|
-
callback();
|
|
666
|
-
});
|
|
667
|
-
};
|
|
668
|
-
sendErrorToUpToDateSubscribers_fn = function(error) {
|
|
669
|
-
__privateGet(this, _upToDateSubscribers).forEach(
|
|
670
|
-
([_, errorCallback]) => errorCallback(error)
|
|
671
|
-
);
|
|
672
|
-
};
|
|
673
749
|
/**
|
|
674
750
|
* Resets the state of the stream, optionally with a provided
|
|
675
751
|
* shape handle
|
|
676
752
|
*/
|
|
677
|
-
reset_fn = function(
|
|
753
|
+
reset_fn = function(handle) {
|
|
678
754
|
__privateSet(this, _lastOffset, `-1`);
|
|
679
755
|
__privateSet(this, _liveCacheBuster, ``);
|
|
680
|
-
__privateSet(this, _shapeHandle,
|
|
756
|
+
__privateSet(this, _shapeHandle, handle);
|
|
681
757
|
__privateSet(this, _isUpToDate, false);
|
|
682
758
|
__privateSet(this, _connected, false);
|
|
683
759
|
__privateSet(this, _schema, void 0);
|
|
@@ -689,17 +765,21 @@ _ShapeStream.Replica = {
|
|
|
689
765
|
var ShapeStream = _ShapeStream;
|
|
690
766
|
function validateOptions(options) {
|
|
691
767
|
if (!options.url) {
|
|
692
|
-
throw new
|
|
768
|
+
throw new MissingShapeUrlError();
|
|
693
769
|
}
|
|
694
770
|
if (options.signal && !(options.signal instanceof AbortSignal)) {
|
|
695
|
-
throw new
|
|
696
|
-
`Invalid signal option. It must be an instance of AbortSignal.`
|
|
697
|
-
);
|
|
771
|
+
throw new InvalidSignalError();
|
|
698
772
|
}
|
|
699
|
-
if (options.offset !== void 0 && options.offset !== `-1` && !options.
|
|
700
|
-
throw new
|
|
701
|
-
|
|
773
|
+
if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
|
|
774
|
+
throw new MissingShapeHandleError();
|
|
775
|
+
}
|
|
776
|
+
if (options.params) {
|
|
777
|
+
const reservedParams = Object.keys(options.params).filter(
|
|
778
|
+
(key) => RESERVED_PARAMS.has(key)
|
|
702
779
|
);
|
|
780
|
+
if (reservedParams.length > 0) {
|
|
781
|
+
throw new ReservedParamError(reservedParams);
|
|
782
|
+
}
|
|
703
783
|
}
|
|
704
784
|
return;
|
|
705
785
|
}
|
|
@@ -719,19 +799,16 @@ var Shape = class {
|
|
|
719
799
|
__privateMethod(this, _Shape_instances, process_fn).bind(this),
|
|
720
800
|
__privateMethod(this, _Shape_instances, handleError_fn).bind(this)
|
|
721
801
|
);
|
|
722
|
-
const unsubscribe = __privateGet(this, _stream).subscribeOnceToUpToDate(
|
|
723
|
-
() => {
|
|
724
|
-
unsubscribe();
|
|
725
|
-
},
|
|
726
|
-
(e) => {
|
|
727
|
-
__privateMethod(this, _Shape_instances, handleError_fn).call(this, e);
|
|
728
|
-
throw e;
|
|
729
|
-
}
|
|
730
|
-
);
|
|
731
802
|
}
|
|
732
803
|
get isUpToDate() {
|
|
733
804
|
return __privateGet(this, _stream).isUpToDate;
|
|
734
805
|
}
|
|
806
|
+
get lastOffset() {
|
|
807
|
+
return __privateGet(this, _stream).lastOffset;
|
|
808
|
+
}
|
|
809
|
+
get handle() {
|
|
810
|
+
return __privateGet(this, _stream).shapeHandle;
|
|
811
|
+
}
|
|
735
812
|
get rows() {
|
|
736
813
|
return this.value.then((v) => Array.from(v.values()));
|
|
737
814
|
}
|