@electric-sql/client 0.5.0 → 0.6.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.
@@ -1,7 +1,12 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
2
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
4
6
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __typeError = (msg) => {
8
+ throw TypeError(msg);
9
+ };
5
10
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
11
  var __spreadValues = (a, b) => {
7
12
  for (var prop in b || (b = {}))
@@ -14,6 +19,7 @@ var __spreadValues = (a, b) => {
14
19
  }
15
20
  return a;
16
21
  };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
17
23
  var __objRest = (source, exclude) => {
18
24
  var target = {};
19
25
  for (var prop in source)
@@ -26,6 +32,11 @@ var __objRest = (source, exclude) => {
26
32
  }
27
33
  return target;
28
34
  };
35
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
36
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
37
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
38
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
39
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
29
40
 
30
41
  // src/parser.ts
31
42
  var parseNumber = (value) => Number(value);
@@ -146,34 +157,11 @@ function isChangeMessage(message) {
146
157
  function isControlMessage(message) {
147
158
  return !isChangeMessage(message);
148
159
  }
160
+ function isUpToDateMessage(message) {
161
+ return isControlMessage(message) && message.headers.control === `up-to-date`;
162
+ }
149
163
 
150
- // src/client.ts
151
- var BackoffDefaults = {
152
- initialDelay: 100,
153
- maxDelay: 1e4,
154
- multiplier: 1.3
155
- };
156
- var MessageProcessor = class {
157
- constructor(callback) {
158
- this.messageQueue = [];
159
- this.isProcessing = false;
160
- this.callback = callback;
161
- }
162
- process(messages) {
163
- this.messageQueue.push(messages);
164
- if (!this.isProcessing) {
165
- this.processQueue();
166
- }
167
- }
168
- async processQueue() {
169
- this.isProcessing = true;
170
- while (this.messageQueue.length > 0) {
171
- const messages = this.messageQueue.shift();
172
- await this.callback(messages);
173
- }
174
- this.isProcessing = false;
175
- }
176
- };
164
+ // src/error.ts
177
165
  var FetchError = class _FetchError extends Error {
178
166
  constructor(status, text, json, headers, url, message) {
179
167
  super(
@@ -200,320 +188,530 @@ var FetchError = class _FetchError extends Error {
200
188
  return new _FetchError(status, text, json, headers, url);
201
189
  }
202
190
  };
191
+ var FetchBackoffAbortError = class extends Error {
192
+ constructor() {
193
+ super(`Fetch with backoff aborted`);
194
+ }
195
+ };
196
+
197
+ // src/constants.ts
198
+ var SHAPE_ID_HEADER = `electric-shape-id`;
199
+ var CHUNK_LAST_OFFSET_HEADER = `electric-chunk-last-offset`;
200
+ var CHUNK_UP_TO_DATE_HEADER = `electric-chunk-up-to-date`;
201
+ var SHAPE_SCHEMA_HEADER = `electric-schema`;
202
+ var SHAPE_ID_QUERY_PARAM = `shape_id`;
203
+ var OFFSET_QUERY_PARAM = `offset`;
204
+ var WHERE_QUERY_PARAM = `where`;
205
+ var LIVE_QUERY_PARAM = `live`;
206
+
207
+ // src/fetch.ts
208
+ var BackoffDefaults = {
209
+ initialDelay: 100,
210
+ maxDelay: 1e4,
211
+ multiplier: 1.3
212
+ };
213
+ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
214
+ const {
215
+ initialDelay,
216
+ maxDelay,
217
+ multiplier,
218
+ debug = false,
219
+ onFailedAttempt
220
+ } = backoffOptions;
221
+ return async (...args) => {
222
+ var _a;
223
+ const url = args[0];
224
+ const options = args[1];
225
+ let delay = initialDelay;
226
+ let attempt = 0;
227
+ while (true) {
228
+ try {
229
+ const result = await fetchClient(...args);
230
+ if (result.ok) return result;
231
+ else throw await FetchError.fromResponse(result, url.toString());
232
+ } catch (e) {
233
+ onFailedAttempt == null ? void 0 : onFailedAttempt();
234
+ if ((_a = options == null ? void 0 : options.signal) == null ? void 0 : _a.aborted) {
235
+ throw new FetchBackoffAbortError();
236
+ } else if (e instanceof FetchError && e.status >= 400 && e.status < 500) {
237
+ throw e;
238
+ } else {
239
+ await new Promise((resolve) => setTimeout(resolve, delay));
240
+ delay = Math.min(delay * multiplier, maxDelay);
241
+ if (debug) {
242
+ attempt++;
243
+ console.log(`Retry attempt #${attempt} after ${delay}ms`);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ };
249
+ }
250
+ var ChunkPrefetchDefaults = {
251
+ maxChunksToPrefetch: 2
252
+ };
253
+ function createFetchWithChunkBuffer(fetchClient, prefetchOptions = ChunkPrefetchDefaults) {
254
+ const { maxChunksToPrefetch } = prefetchOptions;
255
+ let prefetchQueue;
256
+ const prefetchClient = async (...args) => {
257
+ const url = args[0].toString();
258
+ const prefetchedRequest = prefetchQueue == null ? void 0 : prefetchQueue.consume(...args);
259
+ if (prefetchedRequest) {
260
+ return prefetchedRequest;
261
+ }
262
+ prefetchQueue == null ? void 0 : prefetchQueue.abort();
263
+ const response = await fetchClient(...args);
264
+ const nextUrl = getNextChunkUrl(url, response);
265
+ if (nextUrl) {
266
+ prefetchQueue = new PrefetchQueue({
267
+ fetchClient,
268
+ maxPrefetchedRequests: maxChunksToPrefetch,
269
+ url: nextUrl,
270
+ requestInit: args[1]
271
+ });
272
+ }
273
+ return response;
274
+ };
275
+ return prefetchClient;
276
+ }
277
+ var _fetchClient, _maxPrefetchedRequests, _prefetchQueue, _queueHeadUrl, _queueTailUrl, _PrefetchQueue_instances, prefetch_fn;
278
+ var PrefetchQueue = class {
279
+ constructor(options) {
280
+ __privateAdd(this, _PrefetchQueue_instances);
281
+ __privateAdd(this, _fetchClient);
282
+ __privateAdd(this, _maxPrefetchedRequests);
283
+ __privateAdd(this, _prefetchQueue, /* @__PURE__ */ new Map());
284
+ __privateAdd(this, _queueHeadUrl);
285
+ __privateAdd(this, _queueTailUrl);
286
+ var _a;
287
+ __privateSet(this, _fetchClient, (_a = options.fetchClient) != null ? _a : (...args) => fetch(...args));
288
+ __privateSet(this, _maxPrefetchedRequests, options.maxPrefetchedRequests);
289
+ __privateSet(this, _queueHeadUrl, options.url.toString());
290
+ __privateSet(this, _queueTailUrl, __privateGet(this, _queueHeadUrl));
291
+ __privateMethod(this, _PrefetchQueue_instances, prefetch_fn).call(this, options.url, options.requestInit);
292
+ }
293
+ abort() {
294
+ __privateGet(this, _prefetchQueue).forEach(([_, aborter]) => aborter.abort());
295
+ }
296
+ consume(...args) {
297
+ var _a;
298
+ const url = args[0].toString();
299
+ const request = (_a = __privateGet(this, _prefetchQueue).get(url)) == null ? void 0 : _a[0];
300
+ if (!request || url !== __privateGet(this, _queueHeadUrl)) return;
301
+ __privateGet(this, _prefetchQueue).delete(url);
302
+ request.then((response) => {
303
+ const nextUrl = getNextChunkUrl(url, response);
304
+ __privateSet(this, _queueHeadUrl, nextUrl);
305
+ if (!__privateGet(this, _prefetchQueue).has(__privateGet(this, _queueTailUrl))) {
306
+ __privateMethod(this, _PrefetchQueue_instances, prefetch_fn).call(this, __privateGet(this, _queueTailUrl), args[1]);
307
+ }
308
+ }).catch(() => {
309
+ });
310
+ return request;
311
+ }
312
+ };
313
+ _fetchClient = new WeakMap();
314
+ _maxPrefetchedRequests = new WeakMap();
315
+ _prefetchQueue = new WeakMap();
316
+ _queueHeadUrl = new WeakMap();
317
+ _queueTailUrl = new WeakMap();
318
+ _PrefetchQueue_instances = new WeakSet();
319
+ prefetch_fn = function(...args) {
320
+ var _a, _b;
321
+ const url = args[0].toString();
322
+ if (__privateGet(this, _prefetchQueue).size >= __privateGet(this, _maxPrefetchedRequests)) return;
323
+ const aborter = new AbortController();
324
+ try {
325
+ const request = __privateGet(this, _fetchClient).call(this, url, __spreadProps(__spreadValues({}, (_a = args[1]) != null ? _a : {}), {
326
+ signal: chainAborter(aborter, (_b = args[1]) == null ? void 0 : _b.signal)
327
+ }));
328
+ __privateGet(this, _prefetchQueue).set(url, [request, aborter]);
329
+ request.then((response) => {
330
+ if (!response.ok || aborter.signal.aborted) return;
331
+ const nextUrl = getNextChunkUrl(url, response);
332
+ if (!nextUrl || nextUrl === url) return;
333
+ __privateSet(this, _queueTailUrl, nextUrl);
334
+ return __privateMethod(this, _PrefetchQueue_instances, prefetch_fn).call(this, nextUrl, args[1]);
335
+ }).catch(() => {
336
+ });
337
+ } catch (_) {
338
+ }
339
+ };
340
+ function getNextChunkUrl(url, res) {
341
+ const shapeId = res.headers.get(SHAPE_ID_HEADER);
342
+ const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER);
343
+ const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER);
344
+ if (!shapeId || !lastOffset || isUpToDate) return;
345
+ const nextUrl = new URL(url);
346
+ if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return;
347
+ nextUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, shapeId);
348
+ nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset);
349
+ return nextUrl.toString();
350
+ }
351
+ function chainAborter(aborter, sourceSignal) {
352
+ if (!sourceSignal) return aborter.signal;
353
+ if (sourceSignal.aborted) aborter.abort();
354
+ else
355
+ sourceSignal.addEventListener(`abort`, () => aborter.abort(), {
356
+ once: true
357
+ });
358
+ return aborter.signal;
359
+ }
360
+
361
+ // src/client.ts
362
+ var _fetchClient2, _messageParser, _subscribers, _upToDateSubscribers, _lastOffset, _lastSyncedAt, _isUpToDate, _connected, _shapeId, _schema, _ShapeStream_instances, publish_fn, sendErrorToSubscribers_fn, notifyUpToDateSubscribers_fn, sendErrorToUpToDateSubscribers_fn, reset_fn;
203
363
  var ShapeStream = class {
204
364
  constructor(options) {
205
- this.subscribers = /* @__PURE__ */ new Map();
206
- this.upToDateSubscribers = /* @__PURE__ */ new Map();
365
+ __privateAdd(this, _ShapeStream_instances);
366
+ __privateAdd(this, _fetchClient2);
367
+ __privateAdd(this, _messageParser);
368
+ __privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
369
+ __privateAdd(this, _upToDateSubscribers, /* @__PURE__ */ new Map());
370
+ __privateAdd(this, _lastOffset);
371
+ __privateAdd(this, _lastSyncedAt);
207
372
  // unix time
208
- this.isUpToDate = false;
209
- this.connected = false;
373
+ __privateAdd(this, _isUpToDate, false);
374
+ __privateAdd(this, _connected, false);
375
+ __privateAdd(this, _shapeId);
376
+ __privateAdd(this, _schema);
210
377
  var _a, _b, _c;
211
- this.validateOptions(options);
378
+ validateOptions(options);
212
379
  this.options = __spreadValues({ subscribe: true }, options);
213
- this.lastOffset = (_a = this.options.offset) != null ? _a : `-1`;
214
- this.shapeId = this.options.shapeId;
215
- this.messageParser = new MessageParser(options.parser);
216
- this.backoffOptions = (_b = options.backoffOptions) != null ? _b : BackoffDefaults;
217
- this.fetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
380
+ __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
381
+ __privateSet(this, _shapeId, this.options.shapeId);
382
+ __privateSet(this, _messageParser, new MessageParser(options.parser));
383
+ const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
384
+ const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
385
+ onFailedAttempt: () => {
386
+ var _a2, _b2;
387
+ __privateSet(this, _connected, false);
388
+ (_b2 = (_a2 = options.backoffOptions) == null ? void 0 : _a2.onFailedAttempt) == null ? void 0 : _b2.call(_a2);
389
+ }
390
+ }));
391
+ __privateSet(this, _fetchClient2, createFetchWithChunkBuffer(fetchWithBackoffClient));
218
392
  this.start();
219
393
  }
