@electric-sql/client
Version:
Postgres everywhere - your data, in sync, wherever you need it.
1,542 lines (1,528 loc) • 119 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);
}
};
var StaleCacheError = class extends Error {
constructor(message) {
super(message);
this.name = `StaleCacheError`;
}
};
// 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) {
return this.transformMessageValue(value, schema);
}
return value;
});
}
/**
* Parse an array of ChangeMessages from a snapshot response.
* Applies type parsing and transformations to the value and old_value properties.
*/
parseSnapshotData(messages, schema) {
return messages.map((message) => {
const msg = message;
if (msg.value && typeof msg.value === `object` && msg.value !== null) {
msg.value = this.transformMessageValue(msg.value, schema);
}
if (msg.old_value && typeof msg.old_value === `object` && msg.old_value !== null) {
msg.old_value = this.transformMessageValue(msg.old_value, schema);
}
return msg;
});
}
/**
* Transform a message value or old_value object by parsing its columns.
*/
transformMessageValue(value, schema) {
const row = value;
Object.keys(row).forEach((key) => {
row[key] = this.parseRow(key, row[key], schema);
});
return this.transformer ? this.transformer(row) : row;
}
// 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/column-mapper.ts
function quoteIdentifier(identifier) {
const escaped = identifier.replace(/"/g, `""`);
return `"${escaped}"`;
}
function snakeToCamel(str) {
var _a, _b, _c, _d;
const leadingUnderscores = (_b = (_a = str.match(/^_+/)) == null ? void 0 : _a[0]) != null ? _b : ``;
const withoutLeading = str.slice(leadingUnderscores.length);
const trailingUnderscores = (_d = (_c = withoutLeading.match(/_+$/)) == null ? void 0 : _c[0]) != null ? _d : ``;
const core = trailingUnderscores ? withoutLeading.slice(
0,
withoutLeading.length - trailingUnderscores.length
) : withoutLeading;
const normalized = core.toLowerCase();
const camelCased = normalized.replace(
/_+([a-z])/g,
(_, letter) => letter.toUpperCase()
);
return leadingUnderscores + camelCased + trailingUnderscores;
}
function camelToSnake(str) {
return str.replace(/([a-z])([A-Z])/g, `$1_$2`).replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`).toLowerCase();
}
function createColumnMapper(mapping) {
const reverseMapping = {};
for (const [dbName, appName] of Object.entries(mapping)) {
reverseMapping[appName] = dbName;
}
return {
decode: (dbColumnName) => {
var _a;
return (_a = mapping[dbColumnName]) != null ? _a : dbColumnName;
},
encode: (appColumnName) => {
var _a;
return (_a = reverseMapping[appColumnName]) != null ? _a : appColumnName;
}
};
}
function encodeWhereClause(whereClause, encode) {
if (!whereClause || !encode) return whereClause != null ? whereClause : ``;
const sqlKeywords = /* @__PURE__ */ new Set([
`SELECT`,
`FROM`,
`WHERE`,
`AND`,
`OR`,
`NOT`,
`IN`,
`IS`,
`NULL`,
`NULLS`,
`FIRST`,
`LAST`,
`TRUE`,
`FALSE`,
`LIKE`,
`ILIKE`,
`BETWEEN`,
`ASC`,
`DESC`,
`LIMIT`,
`OFFSET`,
`ORDER`,
`BY`,
`GROUP`,
`HAVING`,
`DISTINCT`,
`AS`,
`ON`,
`JOIN`,
`LEFT`,
`RIGHT`,
`INNER`,
`OUTER`,
`CROSS`,
`CASE`,
`WHEN`,
`THEN`,
`ELSE`,
`END`,
`CAST`,
`LOWER`,
`UPPER`,
`COALESCE`,
`NULLIF`
]);
const quotedRanges = [];
let pos = 0;
while (pos < whereClause.length) {
const ch = whereClause[pos];
if (ch === `'` || ch === `"`) {
const start = pos;
const quoteChar = ch;
pos++;
while (pos < whereClause.length) {
if (whereClause[pos] === quoteChar) {
if (whereClause[pos + 1] === quoteChar) {
pos += 2;
} else {
pos++;
break;
}
} else {
pos++;
}
}
quotedRanges.push({ start, end: pos });
} else {
pos++;
}
}
const isInQuotedString = (pos2) => {
return quotedRanges.some((range) => pos2 >= range.start && pos2 < range.end);
};
const identifierPattern = new RegExp("(?<![a-zA-Z0-9_])([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])", "g");
return whereClause.replace(identifierPattern, (match, _p1, offset) => {
if (isInQuotedString(offset)) {
return match;
}
if (sqlKeywords.has(match.toUpperCase())) {
return match;
}
if (match.startsWith(`$`)) {
return match;
}
const encoded = encode(match);
return encoded;
});
}
function snakeCamelMapper(schema) {
if (schema) {
const mapping = {};
for (const dbColumn of Object.keys(schema)) {
mapping[dbColumn] = snakeToCamel(dbColumn);
}
return createColumnMapper(mapping);
}
return {
decode: (dbColumnName) => {
return snakeToCamel(dbColumnName);
},
encode: (appColumnName) => {
return camelToSnake(appColumnName);
}
};
}
// src/helpers.ts
function isChangeMessage(message) {
return message != null && `key` in message;
}
function isControlMessage(message) {
return message != null && `headers` in message && `control` in message.headers;
}
function isUpToDateMessage(message) {
return isControlMessage(message) && message.headers.control === `up-to-date`;
}
function getOffset(message) {
if (message.headers.control != `up-to-date`) return;
const lsn = message.headers.global_last_seen_lsn;
return lsn ? `${lsn}_0` : void 0;
}
function bigintReplacer(_key, value) {
return typeof value === `bigint` ? value.toString() : value;
}
function bigintSafeStringify(value) {
return JSON.stringify(value, bigintReplacer);
}
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 SYSTEM_WAKE = `system-wake`;
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 SUBSET_PARAM_WHERE_EXPR = `subset__where_expr`;
var SUBSET_PARAM_ORDER_BY_EXPR = `subset__order_by_expr`;
var CACHE_BUSTER_QUERY_PARAM = `cache-buster`;
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,
SUBSET_PARAM_WHERE_EXPR,
SUBSET_PARAM_ORDER_BY_EXPR,
CACHE_BUSTER_QUERY_PARAM
];
// src/fetch.ts
var HTTP_RETRY_STATUS_CODES = [429];
var BackoffDefaults = {
initialDelay: 1e3,
maxDelay: 32e3,
multiplier: 2,
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 method = getRequestMethod(args[0], args[1]);
if (method !== `GET`) {
prefetchQueue == null ? void 0 : prefetchQueue.abort();
prefetchQueue = void 0;
return fetchClient(...args);
}
const prefetchedRequest = prefetchQueue == null ? void 0 : prefetchQueue.consume(...args);
if (prefetchedRequest) {
return prefetchedRequest;
}
prefetchQueue == null ? void 0 : prefetchQueue.abort();
prefetchQueue = void 0;
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 = [
CHUNK_LAST_OFFSET_HEADER,
SHAPE_HANDLE_HEADER
];
var requiredLiveResponseHeaders = [LIVE_CACHE_BUSTER_HEADER];
var requiredNonLiveResponseHeaders = [SHAPE_SCHEMA_HEADER];
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());
__privateGet(this, _prefetchQueue).clear();
}
consume(...args) {
const url = args[0].toString();
const entry = __privateGet(this, _prefetchQueue).get(url);
if (!entry || url !== __privateGet(this, _queueHeadUrl)) return;
const [request, aborter] = entry;
if (aborter.signal.aborted) {
__privateGet(this, _prefetchQueue).delete(url);
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;
const expiredHandle = nextUrl.searchParams.get(EXPIRED_HANDLE_QUERY_PARAM);
if (expiredHandle && shapeHandle === expiredHandle) {
console.warn(
`[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Skipping prefetch to prevent infinite 409 loop.`
);
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() {
}
function getRequestMethod(input, init) {
if (init == null ? void 0 : init.method) {
return init.method.toUpperCase();
}
if (typeof Request !== `undefined` && input instanceof Request) {
return input.method.toUpperCase();
}
return `GET`;
}
// src/expression-compiler.ts
function compileExpression(expr, columnMapper) {
switch (expr.type) {
case `ref`: {
const mappedColumn = columnMapper ? columnMapper(expr.column) : expr.column;
return quoteIdentifier(mappedColumn);
}
case `val`:
return `$${expr.paramIndex}`;
case `func`:
return compileFunction(expr, columnMapper);
default: {
const _exhaustive = expr;
throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustive)}`);
}
}
}
function compileFunction(expr, columnMapper) {
const args = expr.args.map((arg) => compileExpression(arg, columnMapper));
switch (expr.name) {
// Binary comparison operators
case `eq`:
return `${args[0]} = ${args[1]}`;
case `gt`:
return `${args[0]} > ${args[1]}`;
case `gte`:
return `${args[0]} >= ${args[1]}`;
case `lt`:
return `${args[0]} < ${args[1]}`;
case `lte`:
return `${args[0]} <= ${args[1]}`;
// Logical operators
case `and`:
return args.map((a) => `(${a})`).join(` AND `);
case `or`:
return args.map((a) => `(${a})`).join(` OR `);
case `not`:
return `NOT (${args[0]})`;
// Special operators
case `in`:
return `${args[0]} = ANY(${args[1]})`;
case `like`:
return `${args[0]} LIKE ${args[1]}`;
case `ilike`:
return `${args[0]} ILIKE ${args[1]}`;
case `isNull`:
case `isUndefined`:
return `${args[0]} IS NULL`;
// String functions
case `upper`:
return `UPPER(${args[0]})`;
case `lower`:
return `LOWER(${args[0]})`;
case `length`:
return `LENGTH(${args[0]})`;
case `concat`:
return `CONCAT(${args.join(`, `)})`;
// Other functions
case `coalesce`:
return `COALESCE(${args.join(`, `)})`;
default:
throw new Error(`Unknown function: ${expr.name}`);
}
}
function compileOrderBy(clauses, columnMapper) {
return clauses.map((clause) => {
const mappedColumn = columnMapper ? columnMapper(clause.column) : clause.column;
let sql = quoteIdentifier(mappedColumn);
if (clause.direction === `desc`) sql += ` DESC`;
if (clause.nulls === `first`) sql += ` NULLS FIRST`;
if (clause.nulls === `last`) sql += ` NULLS LAST`;
return sql;
}).join(`, `);
}
// ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/lib/esm/parse.js
async function getBytes(stream, onChunk) {
const reader = stream.getReader();
let result;
while (!(result = await reader.read()).done) {
onChunk(result.value);
}
}
function getLines(onLine) {
let buffer;
let position;
let fieldLength;
let discardTrailingNewline = false;
return function onChunk(arr) {
if (buffer === void 0) {
buffer = arr;
position = 0;
fieldLength = -1;
} else {
buffer = concat(buffer, arr);
}
const bufLength = buffer.length;
let lineStart = 0;
while (position < bufLength) {
if (discardTrailingNewline) {
if (buffer[position] === 10) {
lineStart = ++position;
}
discardTrailingNewline = false;
}
let lineEnd = -1;
for (; position < bufLength && lineEnd === -1; ++position) {
switch (buffer[position]) {
case 58:
if (fieldLength === -1) {
fieldLength = position - lineStart;
}
break;
case 13:
discardTrailingNewline = true;
case 10:
lineEnd = position;
break;
}
}
if (lineEnd === -1) {
break;
}
onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
lineStart = position;
fieldLength = -1;
}
if (lineStart === bufLength) {
buffer = void 0;
} else if (lineStart !== 0) {
buffer = buffer.subarray(lineStart);
position -= lineStart;
}
};
}
function getMessages(onId, onRetry, onMessage) {
let message = newMessage();
const decoder = new TextDecoder();
return function onLine(line, fieldLength) {
if (line.length === 0) {
onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
message = newMessage();
} else if (fieldLength > 0) {
const field = decoder.decode(line.subarray(0, fieldLength));
const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
const value = decoder.decode(line.subarray(valueOffset));
switch (field) {
case "data":
message.data = message.data ? message.data + "\n" + value : value;
break;
case "event":
message.event = value;
break;
case "id":
onId(message.id = value);
break;
case "retry":
const retry = parseInt(value, 10);
if (!isNaN(retry)) {
onRetry(message.retry = retry);
}
break;
}
}
};
}
function concat(a, b) {
const res = new Uint8Array(a.length + b.length);
res.set(a);
res.set(b, a.length);
return res;
}
function newMessage() {
return {
data: "",
event: "",
id: "",
retry: void 0
};
}
// ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/lib/esm/fetch.js
var __rest = function(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var EventStreamContentType = "text/event-stream";
var DefaultRetryInterval = 1e3;
var LastEventId = "last-event-id";
function fetchEventSource(input, _a) {
var { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch } = _a, rest = __rest(_a, ["signal", "headers", "onopen", "onmessage", "onclose", "onerror", "openWhenHidden", "fetch"]);
return new Promise((resolve, reject) => {
const headers = Object.assign({}, inputHeaders);
if (!headers.accept) {
headers.accept = EventStreamContentType;
}
let curRequestController;
function onVisibilityChange() {
curRequestController.abort();
if (typeof document !== "undefined" && !document.hidden) {
create();
}
}
if (typeof document !== "undefined" && !openWhenHidden) {
document.addEventListener("visibilitychange", onVisibilityChange);
}
let retryInterval = DefaultRetryInterval;
let retryTimer = 0;
function dispose() {
if (typeof document !== "undefined") {
document.removeEventListener("visibilitychange", onVisibilityChange);
}
clearTimeout(retryTimer);
curRequestController.abort();
}
inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener("abort", () => {
dispose();
});
const fetch2 = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
async function create() {
var _a2;
curRequestController = new AbortController();
const sig = inputSignal.aborted ? inputSignal : curRequestController.signal;
try {
const response = await fetch2(input, Object.assign(Object.assign({}, rest), { headers, signal: sig }));
await onopen(response);
await getBytes(response.body, getLines(getMessages((id) => {
if (id) {
headers[LastEventId] = id;
} else {
delete headers[LastEventId];
}
}, (retry) => {
retryInterval = retry;
}, onmessage)));
onclose === null || onclose === void 0 ? void 0 : onclose();
dispose();
resolve();
} catch (err) {
if (sig.aborted) {
dispose();
reject(err);
} else if (!curRequestController.signal.aborted) {
try {
const interval = (_a2 = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a2 !== void 0 ? _a2 : retryInterval;
clearTimeout(retryTimer);
retryTimer = setTimeout(create, interval);
} catch (innerErr) {
dispose();
reject(innerErr);
}
}
}
}
create();
});
}
function defaultOnOpen(response) {
const contentType = response.headers.get("content-type");
if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(EventStreamContentType))) {
throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
}
}
// 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();
}
delete(shapeUrl) {
delete this.data[shapeUrl];
this.save();
}
};
var expiredShapesCache = new ExpiredShapesCache();
// src/up-to-date-tracker.ts
var UpToDateTracker = class {
constructor() {
this.data = {};
this.storageKey = `electric_up_to_date_tracker`;
this.cacheTTL = 6e4;
// 60s to match typical CDN s-maxage cache duration
this.maxEntries = 250;
this.writeThrottleMs = 6e4;
// Throttle localStorage writes to once per 60s
this.lastWriteTime = 0;
this.load();
this.cleanup();
}
/**
* Records that a shape received an up-to-date message with a specific cursor.
* This timestamp and cursor are used to detect cache replay scenarios.
* Updates in-memory immediately, but throttles localStorage writes.
*/
recordUpToDate(shapeKey, cursor) {
this.data[shapeKey] = {
timestamp: Date.now(),
cursor
};
const keys = Object.keys(this.data);
if (keys.length > this.maxEntries) {
const oldest = keys.reduce(
(min, k) => this.data[k].timestamp < this.data[min].timestamp ? k : min
);
delete this.data[oldest];
}
this.scheduleSave();
}
/**
* Schedules a throttled save to localStorage.
* Writes immediately if enough time has passed, otherwise schedules for later.
*/
scheduleSave() {
const now = Date.now();
const timeSinceLastWrite = now - this.lastWriteTime;
if (timeSinceLastWrite >= this.writeThrottleMs) {
this.lastWriteTime = now;
this.save();
} else if (!this.pendingSaveTimer) {
const delay = this.writeThrottleMs - timeSinceLastWrite;
this.pendingSaveTimer = setTimeout(() => {
this.lastWriteTime = Date.now();
this.pendingSaveTimer = void 0;
this.save();
}, delay);
}
}
/**
* Checks if we should enter replay mode for this shape.
* Returns the last seen cursor if there's a recent up-to-date (< 60s),
* which means we'll likely be replaying cached responses.
* Returns null if no recent up-to-date exists.
*/
shouldEnterReplayMode(shapeKey) {
const entry = this.data[shapeKey];
if (!entry) {
return null;
}
const age = Date.now() - entry.timestamp;
if (age >= this.cacheTTL) {
return null;
}
return entry.cursor;
}
/**
* Cleans up expired entries from the cache.
* Called on initialization and can be called periodically.
*/
cleanup() {
const now = Date.now();
const keys = Object.keys(this.data);
let modified = false;
for (const key of keys) {
const age = now - this.data[key].timestamp;
if (age > this.cacheTTL) {
delete this.data[key];
modified = true;
}
}
if (modified) {
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 = {};
}
}
/**
* Clears all tracked up-to-date timestamps.
* Useful for testing or manual cache invalidation.
*/
clear() {
this.data = {};
if (this.pendingSaveTimer) {
clearTimeout(this.pendingSaveTimer);
this.pendingSaveTimer = void 0;
}
this.save();
}
delete(shapeKey) {
delete this.data[shapeKey];
this.save();
}
};
var upToDateTracker = new UpToDateTracker();
// 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/shape-stream-state.ts
var ShapeStreamState = class {
// --- Derived booleans ---
get isUpToDate() {
return false;
}
// --- Per-state field defaults ---
get staleCacheBuster() {
return void 0;
}
get staleCacheRetryCount() {
return 0;
}
get sseFallbackToLongPolling() {
return false;
}
get consecutiveShortSseConnections() {
return 0;
}
get replayCursor() {
return void 0;
}
// --- Default no-op methods ---
canEnterReplayMode() {
return false;
}
enterReplayMode(_cursor) {
return this;
}
shouldUseSse(_opts) {
return false;
}
handleSseConnectionClosed(_input) {
return {
state: this,
fellBackToLongPolling: false,
wasShortConnection: false
};
}
// --- URL param application ---
/** Adds state-specific query parameters to the fetch URL. */
applyUrlParams(_url, _context) {
}
// --- Default response/message handlers (Paused/Error never receive these) ---
handleResponseMetadata(_input) {
return { action: `ignored`, state: this };
}
handleMessageBatch(_input) {
return { state: this, suppressBatch: false, becameUpToDate: false };
}
pause() {
return new PausedState(this);
}
toErrorState(error) {
return new ErrorState(this, error);
}
markMustRefetch(handle) {
return new InitialState({
handle,
offset: `-1`,
liveCacheBuster: ``,
lastSyncedAt: this.lastSyncedAt,
schema: void 0
});
}
};
var _shared;
var ActiveState = class extends ShapeStreamState {
constructor(shared) {
super();
__privateAdd(this, _shared);
__privateSet(this, _shared, shared);
}
get handle() {
return __privateGet(this, _shared).handle;
}
get offset() {
return __privateGet(this, _shared).offset;
}
get schema() {
return __privateGet(this, _shared).schema;
}
get liveCacheBuster() {
return __privateGet(this, _shared).liveCacheBuster;
}
get lastSyncedAt() {
return __privateGet(this, _shared).lastSyncedAt;
}
/** Expose shared fields to subclasses for spreading into new instances. */
get currentFields() {
return __privateGet(this, _shared);
}
// --- URL param application ---
applyUrlParams(url, _context) {
url.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _shared).offset);
if (__privateGet(this, _shared).handle) {
url.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shared).handle);
}
}
// --- Helpers for subclass handleResponseMetadata implementations ---
/** Extracts updated SharedStateFields from response headers. */
parseResponseFields(input) {
var _a, _b, _c;
const responseHandle = input.responseHandle;
const handle = responseHandle && responseHandle !== input.expiredHandle ? responseHandle : __privateGet(this, _shared).handle;
const offset = (_a = input.responseOffset) != null ? _a : __privateGet(this, _shared).offset;
const liveCacheBuster = (_b = input.responseCursor) != null ? _b : __privateGet(this, _shared).liveCacheBuster;
const schema = (_c = __privateGet(this, _shared).schema) != null ? _c : input.responseSchema;
const lastSyncedAt = input.status === 204 ? input.now : __privateGet(this, _shared).lastSyncedAt;
return { handle, offset, schema, liveCacheBuster, lastSyncedAt };
}
/**
* Stale detection. Returns a transition if the response is stale,
* or null if it is not stale and the caller should proceed normally.
*/
checkStaleResponse(input) {
const responseHandle = input.responseHandle;
const expiredHandle = input.expiredHandle;
if (!responseHandle || responseHandle !== expiredHandle) {
return null;
}
const retryCount = this.staleCacheRetryCount + 1;
return {
action: `stale-retry`,
state: new StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
staleCacheBuster: input.createCacheBuster(),
staleCacheRetryCount: retryCount
})),
exceededMaxRetries: retryCount > input.maxStaleCacheRetries
};
}
// --- handleMessageBatch: template method with onUpToDate override point ---
handleMessageBatch(input) {
if (!input.hasMessages || !input.hasUpToDateMessage) {
return { state: this, suppressBatch: false, becameUpToDate: false };
}
let offset = __privateGet(this, _shared).offset;
if (input.isSse && input.upToDateOffset) {
offset = input.upToDateOffset;
}
const shared = {
handle: __privateGet(this, _shared).handle,
offset,
schema: __privateGet(this, _shared).schema,
liveCacheBuster: __privateGet(this, _shared).liveCacheBuster,
lastSyncedAt: input.now
};
return this.onUpToDate(shared, input);
}
/** Override point for up-to-date handling. Default → LiveState. */
onUpToDate(shared, _input) {
return {
state: new LiveState(shared),
suppressBatch: false,
becameUpToDate: true
};
}
};
_shared = new WeakMap();
var FetchingState = class extends ActiveState {
handleResponseMetadata(input) {
const staleResult = this.checkStaleResponse(input);
if (staleResult) return staleResult;
const shared = this.parseResponseFields(input);
if (input.status === 204) {
return {
action: `accepted`,
state: new LiveState(shared, { sseFallbackToLongPolling: true })
};
}
return { action: `accepted`, state: new SyncingState(shared) };
}
canEnterReplayMode() {
return true;
}
enterReplayMode(cursor) {
return new ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
replayCursor: cursor
}));
}
};
var InitialState = class _InitialState extends FetchingState {
constructor(shared) {
super(shared);
this.kind = `initial`;
}
withHandle(handle) {
return new _InitialState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
}
};
var SyncingState = class _SyncingState extends FetchingState {
constructor(shared) {
super(shared);
this.kind = `syncing`;
}
withHandle(handle) {
return new _SyncingState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
}
};
var _staleCacheBuster, _staleCacheRetryCount;
var _StaleRetryState = class _StaleRetryState extends FetchingState {
constructor(fields) {
const _a = fields, { staleCacheBuster, staleCacheRetryCount } = _a, shared = __objRest(_a, ["staleCacheBuster", "staleCacheRetryCount"]);
super(shared);
this.kind = `stale-retry`;
__privateAdd(this, _staleCacheBuster);
__privateAdd(this, _staleCacheRetryCount);
__privateSet(this, _staleCacheBuster, staleCacheBuster);
__privateSet(this, _staleCacheRetryCount, staleCacheRetryCount);
}
get staleCacheBuster() {
return __privateGet(this, _staleCacheBuster);
}
get staleCacheRetryCount() {
return __privateGet(this, _staleCacheRetryCount);
}
// StaleRetryState must not enter replay mode — it would lose the retry count
canEnterReplayMode() {
return false;
}
withHandle(handle) {
return new _StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
handle,
staleCacheBuster: __privateGet(this, _staleCacheBuster),