@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 CHANGED
@@ -93,4 +93,79 @@ shape.subscribe(({ rows }) => {
93
93
  }
94
94
  ```
95
95
 
96
- See the [Docs](https://electric-sql.com) and [Examples](https://electric-sql.com/examples/basic) for more information.
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
+ ```
@@ -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 Error(`Column ${columnName != null ? columnName : `unknown`} is not nullable`);
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, _upToDateSubscribers, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeHandle, _databaseId, _schema, _error, _replica, _ShapeStream_instances, publish_fn, sendErrorToSubscribers_fn, notifyUpToDateSubscribers_fn, sendErrorToUpToDateSubscribers_fn, reset_fn;
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, _error);
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.shapeHandle);
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.start();
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
- get error() {
527
- return __privateGet(this, _error);
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.isUpToDate;
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
- _error = new WeakMap();
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(shapeHandle) {
753
+ reset_fn = function(handle) {
727
754
  __privateSet(this, _lastOffset, `-1`);
728
755
  __privateSet(this, _liveCacheBuster, ``);
729
- __privateSet(this, _shapeHandle, 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 Error(`Invalid shape options. It must provide the url`);
768
+ throw new MissingShapeUrlError();
742
769
  }
743
770
  if (options.signal && !(options.signal instanceof AbortSignal)) {
744
- throw new Error(
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.shapeHandle) {
749
- throw new Error(
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 Error(
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
  }