394
+ get shapeId() {
395
+ return __privateGet(this, _shapeId);
396
+ }
397
+ get isUpToDate() {
398
+ return __privateGet(this, _isUpToDate);
399
+ }
220
400
  async start() {
221
401
  var _a;
222
- this.isUpToDate = false;
402
+ __privateSet(this, _isUpToDate, false);
223
403
  const { url, where, signal } = this.options;
224
404
  try {
225
- while (!(signal == null ? void 0 : signal.aborted) && !this.isUpToDate || this.options.subscribe) {
405
+ while (!(signal == null ? void 0 : signal.aborted) && !__privateGet(this, _isUpToDate) || this.options.subscribe) {
226
406
  const fetchUrl = new URL(url);
227
- if (where) fetchUrl.searchParams.set(`where`, where);
228
- fetchUrl.searchParams.set(`offset`, this.lastOffset);
229
- if (this.isUpToDate) {
230
- fetchUrl.searchParams.set(`live`, `true`);
407
+ if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where);
408
+ fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
409
+ if (__privateGet(this, _isUpToDate)) {
410
+ fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
231
411
  }
232
- if (this.shapeId) {
233
- fetchUrl.searchParams.set(`shape_id`, this.shapeId);
412
+ if (__privateGet(this, _shapeId)) {
413
+ fetchUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, __privateGet(this, _shapeId));
234
414
  }
235
415
  let response;
236
416
  try {
237
- const maybeResponse = await this.fetchWithBackoff(fetchUrl);
238
- if (maybeResponse) response = maybeResponse;
239
- else break;
417
+ response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), { signal });
418
+ __privateSet(this, _connected, true);
240
419
  } catch (e) {
420
+ if (e instanceof FetchBackoffAbortError) break;
241
421
  if (!(e instanceof FetchError)) throw e;
242
422
  if (e.status == 400) {
243
- this.reset();
244
- this.publish(e.json);
423
+ __privateMethod(this, _ShapeStream_instances, reset_fn).call(this);
424
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
245
425
  continue;
246
426
  } else if (e.status == 409) {
247
- const newShapeId = e.headers[`x-electric-shape-id`];
248
- this.reset(newShapeId);
249
- this.publish(e.json);
427
+ const newShapeId = e.headers[SHAPE_ID_HEADER];
428
+ __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeId);
429
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
250
430
  continue;
251
431
  } else if (e.status >= 400 && e.status < 500) {
252
- this.sendErrorToUpToDateSubscribers(e);
253
- this.sendErrorToSubscribers(e);
432
+ __privateMethod(this, _ShapeStream_instances, sendErrorToUpToDateSubscribers_fn).call(this, e);
433
+ __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
254
434
  throw e;
255
435
  }
256
436
  }
257
437
  const { headers, status } = response;
258
- const shapeId = headers.get(`X-Electric-Shape-Id`);
438
+ const shapeId = headers.get(SHAPE_ID_HEADER);
259
439
  if (shapeId) {
260
- this.shapeId = shapeId;
440
+ __privateSet(this, _shapeId, shapeId);
261
441
  }
262
- const lastOffset = headers.get(`X-Electric-Chunk-Last-Offset`);
442
+ const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
263
443
  if (lastOffset) {
264
- this.lastOffset = lastOffset;
444
+ __privateSet(this, _lastOffset, lastOffset);
265
445
  }
266
446
  const getSchema = () => {
267
- const schemaHeader = headers.get(`X-Electric-Schema`);
447
+ const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
268
448
  return schemaHeader ? JSON.parse(schemaHeader) : {};
269
449
  };
270
- this.schema = (_a = this.schema) != null ? _a : getSchema();
450
+ __privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
271
451
  const messages = status === 204 ? `[]` : await response.text();
272
452
  if (status === 204) {
273
- this.lastSyncedAt = Date.now();
453
+ __privateSet(this, _lastSyncedAt, Date.now());
274
454
  }
275
- const batch = this.messageParser.parse(messages, this.schema);
455
+ const batch = __privateGet(this, _messageParser).parse(messages, __privateGet(this, _schema));
276
456
  if (batch.length > 0) {
457
+ const prevUpToDate = __privateGet(this, _isUpToDate);
277
458
  const lastMessage = batch[batch.length - 1];
278
- if (isControlMessage(lastMessage) && lastMessage.headers.control === `up-to-date`) {
279
- this.lastSyncedAt = Date.now();
280
- if (!this.isUpToDate) {
281
- this.isUpToDate = true;
282
- this.notifyUpToDateSubscribers();
283
- }
459
+ if (isUpToDateMessage(lastMessage)) {
460
+ __privateSet(this, _lastSyncedAt, Date.now());
461
+ __privateSet(this, _isUpToDate, true);
462
+ }
463
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
464
+ if (!prevUpToDate && __privateGet(this, _isUpToDate)) {
465
+ __privateMethod(this, _ShapeStream_instances, notifyUpToDateSubscribers_fn).call(this);
284
466
  }
285
- this.publish(batch);
286
467
  }
287
468
  }
288
469
  } finally {
289
- this.connected = false;
470
+ __privateSet(this, _connected, false);
290
471
  }
