@electric-sql/client
Version:
Postgres everywhere - your data, in sync, wherever you need it.
1,317 lines (1,308 loc) • 61.4 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
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);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var __privateWrapper = (obj, member, setter, getter) => ({
set _(value) {
__privateSet(obj, member, value, setter);
},
get _() {
return __privateGet(obj, member, getter);
}
});
// src/error.ts
var FetchError = class _FetchError extends Error {
constructor(status, text, json, headers, url, message) {
super(
message || `HTTP Error ${status} at ${url}: ${text != null ? text : JSON.stringify(json)}`
);
this.url = url;
this.name = `FetchError`;
this.status = status;
this.text = text;
this.json = json;
this.headers = headers;
}
static async fromResponse(response, url) {
const status = response.status;
const headers = Object.fromEntries([...response.headers.entries()]);
let text = void 0;
let json = void 0;
const contentType = response.headers.get(`content-type`);
if (!response.bodyUsed) {
if (contentType && contentType.includes(`application/json`)) {
json = await response.json();
} else {
text = await response.text();
}
}
return new _FetchError(status, text, json, headers, url);
}
};
var FetchBackoffAbortError = class extends Error {
constructor() {
super(`Fetch with backoff aborted`);
this.name = `FetchBackoffAbortError`;
}
};
var MissingShapeUrlError = class extends Error {
constructor() {
super(`Invalid shape options: missing required url parameter`);
this.name = `MissingShapeUrlError`;
}
};
var InvalidSignalError = class extends Error {
constructor() {
super(`Invalid signal option. It must be an instance of AbortSignal.`);
this.name = `InvalidSignalError`;
}
};
var MissingShapeHandleError = class extends Error {
constructor() {
super(
`shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)`
);
this.name = `MissingShapeHandleError`;
}
};
var ReservedParamError = class extends Error {
constructor(reservedParams) {
super(
`Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`
);
this.name = `ReservedParamError`;
}
};
var ParserNullValueError = class extends Error {
constructor(columnName) {
super(`Column "${columnName != null ? columnName : `unknown`}" does not allow NULL values`);
this.name = `ParserNullValueError`;
}
};
var MissingHeadersError = class extends Error {
constructor(url, missingHeaders) {
let msg = `The response for the shape request to ${url} didn't include the following required headers:
`;
missingHeaders.forEach((h) => {
msg += `- ${h}
`;
});
msg += `
This is often due to a proxy not setting CORS correctly so that all Electric headers can be read by the client.`;
msg += `
For more information visit the troubleshooting guide: /docs/guides/troubleshooting/missing-headers`;
super(msg);
}
};
// src/parser.ts
var parseNumber = (value) => Number(value);
var parseBool = (value) => value === `true` || value === `t`;
var parseBigInt = (value) => BigInt(value);
var parseJson = (value) => JSON.parse(value);
var identityParser = (v) => v;
var defaultParser = {
int2: parseNumber,
int4: parseNumber,
int8: parseBigInt,
bool: parseBool,
float4: parseNumber,
float8: parseNumber,
json: parseJson,
jsonb: parseJson
};
function pgArrayParser(value, parser) {
let i = 0;
let char = null;
let str = ``;
let quoted = false;
let last = 0;
let p = void 0;
function extractValue(x, start, end) {
let val = x.slice(start, end);
val = val === `NULL` ? null : val;
return parser ? parser(val) : val;
}
function loop(x) {
const xs = [];
for (; i < x.length; i++) {
char = x[i];
if (quoted) {
if (char === `\\`) {
str += x[++i];
} else if (char === `"`) {
xs.push(parser ? parser(str) : str);
str = ``;
quoted = x[i + 1] === `"`;
last = i + 2;
} else {
str += char;
}
} else if (char === `"`) {
quoted = true;
} else if (char === `{`) {
last = ++i;
xs.push(loop(x));
} else if (char === `}`) {
quoted = false;
last < i && xs.push(extractValue(x, last, i));
last = i + 1;
break;
} else if (char === `,` && p !== `}` && p !== `"`) {
xs.push(extractValue(x, last, i));
last = i + 1;
}
p = char;
}
last < i && xs.push(xs.push(extractValue(x, last, i + 1)));
return xs;
}
return loop(value)[0];
}
var MessageParser = class {
constructor(parser, transformer) {
this.parser = __spreadValues(__spreadValues({}, defaultParser), parser);
this.transformer = transformer;
}
parse(messages, schema) {
return JSON.parse(messages, (key, value) => {
if ((key === `value` || key === `old_value`) && typeof value === `object` && value !== null) {
const row = value;
Object.keys(row).forEach((key2) => {
row[key2] = this.parseRow(key2, row[key2], schema);
});
if (this.transformer) value = this.transformer(value);
}
return value;
});
}
// Parses the message values using the provided parser based on the schema information
parseRow(key, value, schema) {
var _b;
const columnInfo = schema[key];
if (!columnInfo) {
return value;
}
const _a = columnInfo, { type: typ, dims: dimensions } = _a, additionalInfo = __objRest(_a, ["type", "dims"]);
const typeParser = (_b = this.parser[typ]) != null ? _b : identityParser;
const parser = makeNullableParser(typeParser, columnInfo, key);
if (dimensions && dimensions > 0) {
const nullablePgArrayParser = makeNullableParser(
(value2, _) => pgArrayParser(value2, parser),
columnInfo,
key
);
return nullablePgArrayParser(value);
}
return parser(value, additionalInfo);
}
};
function makeNullableParser(parser, columnInfo, columnName) {
var _a;
const isNullable = !((_a = columnInfo.not_null) != null ? _a : false);
return (value) => {
if (value === null) {
if (!isNullable) {
throw new ParserNullValueError(columnName != null ? columnName : `unknown`);
}
return null;
}
return parser(value, columnInfo);
};
}
// src/helpers.ts
function isChangeMessage(message) {
return `key` in message;
}
function isControlMessage(message) {
return !isChangeMessage(message);
}
function isUpToDateMessage(message) {
return isControlMessage(message) && message.headers.control === `up-to-date`;
}
function getOffset(message) {
const lsn = message.headers.global_last_seen_lsn;
if (!lsn) {
return;
}
return `${lsn}_0`;
}
function isVisibleInSnapshot(txid, snapshot) {
const xid = BigInt(txid);
const xmin = BigInt(snapshot.xmin);
const xmax = BigInt(snapshot.xmax);
const xip = snapshot.xip_list.map(BigInt);
return xid < xmin || xid < xmax && !xip.includes(xid);
}
// src/constants.ts
var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
var SHAPE_HANDLE_HEADER = `electric-handle`;
var CHUNK_LAST_OFFSET_HEADER = `electric-offset`;
var SHAPE_SCHEMA_HEADER = `electric-schema`;
var CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`;
var COLUMNS_QUERY_PARAM = `columns`;
var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`;
var EXPIRED_HANDLE_QUERY_PARAM = `expired_handle`;
var SHAPE_HANDLE_QUERY_PARAM = `handle`;
var LIVE_QUERY_PARAM = `live`;
var OFFSET_QUERY_PARAM = `offset`;
var TABLE_QUERY_PARAM = `table`;
var WHERE_QUERY_PARAM = `where`;
var REPLICA_PARAM = `replica`;
var WHERE_PARAMS_PARAM = `params`;
var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
var LIVE_SSE_QUERY_PARAM = `live_sse`;
var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
var PAUSE_STREAM = `pause-stream`;
var LOG_MODE_QUERY_PARAM = `log`;
var SUBSET_PARAM_WHERE = `subset__where`;
var SUBSET_PARAM_LIMIT = `subset__limit`;
var SUBSET_PARAM_OFFSET = `subset__offset`;
var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
LIVE_QUERY_PARAM,
LIVE_SSE_QUERY_PARAM,
SHAPE_HANDLE_QUERY_PARAM,
OFFSET_QUERY_PARAM,
LIVE_CACHE_BUSTER_QUERY_PARAM,
EXPIRED_HANDLE_QUERY_PARAM,
LOG_MODE_QUERY_PARAM,
SUBSET_PARAM_WHERE,
SUBSET_PARAM_LIMIT,
SUBSET_PARAM_OFFSET,
SUBSET_PARAM_ORDER_BY,
SUBSET_PARAM_WHERE_PARAMS
];
// src/fetch.ts
var HTTP_RETRY_STATUS_CODES = [429];
var BackoffDefaults = {
initialDelay: 100,
maxDelay: 6e4,
// Cap at 60s - reasonable for long-lived connections
multiplier: 1.3,
maxRetries: Infinity
// Retry forever - clients may go offline and come back
};
function parseRetryAfterHeader(retryAfter) {
if (!retryAfter) return 0;
const retryAfterSec = Number(retryAfter);
if (Number.isFinite(retryAfterSec) && retryAfterSec > 0) {
return retryAfterSec * 1e3;
}
const retryDate = Date.parse(retryAfter);
if (!isNaN(retryDate)) {
const deltaMs = retryDate - Date.now();
return Math.max(0, Math.min(deltaMs, 36e5));
}
return 0;
}
function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
const {
initialDelay,
maxDelay,
multiplier,
debug = false,
onFailedAttempt,
maxRetries = Infinity
} = backoffOptions;
return async (...args) => {
var _a;
const url = args[0];
const options = args[1];
let delay = initialDelay;
let attempt = 0;
while (true) {
try {
const result = await fetchClient(...args);
if (result.ok) {
return result;
}
const err = await FetchError.fromResponse(result, url.toString());
throw err;
} catch (e) {
onFailedAttempt == null ? void 0 : onFailedAttempt();
if ((_a = options == null ? void 0 : options.signal) == null ? void 0 : _a.aborted) {
throw new FetchBackoffAbortError();
} else if (e instanceof FetchError && !HTTP_RETRY_STATUS_CODES.includes(e.status) && e.status >= 400 && e.status < 500) {
throw e;
} else {
attempt++;
if (attempt > maxRetries) {
if (debug) {
console.log(
`Max retries reached (${attempt}/${maxRetries}), giving up`
);
}
throw e;
}
const serverMinimumMs = e instanceof FetchError && e.headers ? parseRetryAfterHeader(e.headers[`retry-after`]) : 0;
const jitter = Math.random() * delay;
const clientBackoffMs = Math.min(jitter, maxDelay);
const waitMs = Math.max(serverMinimumMs, clientBackoffMs);
if (debug) {
const source = serverMinimumMs > 0 ? `server+client` : `client`;
console.log(
`Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`
);
}
await new Promise((resolve) => setTimeout(resolve, waitMs));
delay = Math.min(delay * multiplier, maxDelay);
}
}
}
};
}
var NO_BODY_STATUS_CODES = [201, 204, 205];
function createFetchWithConsumedMessages(fetchClient) {
return async (...args) => {
var _a, _b;
const url = args[0];
const res = await fetchClient(...args);
try {
if (res.status < 200 || NO_BODY_STATUS_CODES.includes(res.status)) {
return res;
}
const text = await res.text();
return new Response(text, res);
} catch (err) {
if ((_b = (_a = args[1]) == null ? void 0 : _a.signal) == null ? void 0 : _b.aborted) {
throw new FetchBackoffAbortError();
}
throw new FetchError(
res.status,
void 0,
void 0,
Object.fromEntries([...res.headers.entries()]),
url.toString(),
err instanceof Error ? err.message : typeof err === `string` ? err : `failed to read body`
);
}
};
}
var ChunkPrefetchDefaults = {
maxChunksToPrefetch: 2
};
function createFetchWithChunkBuffer(fetchClient, prefetchOptions = ChunkPrefetchDefaults) {
const { maxChunksToPrefetch } = prefetchOptions;
let prefetchQueue;
const prefetchClient = async (...args) => {
const url = args[0].toString();
const prefetchedRequest = prefetchQueue == null ? void 0 : prefetchQueue.consume(...args);
if (prefetchedRequest) {
return prefetchedRequest;
}
prefetchQueue == null ? void 0 : prefetchQueue.abort();
const response = await fetchClient(...args);
const nextUrl = getNextChunkUrl(url, response);
if (nextUrl) {
prefetchQueue = new PrefetchQueue({
fetchClient,
maxPrefetchedRequests: maxChunksToPrefetch,
url: nextUrl,
requestInit: args[1]
});
}
return response;
};
return prefetchClient;
}
var requiredElectricResponseHeaders = [
`electric-offset`,
`electric-handle`
];
var requiredLiveResponseHeaders = [`electric-cursor`];
var requiredNonLiveResponseHeaders = [`electric-schema`];
function createFetchWithResponseHeadersCheck(fetchClient) {
return async (...args) => {
const response = await fetchClient(...args);
if (response.ok) {
const headers = response.headers;
const missingHeaders = [];
const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
const input = args[0];
const urlString = input.toString();
const url = new URL(urlString);
const isSnapshotRequest = [
SUBSET_PARAM_WHERE,
SUBSET_PARAM_WHERE_PARAMS,
SUBSET_PARAM_LIMIT,
SUBSET_PARAM_OFFSET,
SUBSET_PARAM_ORDER_BY
].some((p) => url.searchParams.has(p));
if (isSnapshotRequest) {
return response;
}
addMissingHeaders(requiredElectricResponseHeaders);
if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {
addMissingHeaders(requiredLiveResponseHeaders);
}
if (!url.searchParams.has(LIVE_QUERY_PARAM) || url.searchParams.get(LIVE_QUERY_PARAM) === `false`) {
addMissingHeaders(requiredNonLiveResponseHeaders);
}
if (missingHeaders.length > 0) {
throw new MissingHeadersError(urlString, missingHeaders);
}
}
return response;
};
}
var _fetchClient, _maxPrefetchedRequests, _prefetchQueue, _queueHeadUrl, _queueTailUrl, _PrefetchQueue_instances, prefetch_fn;
var PrefetchQueue = class {
constructor(options) {
__privateAdd(this, _PrefetchQueue_instances);
__privateAdd(this, _fetchClient);
__privateAdd(this, _maxPrefetchedRequests);
__privateAdd(this, _prefetchQueue, /* @__PURE__ */ new Map());
__privateAdd(this, _queueHeadUrl);
__privateAdd(this, _queueTailUrl);
var _a;
__privateSet(this, _fetchClient, (_a = options.fetchClient) != null ? _a : (...args) => fetch(...args));
__privateSet(this, _maxPrefetchedRequests, options.maxPrefetchedRequests);
__privateSet(this, _queueHeadUrl, options.url.toString());
__privateSet(this, _queueTailUrl, __privateGet(this, _queueHeadUrl));
__privateMethod(this, _PrefetchQueue_instances, prefetch_fn).call(this, options.url, options.requestInit);
}
abort() {
__privateGet(this, _prefetchQueue).forEach(([_, aborter]) => aborter.abort());
}
consume(...args) {
var _a;
const url = args[0].toString();
const request = (_a = __privateGet(this, _prefetchQueue).get(url)) == null ? void 0 : _a[0];
if (!request || url !== __privateGet(this, _queueHeadUrl)) return;
__privateGet(this, _prefetchQueue).delete(url);
request.then((response) => {
const nextUrl = getNextChunkUrl(url, response);
__privateSet(this, _queueHeadUrl, nextUrl);
if (__privateGet(this, _queueTailUrl) && !__privateGet(this, _prefetchQueue).has(__privateGet(this, _queueTailUrl))) {
__privateMethod(this, _PrefetchQueue_instances, prefetch_fn).call(this, __privateGet(this, _queueTailUrl), args[1]);
}
}).catch(() => {
});
return request;
}
};
_fetchClient = new WeakMap();
_maxPrefetchedRequests = new WeakMap();
_prefetchQueue = new WeakMap();
_queueHeadUrl = new WeakMap();
_queueTailUrl = new WeakMap();
_PrefetchQueue_instances = new WeakSet();
prefetch_fn = function(...args) {
var _a, _b;
const url = args[0].toString();
if (__privateGet(this, _prefetchQueue).size >= __privateGet(this, _maxPrefetchedRequests)) return;
const aborter = new AbortController();
try {
const { signal, cleanup } = chainAborter(aborter, (_a = args[1]) == null ? void 0 : _a.signal);
const request = __privateGet(this, _fetchClient).call(this, url, __spreadProps(__spreadValues({}, (_b = args[1]) != null ? _b : {}), { signal }));
__privateGet(this, _prefetchQueue).set(url, [request, aborter]);
request.then((response) => {
if (!response.ok || aborter.signal.aborted) return;
const nextUrl = getNextChunkUrl(url, response);
if (!nextUrl || nextUrl === url) {
__privateSet(this, _queueTailUrl, void 0);
return;
}
__privateSet(this, _queueTailUrl, nextUrl);
return __privateMethod(this, _PrefetchQueue_instances, prefetch_fn).call(this, nextUrl, args[1]);
}).catch(() => {
}).finally(cleanup);
} catch (_) {
}
};
function getNextChunkUrl(url, res) {
const shapeHandle = res.headers.get(SHAPE_HANDLE_HEADER);
const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER);
const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER);
if (!shapeHandle || !lastOffset || isUpToDate) return;
const nextUrl = new URL(url);
if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return;
nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle);
nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset);
nextUrl.searchParams.sort();
return nextUrl.toString();
}
function chainAborter(aborter, sourceSignal) {
let cleanup = noop;
if (!sourceSignal) {
} else if (sourceSignal.aborted) {
aborter.abort();
} else {
const abortParent = () => aborter.abort();
sourceSignal.addEventListener(`abort`, abortParent, {
once: true,
signal: aborter.signal
});
cleanup = () => sourceSignal.removeEventListener(`abort`, abortParent);
}
return {
signal: aborter.signal,
cleanup
};
}
function noop() {
}
// src/client.ts
import {
fetchEventSource
} from "@microsoft/fetch-event-source";
// src/expired-shapes-cache.ts
var ExpiredShapesCache = class {
constructor() {
this.data = {};
this.max = 250;
this.storageKey = `electric_expired_shapes`;
this.load();
}
getExpiredHandle(shapeUrl) {
const entry = this.data[shapeUrl];
if (entry) {
entry.lastUsed = Date.now();
this.save();
return entry.expiredHandle;
}
return null;
}
markExpired(shapeUrl, handle) {
this.data[shapeUrl] = { expiredHandle: handle, lastUsed: Date.now() };
const keys = Object.keys(this.data);
if (keys.length > this.max) {
const oldest = keys.reduce(
(min, k) => this.data[k].lastUsed < this.data[min].lastUsed ? k : min
);
delete this.data[oldest];
}
this.save();
}
save() {
if (typeof localStorage === `undefined`) return;
try {
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
} catch (e) {
}
}
load() {
if (typeof localStorage === `undefined`) return;
try {
const stored = localStorage.getItem(this.storageKey);
if (stored) {
this.data = JSON.parse(stored);
}
} catch (e) {
this.data = {};
}
}
clear() {
this.data = {};
this.save();
}
};
var expiredShapesCache = new ExpiredShapesCache();
// src/snapshot-tracker.ts
var SnapshotTracker = class {
constructor() {
this.activeSnapshots = /* @__PURE__ */ new Map();
this.xmaxSnapshots = /* @__PURE__ */ new Map();
this.snapshotsByDatabaseLsn = /* @__PURE__ */ new Map();
}
/**
* Add a new snapshot for tracking
*/
addSnapshot(metadata, keys) {
var _a, _b, _c, _d;
this.activeSnapshots.set(metadata.snapshot_mark, {
xmin: BigInt(metadata.xmin),
xmax: BigInt(metadata.xmax),
xip_list: metadata.xip_list.map(BigInt),
keys
});
const xmaxSet = (_b = (_a = this.xmaxSnapshots.get(BigInt(metadata.xmax))) == null ? void 0 : _a.add(metadata.snapshot_mark)) != null ? _b : /* @__PURE__ */ new Set([metadata.snapshot_mark]);
this.xmaxSnapshots.set(BigInt(metadata.xmax), xmaxSet);
const databaseLsnSet = (_d = (_c = this.snapshotsByDatabaseLsn.get(BigInt(metadata.database_lsn))) == null ? void 0 : _c.add(metadata.snapshot_mark)) != null ? _d : /* @__PURE__ */ new Set([metadata.snapshot_mark]);
this.snapshotsByDatabaseLsn.set(
BigInt(metadata.database_lsn),
databaseLsnSet
);
}
/**
* Remove a snapshot from tracking
*/
removeSnapshot(snapshotMark) {
this.activeSnapshots.delete(snapshotMark);
}
/**
* Check if a change message should be filtered because its already in an active snapshot
* Returns true if the message should be filtered out (not processed)
*/
shouldRejectMessage(message) {
const txids = message.headers.txids || [];
if (txids.length === 0) return false;
const xid = Math.max(...txids);
for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {
if (xid >= xmax) {
for (const snapshot of snapshots) {
this.removeSnapshot(snapshot);
}
}
}
return [...this.activeSnapshots.values()].some(
(x) => x.keys.has(message.key) && isVisibleInSnapshot(xid, x)
);
}
lastSeenUpdate(newDatabaseLsn) {
for (const [dbLsn, snapshots] of this.snapshotsByDatabaseLsn.entries()) {
if (dbLsn <= newDatabaseLsn) {
for (const snapshot of snapshots) {
this.removeSnapshot(snapshot);
}
}
}
}
};
// src/client.ts
var RESERVED_PARAMS = /* @__PURE__ */ new Set([
LIVE_CACHE_BUSTER_QUERY_PARAM,
SHAPE_HANDLE_QUERY_PARAM,
LIVE_QUERY_PARAM,
OFFSET_QUERY_PARAM
]);
async function resolveValue(value) {
if (typeof value === `function`) {
return value();
}
return value;
}
async function toInternalParams(params) {
const entries = Object.entries(params);
const resolvedEntries = await Promise.all(
entries.map(async ([key, value]) => {
if (value === void 0) return [key, void 0];
const resolvedValue = await resolveValue(value);
return [
key,
Array.isArray(resolvedValue) ? resolvedValue.join(`,`) : resolvedValue
];
})
);
return Object.fromEntries(
resolvedEntries.filter(([_, value]) => value !== void 0)
);
}
async function resolveHeaders(headers) {
if (!headers) return {};
const entries = Object.entries(headers);
const resolvedEntries = await Promise.all(
entries.map(async ([key, value]) => [key, await resolveValue(value)])
);
return Object.fromEntries(resolvedEntries);
}
function canonicalShapeKey(url) {
const cleanUrl = new URL(url.origin + url.pathname);
for (const [key, value] of url.searchParams) {
if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
cleanUrl.searchParams.set(key, value);
}
}
cleanUrl.searchParams.sort();
return cleanUrl.toString();
}
var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _isMidStream, _connected, _shapeHandle, _mode, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _activeSnapshotRequests, _midStreamPromise, _midStreamPromiseResolver, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _ShapeStream_instances, start_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, pause_fn, resume_fn, nextTick_fn, waitForStreamEnd_fn, publish_fn, sendErrorToSubscribers_fn, subscribeToVisibilityChanges_fn, reset_fn, fetchSnapshot_fn;
var ShapeStream = class {
// Maximum delay cap (ms)
constructor(options) {
__privateAdd(this, _ShapeStream_instances);
__privateAdd(this, _error, null);
__privateAdd(this, _fetchClient2);
__privateAdd(this, _sseFetchClient);
__privateAdd(this, _messageParser);
__privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
__privateAdd(this, _started, false);
__privateAdd(this, _state, `active`);
__privateAdd(this, _lastOffset);
__privateAdd(this, _liveCacheBuster);
// Seconds since our Electric Epoch 😎
__privateAdd(this, _lastSyncedAt);
// unix time
__privateAdd(this, _isUpToDate, false);
__privateAdd(this, _isMidStream, true);
__privateAdd(this, _connected, false);
__privateAdd(this, _shapeHandle);
__privateAdd(this, _mode);
__privateAdd(this, _schema);
__privateAdd(this, _onError);
__privateAdd(this, _requestAbortController);
__privateAdd(this, _isRefreshing, false);
__privateAdd(this, _tickPromise);
__privateAdd(this, _tickPromiseResolver);
__privateAdd(this, _tickPromiseRejecter);
__privateAdd(this, _messageChain, Promise.resolve([]));
// promise chain for incoming messages
__privateAdd(this, _snapshotTracker, new SnapshotTracker());
__privateAdd(this, _activeSnapshotRequests, 0);
// counter for concurrent snapshot requests
__privateAdd(this, _midStreamPromise);
__privateAdd(this, _midStreamPromiseResolver);
__privateAdd(this, _lastSseConnectionStartTime);
__privateAdd(this, _minSseConnectionDuration, 1e3);
// Minimum expected SSE connection duration (1 second)
__privateAdd(this, _consecutiveShortSseConnections, 0);
__privateAdd(this, _maxShortSseConnections, 3);
// Fall back to long polling after this many short connections
__privateAdd(this, _sseFallbackToLongPolling, false);
__privateAdd(this, _sseBackoffBaseDelay, 100);
// Base delay for exponential backoff (ms)
__privateAdd(this, _sseBackoffMaxDelay, 5e3);
var _a, _b, _c, _d;
this.options = __spreadValues({ subscribe: true }, options);
validateOptions(this.options);
__privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
__privateSet(this, _liveCacheBuster, ``);
__privateSet(this, _shapeHandle, this.options.handle);
__privateSet(this, _messageParser, new MessageParser(
options.parser,
options.transformer
));
__privateSet(this, _onError, this.options.onError);
__privateSet(this, _mode, (_b = this.options.log) != null ? _b : `full`);
const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
const backOffOpts = __spreadProps(__spreadValues({}, (_d = options.backoffOptions) != null ? _d : BackoffDefaults), {
onFailedAttempt: () => {
var _a2, _b2;
__privateSet(this, _connected, false);
(_b2 = (_a2 = options.backoffOptions) == null ? void 0 : _a2.onFailedAttempt) == null ? void 0 : _b2.call(_a2);
}
});
const fetchWithBackoffClient = createFetchWithBackoff(
baseFetchClient,
backOffOpts
);
__privateSet(this, _sseFetchClient, createFetchWithResponseHeadersCheck(
createFetchWithChunkBuffer(fetchWithBackoffClient)
));
__privateSet(this, _fetchClient2, createFetchWithConsumedMessages(__privateGet(this, _sseFetchClient)));
__privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
}
get shapeHandle() {
return __privateGet(this, _shapeHandle);
}
get error() {
return __privateGet(this, _error);
}
get isUpToDate() {
return __privateGet(this, _isUpToDate);
}
get lastOffset() {
return __privateGet(this, _lastOffset);
}
get mode() {
return __privateGet(this, _mode);
}
subscribe(callback, onError = () => {
}) {
const subscriptionId = Math.random();
__privateGet(this, _subscribers).set(subscriptionId, [callback, onError]);
if (!__privateGet(this, _started)) __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
return () => {
__privateGet(this, _subscribers).delete(subscriptionId);
};
}
unsubscribeAll() {
__privateGet(this, _subscribers).clear();
}
/** Unix time at which we last synced. Undefined when `isLoading` is true. */
lastSyncedAt() {
return __privateGet(this, _lastSyncedAt);
}
/** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
lastSynced() {
if (__privateGet(this, _lastSyncedAt) === void 0) return Infinity;
return Date.now() - __privateGet(this, _lastSyncedAt);
}
/** Indicates if we are connected to the Electric sync service. */
isConnected() {
return __privateGet(this, _connected);
}
/** True during initial fetch. False afterwise. */
isLoading() {
return !__privateGet(this, _isUpToDate);
}
hasStarted() {
return __privateGet(this, _started);
}
isPaused() {
return __privateGet(this, _state) === `paused`;
}
/**
* Refreshes the shape stream.
* This preemptively aborts any ongoing long poll and reconnects without
* long polling, ensuring that the stream receives an up to date message with the
* latest LSN from Postgres at that point in time.
*/
async forceDisconnectAndRefresh() {
var _a, _b;
__privateSet(this, _isRefreshing, true);
if (__privateGet(this, _isUpToDate) && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
(_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
}
await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
__privateSet(this, _isRefreshing, false);
}
/**
* Request a snapshot for subset of data.
*
* Only available when mode is `changes_only`.
* Returns the insertion point & the data, but more importantly injects the data
* into the subscribed data stream. Returned value is unlikely to be useful for the caller,
* unless the caller has complicated additional logic.
*
* Data will be injected in a way that's also tracking further incoming changes, and it'll
* skip the ones that are already in the snapshot.
*
* @param opts - The options for the snapshot request.
* @returns The metadata and the data for the snapshot.
*/
async requestSnapshot(opts) {
if (__privateGet(this, _mode) === `full`) {
throw new Error(
`Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
);
}
if (!__privateGet(this, _started)) await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
await __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
__privateWrapper(this, _activeSnapshotRequests)._++;
try {
if (__privateGet(this, _activeSnapshotRequests) === 1) {
__privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
}
const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
const { metadata, data } = await __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
const dataWithEndBoundary = data.concat([
{ headers: __spreadValues({ control: `snapshot-end` }, metadata) }
]);
__privateGet(this, _snapshotTracker).addSnapshot(
metadata,
new Set(data.map((message) => message.key))
);
__privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, dataWithEndBoundary, false);
return {
metadata,
data
};
} finally {
__privateWrapper(this, _activeSnapshotRequests)._--;
if (__privateGet(this, _activeSnapshotRequests) === 0) {
__privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
}
}
}
};
_error = new WeakMap();
_fetchClient2 = new WeakMap();
_sseFetchClient = new WeakMap();
_messageParser = new WeakMap();
_subscribers = new WeakMap();
_started = new WeakMap();
_state = new WeakMap();
_lastOffset = new WeakMap();
_liveCacheBuster = new WeakMap();
_lastSyncedAt = new WeakMap();
_isUpToDate = new WeakMap();
_isMidStream = new WeakMap();
_connected = new WeakMap();
_shapeHandle = new WeakMap();
_mode = new WeakMap();
_schema = new WeakMap();
_onError = new WeakMap();
_requestAbortController = new WeakMap();
_isRefreshing = new WeakMap();
_tickPromise = new WeakMap();
_tickPromiseResolver = new WeakMap();
_tickPromiseRejecter = new WeakMap();
_messageChain = new WeakMap();
_snapshotTracker = new WeakMap();
_activeSnapshotRequests = new WeakMap();
_midStreamPromise = new WeakMap();
_midStreamPromiseResolver = new WeakMap();
_lastSseConnectionStartTime = new WeakMap();
_minSseConnectionDuration = new WeakMap();
_consecutiveShortSseConnections = new WeakMap();
_maxShortSseConnections = new WeakMap();
_sseFallbackToLongPolling = new WeakMap();
_sseBackoffBaseDelay = new WeakMap();
_sseBackoffMaxDelay = new WeakMap();
_ShapeStream_instances = new WeakSet();
start_fn = async function() {
var _a, _b, _c, _d, _e;
__privateSet(this, _started, true);
try {
await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
} catch (err) {
__privateSet(this, _error, err);
if (__privateGet(this, _onError)) {
const retryOpts = await __privateGet(this, _onError).call(this, err);
if (retryOpts && typeof retryOpts === `object`) {
if (retryOpts.params) {
this.options.params = __spreadValues(__spreadValues({}, (_a = this.options.params) != null ? _a : {}), retryOpts.params);
}
if (retryOpts.headers) {
this.options.headers = __spreadValues(__spreadValues({}, (_b = this.options.headers) != null ? _b : {}), retryOpts.headers);
}
__privateSet(this, _error, null);
__privateSet(this, _started, false);
await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
return;
}
if (err instanceof Error) {
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
}
__privateSet(this, _connected, false);
(_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
return;
}
if (err instanceof Error) {
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
}
__privateSet(this, _connected, false);
(_d = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _d.call(this);
throw err;
}
__privateSet(this, _connected, false);
(_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
};
requestShape_fn = async function() {
var _a, _b;
if (__privateGet(this, _state) === `pause-requested`) {
__privateSet(this, _state, `paused`);
return;
}
if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _isUpToDate))) {
return;
}
const resumingFromPause = __privateGet(this, _state) === `paused`;
__privateSet(this, _state, `active`);
const { url, signal } = this.options;
const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
const requestAbortController = __privateGet(this, _requestAbortController);
try {
await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
fetchUrl,
requestAbortController,
headers: requestHeaders,
resumingFromPause
});
} catch (e) {
if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && requestAbortController.signal.aborted && requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH) {
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
}
if (e instanceof FetchBackoffAbortError) {
if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM) {
__privateSet(this, _state, `paused`);
}
return;
}
if (!(e instanceof FetchError)) throw e;
if (e.status == 409) {
if (__privateGet(this, _shapeHandle)) {
const shapeKey = canonicalShapeKey(fetchUrl);
expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
}
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, Array.isArray(e.json) ? e.json : [e.json]);
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
} else {
throw e;
}
} finally {
if (abortListener && signal) {
signal.removeEventListener(`abort`, abortListener);
}
__privateSet(this, _requestAbortController, void 0);
}
(_b = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _b.call(this);
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
};
constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
const [requestHeaders, params] = await Promise.all([
resolveHeaders(this.options.headers),
this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
]);
if (params) validateParams(params);
const fetchUrl = new URL(url);
if (params) {
if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
if (params.where) setQueryParam(fetchUrl, WHERE_QUERY_PARAM, params.where);
if (params.columns)
setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns);
if (params.replica) setQueryParam(fetchUrl, REPLICA_PARAM, params.replica);
if (params.params)
setQueryParam(fetchUrl, WHERE_PARAMS_PARAM, params.params);
const customParams = __spreadValues({}, params);
delete customParams.table;
delete customParams.where;
delete customParams.columns;
delete customParams.replica;
delete customParams.params;
for (const [key, value] of Object.entries(customParams)) {
setQueryParam(fetchUrl, key, value);
}
}
if (subsetParams) {
if (subsetParams.where)
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, subsetParams.where);
if (subsetParams.params)
setQueryParam(fetchUrl, SUBSET_PARAM_WHERE_PARAMS, subsetParams.params);
if (subsetParams.limit)
setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
if (subsetParams.offset)
setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
if (subsetParams.orderBy)
setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, subsetParams.orderBy);
}
fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
if (__privateGet(this, _isUpToDate)) {
if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
}
fetchUrl.searchParams.set(
LIVE_CACHE_BUSTER_QUERY_PARAM,
__privateGet(this, _liveCacheBuster)
);
}
if (__privateGet(this, _shapeHandle)) {
fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
}
const shapeKey = canonicalShapeKey(fetchUrl);
const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
if (expiredHandle) {
fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
}
fetchUrl.searchParams.sort();
return {
fetchUrl,
requestHeaders
};
};
createAbortListener_fn = async function(signal) {
var _a;
__privateSet(this, _requestAbortController, new AbortController());
if (signal) {
const abortListener = () => {
var _a2;
(_a2 = __privateGet(this, _requestAbortController)) == null ? void 0 : _a2.abort(signal.reason);
};
signal.addEventListener(`abort`, abortListener, { once: true });
if (signal.aborted) {
(_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.abort(signal.reason);
}
return abortListener;
}
};
onInitialResponse_fn = async function(response) {
var _a;
const { headers, status } = response;
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
if (shapeHandle) {
__privateSet(this, _shapeHandle, shapeHandle);
}
const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
if (lastOffset) {
__privateSet(this, _lastOffset, lastOffset);
}
const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
if (liveCacheBuster) {
__privateSet(this, _liveCacheBuster, liveCacheBuster);
}
const getSchema = () => {
const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
return schemaHeader ? JSON.parse(schemaHeader) : {};
};
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
if (status === 204) {
__privateSet(this, _lastSyncedAt, Date.now());
}
};
onMessages_fn = async function(batch, isSseMessage = false) {
var _a;
if (batch.length > 0) {
__privateSet(this, _isMidStream, true);
const lastMessage = batch[batch.length - 1];
if (isUpToDateMessage(lastMessage)) {
if (isSseMessage) {
const offset = getOffset(lastMessage);
if (offset) {
__privateSet(this, _lastOffset, offset);
}
}
__privateSet(this, _lastSyncedAt, Date.now());
__privateSet(this, _isUpToDate, true);
__privateSet(this, _isMidStream, false);
(_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
}
const messagesToProcess = batch.filter((message) => {
if (isChangeMessage(message)) {
return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
}
return true;
});
await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
}
};
fetchShape_fn = async function(opts) {
var _a;
const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`);
return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
}
return __privateMethod(this, _ShapeStream_instances, requestShapeLongPoll_fn).call(this, opts);
};
requestShapeLongPoll_fn = async function(opts) {
const { fetchUrl, requestAbortController, headers } = opts;
const response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
signal: requestAbortController.signal,
headers
});
__privateSet(this, _connected, true);
await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
const schema = __privateGet(this, _schema);
const res = await response.text();
const messages = res || `[]`;
const batch = __privateGet(this, _messageParser).parse(messages, schema);
await __privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, batch);
};
requestShapeSSE_fn = async function(opts) {
const { fetchUrl, requestAbortController, headers } = opts;
const fetch2 = __privateGet(this, _sseFetchClient);
__privateSet(this, _lastSseConnectionStartTime, Date.now());
try {
let buffer = [];
await fetchEventSource(fetchUrl.toString(), {
headers,
fetch: fetch2,
onopen: async (response) => {
__privateSet(this, _connected, true);
await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
},
onmessage: (event) => {
if (event.data) {
const schema = __privateGet(this, _schema);
const message = __privateGet(this, _messageParser).parse(
event.data,
schema
);
buffer.push(message);
if (isUpToDateMessage(message)) {
__privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, buffer, true);
buffer = [];
}
}
},
onerror: (error) => {
throw error;
},
signal: requestAbortController.signal
});
} catch (error) {
if (requestAbortController.signal.aborted) {
throw new FetchBackoffAbortError();
}
throw error;
} finally {
const connectionDuration = Date.now() - __privateGet(this, _lastSseConnectionStartTime);
const wasAborted = requestAbortController.signal.aborted;
if (connectionDuration < __privateGet(this, _minSseConnectionDuration) && !wasAborted) {
__privateWrapper(this, _consecutiveShortSseConnections)._++;
if (__privateGet(this, _consecutiveShortSseConnections) >= __privateGet(this, _maxShortSseConnections)) {
__privateSet(this, _sseFallbackToLongPolling, true);
console.warn(
`[Electric] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy. Note: Do NOT disable caching entirely - Electric uses cache headers to enable request collapsing for efficiency.`
);
} else {
const maxDelay = Math.min(
__privateGet(this, _sseBackoffMaxDelay),
__privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _consecutiveShortSseConnections))
);
const delayMs = Math.floor(Math.random() * maxDelay);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
} else if (connectionDuration >= __privateGet(this, _minSseConnectionDuration)) {
__privateSet(this, _consecutiveShortSseConnections, 0);
}
}
};
pause_fn = function() {
var _a;
if (__privateGet(this, _started) && __privateGet(this, _state) === `active`) {
__privateSet(this, _state, `pause-requested`);
(_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.abort(PAUSE_STREAM);
}
};
resume_fn = function() {
if (__privateGet(this, _started) && __privateGet(this, _state) === `paused`) {
__privateMethod(this, _ShapeStream_instances, start_fn).call(this);
}
};
nextTick_fn = async function() {
if (__privateGet(this, _tickPromise)) {
return __privateGet(this, _tickPromise);
}
__privateSet(this, _tickPromise, new Promise((resolve, reject) => {
__privateSet(this, _tickPromiseResolver, resolve);
__privateSet(this, _tickPromiseRejecter, reject);
}));
__privateGet(this, _tickPromise).finally(() => {
__privateSet(this, _tickPromise, void 0);
__privateSet(this, _tickPromiseResolver, void 0);
__privateSet(this, _tickPromiseRejecter, void 0);
});
return __privateGet(this, _tickPromise);
};
waitForStreamEnd_fn = async function() {
if (!__privateGet(this, _isMidStream)) {
return;
}
if (__privateGet(this, _midStreamPromise)) {
return __privateGet(this, _midStreamPromise);
}
__privateSet(this, _midStreamPromise, new Promise((resolve) => {
__privateSet(this,