291
472
  }
292
473
  subscribe(callback, onError) {
293
474
  const subscriptionId = Math.random();
294
- const subscriber = new MessageProcessor(callback);
295
- this.subscribers.set(subscriptionId, [subscriber, onError]);
475
+ __privateGet(this, _subscribers).set(subscriptionId, [callback, onError]);
296
476
  return () => {
297
- this.subscribers.delete(subscriptionId);
477
+ __privateGet(this, _subscribers).delete(subscriptionId);
298
478
  };
299
479
  }
300
480
  unsubscribeAll() {
301
- this.subscribers.clear();
302
- }
303
- publish(messages) {
304
- this.subscribers.forEach(([subscriber, _]) => {
305
- subscriber.process(messages);
306
- });
307
- }
308
- sendErrorToSubscribers(error) {
309
- this.subscribers.forEach(([_, errorFn]) => {
310
- errorFn == null ? void 0 : errorFn(error);
311
- });
481
+ __privateGet(this, _subscribers).clear();
312
482
  }
313
483
  subscribeOnceToUpToDate(callback, error) {
314
484
  const subscriptionId = Math.random();
315
- this.upToDateSubscribers.set(subscriptionId, [callback, error]);
485
+ __privateGet(this, _upToDateSubscribers).set(subscriptionId, [callback, error]);
316
486
  return () => {
317
- this.upToDateSubscribers.delete(subscriptionId);
487
+ __privateGet(this, _upToDateSubscribers).delete(subscriptionId);
318
488
  };
319
489
  }
320
490
  unsubscribeAllUpToDateSubscribers() {
321
- this.upToDateSubscribers.clear();
491
+ __privateGet(this, _upToDateSubscribers).clear();
492
+ }
493
+ /** Unix time at which we last synced. Undefined when `isLoading` is true. */
494
+ lastSyncedAt() {
495
+ return __privateGet(this, _lastSyncedAt);
322
496
  }
323
497
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
324
498
  lastSynced() {
325
- if (this.lastSyncedAt === void 0) return Infinity;
326
- return Date.now() - this.lastSyncedAt;
499
+ if (__privateGet(this, _lastSyncedAt) === void 0) return Infinity;
500
+ return Date.now() - __privateGet(this, _lastSyncedAt);
327
501
  }
502
+ /** Indicates if we are connected to the Electric sync service. */
328
503
  isConnected() {
329
- return this.connected;
504
+ return __privateGet(this, _connected);
330
505
  }
331
506
  /** True during initial fetch. False afterwise. */
332
507
  isLoading() {
333
508
  return !this.isUpToDate;
334
509
  }
335
- notifyUpToDateSubscribers() {
336
- this.upToDateSubscribers.forEach(([callback]) => {
337
- callback();
338
- });
510
+ };
511
+ _fetchClient2 = new WeakMap();
512
+ _messageParser = new WeakMap();
513
+ _subscribers = new WeakMap();
514
+ _upToDateSubscribers = new WeakMap();
515
+ _lastOffset = new WeakMap();
516
+ _lastSyncedAt = new WeakMap();
517
+ _isUpToDate = new WeakMap();
518
+ _connected = new WeakMap();
519
+ _shapeId = new WeakMap();
520
+ _schema = new WeakMap();
521
+ _ShapeStream_instances = new WeakSet();
522
+ publish_fn = async function(messages) {
523
+ await Promise.all(
524
+ Array.from(__privateGet(this, _subscribers).values()).map(async ([callback, __]) => {
525
+ try {
526
+ await callback(messages);
527
+ } catch (err) {
528
+ queueMicrotask(() => {
529
+ throw err;
530
+ });
531
+ }
532
+ })
533
+ );
534
+ };
535
+ sendErrorToSubscribers_fn = function(error) {
536
+ __privateGet(this, _subscribers).forEach(([_, errorFn]) => {
537
+ errorFn == null ? void 0 : errorFn(error);
538
+ });
539
+ };
540
+ notifyUpToDateSubscribers_fn = function() {
541
+ __privateGet(this, _upToDateSubscribers).forEach(([callback]) => {
542
+ callback();
543
+ });
544
+ };
545
+ sendErrorToUpToDateSubscribers_fn = function(error) {
546
+ __privateGet(this, _upToDateSubscribers).forEach(
547
+ ([_, errorCallback]) => errorCallback(error)
548
+ );
549
+ };
550
+ /**
551
+ * Resets the state of the stream, optionally with a provided
552
+ * shape ID
553
+ */
554
+ reset_fn = function(shapeId) {
555
+ __privateSet(this, _lastOffset, `-1`);
556
+ __privateSet(this, _shapeId, shapeId);
557
+ __privateSet(this, _isUpToDate, false);
558
+ __privateSet(this, _connected, false);
559
+ __privateSet(this, _schema, void 0);
560
+ };
561
+ function validateOptions(options) {
562
+ if (!options.url) {
563
+ throw new Error(`Invalid shape option. It must provide the url`);
339
564
  }
340
- sendErrorToUpToDateSubscribers(error) {
341
- this.upToDateSubscribers.forEach(
342
- ([_, errorCallback]) => errorCallback(error)
565
+ if (options.signal && !(options.signal instanceof AbortSignal)) {
566
+ throw new Error(
567
+ `Invalid signal option. It must be an instance of AbortSignal.`
343
568
  );
344
569
  }
345
- /**
346
- * Resets the state of the stream, optionally with a provided
347
- * shape ID
348
- */
349
- reset(shapeId) {
350
- this.lastOffset = `-1`;
351
- this.shapeId = shapeId;
352
- this.isUpToDate = false;
353
- this.connected = false;
354
- this.schema = void 0;
355
- }
356
- validateOptions(options) {
357
- if (!options.url) {
358
- throw new Error(`Invalid shape option. It must provide the url`);
359
- }
360
- if (options.signal && !(options.signal instanceof AbortSignal)) {
361
- throw new Error(
362
- `Invalid signal option. It must be an instance of AbortSignal.`
363
- );
364
- }
365
- if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeId) {
366
- throw new Error(
367
- `shapeId is required if this isn't an initial fetch (i.e. offset > -1)`
368
- );
369
- }
370
- }
371
- async fetchWithBackoff(url) {
372
- const { initialDelay, maxDelay, multiplier } = this.backoffOptions;
373
- const signal = this.options.signal;
374
- let delay = initialDelay;
375
- let attempt = 0;
376
- while (true) {
377
- try {
378
- const result = await this.fetchClient(url.toString(), { signal });
379
- if (result.ok) {
380
- if (this.options.subscribe) {
381
- this.connected = true;
382
- }
383
- return result;
384
- } else throw await FetchError.fromResponse(result, url.toString());
385
- } catch (e) {
386
- this.connected = false;
387
- if (signal == null ? void 0 : signal.aborted) {
388
- return void 0;
389
- } else if (e instanceof FetchError && e.status >= 400 && e.status < 500) {
390
- throw e;
391
- } else {
392
- await new Promise((resolve) => setTimeout(resolve, delay));
393
- delay = Math.min(delay * multiplier, maxDelay);
394
- attempt++;
395
- console.log(`Retry attempt #${attempt} after ${delay}ms`);
396
- }
397
- }
398
- }
570
+ if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeId) {
571
+ throw new Error(
572
+ `shapeId is required if this isn't an initial fetch (i.e. offset > -1)`
573
+ );
399
574
  }
400
- };
575
+ return;
576
+ }
577
+
578
+ // src/shape.ts
579
+ var _stream, _data, _subscribers2, _hasNotifiedSubscribersUpToDate, _error, _Shape_instances, process_fn, handleError_fn, notify_fn;
401
580
  var Shape = class {
402
581
  constructor(stream) {
403
- this.data = /* @__PURE__ */ new Map();
404
- this.subscribers = /* @__PURE__ */ new Map();
405
- this.error = false;
406
- this.hasNotifiedSubscribersUpToDate = false;
407
- this.stream = stream;
408
- this.stream.subscribe(this.process.bind(this), this.handleError.bind(this));
409
- const unsubscribe = this.stream.subscribeOnceToUpToDate(
582
+ __privateAdd(this, _Shape_instances);
583
+ __privateAdd(this, _stream);
584
+ __privateAdd(this, _data, /* @__PURE__ */ new Map());
585
+ __privateAdd(this, _subscribers2, /* @__PURE__ */ new Map());
586
+ __privateAdd(this, _hasNotifiedSubscribersUpToDate, false);
587
+ __privateAdd(this, _error, false);
588
+ __privateSet(this, _stream, stream);
589
+ __privateGet(this, _stream).subscribe(
590
+ __privateMethod(this, _Shape_instances, process_fn).bind(this),
591
+ __privateMethod(this, _Shape_instances, handleError_fn).bind(this)
592
+ );
593
+ const unsubscribe = __privateGet(this, _stream).subscribeOnceToUpToDate(
410
594
  () => {
411
595
  unsubscribe();
412
596
  },
413
597
  (e) => {
414
- this.handleError(e);
598
+ __privateMethod(this, _Shape_instances, handleError_fn).call(this, e);
415
599
  throw e;
416
600
  }
417
601
  );
418
602
  }
419
- lastSynced() {
420
- return this.stream.lastSynced();
421
- }
422
- isConnected() {
423
- return this.stream.isConnected();
424
- }
425
- /** True during initial fetch. False afterwise. */
426
- isLoading() {
427
- return this.stream.isLoading();
603
+ get isUpToDate() {
604
+ return __privateGet(this, _stream).isUpToDate;
428
605
  }
429
606
  get value() {
430
- return new Promise((resolve) => {
431
- if (this.stream.isUpToDate) {
607
+ return new Promise((resolve, reject) => {
608
+ if (__privateGet(this, _stream).isUpToDate) {
432
609
  resolve(this.valueSync);
433
610
  } else {
434
- const unsubscribe = this.stream.subscribeOnceToUpToDate(
435
- () => {
436
- unsubscribe();
437
- resolve(this.valueSync);
438
- },
439
- (e) => {
440
- throw e;
441
- }
442
- );
611
+ const unsubscribe = this.subscribe((shapeData) => {
612
+ unsubscribe();
613
+ if (__privateGet(this, _error)) reject(__privateGet(this, _error));
614
+ resolve(shapeData);
615
+ });
443
616
  }
444
617
  });
445
618
  }
446
619
  get valueSync() {
447
- return this.data;
620
+ return __privateGet(this, _data);
621
+ }
622
+ get error() {
623
+ return __privateGet(this, _error);
624
+ }
625
+ /** Unix time at which we last synced. Undefined when `isLoading` is true. */
626
+ lastSyncedAt() {
627
+ return __privateGet(this, _stream).lastSyncedAt();
628
+ }
629
+ /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
630
+ lastSynced() {
631
+ return __privateGet(this, _stream).lastSynced();
632
+ }
633
+ /** True during initial fetch. False afterwise. */
634
+ isLoading() {
635
+ return __privateGet(this, _stream).isLoading();
636
+ }
637
+ /** Indicates if we are connected to the Electric sync service. */
638
+ isConnected() {
639
+ return __privateGet(this, _stream).isConnected();
448
640
  }
449
641
  subscribe(callback) {
450
642
  const subscriptionId = Math.random();
451
- this.subscribers.set(subscriptionId, callback);
643
+ __privateGet(this, _subscribers2).set(subscriptionId, callback);
452
644
  return () => {
453
- this.subscribers.delete(subscriptionId);
645
+ __privateGet(this, _subscribers2).delete(subscriptionId);
454
646
  };
455
647
  }
456
648
  unsubscribeAll() {
457
- this.subscribers.clear();
649
+ __privateGet(this, _subscribers2).clear();
458
650
  }
459
651
  get numSubscribers() {
460
- return this.subscribers.size;
461
- }
462
- process(messages) {
463
- let dataMayHaveChanged = false;
464
- let isUpToDate = false;
465
- let newlyUpToDate = false;
466
- messages.forEach((message) => {
467
- if (isChangeMessage(message)) {
468
- dataMayHaveChanged = [`insert`, `update`, `delete`].includes(
469
- message.headers.operation
470
- );
471
- switch (message.headers.operation) {
472
- case `insert`:
473
- this.data.set(message.key, message.value);
474
- break;
475
- case `update`:
476
- this.data.set(message.key, __spreadValues(__spreadValues({}, this.data.get(message.key)), message.value));
477
- break;
478
- case `delete`:
479
- this.data.delete(message.key);
480
- break;
481
- }
482
- }
483
- if (isControlMessage(message)) {
484
- switch (message.headers.control) {
485
- case `up-to-date`:
486
- isUpToDate = true;
487
- if (!this.hasNotifiedSubscribersUpToDate) {
488
- newlyUpToDate = true;
489
- }
490
- break;
491
- case `must-refetch`:
492
- this.data.clear();
493
- this.error = false;
494
- isUpToDate = false;
495
- newlyUpToDate = false;
496
- break;
497
- }
652
+ return __privateGet(this, _subscribers2).size;
653
+ }
654
+ };
655
+ _stream = new WeakMap();
656
+ _data = new WeakMap();
657
+ _subscribers2 = new WeakMap();
658
+ _hasNotifiedSubscribersUpToDate = new WeakMap();
659
+ _error = new WeakMap();
660
+ _Shape_instances = new WeakSet();
661
+ process_fn = function(messages) {
662
+ let dataMayHaveChanged = false;
663
+ let isUpToDate = false;
664
+ let newlyUpToDate = false;
665
+ messages.forEach((message) => {
666
+ if (isChangeMessage(message)) {
667
+ dataMayHaveChanged = [`insert`, `update`, `delete`].includes(
668
+ message.headers.operation
669
+ );
670
+ switch (message.headers.operation) {
671
+ case `insert`:
672
+ __privateGet(this, _data).set(message.key, message.value);
673
+ break;
674
+ case `update`:
675
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
676
+ break;
677
+ case `delete`:
678
+ __privateGet(this, _data).delete(message.key);
679
+ break;
498
680
  }
499
- });
500
- if (newlyUpToDate || isUpToDate && dataMayHaveChanged) {
501
- this.hasNotifiedSubscribersUpToDate = true;
502
- this.notify();
503
681
  }
504
- }
505
- handleError(e) {
506
- if (e instanceof FetchError) {
507
- this.error = e;
508
- this.notify();
682
+ if (isControlMessage(message)) {
683
+ switch (message.headers.control) {
684
+ case `up-to-date`:
685
+ isUpToDate = true;
686
+ if (!__privateGet(this, _hasNotifiedSubscribersUpToDate)) {
687
+ newlyUpToDate = true;
688
+ }
689
+ break;
690
+ case `must-refetch`:
691
+ __privateGet(this, _data).clear();
692
+ __privateSet(this, _error, false);
693
+ isUpToDate = false;
694
+ newlyUpToDate = false;
695
+ break;
696
+ }
509
697
  }
698
+ });
699
+ if (newlyUpToDate || isUpToDate && dataMayHaveChanged) {
700
+ __privateSet(this, _hasNotifiedSubscribersUpToDate, true);
701
+ __privateMethod(this, _Shape_instances, notify_fn).call(this);
510
702
  }
511
- notify() {
512
- this.subscribers.forEach((callback) => {
513
- callback(this.valueSync);
514
- });
703
+ };
704
+ handleError_fn = function(e) {
705
+ if (e instanceof FetchError) {
706
+ __privateSet(this, _error, e);
707
+ __privateMethod(this, _Shape_instances, notify_fn).call(this);
515
708
  }
516
709
  };
710
+ notify_fn = function() {
711
+ __privateGet(this, _subscribers2).forEach((callback) => {
712
+ callback(this.valueSync);
713
+ });
714
+ };
517
715
  export {
518
716
  BackoffDefaults,
519
717
  FetchError,