@electric-sql/client
Version:
Postgres everywhere - your data, in sync, wherever you need it.
1 lines • 278 kB
Source Map (JSON)
{"version":3,"sources":["../src/error.ts","../src/parser.ts","../src/column-mapper.ts","../src/helpers.ts","../src/constants.ts","../src/fetch.ts","../src/expression-compiler.ts","../../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/src/parse.ts","../../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/src/fetch.ts","../src/expired-shapes-cache.ts","../src/up-to-date-tracker.ts","../src/snapshot-tracker.ts","../src/shape-stream-state.ts","../src/pause-lock.ts","../src/runtime-visibility.ts","../src/client.ts","../src/shape.ts"],"sourcesContent":["export class FetchError extends Error {\n status: number\n text?: string\n json?: object\n headers: Record<string, string>\n\n constructor(\n status: number,\n text: string | undefined,\n json: object | undefined,\n headers: Record<string, string>,\n public url: string,\n message?: string\n ) {\n super(\n message ||\n `HTTP Error ${status} at ${url}: ${text ?? JSON.stringify(json)}`\n )\n this.name = `FetchError`\n this.status = status\n this.text = text\n this.json = json\n this.headers = headers\n }\n\n static async fromResponse(\n response: Response,\n url: string\n ): Promise<FetchError> {\n const status = response.status\n const headers = Object.fromEntries([...response.headers.entries()])\n let text: string | undefined = undefined\n let json: object | undefined = undefined\n\n const contentType = response.headers.get(`content-type`)\n if (!response.bodyUsed) {\n if (contentType && contentType.includes(`application/json`)) {\n json = (await response.json()) as object\n } else {\n text = await response.text()\n }\n }\n\n return new FetchError(status, text, json, headers, url)\n }\n}\n\nexport class FetchBackoffAbortError extends Error {\n constructor() {\n super(`Fetch with backoff aborted`)\n this.name = `FetchBackoffAbortError`\n }\n}\n\nexport class InvalidShapeOptionsError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `InvalidShapeOptionsError`\n }\n}\n\nexport class MissingShapeUrlError extends Error {\n constructor() {\n super(`Invalid shape options: missing required url parameter`)\n this.name = `MissingShapeUrlError`\n }\n}\n\nexport class InvalidSignalError extends Error {\n constructor() {\n super(`Invalid signal option. It must be an instance of AbortSignal.`)\n this.name = `InvalidSignalError`\n }\n}\n\nexport class MissingShapeHandleError extends Error {\n constructor() {\n super(\n `shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)`\n )\n this.name = `MissingShapeHandleError`\n }\n}\n\nexport class ReservedParamError extends Error {\n constructor(reservedParams: string[]) {\n super(\n `Cannot use reserved Electric parameter names in custom params: ${reservedParams.join(`, `)}`\n )\n this.name = `ReservedParamError`\n }\n}\n\nexport class ParserNullValueError extends Error {\n constructor(columnName: string) {\n super(`Column \"${columnName ?? `unknown`}\" does not allow NULL values`)\n this.name = `ParserNullValueError`\n }\n}\n\nexport class ShapeStreamAlreadyRunningError extends Error {\n constructor() {\n super(`ShapeStream is already running`)\n this.name = `ShapeStreamAlreadyRunningError`\n }\n}\n\nexport class MissingHeadersError extends Error {\n constructor(url: string, missingHeaders: Array<string>) {\n let msg = `The response for the shape request to ${url} didn't include the following required headers:\\n`\n missingHeaders.forEach((h) => {\n msg += `- ${h}\\n`\n })\n msg += `\\nThis is often due to a proxy not setting CORS correctly so that all Electric headers can be read by the client.`\n msg += `\\nFor more information visit the troubleshooting guide: /docs/guides/troubleshooting/missing-headers`\n super(msg)\n }\n}\n\nexport class StaleCacheError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `StaleCacheError`\n }\n}\n","import { ColumnInfo, GetExtensions, Row, Schema, Value } from './types'\nimport { ParserNullValueError } from './error'\n\ntype Token = string\ntype NullableToken = Token | null\nexport type ParseFunction<Extensions = never> = (\n value: Token,\n additionalInfo?: Omit<ColumnInfo, `type` | `dims`>\n) => Value<Extensions>\ntype NullableParseFunction<Extensions = never> = (\n value: NullableToken,\n additionalInfo?: Omit<ColumnInfo, `type` | `dims`>\n) => Value<Extensions>\n/**\n * @typeParam Extensions - Additional types that can be parsed by this parser beyond the standard SQL types.\n * Defaults to no additional types.\n */\nexport type Parser<Extensions = never> = {\n [key: string]: ParseFunction<Extensions>\n}\n\nexport type TransformFunction<Extensions = never> = (\n message: Row<Extensions>\n) => Row<Extensions>\n\nconst parseNumber = (value: string) => Number(value)\nconst parseBool = (value: string) => value === `true` || value === `t`\nconst parseBigInt = (value: string) => BigInt(value)\nconst parseJson = (value: string) => JSON.parse(value)\nconst identityParser: ParseFunction = (v: string) => v\n\nexport const defaultParser: Parser = {\n int2: parseNumber,\n int4: parseNumber,\n int8: parseBigInt,\n bool: parseBool,\n float4: parseNumber,\n float8: parseNumber,\n json: parseJson,\n jsonb: parseJson,\n}\n\n// Taken from: https://github.com/electric-sql/pglite/blob/main/packages/pglite/src/types.ts#L233-L279\nexport function pgArrayParser<Extensions>(\n value: Token,\n parser?: NullableParseFunction<Extensions>\n): Value<Extensions> {\n let i = 0\n let char = null\n let str = ``\n let quoted = false\n let last = 0\n let p: string | undefined = undefined\n\n function extractValue(x: Token, start: number, end: number) {\n let val: Token | null = x.slice(start, end)\n val = val === `NULL` ? null : val\n return parser ? parser(val) : val\n }\n\n function loop(x: string): Array<Value<Extensions>> {\n const xs = []\n for (; i < x.length; i++) {\n char = x[i]\n if (quoted) {\n if (char === `\\\\`) {\n str += x[++i]\n } else if (char === `\"`) {\n xs.push(parser ? parser(str) : str)\n str = ``\n quoted = x[i + 1] === `\"`\n last = i + 2\n } else {\n str += char\n }\n } else if (char === `\"`) {\n quoted = true\n } else if (char === `{`) {\n last = ++i\n xs.push(loop(x))\n } else if (char === `}`) {\n quoted = false\n last < i && xs.push(extractValue(x, last, i))\n last = i + 1\n break\n } else if (char === `,` && p !== `}` && p !== `\"`) {\n xs.push(extractValue(x, last, i))\n last = i + 1\n }\n p = char\n }\n last < i && xs.push(xs.push(extractValue(x, last, i + 1)))\n return xs\n }\n\n return loop(value)[0]\n}\n\nexport class MessageParser<T extends Row<unknown>> {\n private parser: Parser<GetExtensions<T>>\n private transformer?: TransformFunction<GetExtensions<T>>\n constructor(\n parser?: Parser<GetExtensions<T>>,\n transformer?: TransformFunction<GetExtensions<T>>\n ) {\n // Merge the provided parser with the default parser\n // to use the provided parser whenever defined\n // and otherwise fall back to the default parser\n this.parser = { ...defaultParser, ...parser }\n this.transformer = transformer\n }\n\n parse<Result>(messages: string, schema: Schema): Result {\n return JSON.parse(messages, (key, value) => {\n // typeof value === `object` && value !== null\n // is needed because there could be a column named `value`\n // and the value associated to that column will be a string or null.\n // But `typeof null === 'object'` so we need to make an explicit check.\n // We also parse the `old_value`, which appears on updates when `replica=full`.\n if (\n (key === `value` || key === `old_value`) &&\n typeof value === `object` &&\n value !== null\n ) {\n return this.transformMessageValue(value, schema)\n }\n return value\n }) as Result\n }\n\n /**\n * Parse an array of ChangeMessages from a snapshot response.\n * Applies type parsing and transformations to the value and old_value properties.\n */\n parseSnapshotData<Result>(\n messages: Array<unknown>,\n schema: Schema\n ): Array<Result> {\n return messages.map((message) => {\n const msg = message as Record<string, unknown>\n\n // Transform the value property if it exists\n if (msg.value && typeof msg.value === `object` && msg.value !== null) {\n msg.value = this.transformMessageValue(msg.value, schema)\n }\n\n // Transform the old_value property if it exists\n if (\n msg.old_value &&\n typeof msg.old_value === `object` &&\n msg.old_value !== null\n ) {\n msg.old_value = this.transformMessageValue(msg.old_value, schema)\n }\n\n return msg as Result\n })\n }\n\n /**\n * Transform a message value or old_value object by parsing its columns.\n */\n private transformMessageValue(\n value: unknown,\n schema: Schema\n ): Row<GetExtensions<T>> {\n const row = value as Record<string, Value<GetExtensions<T>>>\n Object.keys(row).forEach((key) => {\n row[key] = this.parseRow(key, row[key] as NullableToken, schema)\n })\n\n return this.transformer ? this.transformer(row) : row\n }\n\n // Parses the message values using the provided parser based on the schema information\n private parseRow(\n key: string,\n value: NullableToken,\n schema: Schema\n ): Value<GetExtensions<T>> {\n const columnInfo = schema[key]\n if (!columnInfo) {\n // We don't have information about the value\n // so we just return it\n return value\n }\n\n // Copy the object but don't include `dimensions` and `type`\n const { type: typ, dims: dimensions, ...additionalInfo } = columnInfo\n\n // Pick the right parser for the type\n // and support parsing null values if needed\n // if no parser is provided for the given type, just return the value as is\n const typeParser = this.parser[typ] ?? identityParser\n const parser = makeNullableParser(typeParser, columnInfo, key)\n\n if (dimensions && dimensions > 0) {\n // It's an array\n const nullablePgArrayParser = makeNullableParser(\n (value, _) => pgArrayParser(value, parser),\n columnInfo,\n key\n )\n return nullablePgArrayParser(value)\n }\n\n return parser(value, additionalInfo)\n }\n}\n\nfunction makeNullableParser<Extensions>(\n parser: ParseFunction<Extensions>,\n columnInfo: ColumnInfo,\n columnName?: string\n): NullableParseFunction<Extensions> {\n const isNullable = !(columnInfo.not_null ?? false)\n // The sync service contains `null` value for a column whose value is NULL\n // but if the column value is an array that contains a NULL value\n // then it will be included in the array string as `NULL`, e.g.: `\"{1,NULL,3}\"`\n return (value: NullableToken) => {\n if (value === null) {\n if (!isNullable) {\n throw new ParserNullValueError(columnName ?? `unknown`)\n }\n return null\n }\n return parser(value, columnInfo)\n }\n}\n","import { Schema } from './types'\n\ntype DbColumnName = string\ntype AppColumnName = string\n\n/**\n * Quote a PostgreSQL identifier for safe use in query parameters.\n *\n * Wraps the identifier in double quotes and escapes any internal\n * double quotes by doubling them. This ensures identifiers with\n * special characters (commas, spaces, etc.) are handled correctly.\n *\n * @param identifier - The identifier to quote\n * @returns The quoted identifier\n *\n * @example\n * ```typescript\n * quoteIdentifier('user_id') // '\"user_id\"'\n * quoteIdentifier('foo,bar') // '\"foo,bar\"'\n * quoteIdentifier('has\"quote') // '\"has\"\"quote\"'\n * ```\n *\n * @internal\n */\nexport function quoteIdentifier(identifier: string): string {\n // Escape internal double quotes by doubling them\n const escaped = identifier.replace(/\"/g, `\"\"`)\n return `\"${escaped}\"`\n}\n\n/**\n * A bidirectional column mapper that handles transforming column **names**\n * between database format (e.g., snake_case) and application format (e.g., camelCase).\n *\n * **Important**: ColumnMapper only transforms column names, not column values or types.\n * For type conversions (e.g., string → Date), use the `parser` option.\n * For value transformations (e.g., encryption), use the `transformer` option.\n *\n * @example\n * ```typescript\n * const mapper = snakeCamelMapper()\n * mapper.decode('user_id') // 'userId'\n * mapper.encode('userId') // 'user_id'\n * ```\n */\nexport interface ColumnMapper {\n /**\n * Transform a column name from database format to application format.\n * Applied to column names in query results.\n */\n decode: (dbColumnName: DbColumnName) => AppColumnName\n\n /**\n * Transform a column name from application format to database format.\n * Applied to column names in WHERE clauses and other query parameters.\n */\n encode: (appColumnName: AppColumnName) => DbColumnName\n}\n\n/**\n * Converts a snake_case string to camelCase.\n *\n * Handles edge cases:\n * - Preserves leading underscores: `_user_id` → `_userId`\n * - Preserves trailing underscores: `user_id_` → `userId_`\n * - Preserves multi-underscore count for injectivity:\n * `user__id` → `user_Id`, `user___id` → `user__Id`\n * - Normalizes to lowercase first: `user_Column` → `userColumn`\n *\n * The transform is designed to be injective on lowercase input and\n * round-trippable via `camelToSnake`, so distinct db columns never\n * collide on the same app key.\n *\n * @example\n * snakeToCamel('user_id') // 'userId'\n * snakeToCamel('project_id') // 'projectId'\n * snakeToCamel('created_at') // 'createdAt'\n * snakeToCamel('_private') // '_private'\n * snakeToCamel('user__id') // 'user_Id'\n * snakeToCamel('user___id') // 'user__Id'\n * snakeToCamel('user_id_') // 'userId_'\n */\nexport function snakeToCamel(str: string): string {\n // Preserve leading underscores\n const leadingUnderscores = str.match(/^_+/)?.[0] ?? ``\n const withoutLeading = str.slice(leadingUnderscores.length)\n\n // Preserve trailing underscores for round-trip safety\n const trailingUnderscores = withoutLeading.match(/_+$/)?.[0] ?? ``\n const core = trailingUnderscores\n ? withoutLeading.slice(\n 0,\n withoutLeading.length - trailingUnderscores.length\n )\n : withoutLeading\n\n // Convert to lowercase\n const normalized = core.toLowerCase()\n\n // Convert snake_case to camelCase. For a run of n underscores before\n // a letter, keep (n-1) literal underscores and uppercase the letter.\n // This preserves underscore count so distinct inputs never collide.\n const camelCased = normalized.replace(/_+([a-z])/g, (match, letter) => {\n const extraUnderscores = `_`.repeat(match.length - 2)\n return extraUnderscores + letter.toUpperCase()\n })\n\n return leadingUnderscores + camelCased + trailingUnderscores\n}\n\n/**\n * Converts a camelCase string to snake_case.\n *\n * Handles consecutive capitals (acronyms) properly:\n * - `userID` → `user_id`\n * - `userHTTPSURL` → `user_https_url`\n *\n * @example\n * camelToSnake('userId') // 'user_id'\n * camelToSnake('projectId') // 'project_id'\n * camelToSnake('createdAt') // 'created_at'\n * camelToSnake('userID') // 'user_id'\n * camelToSnake('parseHTMLString') // 'parse_html_string'\n */\nexport function camelToSnake(str: string): string {\n return (\n str\n // Insert underscore before uppercase letters that follow a\n // lowercase letter or an underscore. The `_` case preserves the\n // injective encoding from snakeToCamel: `user_Id` → `user__id`.\n .replace(/([a-z_])([A-Z])/g, `$1_$2`)\n // Insert underscore before uppercase letters that are followed by lowercase letters\n // This handles acronyms: userID -> user_ID, but parseHTMLString -> parse_HTML_String\n .replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`)\n .toLowerCase()\n )\n}\n\n/**\n * Creates a column mapper from an explicit mapping of database columns to application columns.\n *\n * @param mapping - Object mapping database column names (keys) to application column names (values)\n * @returns A ColumnMapper that can encode and decode column names bidirectionally\n *\n * @example\n * const mapper = createColumnMapper({\n * user_id: 'userId',\n * project_id: 'projectId',\n * created_at: 'createdAt'\n * })\n *\n * // Use with ShapeStream\n * const stream = new ShapeStream({\n * url: 'http://localhost:3000/v1/shape',\n * params: { table: 'todos' },\n * columnMapper: mapper\n * })\n */\nexport function createColumnMapper(\n mapping: Record<string, string>\n): ColumnMapper {\n // Build reverse mapping: app name -> db name\n const reverseMapping: Record<string, string> = {}\n for (const [dbName, appName] of Object.entries(mapping)) {\n reverseMapping[appName] = dbName\n }\n\n return {\n decode: (dbColumnName: string) => {\n return mapping[dbColumnName] ?? dbColumnName\n },\n\n encode: (appColumnName: string) => {\n return reverseMapping[appColumnName] ?? appColumnName\n },\n }\n}\n\n/**\n * Encodes column names in a WHERE clause using the provided encoder function.\n * Uses regex to identify column references and replace them.\n *\n * Handles common SQL patterns:\n * - Simple comparisons: columnName = $1\n * - Function calls: LOWER(columnName)\n * - Qualified names: table.columnName\n * - Operators: columnName IS NULL, columnName IN (...)\n * - Quoted strings: Preserves string literals unchanged\n *\n * Note: This uses regex-based replacement which works for most common cases\n * but may not handle all complex SQL expressions perfectly. For complex queries,\n * test thoroughly or use database column names directly in WHERE clauses.\n *\n * @param whereClause - The WHERE clause string to encode\n * @param encode - Optional encoder function. If undefined, returns whereClause unchanged.\n * @returns The encoded WHERE clause\n *\n * @internal\n */\nexport function encodeWhereClause(\n whereClause: string | undefined,\n encode?: (columnName: string) => string\n): string {\n if (!whereClause || !encode) return whereClause ?? ``\n\n // SQL keywords that should not be transformed (common ones)\n const sqlKeywords = new Set([\n `SELECT`,\n `FROM`,\n `WHERE`,\n `AND`,\n `OR`,\n `NOT`,\n `IN`,\n `IS`,\n `NULL`,\n `NULLS`,\n `FIRST`,\n `LAST`,\n `TRUE`,\n `FALSE`,\n `LIKE`,\n `ILIKE`,\n `BETWEEN`,\n `ASC`,\n `DESC`,\n `LIMIT`,\n `OFFSET`,\n `ORDER`,\n `BY`,\n `GROUP`,\n `HAVING`,\n `DISTINCT`,\n `AS`,\n `ON`,\n `JOIN`,\n `LEFT`,\n `RIGHT`,\n `INNER`,\n `OUTER`,\n `CROSS`,\n `CASE`,\n `WHEN`,\n `THEN`,\n `ELSE`,\n `END`,\n `CAST`,\n `LOWER`,\n `UPPER`,\n `COALESCE`,\n `NULLIF`,\n ])\n\n // Track positions of quoted strings and double-quoted identifiers to skip them\n const quotedRanges: Array<{ start: number; end: number }> = []\n\n // Find all single-quoted strings and double-quoted identifiers\n let pos = 0\n while (pos < whereClause.length) {\n const ch = whereClause[pos]\n if (ch === `'` || ch === `\"`) {\n const start = pos\n const quoteChar = ch\n pos++ // Skip opening quote\n // Find closing quote, handling escaped quotes ('' or \"\")\n while (pos < whereClause.length) {\n if (whereClause[pos] === quoteChar) {\n if (whereClause[pos + 1] === quoteChar) {\n pos += 2 // Skip escaped quote\n } else {\n pos++ // Skip closing quote\n break\n }\n } else {\n pos++\n }\n }\n quotedRanges.push({ start, end: pos })\n } else {\n pos++\n }\n }\n\n // Helper to check if position is within a quoted string or double-quoted identifier\n const isInQuotedString = (pos: number): boolean => {\n return quotedRanges.some((range) => pos >= range.start && pos < range.end)\n }\n\n // Pattern explanation:\n // (?<![a-zA-Z0-9_]) - negative lookbehind: not preceded by identifier char\n // ([a-zA-Z_][a-zA-Z0-9_]*) - capture: valid SQL identifier\n // (?![a-zA-Z0-9_]) - negative lookahead: not followed by identifier char\n //\n // This avoids matching:\n // - Parts of longer identifiers\n // - SQL keywords (handled by checking if result differs from input)\n const identifierPattern =\n /(?<![a-zA-Z0-9_])([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])/g\n\n return whereClause.replace(identifierPattern, (match, _p1, offset) => {\n // Don't transform if inside quoted string\n if (isInQuotedString(offset)) {\n return match\n }\n\n // Don't transform SQL keywords\n if (sqlKeywords.has(match.toUpperCase())) {\n return match\n }\n\n // Don't transform parameter placeholders ($1, $2, etc.)\n // This regex won't match them anyway, but being explicit\n if (match.startsWith(`$`)) {\n return match\n }\n\n // Apply encoding\n const encoded = encode(match)\n return encoded\n })\n}\n\n/**\n * Creates a column mapper that automatically converts between snake_case and camelCase.\n * This is the most common use case for column mapping.\n *\n * When a schema is provided, it will only map columns that exist in the schema.\n * Otherwise, it will map any column name it encounters.\n *\n * **⚠️ Limitations and Edge Cases:**\n * - **WHERE clause encoding**: Uses regex-based parsing which may not handle all complex\n * SQL expressions. Test thoroughly with your queries, especially those with:\n * - Complex nested expressions\n * - Custom operators or functions\n * - Column names that conflict with SQL keywords\n * - Quoted identifiers (e.g., `\"$price\"`, `\"user-id\"`) - not supported\n * - Column names with special characters (non-alphanumeric except underscore)\n * - **Acronym ambiguity**: `userID` → `user_id` → `userId` (ID becomes Id after roundtrip)\n * Use `createColumnMapper()` with explicit mapping if you need exact control\n * - **Type conversion**: This only renames columns, not values. Use `parser` for type conversion\n *\n * **When to use explicit mapping instead:**\n * - You have column names that don't follow snake_case/camelCase patterns\n * - You need exact control over mappings (e.g., `id` → `identifier`)\n * - Your WHERE clauses are complex and automatic encoding fails\n * - You have quoted identifiers or column names with special characters\n *\n * @param schema - Optional database schema to constrain mapping to known columns\n * @returns A ColumnMapper for snake_case ↔ camelCase conversion\n *\n * @example\n * // Basic usage\n * const mapper = snakeCamelMapper()\n *\n * // With schema - only maps columns in schema (recommended)\n * const mapper = snakeCamelMapper(schema)\n *\n * // Use with ShapeStream\n * const stream = new ShapeStream({\n * url: 'http://localhost:3000/v1/shape',\n * params: { table: 'todos' },\n * columnMapper: snakeCamelMapper()\n * })\n *\n * @example\n * // If automatic encoding fails, fall back to manual column names in WHERE clauses:\n * stream.requestSnapshot({\n * where: \"user_id = $1\", // Use database column names directly if needed\n * params: { \"1\": \"123\" }\n * })\n */\nexport function snakeCamelMapper(schema?: Schema): ColumnMapper {\n // If schema provided, build explicit mapping\n if (schema) {\n const mapping: Record<string, string> = {}\n for (const dbColumn of Object.keys(schema)) {\n mapping[dbColumn] = snakeToCamel(dbColumn)\n }\n return createColumnMapper(mapping)\n }\n\n // Otherwise, map dynamically\n return {\n decode: (dbColumnName: string) => {\n return snakeToCamel(dbColumnName)\n },\n\n encode: (appColumnName: string) => {\n return camelToSnake(appColumnName)\n },\n }\n}\n","import {\n ChangeMessage,\n ControlMessage,\n Message,\n NormalizedPgSnapshot,\n Offset,\n PostgresSnapshot,\n Row,\n} from './types'\n\n/**\n * Type guard for checking {@link Message} is {@link ChangeMessage}.\n *\n * See [TS docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)\n * for information on how to use type guards.\n *\n * @param message - the message to check\n * @returns true if the message is a {@link ChangeMessage}\n *\n * @example\n * ```ts\n * if (isChangeMessage(message)) {\n * const msgChng: ChangeMessage = message // Ok\n * const msgCtrl: ControlMessage = message // Err, type mismatch\n * }\n * ```\n */\nexport function isChangeMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ChangeMessage<T> {\n return message != null && `key` in message\n}\n\n/**\n * Type guard for checking {@link Message} is {@link ControlMessage}.\n *\n * See [TS docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)\n * for information on how to use type guards.\n *\n * @param message - the message to check\n * @returns true if the message is a {@link ControlMessage}\n *\n * * @example\n * ```ts\n * if (isControlMessage(message)) {\n * const msgChng: ChangeMessage = message // Err, type mismatch\n * const msgCtrl: ControlMessage = message // Ok\n * }\n * ```\n */\nexport function isControlMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ControlMessage {\n return message != null && `headers` in message && `control` in message.headers\n}\n\nexport function isUpToDateMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n\n/**\n * Parses the LSN from the up-to-date message and turns it into an offset.\n * The LSN is only present in the up-to-date control message when in SSE mode.\n * If we are not in SSE mode this function will return undefined.\n */\nexport function getOffset(message: ControlMessage): Offset | undefined {\n if (message.headers.control != `up-to-date`) return\n const lsn = message.headers.global_last_seen_lsn\n return lsn ? (`${lsn}_0` as Offset) : undefined\n}\n\nfunction bigintReplacer(_key: string, value: unknown): unknown {\n return typeof value === `bigint` ? value.toString() : value\n}\n\n/**\n * BigInt-safe version of JSON.stringify.\n * Converts BigInt values to their string representation (as JSON strings,\n * e.g. `{ id: 42n }` becomes `{\"id\":\"42\"}`) instead of throwing.\n * Assumes input is a JSON-serializable value — passing `undefined` at the\n * top level will return `undefined` (matching `JSON.stringify` behavior).\n */\nexport function bigintSafeStringify(value: unknown): string {\n return JSON.stringify(value, bigintReplacer)\n}\n\n/**\n * Canonical, BigInt-safe stringify. Recursively sorts object keys so that\n * permutation-equivalent inputs produce identical output. Suitable for\n * dedup keys and cache lookups.\n */\nexport function canonicalBigintSafeStringify(value: unknown): string {\n return JSON.stringify(canonicalize(value))\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (typeof value === `bigint`) return value.toString()\n if (value === null || typeof value !== `object`) return value\n if (Array.isArray(value)) return value.map(canonicalize)\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = canonicalize((value as Record<string, unknown>)[k])\n }\n return sorted\n}\n\n/**\n * Checks if a transaction is visible in a snapshot.\n *\n * @param txid - the transaction id to check\n * @param snapshot - the information about the snapshot\n * @returns true if the transaction is visible in the snapshot\n */\nexport function isVisibleInSnapshot(\n txid: number | bigint | `${bigint}`,\n snapshot: PostgresSnapshot | NormalizedPgSnapshot\n): boolean {\n const xid = BigInt(txid)\n const xmin = BigInt(snapshot.xmin)\n const xmax = BigInt(snapshot.xmax)\n const xip = snapshot.xip_list.map(BigInt)\n\n // If the transaction id is less than the minimum transaction id, it is visible in the snapshot.\n // If the transaction id is less than the maximum transaction id and not in the list of active\n // transactions at the time of the snapshot, it has been committed before the snapshot was taken\n // and is therefore visible in the snapshot.\n // Otherwise, it is not visible in the snapshot.\n\n return xid < xmin || (xid < xmax && !xip.includes(xid))\n}\n","export const LIVE_CACHE_BUSTER_HEADER = `electric-cursor`\nexport const SHAPE_HANDLE_HEADER = `electric-handle`\nexport const CHUNK_LAST_OFFSET_HEADER = `electric-offset`\nexport const SHAPE_SCHEMA_HEADER = `electric-schema`\nexport const CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`\nexport const SNAPSHOT_HEADER = `electric-snapshot`\nexport const COLUMNS_QUERY_PARAM = `columns`\nexport const LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`\nexport const EXPIRED_HANDLE_QUERY_PARAM = `expired_handle`\nexport const SHAPE_HANDLE_QUERY_PARAM = `handle`\nexport const LIVE_QUERY_PARAM = `live`\nexport const OFFSET_QUERY_PARAM = `offset`\nexport const TABLE_QUERY_PARAM = `table`\nexport const WHERE_QUERY_PARAM = `where`\nexport const REPLICA_PARAM = `replica`\nexport const WHERE_PARAMS_PARAM = `params`\n/**\n * @deprecated Use {@link LIVE_SSE_QUERY_PARAM} instead.\n */\nexport const EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`\nexport const LIVE_SSE_QUERY_PARAM = `live_sse`\nexport const FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`\nexport const PAUSE_STREAM = `pause-stream`\nexport const SYSTEM_WAKE = `system-wake`\nexport const LIVE_REQUEST_TIMEOUT = `live-request-timeout`\nexport const LOG_MODE_QUERY_PARAM = `log`\nexport const SUBSET_PARAM_WHERE = `subset__where`\nexport const SUBSET_PARAM_LIMIT = `subset__limit`\nexport const SUBSET_PARAM_OFFSET = `subset__offset`\nexport const SUBSET_PARAM_ORDER_BY = `subset__order_by`\nexport const SUBSET_PARAM_WHERE_PARAMS = `subset__params`\nexport const SUBSET_PARAM_WHERE_EXPR = `subset__where_expr`\nexport const SUBSET_PARAM_ORDER_BY_EXPR = `subset__order_by_expr`\nexport const CACHE_BUSTER_QUERY_PARAM = `cache-buster` // Random cache buster to bypass stale CDN responses\n\n// Query parameters that should be passed through when proxying Electric requests\nexport const ELECTRIC_PROTOCOL_QUERY_PARAMS: Array<string> = [\n LIVE_QUERY_PARAM,\n LIVE_SSE_QUERY_PARAM,\n EXPERIMENTAL_LIVE_SSE_QUERY_PARAM,\n SHAPE_HANDLE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n EXPIRED_HANDLE_QUERY_PARAM,\n LOG_MODE_QUERY_PARAM,\n SUBSET_PARAM_WHERE,\n SUBSET_PARAM_LIMIT,\n SUBSET_PARAM_OFFSET,\n SUBSET_PARAM_ORDER_BY,\n SUBSET_PARAM_WHERE_PARAMS,\n SUBSET_PARAM_WHERE_EXPR,\n SUBSET_PARAM_ORDER_BY_EXPR,\n CACHE_BUSTER_QUERY_PARAM,\n]\n","import {\n CHUNK_LAST_OFFSET_HEADER,\n CHUNK_UP_TO_DATE_HEADER,\n EXPIRED_HANDLE_QUERY_PARAM,\n LIVE_CACHE_BUSTER_HEADER,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_SCHEMA_HEADER,\n SHAPE_HANDLE_HEADER,\n SHAPE_HANDLE_QUERY_PARAM,\n SNAPSHOT_HEADER,\n SUBSET_PARAM_LIMIT,\n SUBSET_PARAM_OFFSET,\n SUBSET_PARAM_ORDER_BY,\n SUBSET_PARAM_WHERE,\n SUBSET_PARAM_WHERE_PARAMS,\n} from './constants'\nimport {\n FetchError,\n FetchBackoffAbortError,\n MissingHeadersError,\n} from './error'\n\n// Some specific 4xx and 5xx HTTP status codes that we definitely\n// want to retry\nconst HTTP_RETRY_STATUS_CODES = [429]\n\nexport interface BackoffOptions {\n /**\n * Initial delay before retrying in milliseconds\n */\n initialDelay: number\n /**\n * Maximum retry delay in milliseconds\n * After reaching this, delay stays constant (e.g., retry every 60s)\n */\n maxDelay: number\n multiplier: number\n onFailedAttempt?: () => void\n debug?: boolean\n /**\n * Maximum number of retry attempts before giving up.\n * Set to Infinity (default) for indefinite retries - needed for offline scenarios\n * where clients may go offline and come back later.\n */\n maxRetries?: number\n}\n\nexport const BackoffDefaults = {\n initialDelay: 1_000,\n maxDelay: 32_000,\n multiplier: 2,\n maxRetries: Infinity, // Retry forever - clients may go offline and come back\n}\n\n/**\n * Parse Retry-After header value and return delay in milliseconds\n * Supports both delta-seconds format and HTTP-date format\n * Returns 0 if header is not present or invalid\n */\nexport function parseRetryAfterHeader(retryAfter: string | undefined): number {\n if (!retryAfter) return 0\n\n // Try parsing as seconds (delta-seconds format)\n const retryAfterSec = Number(retryAfter)\n if (Number.isFinite(retryAfterSec) && retryAfterSec > 0) {\n return retryAfterSec * 1000\n }\n\n // Try parsing as HTTP-date\n const retryDate = Date.parse(retryAfter)\n if (!isNaN(retryDate)) {\n // Handle clock skew: clamp to non-negative, cap at reasonable max\n const deltaMs = retryDate - Date.now()\n return Math.max(0, Math.min(deltaMs, 3600_000)) // Cap at 1 hour\n }\n\n return 0\n}\n\nasync function abortableSleep(\n waitMs: number,\n signal?: AbortSignal\n): Promise<void> {\n if (waitMs <= 0) return\n if (signal?.aborted) throw new FetchBackoffAbortError()\n\n await new Promise<void>((resolve, reject) => {\n let settled = false\n const done = (err?: Error) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n signal?.removeEventListener(`abort`, onAbort)\n err ? reject(err) : resolve()\n }\n const onAbort = () => done(new FetchBackoffAbortError())\n const timer = setTimeout(() => done(), waitMs)\n signal?.addEventListener(`abort`, onAbort, { once: true })\n if (signal?.aborted) onAbort()\n })\n}\n\nexport function createFetchWithBackoff(\n fetchClient: typeof fetch,\n backoffOptions: BackoffOptions = BackoffDefaults\n): typeof fetch {\n const {\n initialDelay,\n maxDelay,\n multiplier,\n debug = false,\n onFailedAttempt,\n maxRetries = Infinity,\n } = backoffOptions\n return async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const url = args[0]\n const options = args[1]\n\n let delay = initialDelay\n let attempt = 0\n\n while (true) {\n try {\n const result = await fetchClient(...args)\n if (result.ok) {\n return result\n }\n\n const err = await FetchError.fromResponse(result, url.toString())\n\n throw err\n } catch (e) {\n onFailedAttempt?.()\n if (options?.signal?.aborted) {\n throw new FetchBackoffAbortError()\n } else if (\n e instanceof FetchError &&\n !HTTP_RETRY_STATUS_CODES.includes(e.status) &&\n e.status >= 400 &&\n e.status < 500\n ) {\n // Any client errors cannot be backed off on, leave it to the caller to handle.\n throw e\n } else {\n // Check max retries\n attempt++\n if (attempt > maxRetries) {\n if (debug) {\n console.log(\n `Max retries reached (${attempt}/${maxRetries}), giving up`\n )\n }\n throw e\n }\n\n // Calculate wait time honoring server-driven backoff as a floor\n // Precedence: max(serverMinimum, min(clientMaxDelay, backoffWithJitter))\n\n // 1. Parse server-provided Retry-After (if present)\n const serverMinimumMs =\n e instanceof FetchError && e.headers\n ? parseRetryAfterHeader(e.headers[`retry-after`])\n : 0\n\n // 2. Calculate client backoff with full jitter strategy\n // Full jitter: random_between(0, min(cap, exponential_backoff))\n // See: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n const jitter = Math.random() * delay // random value between 0 and current delay\n const clientBackoffMs = Math.min(jitter, maxDelay) // cap at maxDelay\n\n // 3. Server minimum is the floor, client cap is the ceiling\n const waitMs = Math.max(serverMinimumMs, clientBackoffMs)\n\n if (debug) {\n const source = serverMinimumMs > 0 ? `server+client` : `client`\n console.log(\n `Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`\n )\n }\n\n // Wait for the calculated duration, but let stream-level aborts\n // interrupt the backoff immediately (e.g. system wake reconnects).\n await abortableSleep(waitMs, options?.signal ?? undefined)\n\n // Increase the delay for the next attempt (capped at maxDelay)\n delay = Math.min(delay * multiplier, maxDelay)\n }\n }\n }\n }\n}\n\nconst NO_BODY_STATUS_CODES = [201, 204, 205]\n\nexport async function consumeResponseBody(\n res: Response,\n url: string,\n signal?: AbortSignal\n): Promise<Response> {\n try {\n if (res.status < 200 || NO_BODY_STATUS_CODES.includes(res.status)) {\n return res\n }\n\n const text = await res.text()\n return new Response(text, res)\n } catch (err) {\n if (signal?.aborted) {\n throw new FetchBackoffAbortError()\n }\n\n throw new FetchError(\n res.status,\n undefined,\n undefined,\n Object.fromEntries([...res.headers.entries()]),\n url,\n err instanceof Error\n ? err.message\n : typeof err === `string`\n ? err\n : `failed to read body`\n )\n }\n}\n\n// Ensure body can actually be read in its entirety\nexport function createFetchWithConsumedMessages(fetchClient: typeof fetch) {\n return async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const url = args[0]\n const res = await fetchClient(...args)\n return consumeResponseBody(\n res,\n url.toString(),\n args[1]?.signal ?? undefined\n )\n }\n}\n\ninterface ChunkPrefetchOptions {\n maxChunksToPrefetch: number\n}\n\nconst ChunkPrefetchDefaults = {\n maxChunksToPrefetch: 2,\n}\n\n/**\n * Creates a fetch client that prefetches subsequent log chunks for\n * consumption by the shape stream without waiting for the chunk bodies\n * themselves to be loaded.\n *\n * @param fetchClient the client to wrap\n * @param prefetchOptions options to configure prefetching\n * @returns wrapped client with prefetch capabilities\n */\nexport function createFetchWithChunkBuffer(\n fetchClient: typeof fetch,\n prefetchOptions: ChunkPrefetchOptions = ChunkPrefetchDefaults\n): typeof fetch {\n const { maxChunksToPrefetch } = prefetchOptions\n\n let prefetchQueue: PrefetchQueue | undefined\n\n const prefetchClient = async (...args: Parameters<typeof fetchClient>) => {\n const url = args[0].toString()\n const method = getRequestMethod(args[0], args[1])\n\n // Prefetch is only valid for GET requests. The prefetch queue matches\n // requests by URL alone and ignores HTTP method/body, so a POST request\n // with the same URL would incorrectly consume the prefetched stream\n // response instead of making its own request.\n if (method !== `GET`) {\n prefetchQueue?.abort()\n prefetchQueue = undefined\n return fetchClient(...args)\n }\n\n // try to consume from the prefetch queue first, and if request is\n // not present abort the prefetch queue as it must no longer be valid\n const prefetchedRequest = prefetchQueue?.consume(...args)\n if (prefetchedRequest) {\n return prefetchedRequest\n }\n\n // Clear the prefetch queue after aborting to prevent returning\n // stale/aborted requests on future calls with the same URL\n prefetchQueue?.abort()\n prefetchQueue = undefined\n\n // perform request and fire off prefetch queue if request is eligible\n const response = await fetchClient(...args)\n const nextUrl = getNextChunkUrl(url, response)\n if (nextUrl) {\n prefetchQueue = new PrefetchQueue({\n fetchClient,\n maxPrefetchedRequests: maxChunksToPrefetch,\n url: nextUrl,\n requestInit: args[1],\n })\n }\n\n return response\n }\n\n return prefetchClient\n}\n\nexport const requiredElectricResponseHeaders = [\n CHUNK_LAST_OFFSET_HEADER,\n SHAPE_HANDLE_HEADER,\n]\n\nexport const requiredLiveResponseHeaders = [LIVE_CACHE_BUSTER_HEADER]\n\nexport const requiredNonLiveResponseHeaders = [SHAPE_SCHEMA_HEADER]\n\nexport function createFetchWithResponseHeadersCheck(\n fetchClient: typeof fetch\n): typeof fetch {\n return async (...args: Parameters<typeof fetchClient>) => {\n const response = await fetchClient(...args)\n\n if (response.ok) {\n // Check that the necessary Electric headers are present on the response\n const headers = response.headers\n const missingHeaders: Array<string> = []\n\n const addMissingHeaders = (requiredHeaders: Array<string>) =>\n missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)))\n\n const input = args[0]\n const urlString = input.toString()\n const url = new URL(urlString)\n\n // Snapshot responses (subset params) return a JSON object and do not include Electric chunk headers\n const isSnapshotRequest = [\n SUBSET_PARAM_WHERE,\n SUBSET_PARAM_WHERE_PARAMS,\n SUBSET_PARAM_LIMIT,\n SUBSET_PARAM_OFFSET,\n SUBSET_PARAM_ORDER_BY,\n ].some((p) => url.searchParams.has(p))\n if (isSnapshotRequest) {\n return response\n }\n\n addMissingHeaders(requiredElectricResponseHeaders)\n if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {\n addMissingHeaders(requiredLiveResponseHeaders)\n }\n\n if (\n !url.searchParams.has(LIVE_QUERY_PARAM) ||\n url.searchParams.get(LIVE_QUERY_PARAM) === `false`\n ) {\n addMissingHeaders(requiredNonLiveResponseHeaders)\n }\n\n if (missingHeaders.length > 0) {\n throw new MissingHeadersError(urlString, missingHeaders)\n }\n }\n\n return response\n }\n}\n\nclass PrefetchQueue {\n readonly #fetchClient: typeof fetch\n readonly #maxPrefetchedRequests: number\n readonly #prefetchQueue = new Map<\n string,\n [Promise<Response>, AbortController]\n >()\n #queueHeadUrl: string | void\n #queueTailUrl: string | void\n\n constructor(options: {\n url: Parameters<typeof fetch>[0]\n requestInit: Parameters<typeof fetch>[1]\n maxPrefetchedRequests: number\n fetchClient?: typeof fetch\n }) {\n this.#fetchClient =\n options.fetchClient ??\n ((...args: Parameters<typeof fetch>) => fetch(...args))\n this.#maxPrefetchedRequests = options.maxPrefetchedRequests\n this.#queueHeadUrl = options.url.toString()\n this.#queueTailUrl = this.#queueHeadUrl\n this.#prefetch(options.url, options.requestInit)\n }\n\n abort(): void {\n this.#prefetchQueue.forEach(([_, aborter]) => aborter.abort())\n this.#prefetchQueue.clear()\n }\n\n consume(...args: Parameters<typeof fetch>): Promise<Response> | void {\n const url = args[0].toString()\n\n const entry = this.#prefetchQueue.get(url)\n // only consume if request is in queue and is the queue \"head\"\n // if request is in the queue but not the head, the queue is being\n // consumed out of order and should be restarted\n if (!entry || url !== this.#queueHeadUrl) return\n\n const [request, aborter] = entry\n // Don't return aborted requests - they will reject with AbortError\n if (aborter.signal.aborted) {\n this.#prefetchQueue.delete(url)\n return\n }\n this.#prefetchQueue.delete(url)\n\n // fire off new prefetch since request has been consumed\n request\n .then((response) => {\n const nextUrl = getNextChunkUrl(url, response)\n this.#queueHeadUrl = nextUrl\n if (\n this.#queueTailUrl &&\n !this.#prefetchQueue.has(this.#queueTailUrl)\n ) {\n this.#prefetch(this.#queueTailUrl, args[1])\n }\n })\n .catch(() => {})\n\n return request\n }\n\n #prefetch(...args: Parameters<typeof fetch>): void {\n const url = args[0].toString()\n\n // only prefetch when queue is not full\n if (this.#prefetchQueue.size >= this.#maxPrefetchedRequests) return\n\n // initialize aborter per request, to avoid aborting consumed requests that\n // are still streaming their bodies to the consumer\n const aborter = new AbortController()\n\n try {\n const { signal, cleanup } = chainAborter(aborter, args[1]?.signal)\n const request = this.#fetchClient(url, { ...(args[1] ?? {}), signal })\n this.#prefetchQueue.set(url, [request, aborter])\n request\n .then((response) => {\n // only keep prefetching if response chain is uninterrupted\n if (!response.ok || aborter.signal.aborted) return\n\n const nextUrl = getNextChunkUrl(url, response)\n\n // only prefetch when there is a next URL\n if (!nextUrl || nextUrl === url) {\n this.#queueTailUrl = undefined\n return\n }\n\n this.#queueTailUrl = nextUrl\n return this.#prefetch(nextUrl, args[1])\n })\n .catch(() => {})\n .finally(cleanup)\n } catch (_) {\n // ignore prefetch errors\n }\n }\n}\n\n/**\n * Generate the next chunk's URL if the url and response are valid\n */\nfunction getNextChunkUrl(url: string, res: Response): string | void {\n const shapeHandle = res.headers.get(SHAPE_HANDLE_HEADER)\n const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER)\n const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER)\n const isSnapshot = res.headers.get(SNAPSHOT_HEADER) === `true`\n\n // only prefetch if shape handle and offset for next chunk are available, and\n // response is not already up-to-date\n // snapshot (subset) responses are one-shot and must never be prefetched:\n // there is no \"next chunk\" URL for them, only a repeat of the same request\n if (!shapeHandle || !lastOffset || isUpToDate || isSnapshot) return\n\n const nextUrl = new URL(url)\n\n // don't prefetch live requests, rushing them will only\n // potentially miss more recent data\n if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return\n\n // don't prefetch if the response handle is the expired handle from the request\n // this can happen when a proxy serves a stale cached response despite the\n // expired_handle cache buster parameter\n const expiredHandle = nextUrl.searchParams.get(EXPIRED_HANDLE_QUERY_PARAM)\n if (expiredHandle && shapeHandle === expiredHandle) {\n console.warn(\n `[Electric] Received stale cached response with expired shape handle. ` +\n `This should not happen and indicates a proxy/CDN caching misconfiguration. ` +\n `The response contained handle \"${shapeHandle}\" which was previously marked as expired. ` +\n `Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. ` +\n `Skipping prefetch to prevent infinite 409 loop.`\n )\n return\n }\n\n nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle)\n nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset)\n nextUrl.searchParams.sort()\n return nextUrl.toString()\n}\n\n/**\n * Chains an abort controller on an optional source signal's\n * aborted state - if the source signal is aborted, the provided abort\n * controller will also abort\n */\nfunction chainAborter(\n aborter: AbortController,\n sourceSignal?: AbortSignal | null\n): {\n signal: AbortSignal\n cleanup: () => void\n} {\n let cleanup = noop\n if (!sourceSignal) {\n // no-op, nothing to chain to\n } else if (sourceSignal.aborted) {\n // source signal is already aborted, abort immediately\n aborter.abort()\n } else {\n // chain to source signal abort event, and add callback to unlink\n // the aborter to avoid memory leaks\n const abortParent = () => aborter.abort()\n sourceSignal.addEventListener(`abort`, abortParent, {\n once: true,\n signal: aborter.signal,\n })\n cleanup = () => sourceSignal.removeEventListener(`abort`, abortParent)\n }\n\n return {\n signal: aborter.signal,\n cleanup,\n }\n}\n\nfunction noop() {}\n\nfunction getRequestMethod(\n input: Parameters<typeof fetch>[0],\n init?: Parameters<typeof fetch>[1]\n): string {\n if (init?.method) {\n return init.method.toUpperCase()\n }\n\n if (typeof Request !== `undefined` && input instanceof Request) {\n return input.method.toUpperCase()\n }\n\n return `GET`\n}\n","import { SerializedExpression, SerializedOrderByClause } from './types'\nimport { quoteIdentifier } from './column-mapper'\n\n/**\n * Compiles a serialized expression into a SQL string.\n * Applies columnMapper transformations to column references.\n *\n * @param expr - The serialized expression to compile\n * @param columnMapper - Optional function to transform column names (e.g., camelCase to snake_case)\n * @returns The compiled SQL string\n *\n * @example\n * ```typescript\n * const expr = { type: 'ref', column: 'userId' }\n * compileExpression(expr, camelToSnake) // '\"user_id\"'\n * ```\n */\nexport function compileExpression(\n expr: SerializedExpression,\n columnMapper?: (col: string) => string\n): string {\n switch (expr.type) {\n case `ref`: {\n // Apply columnMapper, then quote\n const mappedColumn = columnMapper\n ? columnMapper(expr.column)\n : expr.column\n return quoteIdentifier(mappedColumn)\n }\n case `val`:\n return `$${expr.paramIndex}`\n case `func`:\n return compileFunction(expr, columnMapper)\n default: {\n // TypeScript exhaustiveness check\n const _exhaustive: never = expr\n throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustive)}`)\n }\n }\n}\n\n/**\n * Compiles a function expression into SQL.\n */\nfunction compileFunction(\n expr: { type: `func`; name: string; args: SerializedExpression[] },\n columnMapper?: (col: string) => string\n): string {\n const args = expr.args.map((arg) => compileExpression(arg, columnMapper))\n\n switch (expr.name) {\n // Binary comparison operators\n case `eq`:\n return `${args[0]} = ${args[1]}`\n case `gt`:\n return `${args[0]} > ${args[1]}`\n case `gte`:\n return `${args[0]} >= ${args[1]}`\n case `lt`:\n return `${args[0]} < ${args[1]}`\n case `lte`:\n return `${args[0]} <= ${args[1]}`\n\n // Logical operators\n case `and`:\n return args.map((a) => `(${a})`).join(` AND `)\n case `or`:\n return args.map((a) => `(${a})`).join(` OR `)\n case `not`:\n return `NOT (${args[0]})`\n\n // Special operators\n case `in`:\n return `${args[0]} = ANY(${args[1]})`\n case `like`:\n return `${args[0]} LIKE ${args[1]}`\n case `ilike`:\n return `${args[0]} ILIKE ${args[1]}`\n case `isNull`:\n case `isUndefined`:\n return `${args[0]} IS NULL`\n\n // String functions\n case `upper`:\n return `UPPER(${args[0]})`\n case `lower`:\n return `LOWER(${args[0]})`\n case `length`:\n return `LENGTH(${args[0]})`\n case `concat`:\n return `CONCAT(${args.join(`, `)})`\n\n // Other functions\n case `coalesce`:\n return `COALESCE(${args.join(`, `)})`\n\n default:\n throw new Error(`Unknown function: ${expr.name}`)\n }\n}\n\n/**\n * Compiles serialized ORDER BY clauses into a SQL string.\n * Applies columnMapper transformations to column references.\n *\n * @param clauses - The serialized ORDER BY clauses to compile\n * @param columnMapper - Optional function to transform column names\n * @returns The compiled SQL ORDER BY string\n *\n * @example\n * ```typescript\n * const clauses = [{ column: 'createdAt', direction: 'desc', nulls: 'first' }]\n * compileOrderBy(clauses, camelToSnake) // '\"created_at\" DESC NULLS FIRST'\n * ```\n */\nexport function compileOrderBy(\n clauses: SerializedOrderByClause[],\n columnMapper?: (col: string) => string\n): string {\n return clauses\n .map((clause) => {\n const mappedColumn = columnMapper\n ? columnMapper(clause.column)\n : clause.column\n let sql = quoteIdentifier(mappedColumn)\n if (clause.direction === `desc`) sql += ` DESC`\n if (clause.nulls === `first`) sql += ` NULLS FIRST`\n if (clause.nulls === `last`) sql += ` NULLS LAST`\n return sql\n })\n .join(`, `)\n}\n",null,null,"interface ExpiredShapeCacheEntry {\n expiredHandle: string\n lastUsed: number\n}\n\n/**\n * LRU cache for tracking expired shapes with automatic cleanup\n */\nexport class ExpiredShapesCache {\n private data: Record<string, ExpiredShapeCacheEntry> = {}\n private max: number = 250\n private readonly storageKey = `electric_expired_shapes`\n\n getExpiredHandle(shapeUrl: string): string | null {\n const entry = this.data[shapeUrl]\n if (entry) {\n // Update last used time when accessed\n entry.lastUsed = Date.now()\n this.save()\n return entry.expiredHandle\n }\n return null\n }\n\n markExpired(shapeUrl: string, handle: string): void {\n this.data[shapeUrl] = { expiredHandle: handle, lastUsed: Date.now() }\n\n const keys = Object.keys(this.data)\n if (keys.length > this.max) {\n const oldest = keys.reduce((min, k) =>\n this.data[k].lastUsed < this.data[min].lastUsed ? k : min\n )\n delete this.data[oldest]\n }\n\n this.save()\n }\n\n private save(): void {\n if (typeof localStorage === `undefined`) return\n try {\n localStorage.setItem(this.storageKey, JSON.stringify(this.data))\n } catch {\n // Ignore localStorage errors\n }\n }\n\n private load(): void {\n if (typeof localStorage === `undefined`) return\n try {\n const stored = localStorage.getItem(this.storageKey)\n if (stored) {\n this.data = JSON.parse(stored)\n }\n } catch {\n // Ignore localStorage errors, start fresh\n this.data = {}\n }\n }\n\n constructor() {\n this.load()\n }\n\n clear(): void {\n this.data = {}\n this.save()\n }\n\n delete(shapeUrl: string): void {\n delete this.data[shapeUrl]\n this.save()\n }\n}\n\n// Module-level singleton instance\nexport const expiredShapesCache = new ExpiredShapesCache()\n","interface UpToDateEntry {\n timestamp: number\n cursor: string\n}\n\n/**\n * Tracks up-to-date messages to detect when we're replaying cached responses.\n *\n * When a shape receives an up-to-date, we record the timestamp and cursor in localStorage.\n * On page refresh, if we find a recent timestamp (< 60s), we know we'll be replaying\n * cached responses. We suppress their up-to-date notifications until we see a NEW cursor\n * (different from the last recorded one), which indicates fresh data from the server.\n *\n * localStorage writes are throttled to once per 60 seconds to avoid performance issues\n * with frequent updates. In-memory data is always kept current.\n */\nexport class UpToDateTracker {\n private data: Record<string, UpToDateEntry> = {}\n private readonly storageKey = `electric_up_to_date_tracker`\n private readonly cacheTTL = 60_000 // 60s to match typical CDN s-maxage cache duration\n private readonly maxEntries = 250\n private readonly writeThrottleMs = 60_000 // Throttle localStorage writes to once per 60s\n private lastWriteTime = 0\n private pendingSaveTimer?: ReturnType<typeof setTimeout>\n\n constructor() {\n this.load()\n this.cleanup()\n }\n\n /**\n * Records that a shape received an up-to-date message with a specific cursor.\n * This timestamp and cursor are used to detect cache replay scenarios.\n * Updates in-memory immediately, but throttles localStorage writes.\n */\n recordUpToDate(shapeKey: string, cursor: string): void {\n this.data[shapeKey] = {\n timestamp: Date.now(),\n cursor,\n }\n\n // Implement LRU eviction if we exceed max entries\n const keys = Object.keys(this.data)\n if (keys.length > this.maxEntries) {\n const oldest = keys.reduce((min, k) =>\n this.data[k].timestamp < this.data[min].timestamp ? k : min\n )\n delete this.data[oldest]\n }\n\n this.scheduleSave()\n }\n\n /**\n * Schedules a throttled save to localStorage.\n * Writes immediately if enough time has passed, otherwise schedules for later.\n */\n private scheduleSave(): void {\n const now = Date.now()\n const timeSinceLastWrite = now - this.lastWriteTime\n\n if (timeSinceLastWrite >= this.writeThrottleMs) {\n // Enough time has passed, write immediately\n this.lastWriteTime = now\n this.save()\n } else if (!this.pendingSaveTimer) {\n // Schedule a write for when the throttle period expires\n const delay = this.writeThrottleMs - timeSinceLastWrite\n this.pendingSaveTimer = setTimeout(() => {\n this.lastWriteTime = Date.now()\n this.pendingSaveTimer = undefined\n this.save()\n }, delay)\n }\n // else: a save is already scheduled, no need to do anything\n }\n\n /**\n * Checks if we should enter replay mode for this shape.\n * Returns the last seen cursor if there's a recent up-to-date (< 60s),\n * which means we'll likely be replaying cached responses.\n * Returns null if no recent up-to-date exists.\n */\n shouldEnterReplayMode(shapeKey: string): string | null {\n const entry = this.data[shapeKey]\n if (!entry) {\n return null\n }\n\n const age = Date.now() - entry.timestamp\n if (age >= this.cacheTTL) {\n return null\n }\n\n return entry.cursor\n }\n\n /**\n * Cleans up expired entries from the cache.\n * Called on initialization and can be called periodically.\n */\n private cleanup(): void {\n const now = Date.now()\n const keys = Object.keys(this.data)\n let modified = false\n\n for (const key of keys) {\n const age = now - this.data[key].timestamp\n if (age > this.cacheTTL) {\n delete this.data[key]\n modified = true\n }\n }\n\n if (modified) {\n this.save()\n }\n }\n\n private save(): void {\n if (typeof localStorage === `undefined`) return\n try {\n localStorage.setItem(this.storageKey, JSON.stringify(this.data))\n } catch {\n // Ignore localStorage errors (quota exceeded, etc.)\n }\n }\n\n private load(): void {\n if (typeof localStorage === `undefined`) return\n try {\n const stored = localStorage.getItem(this.storageKey)\n if (stored) {\n this.data = JSON.parse(stored)\n }\n } catch {\n // Ignore localStorage errors, start fresh\n this.data = {}\n }\n }\n\n /**\n * Clears all tracked up-to-date timestamps.\n * Useful for testing or manual cache invalidation.\n */\n clear(): void {\n this.data = {}\n if (this.pendingSaveTimer) {\n clearTimeout(this.pendingSaveTimer)\n this.pendingSaveTimer = undefined\n }\n this.save()\n }\n\n delete(shapeKey: string): void {\n delete this.data[shapeKey]\n this.save()\n }\n}\n\n// Module-level singleton instance\nexport const upToDateTracker = new UpToDateTracker()\n","import { isVisibleInSnapshot } from './helpers'\nimport { Row, SnapshotMetadata } from './types'\nimport { ChangeMessage } from './types'\n\n/**\n * Tracks active snapshots and filters out duplicate change messages that are already included in snapshots.\n *\n * When requesting a snapshot in changes_only mode, we need to track which transactions were included in the\n * snapshot to avoid processing duplicate changes that arrive via the live stream. This class maintains that\n * tracking state and provides methods to:\n *\n * - Add new snapshots for tracking via addSnapshot()\n * - Remove completed snapshots via removeSnapshot()\n * - Check if incoming changes should be filtered via shouldRejectMessage()\n */\nexport class SnapshotTracker {\n private activeSnapshots: Map<\n number,\n {\n xmin: bigint\n xmax: bigint\n xip_list: bigint[]\n keys: Set<string>\n databaseLsn: bigint\n }\n > = new Map()\n private xmaxSnapshots: Map<bigint, Set<number>> = new Map()\n private snapshotsByDatabaseLsn: Map<bigint, Set<number>> = new Map()\n\n /**\n * Add a new snapshot for tracking\n */\n addSnapshot(metadata: SnapshotMetadata, keys: Set<string>): void {\n // If this mark already exists, drop its reverse-index entries first\n // so they don't linger with the old (xmax, database_lsn) coordinates.\n this.#detachFromReverseIndexes(metadata.snapshot_mark)\n\n const xmax = BigInt(metadata.xmax)\n const databaseLsn = BigInt(metadata.database_lsn)\n this.activeSnapshots.set(metadata.snapshot_mark, {\n xmin: BigInt(metadata.xmin),\n xmax,\n xip_list: metadata.xip_list.map(BigInt),\n keys,\n databaseLsn,\n })\n this.#addToSet(this.xmaxSnapshots, xmax, metadata.snapshot_mark)\n this.#addToSet(\n this.snapshotsByDatabaseLsn,\n databaseLsn,\n metadata.snapshot_mark\n )\n }\n\n /**\n * Remove a snapshot from tracking\n */\n removeSnapshot(snapshotMark: number): void {\n this.#detachFromReverseIndexes(snapshotMark)\n this.activeSnapshots.delete(snapshotMark)\n }\n\n #detachFromReverseIndexes(snapshotMark: number): void {\n const existing = this.activeSnapshots.get(snapshotMark)\n if (!existing) return\n this.#removeFromSet(this.xmaxSnapshots, existing.xmax, snapshotMark)\n this.#removeFromSet(\n this.snapshotsByDatabaseLsn,\n existing.databaseLsn,\n snapshotMark\n )\n }\n\n #addToSet(map: Map<bigint, Set<number>>, key: bigint, value: number): void {\n const set = map.get(key)\n if (set) {\n set.add(value)\n } else {\n map.set(key, new Set([value]))\n }\n }\n\n #removeFromSet(\n map: Map<bigint, Set<number>>,\n key: bigint,\n value: number\n ): void {\n const set = map.get(key)\n if (!set) return\n set.delete(value)\n if (set.size === 0) map.delete(key)\n }\n\n /**\n * Resolves a 32-bit xid against an epoch-aware xid8.\n *\n * This signed modulo-2^32 calculation requires the reference and xid to be\n * within 2^31 transactions. ShapeStream enforces that lifetime bound by\n * retiring snapshots as global_last_seen_lsn passes their database_lsn.\n *\n * Mirrors `Electric.Postgres.Xid`\n * (`packages/sync-service/lib/electric/postgres/xid.ex`).\n */\n #toXid8(xid: number, referenceXid8: bigint): bigint {\n return referenceXid8 + BigInt.asIntN(32, BigInt(xid) - referenceXid8)\n }\n\n /**\n * Resolves each contributing xid into the epoch nearest `referenceXid8` and\n * returns the latest. Snapshot callers use `xmax` as the reference.\n */\n #resolveLatestXid8(xids: number[], referenceXid8: bigint): bigint {\n return xids.reduce((latest, xid) => {\n const xid8 = this.#toXid8(xid, referenceXid8)\n return xid8 > latest ? xid8 : latest\n }, BigInt(-1))\n }\n\n /**\n * Check if a change message should be filtered because its already in an active snapshot\n * Returns true if the message should be filtered out (not processed)\n */\n shouldRejectMessage(message: ChangeMessage<Row<unknown>>): boolean {\n const txids = message.headers.txids || []\n if (txids.length === 0) return false\n\n for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {\n const xid8 = this.#resolveLatestXid8(txids, xmax)\n if (xid8 >= xmax) {\n for (const snapshot of snapshots) {\n this.removeSnapshot(snapshot)\n }\n }\n }\n\n return [...this.activeSnapshots.values()].some((snapshot) => {\n if (!snapshot.keys.has(message.key)) return false\n const xid8 = this.#resolveLatestXid8(txids, snapshot.xmax)\n return isVisibleInSnapshot(xid8, snapshot)\n })\n }\n\n lastSeenUpdate(newDatabaseLsn: bigint): void {\n for (const [dbLsn, snapshots] of this.snapshotsByDatabaseLsn.entries()) {\n if (dbLsn <= newDatabaseLsn) {\n for (const snapshot of snapshots) {\n this.removeSnapshot(snapshot)\n }\n }\n }\n }\n}\n","/*\n * Shape stream state machine.\n *\n * Class hierarchy:\n *\n * ShapeStreamState (abstract base)\n * ├── ActiveState (abstract — shared field storage & helpers)\n * │ ├── FetchingState (abstract — shared Initial/Syncing/StaleRetry behavior)\n * │ │ ├── InitialState\n * │ │ ├── SyncingState\n * │ │ └── StaleRetryState\n * │ ├── LiveState\n * │ └── ReplayingState\n * ├── PausedState (delegates to previousState)\n * └── ErrorState (delegates to previousState)\n *\n * State transitions:\n *\n * Initial ─response─► Syncing ─up-to-date─► Live\n * │ │\n * └──stale──► StaleRetry\n * │\n * Syncing ◄──response──┘\n *\n * Any state ─pause─► Paused ─resume─► (previous state)\n * Any state ─error─► Error ─retry──► (previous state)\n * Any state ─markMustRefetch─► Initial (offset reset)\n */\nimport { Offset, Schema } from './types'\nimport {\n OFFSET_QUERY_PARAM,\n SHAPE_HANDLE_QUERY_PARAM,\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n LIVE_QUERY_PARAM,\n CACHE_BUSTER_QUERY_PARAM,\n} from './constants'\n\nexport type ShapeStreamStateKind =\n | `initial`\n | `syncing`\n | `live`\n | `replaying`\n | `stale-retry`\n | `paused`\n | `error`\n\n/**\n * Shared fields carried by all active (non-paused, non-error) states.\n */\nexport interface SharedStateFields {\n readonly handle?: string\n readonly offset: Offset\n readonly schema?: Schema\n readonly liveCacheBuster: string\n readonly lastSyncedAt?: number\n}\n\ntype ResponseBaseInput = {\n status: number\n responseHandle: string | null\n responseOffset: Offset | null\n responseCursor: string | null\n responseSchema?: Schema\n expiredHandle?: string | null\n now: number\n}\n\nexport type ResponseMetadataInput = ResponseBaseInput & {\n maxStaleCacheRetries: number\n createCacheBuster: () => string\n}\n\nexport type ResponseMetadataTransition =\n | { action: `accepted`; state: ShapeStreamState }\n | { action: `ignored`; state: ShapeStreamState }\n | {\n action: `stale-retry`\n state: ShapeStreamState\n exceededMaxRetries: boolean\n }\n\nexport interface MessageBatchInput {\n hasMessages: boolean\n hasUpToDateMessage: boolean\n isSse: boolean\n upToDateOffset?: Offset\n now: number\n currentCursor: string\n}\n\nexport interface MessageBatchTransition {\n state: ShapeStreamState\n suppressUpToDate: boolean\n becameUpToDate: boolean\n}\n\nexport interface SseCloseInput {\n connectionDuration: number\n wasAborted: boolean\n minConnectionDuration: number\n maxShortConnections: number\n}\n\nexport interface SseCloseTransition {\n state: ShapeStreamState\n fellBackToLongPolling: boolean\n wasShortConnection: boolean\n}\n\nexport interface UrlParamsContext {\n isSnapshotRequest: boolean\n canLongPoll: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Abstract base — shared by ALL states (including Paused/Error)\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract base class for all shape stream states.\n *\n * Each concrete state carries only its relevant fields — there is no shared\n * flat context bag. Transitions create new immutable state objects.\n *\n * `isUpToDate` returns true for LiveState and delegating states wrapping LiveState.\n */\nexport abstract class ShapeStreamState {\n abstract readonly kind: ShapeStreamStateKind\n\n // --- Shared field getters (all states expose these) ---\n abstract get handle(): string | undefined\n abstract get offset(): Offset\n abstract get schema(): Schema | undefined\n abstract get liveCacheBuster(): string\n abstract get lastSyncedAt(): number | undefined\n\n // --- Derived booleans ---\n get isUpToDate(): boolean {\n return false\n }\n\n // --- Per-state field defaults ---\n get staleCacheBuster(): string | undefined {\n return undefined\n }\n get staleCacheRetryCount(): number {\n return 0\n }\n get sseFallbackToLongPolling(): boolean {\n return false\n }\n get consecutiveShortSseConnections(): number {\n return 0\n }\n get replayCursor(): string | undefined {\n return undefined\n }\n\n // --- Default no-op methods ---\n\n canEnterReplayMode(): boolean {\n return false\n }\n\n enterReplayMode(_cursor: string): ShapeStreamState {\n return this\n }\n\n shouldUseSse(_opts: {\n liveSseEnabled: boolean\n isRefreshing: boolean\n resumingFromPause: boolean\n }): boolean {\n return false\n }\n\n handleSseConnectionClosed(_input: SseCloseInput): SseCloseTransition {\n return {\n state: this,\n fellBackToLongPolling: false,\n wasShortConnection: false,\n }\n }\n\n // --- URL param application ---\n\n /** Adds state-specific query parameters to the fetch URL. */\n applyUrlParams(_url: URL, _context: UrlParamsContext): void {}\n\n // --- Default response/message handlers (Paused/Error never receive these) ---\n\n handleResponseMetadata(\n _input: ResponseMetadataInput\n ): ResponseMetadataTransition {\n return { action: `ignored`, state: this }\n }\n\n handleMessageBatch(_input: MessageBatchInput): MessageBatchTransition {\n return { state: this, suppressUpToDate: false, becameUpToDate: false }\n }\n\n // --- Universal transitions ---\n\n /** Returns a new state identical to this one but with the handle changed. */\n abstract withHandle(handle: string): ShapeStreamState\n\n pause(): PausedState {\n return new PausedState(this)\n }\n\n toErrorState(error: Error): ErrorState {\n return new ErrorState(this, error)\n }\n\n markMustRefetch(handle?: string): InitialState {\n return new InitialState({\n handle,\n offset: `-1`,\n liveCacheBuster: ``,\n lastSyncedAt: this.lastSyncedAt,\n schema: undefined,\n })\n }\n}\n\n// ---------------------------------------------------------------------------\n// ActiveState — intermediate base for all non-paused, non-error states\n// ---------------------------------------------------------------------------\n\n/**\n * Holds shared field storage and provides helpers for response/message\n * handling. All five active states extend this (via FetchingState or directly).\n */\nabstract class ActiveState extends ShapeStreamState {\n readonly #shared: SharedStateFields\n\n constructor(shared: SharedStateFields) {\n super()\n this.#shared = shared\n }\n\n get handle() {\n return this.#shared.handle\n }\n get offset() {\n return this.#shared.offset\n }\n get schema() {\n return this.#shared.schema\n }\n get liveCacheBuster() {\n return this.#shared.liveCacheBuster\n }\n get lastSyncedAt() {\n return this.#shared.lastSyncedAt\n }\n\n /** Expose shared fields to subclasses for spreading into new instances. */\n protected get currentFields(): SharedStateFields {\n return this.#shared\n }\n\n // --- URL param application ---\n\n applyUrlParams(url: URL, _context: UrlParamsContext): void {\n url.searchParams.set(OFFSET_QUERY_PARAM, this.#shared.offset)\n if (this.#shared.handle) {\n url.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, this.#shared.handle)\n }\n }\n\n // --- Helpers for subclass handleResponseMetadata implementations ---\n\n /** Extracts updated SharedStateFields from response headers. */\n protected parseResponseFields(\n input: ResponseMetadataInput\n ): SharedStateFields {\n const responseHandle = input.responseHandle\n const handle =\n responseHandle && responseHandle !== input.expiredHandle\n ? responseHandle\n : this.#shared.handle\n const offset = input.responseOffset ?? this.#shared.offset\n const liveCacheBuster = input.responseCursor ?? this.#shared.liveCacheBuster\n const schema = this.#shared.schema ?? input.responseSchema\n const lastSyncedAt =\n input.status === 204 ? input.now : this.#shared.lastSyncedAt\n\n return { handle, offset, schema, liveCacheBuster, lastSyncedAt }\n }\n\n /**\n * Stale detection. Returns a transition if the response is stale,\n * or null if it is not stale and the caller should proceed normally.\n */\n protected checkStaleResponse(\n input: ResponseMetadataInput\n ): ResponseMetadataTransition | null {\n const responseHandle = input.responseHandle\n const expiredHandle = input.expiredHandle\n\n if (!responseHandle || responseHandle !== expiredHandle) {\n return null // not stale\n }\n\n // Stale response detected — always enter stale-retry to get a cache buster.\n // Without a cache buster, the CDN will keep serving the same stale response\n // and the client loops infinitely (the URL never changes).\n // currentFields preserves the valid local handle when we already have one.\n const retryCount = this.staleCacheRetryCount + 1\n return {\n action: `stale-retry`,\n state: new StaleRetryState({\n ...this.currentFields,\n staleCacheBuster: input.createCacheBuster(),\n staleCacheRetryCount: retryCount,\n }),\n exceededMaxRetries: retryCount > input.maxStaleCacheRetries,\n }\n }\n\n // --- handleMessageBatch: template method with onUpToDate override point ---\n\n handleMessageBatch(input: MessageBatchInput): MessageBatchTransition {\n if (!input.hasMessages || !input.hasUpToDateMessage) {\n return { state: this, suppressUpToDate: false, becameUpToDate: false }\n }\n\n // Has up-to-date message — compute shared fields for the transition\n let offset = this.#shared.offset\n if (input.isSse && input.upToDateOffset) {\n offset = input.upToDateOffset\n }\n\n const shared: SharedStateFields = {\n handle: this.#shared.handle,\n offset,\n schema: this.#shared.schema,\n liveCacheBuster: this.#shared.liveCacheBuster,\n lastSyncedAt: input.now,\n }\n\n return this.onUpToDate(shared, input)\n }\n\n /** Override point for up-to-date handling. Default → LiveState. */\n protected onUpToDate(\n shared: SharedStateFields,\n _input: MessageBatchInput\n ): MessageBatchTransition {\n return {\n state: new LiveState(shared),\n suppressUpToDate: false,\n becameUpToDate: true,\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// FetchingState — Common behavior for Initial/Syncing/StaleRetry\n// ---------------------------------------------------------------------------\n\n/**\n * Captures shared behavior of InitialState, SyncingState, StaleRetryState:\n * - handleResponseMetadata: stale check → parse fields → new SyncingState (or LiveState for 204)\n * - enterReplayMode(cursor) → new ReplayingState\n * - canEnterReplayMode(): boolean — returns true (StaleRetryState overrides to return false,\n * because entering replay would lose the stale-retry count; see C1 in SPEC.md)\n */\nabstract class FetchingState extends ActiveState {\n handleResponseMetadata(\n input: ResponseMetadataInput\n ): ResponseMetadataTransition {\n const staleResult = this.checkStaleResponse(input)\n if (staleResult) return staleResult\n\n const shared = this.parseResponseFields(input)\n\n // NOTE: 204s are deprecated, the Electric server should not send these\n // in latest versions but this is here for backwards compatibility.\n // A 204 means \"no content, you're caught up\" — transition to live.\n // Skip SSE detection: a 204 gives no indication SSE will work, and\n // the 3-attempt fallback cycle adds unnecessary latency.\n if (input.status === 204) {\n return {\n action: `accepted`,\n state: new LiveState(shared, { sseFallbackToLongPolling: true }),\n }\n }\n\n return { action: `accepted`, state: new SyncingState(shared) }\n }\n\n canEnterReplayMode(): boolean {\n return true\n }\n\n enterReplayMode(cursor: string): ReplayingState {\n return new ReplayingState({\n ...this.currentFields,\n replayCursor: cursor,\n })\n }\n}\n\n// ---------------------------------------------------------------------------\n// Concrete states\n// ---------------------------------------------------------------------------\n\nexport class InitialState extends FetchingState {\n readonly kind = `initial` as const\n\n constructor(shared: SharedStateFields) {\n super(shared)\n }\n\n withHandle(handle: string): InitialState {\n return new InitialState({ ...this.currentFields, handle })\n }\n}\n\nexport class SyncingState extends FetchingState {\n readonly kind = `syncing` as const\n\n constructor(shared: SharedStateFields) {\n super(shared)\n }\n\n withHandle(handle: string): SyncingState {\n return new SyncingState({ ...this.currentFields, handle })\n }\n}\n\nexport class StaleRetryState extends FetchingState {\n readonly kind = `stale-retry` as const\n readonly #staleCacheBuster: string\n readonly #staleCacheRetryCount: number\n\n constructor(\n fields: SharedStateFields & {\n staleCacheBuster: string\n staleCacheRetryCount: number\n }\n ) {\n const { staleCacheBuster, staleCacheRetryCount, ...shared } = fields\n super(shared)\n this.#staleCacheBuster = staleCacheBuster\n this.#staleCacheRetryCount = staleCacheRetryCount\n }\n\n get staleCacheBuster() {\n return this.#staleCacheBuster\n }\n get staleCacheRetryCount() {\n return this.#staleCacheRetryCount\n }\n\n // StaleRetryState must not enter replay mode — it would lose the retry count\n canEnterReplayMode(): boolean {\n return false\n }\n\n withHandle(handle: string): StaleRetryState {\n return new StaleRetryState({\n ...this.currentFields,\n handle,\n staleCacheBuster: this.#staleCacheBuster,\n staleCacheRetryCount: this.#staleCacheRetryCount,\n })\n }\n\n applyUrlParams(url: URL, context: UrlParamsContext): void {\n super.applyUrlParams(url, context)\n url.searchParams.set(CACHE_BUSTER_QUERY_PARAM, this.#staleCacheBuster)\n }\n}\n\nexport class LiveState extends ActiveState {\n readonly kind = `live` as const\n readonly #consecutiveShortSseConnections: number\n readonly #sseFallbackToLongPolling: boolean\n\n constructor(\n shared: SharedStateFields,\n sseState?: {\n consecutiveShortSseConnections?: number\n sseFallbackToLongPolling?: boolean\n }\n ) {\n super(shared)\n this.#consecutiveShortSseConnections =\n sseState?.consecutiveShortSseConnections ?? 0\n this.#sseFallbackToLongPolling = sseState?.sseFallbackToLongPolling ?? false\n }\n\n get isUpToDate(): boolean {\n return true\n }\n\n get consecutiveShortSseConnections(): number {\n return this.#consecutiveShortSseConnections\n }\n\n get sseFallbackToLongPolling(): boolean {\n return this.#sseFallbackToLongPolling\n }\n\n withHandle(handle: string): LiveState {\n return new LiveState({ ...this.currentFields, handle }, this.sseState)\n }\n\n applyUrlParams(url: URL, context: UrlParamsContext): void {\n super.applyUrlParams(url, context)\n // Snapshot requests (with subsetParams) should never use live polling\n if (!context.isSnapshotRequest) {\n url.searchParams.set(LIVE_CACHE_BUSTER_QUERY_PARAM, this.liveCacheBuster)\n if (context.canLongPoll) {\n url.searchParams.set(LIVE_QUERY_PARAM, `true`)\n }\n }\n }\n\n private get sseState() {\n return {\n consecutiveShortSseConnections: this.#consecutiveShortSseConnections,\n sseFallbackToLongPolling: this.#sseFallbackToLongPolling,\n }\n }\n\n handleResponseMetadata(\n input: ResponseMetadataInput\n ): ResponseMetadataTransition {\n const staleResult = this.checkStaleResponse(input)\n if (staleResult) return staleResult\n\n const shared = this.parseResponseFields(input)\n return {\n action: `accepted`,\n state: new LiveState(shared, this.sseState),\n }\n }\n\n protected onUpToDate(\n shared: SharedStateFields,\n _input: MessageBatchInput\n ): MessageBatchTransition {\n return {\n state: new LiveState(shared, this.sseState),\n suppressUpToDate: false,\n becameUpToDate: true,\n }\n }\n\n shouldUseSse(opts: {\n liveSseEnabled: boolean\n isRefreshing: boolean\n resumingFromPause: boolean\n }): boolean {\n return (\n opts.liveSseEnabled &&\n !opts.isRefreshing &&\n !opts.resumingFromPause &&\n !this.#sseFallbackToLongPolling\n )\n }\n\n handleSseConnectionClosed(input: SseCloseInput): SseCloseTransition {\n let nextConsecutiveShort = this.#consecutiveShortSseConnections\n let nextFallback = this.#sseFallbackToLongPolling\n let fellBackToLongPolling = false\n let wasShortConnection = false\n\n if (\n input.connectionDuration < input.minConnectionDuration &&\n !input.wasAborted\n ) {\n wasShortConnection = true\n nextConsecutiveShort = nextConsecutiveShort + 1\n\n if (nextConsecutiveShort >= input.maxShortConnections) {\n nextFallback = true\n fellBackToLongPolling = true\n }\n } else if (input.connectionDuration >= input.minConnectionDuration) {\n nextConsecutiveShort = 0\n }\n\n return {\n state: new LiveState(this.currentFields, {\n consecutiveShortSseConnections: nextConsecutiveShort,\n sseFallbackToLongPolling: nextFallback,\n }),\n fellBackToLongPolling,\n wasShortConnection,\n }\n }\n}\n\nexport class ReplayingState extends ActiveState {\n readonly kind = `replaying` as const\n readonly #replayCursor: string\n\n constructor(fields: SharedStateFields & { replayCursor: string }) {\n const { replayCursor, ...shared } = fields\n super(shared)\n this.#replayCursor = replayCursor\n }\n\n get replayCursor() {\n return this.#replayCursor\n }\n\n withHandle(handle: string): ReplayingState {\n return new ReplayingState({\n ...this.currentFields,\n handle,\n replayCursor: this.#replayCursor,\n })\n }\n\n handleResponseMetadata(\n input: ResponseMetadataInput\n ): ResponseMetadataTransition {\n const staleResult = this.checkStaleResponse(input)\n if (staleResult) return staleResult\n\n const shared = this.parseResponseFields(input)\n return {\n action: `accepted`,\n state: new ReplayingState({\n ...shared,\n replayCursor: this.#replayCursor,\n }),\n }\n }\n\n protected onUpToDate(\n shared: SharedStateFields,\n input: MessageBatchInput\n ): MessageBatchTransition {\n // Suppress the replayed up-to-date notification when the cursor has not moved since\n // the previous session (non-SSE only).\n const suppressUpToDate =\n !input.isSse && this.#replayCursor === input.currentCursor\n return {\n state: new LiveState(shared),\n suppressUpToDate,\n becameUpToDate: true,\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delegating states (Paused / Error)\n// ---------------------------------------------------------------------------\n\n// Union of all non-delegating states — used to type previousState fields so\n// that PausedState.previousState is never another PausedState, and\n// ErrorState.previousState is never another ErrorState.\nexport type ShapeStreamActiveState =\n | InitialState\n | SyncingState\n | LiveState\n | ReplayingState\n | StaleRetryState\n\nexport class PausedState extends ShapeStreamState {\n readonly kind = `paused` as const\n readonly previousState: ShapeStreamActiveState | ErrorState\n\n constructor(previousState: ShapeStreamState) {\n super()\n this.previousState = (\n previousState instanceof PausedState\n ? previousState.previousState\n : previousState\n ) as ShapeStreamActiveState | ErrorState\n }\n\n get handle(): string | undefined {\n return this.previousState.handle\n }\n get offset(): Offset {\n return this.previousState.offset\n }\n get schema(): Schema | undefined {\n return this.previousState.schema\n }\n get liveCacheBuster(): string {\n return this.previousState.liveCacheBuster\n }\n get lastSyncedAt(): number | undefined {\n return this.previousState.lastSyncedAt\n }\n get isUpToDate(): boolean {\n return this.previousState.isUpToDate\n }\n get staleCacheBuster(): string | undefined {\n return this.previousState.staleCacheBuster\n }\n get staleCacheRetryCount(): number {\n return this.previousState.staleCacheRetryCount\n }\n get sseFallbackToLongPolling(): boolean {\n return this.previousState.sseFallbackToLongPolling\n }\n get consecutiveShortSseConnections(): number {\n return this.previousState.consecutiveShortSseConnections\n }\n get replayCursor(): string | undefined {\n return this.previousState.replayCursor\n }\n\n handleResponseMetadata(\n input: ResponseMetadataInput\n ): ResponseMetadataTransition {\n const transition = this.previousState.handleResponseMetadata(input)\n if (transition.action === `accepted`) {\n return { action: `accepted`, state: new PausedState(transition.state) }\n }\n if (transition.action === `ignored`) {\n return { action: `ignored`, state: this }\n }\n if (transition.action === `stale-retry`) {\n return {\n action: `stale-retry`,\n state: new PausedState(transition.state),\n exceededMaxRetries: transition.exceededMaxRetries,\n }\n }\n const _exhaustive: never = transition\n throw new Error(\n `PausedState.handleResponseMetadata: unhandled transition action \"${(_exhaustive as ResponseMetadataTransition).action}\"`\n )\n }\n\n withHandle(handle: string): PausedState {\n return new PausedState(this.previousState.withHandle(handle))\n }\n\n applyUrlParams(url: URL, context: UrlParamsContext): void {\n this.previousState.applyUrlParams(url, context)\n }\n\n pause(): PausedState {\n return this\n }\n\n resume(): ShapeStreamState {\n return this.previousState\n }\n}\n\nexport class ErrorState extends ShapeStreamState {\n readonly kind = `error` as const\n readonly previousState: ShapeStreamActiveState | PausedState\n readonly error: Error\n\n constructor(previousState: ShapeStreamState, error: Error) {\n super()\n this.previousState = (\n previousState instanceof ErrorState\n ? previousState.previousState\n : previousState\n ) as ShapeStreamActiveState | PausedState\n this.error = error\n }\n\n get handle(): string | undefined {\n return this.previousState.handle\n }\n get offset(): Offset {\n return this.previousState.offset\n }\n get schema(): Schema | undefined {\n return this.previousState.schema\n }\n get liveCacheBuster(): string {\n return this.previousState.liveCacheBuster\n }\n get lastSyncedAt(): number | undefined {\n return this.previousState.lastSyncedAt\n }\n get isUpToDate(): boolean {\n return this.previousState.isUpToDate\n }\n get staleCacheBuster(): string | undefined {\n return this.previousState.staleCacheBuster\n }\n get staleCacheRetryCount(): number {\n return this.previousState.staleCacheRetryCount\n }\n get sseFallbackToLongPolling(): boolean {\n return this.previousState.sseFallbackToLongPolling\n }\n get consecutiveShortSseConnections(): number {\n return this.previousState.consecutiveShortSseConnections\n }\n get replayCursor(): string | undefined {\n return this.previousState.replayCursor\n }\n\n withHandle(handle: string): ErrorState {\n return new ErrorState(this.previousState.withHandle(handle), this.error)\n }\n\n applyUrlParams(url: URL, context: UrlParamsContext): void {\n this.previousState.applyUrlParams(url, context)\n }\n\n retry(): ShapeStreamState {\n return this.previousState\n }\n\n reset(handle?: string): InitialState {\n return this.previousState.markMustRefetch(handle)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type alias & factory\n// ---------------------------------------------------------------------------\n\nexport function createInitialState(opts: {\n offset: Offset\n handle?: string\n}): InitialState {\n return new InitialState({\n handle: opts.handle,\n offset: opts.offset,\n liveCacheBuster: ``,\n lastSyncedAt: undefined,\n schema: undefined,\n })\n}\n","/**\n * A set-based counting lock for coordinating multiple pause reasons.\n *\n * Multiple independent subsystems (tab visibility, snapshot requests, etc.)\n * may each need the stream paused. A simple boolean flag or counter can't\n * track *why* the stream is paused, leading to bugs where one subsystem's\n * resume overrides another's pause.\n *\n * PauseLock uses a Set of reason strings. The stream is paused when any\n * reason is held, and only resumes when all reasons are released.\n *\n * @example\n * ```ts\n * const lock = new PauseLock({\n * onAcquired: () => abortController.abort(),\n * onReleased: () => startRequestLoop(),\n * })\n *\n * // Tab hidden\n * lock.acquire('visibility') // → onAcquired fires, stream pauses\n *\n * // Snapshot starts while tab hidden\n * lock.acquire('snapshot-1') // → no-op, already paused\n *\n * // Snapshot finishes\n * lock.release('snapshot-1') // → no-op, 'visibility' still held\n *\n * // Tab visible\n * lock.release('visibility') // → onReleased fires, stream resumes\n * ```\n */\nexport class PauseLock {\n #holders = new Set<string>()\n #onAcquired: () => void\n #onReleased: () => void\n\n constructor(callbacks: { onAcquired: () => void; onReleased: () => void }) {\n this.#onAcquired = callbacks.onAcquired\n this.#onReleased = callbacks.onReleased\n }\n\n /**\n * Acquire the lock for a given reason. Idempotent — acquiring the same\n * reason twice is a no-op (but logs a warning since it likely indicates\n * a caller bug).\n *\n * Fires `onAcquired` when the first reason is acquired (transition from\n * unlocked to locked).\n */\n acquire(reason: string): void {\n if (this.#holders.has(reason)) {\n console.warn(\n `[Electric] PauseLock: \"${reason}\" already held — ignoring duplicate acquire`\n )\n return\n }\n const wasUnlocked = this.#holders.size === 0\n this.#holders.add(reason)\n if (wasUnlocked) {\n this.#onAcquired()\n }\n }\n\n /**\n * Release the lock for a given reason. Releasing a reason that isn't\n * held logs a warning (likely indicates an acquire/release mismatch).\n *\n * Fires `onReleased` when the last reason is released (transition from\n * locked to unlocked).\n */\n release(reason: string): void {\n if (!this.#holders.delete(reason)) {\n console.warn(\n `[Electric] PauseLock: \"${reason}\" not held — ignoring release (possible acquire/release mismatch)`\n )\n return\n }\n if (this.#holders.size === 0) {\n this.#onReleased()\n }\n }\n\n /**\n * Whether the lock is currently held by any reason.\n */\n get isPaused(): boolean {\n return this.#holders.size > 0\n }\n\n /**\n * Check if a specific reason is holding the lock.\n */\n isHeldBy(reason: string): boolean {\n return this.#holders.has(reason)\n }\n\n /**\n * Release all reasons matching a prefix. Does NOT fire `onReleased` —\n * this is for cleanup/reset paths where the stream state is being\n * managed separately.\n *\n * This preserves reasons with different prefixes (e.g., 'visibility'\n * is preserved when clearing 'snapshot-*' reasons).\n */\n releaseAllMatching(prefix: string): void {\n for (const reason of this.#holders) {\n if (reason.startsWith(prefix)) {\n this.#holders.delete(reason)\n }\n }\n }\n}\n","import type { RuntimeVisibilityAdapter } from './client'\n\ntype RuntimeVisibilityAdapterFactory = () =>\n | RuntimeVisibilityAdapter\n | undefined\n\nlet defaultRuntimeVisibilityAdapterFactory:\n | RuntimeVisibilityAdapterFactory\n | undefined\n\nexport function setDefaultRuntimeVisibilityAdapterFactory(\n factory: RuntimeVisibilityAdapterFactory | undefined\n): void {\n defaultRuntimeVisibilityAdapterFactory = factory\n}\n\nexport function getDefaultRuntimeVisibilityAdapterFactory():\n | RuntimeVisibilityAdapterFactory\n | undefined {\n return defaultRuntimeVisibilityAdapterFactory\n}\n","import {\n Message,\n Offset,\n Schema,\n Row,\n MaybePromise,\n GetExtensions,\n ChangeMessage,\n SnapshotMetadata,\n SubsetParams,\n} from './types'\nimport { MessageParser, Parser, TransformFunction } from './parser'\nimport {\n ColumnMapper,\n encodeWhereClause,\n quoteIdentifier,\n} from './column-mapper'\nimport {\n getOffset,\n isUpToDateMessage,\n isChangeMessage,\n bigintSafeStringify,\n} from './helpers'\nimport {\n FetchError,\n FetchBackoffAbortError,\n MissingShapeUrlError,\n InvalidSignalError,\n MissingShapeHandleError,\n InvalidShapeOptionsError,\n ReservedParamError,\n MissingHeadersError,\n StaleCacheError,\n} from './error'\nimport {\n BackoffDefaults,\n BackoffOptions,\n consumeResponseBody,\n createFetchWithBackoff,\n createFetchWithChunkBuffer,\n createFetchWithConsumedMessages,\n createFetchWithResponseHeadersCheck,\n} from './fetch'\nimport {\n CHUNK_LAST_OFFSET_HEADER,\n LIVE_CACHE_BUSTER_HEADER,\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n EXPIRED_HANDLE_QUERY_PARAM,\n COLUMNS_QUERY_PARAM,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_HANDLE_HEADER,\n SHAPE_HANDLE_QUERY_PARAM,\n SHAPE_SCHEMA_HEADER,\n WHERE_QUERY_PARAM,\n WHERE_PARAMS_PARAM,\n TABLE_QUERY_PARAM,\n REPLICA_PARAM,\n FORCE_DISCONNECT_AND_REFRESH,\n PAUSE_STREAM,\n SYSTEM_WAKE,\n LIVE_REQUEST_TIMEOUT,\n EXPERIMENTAL_LIVE_SSE_QUERY_PARAM,\n LIVE_SSE_QUERY_PARAM,\n ELECTRIC_PROTOCOL_QUERY_PARAMS,\n LOG_MODE_QUERY_PARAM,\n SUBSET_PARAM_WHERE,\n SUBSET_PARAM_WHERE_PARAMS,\n SUBSET_PARAM_LIMIT,\n SUBSET_PARAM_OFFSET,\n SUBSET_PARAM_ORDER_BY,\n SUBSET_PARAM_WHERE_EXPR,\n SUBSET_PARAM_ORDER_BY_EXPR,\n CACHE_BUSTER_QUERY_PARAM,\n} from './constants'\nimport { compileExpression, compileOrderBy } from './expression-compiler'\nimport {\n EventSourceMessage,\n fetchEventSource,\n} from '@microsoft/fetch-event-source'\nimport { expiredShapesCache } from './expired-shapes-cache'\nimport { upToDateTracker } from './up-to-date-tracker'\nimport { SnapshotTracker } from './snapshot-tracker'\nimport {\n createInitialState,\n ErrorState,\n PausedState,\n ShapeStreamState,\n} from './shape-stream-state'\nimport { PauseLock } from './pause-lock'\nimport { getDefaultRuntimeVisibilityAdapterFactory } from './runtime-visibility'\n\nconst RESERVED_PARAMS: Set<ReservedParamKeys> = new Set([\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n SHAPE_HANDLE_QUERY_PARAM,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n CACHE_BUSTER_QUERY_PARAM,\n])\n\nconst TROUBLESHOOTING_URL = `https://electric-sql.com/docs/guides/troubleshooting`\n\nfunction createCacheBuster(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n}\n\ntype Replica = `full` | `default`\nexport type LogMode = `changes_only` | `full`\n\n/**\n * PostgreSQL-specific shape parameters that can be provided externally\n */\nexport interface PostgresParams<T extends Row<unknown> = Row> {\n /** The root table for the shape. Not required if you set the table in your proxy. */\n table?: string\n\n /**\n * The columns to include in the shape.\n * Must include primary keys, and can only include valid columns.\n * Defaults to all columns of the type `T`. If provided, must include primary keys, and can only include valid columns.\n\n */\n columns?: (keyof T)[]\n\n /** The where clauses for the shape */\n where?: string\n\n /**\n * Positional where clause paramater values. These will be passed to the server\n * and will substitute `$i` parameters in the where clause.\n *\n * It can be an array (note that positional arguments start at 1, the array will be mapped\n * accordingly), or an object with keys matching the used positional parameters in the where clause.\n *\n * If where clause is `id = $1 or id = $2`, params must have keys `\"1\"` and `\"2\"`, or be an array with length 2.\n */\n params?: Record<`${number}`, string> | string[]\n\n /**\n * If `replica` is `default` (the default) then Electric will only send the\n * changed columns in an update.\n *\n * If it's `full` Electric will send the entire row with both changed and\n * unchanged values. `old_value` will also be present on update messages,\n * containing the previous value for changed columns.\n *\n * Setting `replica` to `full` will result in higher bandwidth\n * usage and so is not generally recommended.\n */\n replica?: Replica\n}\ntype SerializableParamValue = string | string[] | Record<string, string>\ntype ParamValue =\n | SerializableParamValue\n | (() => SerializableParamValue | Promise<SerializableParamValue>)\n\n/**\n * External params type - what users provide.\n * Excludes reserved parameters to prevent dynamic variations that could cause stream shape changes.\n */\nexport type ExternalParamsRecord<T extends Row<unknown> = Row> = {\n [K in string]: ParamValue | undefined\n} & Partial<PostgresParams<T>> & { [K in ReservedParamKeys]?: never }\n\ntype ReservedParamKeys =\n | typeof LIVE_CACHE_BUSTER_QUERY_PARAM\n | typeof SHAPE_HANDLE_QUERY_PARAM\n | typeof LIVE_QUERY_PARAM\n | typeof OFFSET_QUERY_PARAM\n | typeof CACHE_BUSTER_QUERY_PARAM\n | `subset__${string}`\n\n/**\n * External headers type - what users provide.\n * Allows string or function values for any header.\n */\nexport type ExternalHeadersRecord = {\n [key: string]: string | (() => string | Promise<string>)\n}\n\n/**\n * Internal params type - used within the library.\n * All values are converted to strings.\n */\ntype InternalParamsRecord = {\n [K in string as K extends ReservedParamKeys ? never : K]:\n | string\n | Record<string, string>\n}\n\n/**\n * Helper function to resolve a function or value to its final value\n */\nexport async function resolveValue<T>(\n value: T | (() => T | Promise<T>)\n): Promise<T> {\n if (typeof value === `function`) {\n return (value as () => T | Promise<T>)()\n }\n return value\n}\n\n/**\n * Helper function to convert external params to internal format\n */\nasync function toInternalParams(\n params: ExternalParamsRecord<Row>\n): Promise<InternalParamsRecord> {\n const entries = Object.entries(params)\n const resolvedEntries = await Promise.all(\n entries.map(async ([key, value]) => {\n if (value === undefined) return [key, undefined]\n const resolvedValue = await resolveValue(value)\n return [\n key,\n Array.isArray(resolvedValue) ? resolvedValue.join(`,`) : resolvedValue,\n ]\n })\n )\n\n return Object.fromEntries(\n resolvedEntries.filter(([_, value]) => value !== undefined)\n )\n}\n\n/**\n * Helper function to resolve headers\n */\nasync function resolveHeaders(\n headers?: ExternalHeadersRecord\n): Promise<Record<string, string>> {\n if (!headers) return {}\n\n const entries = Object.entries(headers)\n const resolvedEntries = await Promise.all(\n entries.map(async ([key, value]) => [key, await resolveValue(value)])\n )\n\n return Object.fromEntries(resolvedEntries)\n}\n\ntype RetryOpts = {\n params?: ExternalParamsRecord\n headers?: ExternalHeadersRecord\n}\n\ntype ShapeStreamErrorHandler = (\n error: Error\n) => void | RetryOpts | Promise<void | RetryOpts>\n\n/**\n * Options for constructing a ShapeStream.\n */\nexport type RuntimeVisibilityState = `visible` | `hidden`\n\nexport type RuntimeVisibilityAdapter = {\n getCurrentState?: () => RuntimeVisibilityState | undefined\n subscribe: (callback: (state: RuntimeVisibilityState) => void) => () => void\n}\n\ntype ReactNativeAppStateStatus = `active` | `background` | `inactive` | null\n\ntype ReactNativeAppStateLike = {\n currentState: ReactNativeAppStateStatus\n addEventListener: (\n type: `change`,\n listener: (state: ReactNativeAppStateStatus) => void\n ) => { remove: () => void }\n}\n\nconst reactNativeAppStateToVisibility = (\n state: ReactNativeAppStateStatus\n): RuntimeVisibilityState | undefined => {\n if (state === null) return undefined\n return state === `active` ? `visible` : `hidden`\n}\n\nexport function createReactNativeRuntimeVisibilityAdapter(\n AppState: ReactNativeAppStateLike\n): RuntimeVisibilityAdapter {\n return {\n getCurrentState: () =>\n reactNativeAppStateToVisibility(AppState.currentState),\n subscribe: (callback) => {\n const subscription = AppState.addEventListener(`change`, (state) => {\n const visibilityState = reactNativeAppStateToVisibility(state)\n if (visibilityState) callback(visibilityState)\n })\n return () => subscription.remove()\n },\n }\n}\n\nfunction getDefaultRuntimeVisibilityAdapter():\n | RuntimeVisibilityAdapter\n | undefined {\n return getDefaultRuntimeVisibilityAdapterFactory()?.()\n}\n\nexport interface ShapeStreamOptions<T = never> {\n /**\n * The full URL to where the Shape is served. This can either be the Electric server\n * directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape`\n */\n url: string\n\n /**\n * The \"offset\" on the shape log. This is typically not set as the ShapeStream\n * will handle this automatically. A common scenario where you might pass an offset\n * is if you're maintaining a local cache of the log. If you've gone offline\n * and are re-starting a ShapeStream to catch-up to the latest state of the Shape,\n * you'd pass in the last offset and shapeHandle you'd seen from the Electric server\n * so it knows at what point in the shape to catch you up from.\n */\n offset?: Offset\n\n /**\n * Similar to `offset`, this isn't typically used unless you're maintaining\n * a cache of the shape log.\n */\n handle?: string\n\n /**\n * HTTP headers to attach to requests made by the client.\n * Values can be strings or functions (sync or async) that return strings.\n * Function values are resolved in parallel when needed, making this useful\n * for authentication tokens or other dynamic headers.\n */\n headers?: ExternalHeadersRecord\n\n /**\n * Additional request parameters to attach to the URL.\n * Values can be strings, string arrays, or functions (sync or async) that return these types.\n * Function values are resolved in parallel when needed, making this useful\n * for user-specific parameters or dynamic filters.\n *\n * These will be merged with Electric's standard parameters.\n * Note: You cannot use Electric's reserved parameter names\n * (offset, handle, live, cursor).\n *\n * PostgreSQL-specific options like table, where, columns, and replica\n * should be specified here.\n */\n params?: ExternalParamsRecord\n\n /**\n * Automatically fetch updates to the Shape. If you just want to sync the current\n * shape and stop, pass false.\n */\n subscribe?: boolean\n\n /**\n * @deprecated No longer experimental, use {@link liveSse} instead.\n */\n experimentalLiveSse?: boolean\n\n /**\n * Use Server-Sent Events (SSE) for live updates.\n */\n liveSse?: boolean\n\n /**\n * Initial data loading mode\n */\n log?: LogMode\n\n signal?: AbortSignal\n fetchClient?: typeof fetch\n\n /**\n * Runtime lifecycle adapter for environments without `document.visibilitychange`,\n * such as React Native. Hidden state pauses the stream and aborts in-flight\n * requests; visible state resumes with a non-live catch-up request.\n */\n runtimeVisibility?: RuntimeVisibilityAdapter\n backoffOptions?: BackoffOptions\n\n /**\n * Maximum time in milliseconds to wait for a live long-poll request or\n * refresh catch-up request before aborting it and reconnecting. This guards\n * against runtimes (notably some React Native fetch implementations) where an\n * in-flight fetch can hang indefinitely across app lifecycle or network\n * transitions.\n *\n * The default (45s) is intentionally longer than Electric's server-side\n * long-poll timeout (20s), so firing the watchdog indicates a request that\n * failed to settle rather than a normal long-poll waiting for data.\n *\n * Must be a positive finite number. Set to `false` to disable the watchdog.\n */\n liveRequestTimeoutMs?: number | false\n parser?: Parser<T>\n\n /**\n * Function to transform rows after parsing (e.g., for encryption, type coercion).\n * Applied to data received from Electric.\n *\n * **Note**: If you're using `transformer` solely for column name transformation\n * (e.g., snake_case → camelCase), consider using `columnMapper` instead, which\n * provides bidirectional transformation and automatically encodes WHERE clauses.\n *\n * **Execution order** when both are provided:\n * 1. `columnMapper.decode` runs first (renames columns)\n * 2. `transformer` runs second (transforms values)\n *\n * @example\n * ```typescript\n * // For column renaming only - use columnMapper\n * import { snakeCamelMapper } from '@electric-sql/client'\n * const stream = new ShapeStream({ columnMapper: snakeCamelMapper() })\n * ```\n *\n * @example\n * ```typescript\n * // For value transformation (encryption, etc.) - use transformer\n * const stream = new ShapeStream({\n * transformer: (row) => ({\n * ...row,\n * encrypted_field: decrypt(row.encrypted_field)\n * })\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Use both together\n * const stream = new ShapeStream({\n * columnMapper: snakeCamelMapper(), // Runs first: renames columns\n * transformer: (row) => ({ // Runs second: transforms values\n * ...row,\n * encryptedData: decrypt(row.encryptedData)\n * })\n * })\n * ```\n */\n transformer?: TransformFunction<T>\n\n /**\n * Bidirectional column name mapper for transforming between database column names\n * (e.g., snake_case) and application column names (e.g., camelCase).\n *\n * The mapper handles both:\n * - **Decoding**: Database → Application (applied to query results)\n * - **Encoding**: Application → Database (applied to WHERE clauses)\n *\n * @example\n * ```typescript\n * // Most common case: snake_case ↔ camelCase\n * import { snakeCamelMapper } from '@electric-sql/client'\n *\n * const stream = new ShapeStream({\n * url: 'http://localhost:3000/v1/shape',\n * params: { table: 'todos' },\n * columnMapper: snakeCamelMapper()\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Custom mapping\n * import { createColumnMapper } from '@electric-sql/client'\n *\n * const stream = new ShapeStream({\n * columnMapper: createColumnMapper({\n * user_id: 'userId',\n * project_id: 'projectId',\n * created_at: 'createdAt'\n * })\n * })\n * ```\n */\n columnMapper?: ColumnMapper\n\n /**\n * A function for handling shapestream errors.\n *\n * **Automatic retries**: The client automatically retries 5xx server errors, network\n * errors, and 429 rate limits with exponential backoff. The `onError` callback is\n * only invoked after these automatic retries are exhausted, or for non-retryable\n * errors like 4xx client errors.\n *\n * When not provided, non-retryable errors will be thrown and syncing will stop.\n *\n * **Return value behavior**:\n * - Return an **object** (RetryOpts or empty `{}`) to retry syncing:\n * - `{}` - Retry with the same params and headers\n * - `{ params }` - Retry with modified params\n * - `{ headers }` - Retry with modified headers (e.g., refreshed auth token)\n * - `{ params, headers }` - Retry with both modified\n * - Return **void** or **undefined** to stop the stream permanently\n *\n * **Important**: If you want syncing to continue after an error (e.g., to retry\n * on network failures), you MUST return at least an empty object `{}`. Simply\n * logging the error and returning nothing will stop syncing.\n *\n * Retries requested by `onError` use exponential backoff with full jitter,\n * using the stream's `backoffOptions` timings, and are still bounded by a\n * consecutive retry guard to prevent permanent retry loops.\n *\n * Supports async functions that return `Promise<void | RetryOpts>`.\n *\n * @example\n * ```typescript\n * // Retry on network errors, stop on others\n * onError: (error) => {\n * console.error('Stream error:', error)\n * if (error instanceof FetchError && error.status >= 500) {\n * return {} // Retry with same params\n * }\n * // Return void to stop on other errors\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Refresh auth token on 401\n * onError: async (error) => {\n * if (error instanceof FetchError && error.status === 401) {\n * const newToken = await refreshAuthToken()\n * return { headers: { Authorization: `Bearer ${newToken}` } }\n * }\n * return {} // Retry other errors\n * }\n * ```\n */\n onError?: ShapeStreamErrorHandler\n\n /**\n * HTTP method to use for subset snapshot requests (`requestSnapshot`/`fetchSnapshot`).\n *\n * - `'GET'` (default): Sends subset params as URL query parameters. May fail with\n * HTTP 414 errors for large queries with many parameters.\n * - `'POST'`: Sends subset params in request body as JSON. Recommended for queries\n * with large parameter lists (e.g., `WHERE id = ANY($1)` with hundreds of IDs).\n *\n * This can be overridden per-request by passing `method` in the subset params.\n *\n * @example\n * ```typescript\n * const stream = new ShapeStream({\n * url: 'http://localhost:3000/v1/shape',\n * params: { table: 'items' },\n * subsetMethod: 'POST', // Use POST for all subset requests\n * })\n * ```\n */\n subsetMethod?: `GET` | `POST`\n\n /**\n * Warn in browser console when using HTTP URLs (default: true).\n * HTTP limits browsers to 6 concurrent connections which can cause\n * slow shapes with multiple subscriptions.\n */\n warnOnHttp?: boolean\n}\n\nexport interface ShapeStreamInterface<T extends Row<unknown> = Row> {\n subscribe(\n callback: (\n messages: Message<T>[]\n ) => MaybePromise<void> | { columns?: (keyof T)[] },\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n isLoading(): boolean\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n hasStarted(): boolean\n\n isUpToDate: boolean\n lastOffset: Offset\n shapeHandle?: string\n error?: unknown\n mode: LogMode\n\n forceDisconnectAndRefresh(): Promise<void>\n\n requestSnapshot(params: SubsetParams): Promise<{\n metadata: SnapshotMetadata\n data: Array<Message<T>>\n }>\n\n fetchSnapshot(opts: SubsetParams): Promise<{\n metadata: SnapshotMetadata\n data: Array<ChangeMessage<T>>\n }>\n}\n\n/**\n * Creates a canonical shape key from a URL excluding only Electric protocol parameters\n */\nexport function canonicalShapeKey(url: URL): string {\n const cleanUrl = new URL(url.origin + url.pathname)\n\n // Copy all params except Electric protocol ones that vary between requests.\n // Use append() so duplicate keys (e.g. ?table=a&table=b) are preserved.\n for (const [key, value] of url.searchParams) {\n if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {\n cleanUrl.searchParams.append(key, value)\n }\n }\n\n cleanUrl.searchParams.sort()\n return cleanUrl.toString()\n}\n\n/**\n * Reads updates to a shape from Electric using HTTP requests and long polling or\n * Server-Sent Events (SSE).\n * Notifies subscribers when new messages come in. Doesn't maintain any history of the\n * log but does keep track of the offset position and is the best way\n * to consume the HTTP `GET /v1/shape` api.\n *\n * @constructor\n * @param {ShapeStreamOptions} options - configure the shape stream\n * @example\n * Register a callback function to subscribe to the messages.\n * ```\n * const stream = new ShapeStream(options)\n * stream.subscribe(messages => {\n * // messages is 1 or more row updates\n * })\n * ```\n *\n * To use Server-Sent Events (SSE) for real-time updates:\n * ```\n * const stream = new ShapeStream({\n * url: `http://localhost:3000/v1/shape`,\n * liveSse: true\n * })\n * ```\n *\n * To abort the stream, abort the `signal`\n * passed in via the `ShapeStreamOptions`.\n * ```\n * const aborter = new AbortController()\n * const issueStream = new ShapeStream({\n * url: `${BASE_URL}/${table}`\n * subscribe: true,\n * signal: aborter.signal,\n * })\n * // Later...\n * aborter.abort()\n * ```\n */\n\nexport class ShapeStream<T extends Row<unknown> = Row>\n implements ShapeStreamInterface<T>\n{\n static readonly Replica = {\n FULL: `full` as Replica,\n DEFAULT: `default` as Replica,\n }\n\n readonly options: ShapeStreamOptions<GetExtensions<T>>\n #error: unknown = null\n\n readonly #fetchClient: typeof fetch\n readonly #sseFetchClient: typeof fetch\n readonly #messageParser: MessageParser<T>\n\n readonly #subscribers = new Map<\n object,\n [\n (messages: Message<T>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n #started = false\n #syncState: ShapeStreamState\n #connected: boolean = false\n #mode: LogMode\n #onError?: ShapeStreamErrorHandler\n #requestAbortController?: AbortController\n #restartAbortControllers = new WeakSet<AbortController>()\n #refreshCount = 0\n #refreshCatchUpWatchdogActive = false\n #snapshotCounter = 0\n\n get #isRefreshing(): boolean {\n return this.#refreshCount > 0\n }\n #tickPromise?: Promise<void>\n #tickPromiseResolver?: () => void\n #tickPromiseRejecter?: (reason?: unknown) => void\n #messageChain = Promise.resolve<void[]>([]) // promise chain for incoming messages\n // Tracks when subscriber callbacks are actively being delivered from\n // #messageChain. requestSnapshot can inject a nested batch from inside a\n // subscriber; in that reentrant case #publish uses this as an intentional\n // escape hatch to deliver the nested snapshot batch immediately rather than\n // queueing it behind the subscriber that is awaiting it.\n #isPublishing = false\n #snapshotTracker = new SnapshotTracker()\n #pauseLock: PauseLock\n #currentFetchUrl?: URL // Current fetch URL for computing shape key\n #lastSseConnectionStartTime?: number\n #minSseConnectionDuration = 1000 // Minimum expected SSE connection duration (1 second)\n #maxShortSseConnections = 3 // Fall back to long polling after this many short connections\n #sseBackoffBaseDelay = 100 // Base delay for exponential backoff (ms)\n #sseBackoffMaxDelay = 5000 // Maximum delay cap (ms)\n #liveRequestTimeoutMs: number | false\n #unsubscribeFromVisibilityChanges?: () => void\n #unsubscribeFromWakeDetection?: () => void\n #maxStaleCacheRetries = 3\n // Fast-loop detection: track recent non-live requests to detect tight retry\n // loops caused by proxy/CDN misconfiguration or stale client-side caches\n #recentRequestEntries: Array<{ timestamp: number; offset: string }> = []\n #fastLoopWindowMs = 500\n #fastLoopThreshold = 5\n #fastLoopBackoffBaseMs = 100\n #fastLoopBackoffMaxMs = 5_000\n #fastLoopConsecutiveCount = 0\n #fastLoopMaxCount = 5\n #pendingRequestShapeCacheBuster?: string\n #maxSnapshotRetries = 5\n #expiredShapeRecoveryKey: string | null = null\n #pendingSelfHealCheck: { shapeKey: string; staleHandle: string } | null = null\n #consecutiveErrorRetries = 0\n #maxConsecutiveErrorRetries = 50\n #onErrorBackoff: {\n initialDelay: number\n maxDelay: number\n multiplier: number\n }\n\n constructor(options: ShapeStreamOptions<GetExtensions<T>>) {\n this.options = { subscribe: true, ...options }\n validateOptions(this.options)\n this.#syncState = createInitialState({\n offset: this.options.offset ?? `-1`,\n handle: this.options.handle,\n })\n\n this.#pauseLock = new PauseLock({\n onAcquired: () => {\n this.#syncState = this.#syncState.pause()\n if (this.#started) {\n this.#requestAbortController?.abort(PAUSE_STREAM)\n }\n },\n onReleased: () => {\n if (!this.#started) return\n if (this.options.signal?.aborted) return\n // Don't transition syncState here — let #requestShape handle\n // the PausedState→previous transition so it can detect\n // resumingFromPause and avoid live long-polling.\n this.#start().catch(() => {\n // Errors from #start are handled internally via onError.\n // This catch prevents unhandled promise rejection in Node/Bun.\n })\n },\n })\n\n // Build transformer chain: columnMapper.decode -> transformer\n // columnMapper transforms column names, transformer transforms values\n let transformer: TransformFunction<GetExtensions<T>> | undefined\n\n if (options.columnMapper) {\n const applyColumnMapper = (\n row: Row<GetExtensions<T>>\n ): Row<GetExtensions<T>> => {\n const result: Record<string, unknown> = {}\n for (const [dbKey, value] of Object.entries(row)) {\n const appKey = options.columnMapper!.decode(dbKey)\n result[appKey] = value\n }\n return result as Row<GetExtensions<T>>\n }\n\n transformer = options.transformer\n ? (row: Row<GetExtensions<T>>) =>\n options.transformer!(applyColumnMapper(row))\n : applyColumnMapper\n } else {\n transformer = options.transformer\n }\n\n this.#messageParser = new MessageParser<T>(options.parser, transformer)\n\n this.#onError = this.options.onError\n this.#mode = this.options.log ?? `full`\n // Default exceeds Electric's 20s server long-poll timeout, so this only\n // fires when the runtime request appears wedged rather than normally held.\n this.#liveRequestTimeoutMs = this.options.liveRequestTimeoutMs ?? 45_000\n\n const baseFetchClient =\n options.fetchClient ??\n ((...args: Parameters<typeof fetch>) => fetch(...args))\n\n const backOffOpts = {\n ...(options.backoffOptions ?? BackoffDefaults),\n onFailedAttempt: () => {\n this.#connected = false\n options.backoffOptions?.onFailedAttempt?.()\n },\n }\n this.#onErrorBackoff = {\n initialDelay: backOffOpts.initialDelay,\n maxDelay: backOffOpts.maxDelay,\n multiplier: backOffOpts.multiplier,\n }\n\n const fetchWithBackoffClient = createFetchWithBackoff(\n baseFetchClient,\n backOffOpts\n )\n\n this.#sseFetchClient = createFetchWithResponseHeadersCheck(\n createFetchWithChunkBuffer(fetchWithBackoffClient)\n )\n\n this.#fetchClient = createFetchWithConsumedMessages(this.#sseFetchClient)\n\n this.#subscribeToVisibilityChanges()\n }\n\n get shapeHandle() {\n return this.#syncState.handle\n }\n\n get error() {\n return this.#error\n }\n\n get isUpToDate() {\n return this.#syncState.isUpToDate\n }\n\n get lastOffset() {\n return this.#syncState.offset\n }\n\n get mode() {\n return this.#mode\n }\n\n async #start(): Promise<void> {\n this.#started = true\n this.#subscribeToWakeDetection()\n\n try {\n await this.#requestShape()\n } catch (err) {\n this.#error = err\n if (err instanceof Error) {\n this.#syncState = this.#syncState.toErrorState(err)\n }\n\n // Check if onError handler wants to retry\n if (this.#onError) {\n const retryOpts = await this.#onError(err as Error)\n // Guard against null (typeof null === \"object\" in JavaScript)\n const isRetryable = !(err instanceof MissingHeadersError)\n if (retryOpts && typeof retryOpts === `object` && isRetryable) {\n // Update params/headers but don't reset offset\n // We want to continue from where we left off, not refetch everything\n if (retryOpts.params) {\n // Merge new params with existing params to preserve other parameters\n this.options.params = {\n ...(this.options.params ?? {}),\n ...retryOpts.params,\n }\n }\n\n if (retryOpts.headers) {\n // Merge new headers with existing headers to preserve other headers\n this.options.headers = {\n ...(this.options.headers ?? {}),\n ...retryOpts.headers,\n }\n }\n\n // Bound the onError retry loop to prevent unbounded retries\n this.#consecutiveErrorRetries++\n if (\n this.#consecutiveErrorRetries > this.#maxConsecutiveErrorRetries\n ) {\n console.warn(\n `[Electric] onError retry loop exhausted after ${this.#maxConsecutiveErrorRetries} consecutive retries. ` +\n `The error was never resolved by the onError handler. ` +\n `Error: ${err instanceof Error ? err.message : String(err)}`,\n new Error(`stack trace`)\n )\n if (err instanceof Error) {\n this.#sendErrorToSubscribers(err)\n }\n this.#teardown()\n return\n }\n\n // Clear the error since we're retrying\n this.#error = null\n if (this.#syncState instanceof ErrorState) {\n this.#syncState = this.#syncState.retry()\n }\n this.#fastLoopConsecutiveCount = 0\n this.#recentRequestEntries = []\n\n await this.#backoffOnErrorRetry(this.#consecutiveErrorRetries)\n if (this.options.signal?.aborted) {\n this.#teardown()\n return\n }\n\n // Restart from current offset\n this.#started = false\n return this.#start()\n }\n // onError returned void, meaning it doesn't want to retry\n // This is an unrecoverable error, notify subscribers\n if (err instanceof Error) {\n this.#sendErrorToSubscribers(err)\n }\n this.#teardown()\n return\n }\n\n // No onError handler provided, this is an unrecoverable error\n // Notify subscribers and throw\n if (err instanceof Error) {\n this.#sendErrorToSubscribers(err)\n }\n this.#teardown()\n throw err\n }\n\n this.#teardown()\n }\n\n #teardown() {\n this.#connected = false\n this.#tickPromiseRejecter?.()\n this.#unsubscribeFromWakeDetection?.()\n }\n\n async #backoffOnErrorRetry(retryAttempt: number): Promise<void> {\n // Non-429 4xx errors intentionally bypass fetch-layer backoff so\n // `onError` can repair auth/params; network failures that exhaust fetch\n // backoff also reach this path. If the handler asks to retry we still\n // need pacing to avoid tight retry loops on persistent failures.\n // Uses the same full-jitter exponential backoff strategy as the fetch layer.\n const { initialDelay, maxDelay, multiplier } = this.#onErrorBackoff\n const cappedDelay = Math.min(\n maxDelay,\n initialDelay * Math.pow(multiplier, retryAttempt - 1) // 1-indexed: first retry uses multiplier^0\n )\n const delayMs = Math.floor(Math.random() * cappedDelay)\n\n const signal = this.options.signal\n if (delayMs <= 0 || signal?.aborted) return\n\n await new Promise<void>((resolve) => {\n let settled = false\n const done = () => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n signal?.removeEventListener(`abort`, done)\n resolve()\n }\n const timer = setTimeout(done, delayMs)\n signal?.addEventListener(`abort`, done, { once: true })\n if (signal?.aborted) done()\n })\n }\n\n async #requestShape(requestShapeCacheBuster?: string): Promise<void> {\n if (this.options.signal?.aborted) {\n this.#teardown()\n return\n }\n\n // ErrorState should never reach the request loop — re-throw so\n // #start's catch block can route it through onError properly.\n if (this.#syncState instanceof ErrorState) {\n throw this.#syncState.error\n }\n\n const activeCacheBuster =\n requestShapeCacheBuster ?? this.#pendingRequestShapeCacheBuster\n\n if (this.#pauseLock.isPaused) {\n if (activeCacheBuster) {\n this.#pendingRequestShapeCacheBuster = activeCacheBuster\n }\n return\n }\n\n if (\n !this.options.subscribe &&\n (this.options.signal?.aborted || this.#syncState.isUpToDate)\n ) {\n return\n }\n\n // Only check for fast loops on non-live requests; live polling is expected to be rapid\n if (!this.#syncState.isUpToDate) {\n await this.#checkFastLoop()\n } else {\n this.#fastLoopConsecutiveCount = 0\n this.#recentRequestEntries = []\n }\n\n let resumingFromPause = false\n if (this.#syncState instanceof PausedState) {\n resumingFromPause = true\n this.#syncState = this.#syncState.resume()\n }\n\n const { url, signal } = this.options\n const { fetchUrl, requestHeaders } = await this.#constructUrl(\n url,\n resumingFromPause\n )\n\n if (activeCacheBuster) {\n fetchUrl.searchParams.set(CACHE_BUSTER_QUERY_PARAM, activeCacheBuster)\n fetchUrl.searchParams.sort()\n }\n const abortListener = await this.#createAbortListener(signal)\n const requestAbortController = this.#requestAbortController! // we know that it is not undefined because it is set by `this.#createAbortListener`\n\n // Re-check after async setup — the lock may have been acquired\n // during URL construction or abort controller creation (e.g., by\n // requestSnapshot), when the abort controller didn't exist yet.\n if (this.#pauseLock.isPaused) {\n if (abortListener && signal) {\n signal.removeEventListener(`abort`, abortListener)\n }\n if (activeCacheBuster) {\n this.#pendingRequestShapeCacheBuster = activeCacheBuster\n }\n this.#requestAbortController = undefined\n return\n }\n\n this.#pendingRequestShapeCacheBuster = undefined\n\n try {\n await this.#fetchShape({\n fetchUrl,\n requestAbortController,\n headers: requestHeaders,\n resumingFromPause,\n })\n } catch (e) {\n const abortReason = requestAbortController.signal.reason\n const isMarkedRestartAbort = this.#restartAbortControllers.delete(\n requestAbortController\n )\n const isRestartAbort =\n requestAbortController.signal.aborted &&\n (isMarkedRestartAbort ||\n abortReason === FORCE_DISCONNECT_AND_REFRESH ||\n abortReason === SYSTEM_WAKE ||\n abortReason === LIVE_REQUEST_TIMEOUT)\n\n if (\n (e instanceof FetchError || e instanceof FetchBackoffAbortError) &&\n isRestartAbort\n ) {\n return this.#requestShape()\n }\n\n if (e instanceof FetchBackoffAbortError) {\n return // interrupted\n }\n\n if (e instanceof StaleCacheError) {\n // Two paths throw StaleCacheError:\n // 1. Normal stale-retry: response handle matched expired handle,\n // #staleCacheBuster set to bypass CDN cache on next request.\n // 2. Self-healing: stale retries exhausted, expired entry cleared,\n // stream reset — retry without expired_handle param.\n return this.#requestShape()\n }\n\n if (!(e instanceof FetchError)) throw e // should never happen\n\n if (e.status == 409) {\n // Upon receiving a 409, start from scratch with the newly\n // provided shape handle (if present). An unconditional cache\n // buster ensures the retry URL is always unique regardless of\n // whether the server returns a new, same, or missing handle.\n\n // Store the current shape URL as expired to avoid future 409s\n if (this.#syncState.handle) {\n const shapeKey = canonicalShapeKey(fetchUrl)\n expiredShapesCache.markExpired(shapeKey, this.#syncState.handle)\n }\n\n const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER]\n if (!newShapeHandle) {\n console.warn(\n `[Electric] Received 409 response without a shape handle header. ` +\n `This likely indicates a proxy or CDN stripping required headers.`\n )\n }\n const nextRequestShapeCacheBuster = createCacheBuster()\n this.#reset(newShapeHandle)\n\n // Notify subscribers that data must be re-fetched so they can\n // clear accumulated state (e.g., Shape clears its row map).\n // We publish a synthetic control message rather than the raw 409\n // body to avoid delivering stale data rows to subscribers.\n await this.#publish([{ headers: { control: `must-refetch` } }])\n\n return this.#requestShape(nextRequestShapeCacheBuster)\n } else {\n // errors that have reached this point are not actionable without\n // additional user input, such as 400s or failures to read the\n // body of a response, so we exit the loop and let #start handle it\n // Note: We don't notify subscribers here because onError might recover\n throw e\n }\n } finally {\n if (abortListener && signal) {\n signal.removeEventListener(`abort`, abortListener)\n }\n this.#requestAbortController = undefined\n }\n\n this.#tickPromiseResolver?.()\n return this.#requestShape()\n }\n\n /**\n * Detects tight retry loops (e.g., from stale client-side caches or\n * proxy/CDN misconfiguration) and attempts recovery. On first detection,\n * clears client-side caches (in-memory and localStorage) and resets the\n * stream to fetch from scratch.\n * If the loop persists, applies exponential backoff and eventually throws.\n */\n async #checkFastLoop(): Promise<void> {\n const now = Date.now()\n const currentOffset = this.#syncState.offset\n\n this.#recentRequestEntries = this.#recentRequestEntries.filter(\n (e) => now - e.timestamp < this.#fastLoopWindowMs\n )\n this.#recentRequestEntries.push({ timestamp: now, offset: currentOffset })\n\n // Only flag as a fast loop if requests are stuck at the same offset.\n // Normal rapid syncing advances the offset with each response.\n const sameOffsetCount = this.#recentRequestEntries.filter(\n (e) => e.offset === currentOffset\n ).length\n\n if (sameOffsetCount < this.#fastLoopThreshold) return\n\n this.#fastLoopConsecutiveCount++\n\n if (this.#fastLoopConsecutiveCount >= this.#fastLoopMaxCount) {\n throw new FetchError(\n 502,\n undefined,\n undefined,\n {},\n this.options.url,\n `Client is stuck in a fast retry loop ` +\n `(${this.#fastLoopThreshold} requests in ${this.#fastLoopWindowMs}ms at the same offset, ` +\n `repeated ${this.#fastLoopMaxCount} times). ` +\n `Client-side caches were cleared automatically on first detection, but the loop persists. ` +\n `This usually indicates a proxy or CDN misconfiguration. ` +\n `Common causes:\\n` +\n ` - Proxy is not including query parameters (handle, offset) in its cache key\\n` +\n ` - CDN is serving stale 409 responses\\n` +\n ` - Proxy is stripping required Electric headers from responses\\n` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`\n )\n }\n\n if (this.#fastLoopConsecutiveCount === 1) {\n console.warn(\n `[Electric] Detected fast retry loop ` +\n `(${this.#fastLoopThreshold} requests in ${this.#fastLoopWindowMs}ms at the same offset). ` +\n `Clearing client-side caches and resetting stream to recover. ` +\n `If this persists, check that your proxy includes all query parameters ` +\n `(especially 'handle' and 'offset') in its cache key, ` +\n `and that required Electric headers are forwarded to the client. ` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`,\n new Error(`stack trace`)\n )\n\n if (this.#currentFetchUrl) {\n const shapeKey = canonicalShapeKey(this.#currentFetchUrl)\n expiredShapesCache.delete(shapeKey)\n upToDateTracker.delete(shapeKey)\n } else {\n expiredShapesCache.clear()\n upToDateTracker.clear()\n }\n this.#reset()\n this.#recentRequestEntries = []\n return\n }\n\n // Exponential backoff with full jitter\n const maxDelay = Math.min(\n this.#fastLoopBackoffMaxMs,\n this.#fastLoopBackoffBaseMs * Math.pow(2, this.#fastLoopConsecutiveCount)\n )\n const delayMs = Math.floor(Math.random() * maxDelay)\n\n await new Promise((resolve) => setTimeout(resolve, delayMs))\n\n this.#recentRequestEntries = []\n }\n\n async #constructUrl(\n url: string,\n resumingFromPause: boolean,\n subsetParams?: SubsetParams\n ) {\n // Resolve headers and params in parallel\n const [requestHeaders, params] = await Promise.all([\n resolveHeaders(this.options.headers),\n this.options.params\n ? toInternalParams(convertWhereParamsToObj(this.options.params))\n : undefined,\n ])\n\n // Validate params after resolution\n if (params) validateParams(params)\n\n const fetchUrl = new URL(url)\n\n // Add PostgreSQL-specific parameters\n if (params) {\n if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table)\n if (params.where && typeof params.where === `string`) {\n const encodedWhere = encodeWhereClause(\n params.where,\n this.options.columnMapper?.encode\n )\n setQueryParam(fetchUrl, WHERE_QUERY_PARAM, encodedWhere)\n }\n if (params.columns) {\n // Get original columns array from options (before toInternalParams converted to string)\n const originalColumns = await resolveValue(this.options.params?.columns)\n if (Array.isArray(originalColumns)) {\n // Apply columnMapper encoding if present\n let encodedColumns = originalColumns.map(String)\n if (this.options.columnMapper) {\n encodedColumns = encodedColumns.map(\n this.options.columnMapper.encode\n )\n }\n // Quote each column name to handle special characters (commas, etc.)\n const serializedColumns = encodedColumns\n .map(quoteIdentifier)\n .join(`,`)\n setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, serializedColumns)\n } else {\n // Fallback: columns was already a string\n setQueryParam(fetchUrl, COLUMNS_QUERY_PARAM, params.columns)\n }\n }\n if (params.replica) setQueryParam(fetchUrl, REPLICA_PARAM, params.replica)\n if (params.params)\n setQueryParam(fetchUrl, WHERE_PARAMS_PARAM, params.params)\n\n // Add any remaining custom parameters\n const customParams = { ...params }\n delete customParams.table\n delete customParams.where\n delete customParams.columns\n delete customParams.replica\n delete customParams.params\n\n for (const [key, value] of Object.entries(customParams)) {\n setQueryParam(fetchUrl, key, value)\n }\n }\n\n if (subsetParams) {\n // Prefer structured expressions when available (allows proper columnMapper application)\n // Fall back to legacy string format for backwards compatibility\n if (subsetParams.whereExpr) {\n // Compile structured expression with columnMapper applied\n const compiledWhere = compileExpression(\n subsetParams.whereExpr,\n this.options.columnMapper?.encode\n )\n setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, compiledWhere)\n // Also send the structured expression for servers that support it\n fetchUrl.searchParams.set(\n SUBSET_PARAM_WHERE_EXPR,\n JSON.stringify(subsetParams.whereExpr)\n )\n } else if (subsetParams.where && typeof subsetParams.where === `string`) {\n // Legacy string format (no columnMapper applied to already-compiled SQL)\n const encodedWhere = encodeWhereClause(\n subsetParams.where,\n this.options.columnMapper?.encode\n )\n setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, encodedWhere)\n }\n\n if (subsetParams.params)\n // Serialize params as JSON to keep the parameter name constant for proxy configs\n fetchUrl.searchParams.set(\n SUBSET_PARAM_WHERE_PARAMS,\n bigintSafeStringify(subsetParams.params)\n )\n if (subsetParams.limit !== undefined)\n setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit)\n if (subsetParams.offset !== undefined)\n setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset)\n\n // Prefer structured ORDER BY expressions when available\n if (subsetParams.orderByExpr) {\n // Compile structured ORDER BY with columnMapper applied\n const compiledOrderBy = compileOrderBy(\n subsetParams.orderByExpr,\n this.options.columnMapper?.encode\n )\n setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, compiledOrderBy)\n // Also send the structured expression for servers that support it\n fetchUrl.searchParams.set(\n SUBSET_PARAM_ORDER_BY_EXPR,\n JSON.stringify(subsetParams.orderByExpr)\n )\n } else if (\n subsetParams.orderBy &&\n typeof subsetParams.orderBy === `string`\n ) {\n // Legacy string format\n const encodedOrderBy = encodeWhereClause(\n subsetParams.orderBy,\n this.options.columnMapper?.encode\n )\n setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy)\n }\n }\n\n // Add state-specific parameters (offset, handle, live cache busters, etc.)\n this.#syncState.applyUrlParams(fetchUrl, {\n isSnapshotRequest: subsetParams !== undefined,\n // Don't long-poll when resuming from pause or refreshing — avoids\n // a 20s hold during which `isConnected` would be false\n canLongPoll: !this.#isRefreshing && !resumingFromPause,\n })\n fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, this.#mode)\n\n // Add cache buster for shapes known to be expired to prevent 409s\n const shapeKey = canonicalShapeKey(fetchUrl)\n const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey)\n if (expiredHandle) {\n fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle)\n }\n\n // sort query params in-place for stable URLs and improved cache hits\n fetchUrl.searchParams.sort()\n\n return {\n fetchUrl,\n requestHeaders,\n }\n }\n\n async #createAbortListener(signal?: AbortSignal) {\n // Create a new AbortController for this request\n this.#requestAbortController = new AbortController()\n\n // If user provided a signal, listen to it and pass on the reason for the abort\n if (signal) {\n const abortListener = () => {\n this.#requestAbortController?.abort(signal.reason)\n }\n\n signal.addEventListener(`abort`, abortListener, { once: true })\n\n if (signal.aborted) {\n // If the signal is already aborted, abort the request immediately\n this.#requestAbortController?.abort(signal.reason)\n }\n\n return abortListener\n }\n }\n\n /**\n * Processes response metadata (headers, status) and updates sync state.\n * Returns `true` if the response body should be processed by the caller,\n * or `false` if the response was ignored (stale) and the body should be skipped.\n * Throws on stale-retry (to trigger a retry with cache buster).\n */\n async #onInitialResponse(response: Response): Promise<boolean> {\n const { headers, status } = response\n const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)\n const shapeKey = this.#currentFetchUrl\n ? canonicalShapeKey(this.#currentFetchUrl)\n : null\n const expiredHandle = shapeKey\n ? expiredShapesCache.getExpiredHandle(shapeKey)\n : null\n\n // If this response is the first one after a self-healing retry, check\n // whether the proxy/CDN returned the exact handle we just marked expired.\n // If so, the client is about to accept stale data silently — loudly warn\n // so operators can detect and fix the proxy misconfiguration.\n if (this.#pendingSelfHealCheck) {\n const { shapeKey: healedKey, staleHandle } = this.#pendingSelfHealCheck\n this.#pendingSelfHealCheck = null\n if (shapeKey === healedKey && shapeHandle === staleHandle) {\n console.warn(\n `[Electric] Self-healing retry received the same handle \"${staleHandle}\" that was just marked expired. ` +\n `This means your proxy/CDN is serving a stale cached response and ignoring cache-buster query params. ` +\n `The client will proceed with this stale data to avoid a permanent failure, but it may be out of date until the cache refreshes. ` +\n `Fix: configure your proxy/CDN to include all query parameters (especially 'handle' and 'offset') in its cache key. ` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`,\n new Error(`stack trace`)\n )\n }\n }\n\n const transition = this.#syncState.handleResponseMetadata({\n status,\n responseHandle: shapeHandle,\n responseOffset: headers.get(CHUNK_LAST_OFFSET_HEADER) as Offset | null,\n responseCursor: headers.get(LIVE_CACHE_BUSTER_HEADER),\n responseSchema: getSchemaFromHeaders(headers),\n expiredHandle,\n now: Date.now(),\n maxStaleCacheRetries: this.#maxStaleCacheRetries,\n createCacheBuster,\n })\n\n this.#syncState = transition.state\n\n // Clear recovery guard on 204 (no-content), since the empty body means\n // #onMessages won't run to clear it via the up-to-date path.\n if (status === 204) {\n this.#expiredShapeRecoveryKey = null\n }\n\n if (transition.action === `accepted` && status === 204) {\n this.#consecutiveErrorRetries = 0\n }\n\n if (transition.action === `stale-retry`) {\n // Cancel the response body to release the connection before retrying.\n await response.body?.cancel()\n if (transition.exceededMaxRetries) {\n if (shapeKey) {\n // Clear the expired entry — keeping it only poisons future sessions.\n expiredShapesCache.delete(shapeKey)\n\n // Try one self-healing retry per shape: reset the stream and\n // retry without the expired_handle param. Since handles are never\n // reused (see SPEC.md S0), the fresh response will have a new\n // handle and won't trigger stale detection.\n if (this.#expiredShapeRecoveryKey !== shapeKey) {\n console.warn(\n `[Electric] Stale cache retries exhausted (${this.#maxStaleCacheRetries} attempts). ` +\n `Clearing expired handle entry and attempting self-healing retry without the expired_handle parameter. ` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`,\n new Error(`stack trace`)\n )\n this.#expiredShapeRecoveryKey = shapeKey\n // Arm a post-self-heal check: if the next response comes back\n // with the same handle we just marked expired, the proxy/CDN is\n // still serving stale data and we'll warn loudly instead of\n // accepting it silently.\n if (shapeHandle) {\n this.#pendingSelfHealCheck = {\n shapeKey,\n staleHandle: shapeHandle,\n }\n }\n this.#reset()\n throw new StaleCacheError(\n `Expired handle entry evicted for self-healing retry`\n )\n }\n }\n throw new FetchError(\n 502,\n undefined,\n undefined,\n {},\n this.#currentFetchUrl?.toString() ?? ``,\n `CDN continues serving stale cached responses after ${this.#maxStaleCacheRetries} retry attempts. ` +\n `This indicates a severe proxy/CDN misconfiguration. ` +\n `Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. ` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`\n )\n }\n console.warn(\n `[Electric] Received stale cached response with expired shape handle. ` +\n `This should not happen and indicates a proxy/CDN caching misconfiguration. ` +\n `The response contained handle \"${shapeHandle}\" which was previously marked as expired. ` +\n `Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. ` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL} ` +\n `Retrying with a random cache buster to bypass the stale cache (attempt ${this.#syncState.staleCacheRetryCount}/${this.#maxStaleCacheRetries}).`,\n new Error(`stack trace`)\n )\n throw new StaleCacheError(\n `Received stale cached response with expired handle \"${shapeHandle}\". ` +\n `This indicates a proxy/CDN caching misconfiguration. ` +\n `Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key.`\n )\n }\n\n if (transition.action === `ignored`) {\n console.warn(\n `[Electric] Response was ignored by state \"${this.#syncState.kind}\". ` +\n `The response body will be skipped. ` +\n `This may indicate a proxy/CDN caching issue or a client state machine bug.`,\n new Error(`stack trace`)\n )\n return false\n }\n\n return true\n }\n\n async #onMessages(\n batch: Array<Message<T>>,\n isSseMessage = false,\n opts: { allowReentrantPublishBypass?: boolean } = {}\n ) {\n if (!Array.isArray(batch)) {\n console.warn(\n `[Electric] #onMessages called with non-array argument (${typeof batch}). ` +\n `This is a client bug — please report it.`,\n new Error(`stack trace`)\n )\n return\n }\n if (batch.length === 0) return\n\n this.#consecutiveErrorRetries = 0\n\n const lastMessage = batch[batch.length - 1]\n const hasUpToDateMessage = isUpToDateMessage(lastMessage)\n const upToDateOffset = hasUpToDateMessage\n ? getOffset(lastMessage)\n : undefined\n\n const transition = this.#syncState.handleMessageBatch({\n hasMessages: true,\n hasUpToDateMessage,\n isSse: isSseMessage,\n upToDateOffset,\n now: Date.now(),\n currentCursor: this.#syncState.liveCacheBuster,\n })\n this.#syncState = transition.state\n\n if (hasUpToDateMessage) {\n this.#refreshCatchUpWatchdogActive = false\n if (!transition.suppressUpToDate && this.#currentFetchUrl) {\n const shapeKey = canonicalShapeKey(this.#currentFetchUrl)\n upToDateTracker.recordUpToDate(\n shapeKey,\n this.#syncState.liveCacheBuster\n )\n this.#expiredShapeRecoveryKey = null\n }\n }\n\n // Filter messages using snapshot tracker\n const messagesToProcess = batch.filter((message) => {\n if (isChangeMessage(message)) {\n const changeLsn = message.headers.lsn\n if (typeof changeLsn === `string` && changeLsn) {\n // A quiet SSE stream may deliver its first later change before the\n // next up-to-date boundary. Retire snapshots against that change's\n // WAL position before resolving its wrapped transaction ID.\n this.#snapshotTracker.lastSeenUpdate(BigInt(changeLsn))\n }\n return !this.#snapshotTracker.shouldRejectMessage(message)\n }\n\n if (isUpToDateMessage(message)) {\n const lastSeenLsn = message.headers.global_last_seen_lsn\n if (typeof lastSeenLsn === `string` && lastSeenLsn) {\n // Process this in message order: changes before the up-to-date\n // boundary still need snapshot deduplication, while later changes do\n // not once the database has passed the snapshot's LSN.\n this.#snapshotTracker.lastSeenUpdate(BigInt(lastSeenLsn))\n }\n\n // A replayed up-to-date for the previous session's cursor still\n // carries real WAL progress, which is applied above; only its\n // delivery to subscribers is skipped.\n if (transition.suppressUpToDate) return false\n }\n\n return true // Always process control messages\n })\n\n // A replay batch that carried nothing but the suppressed up-to-date has\n // nothing to deliver. Skip the callback rather than publishing an empty\n // batch: subscribers are promised one or more messages per notification,\n // and the pre-suppression code never notified for a suppressed replay.\n // Batches emptied by the snapshot tracker alone are still published as\n // before.\n if (messagesToProcess.length === 0 && transition.suppressUpToDate) return\n\n await this.#publish(messagesToProcess, {\n allowReentrantBypass: opts.allowReentrantPublishBypass,\n })\n }\n\n /**\n * Fetches the shape from the server using either long polling or SSE.\n * Upon receiving a successful response, the #onInitialResponse method is called.\n * Afterwards, the #onMessages method is called for all the incoming updates.\n * @param opts - The options for the request.\n * @returns A promise that resolves when the request is complete (i.e. the long poll receives a response or the SSE connection is closed).\n */\n async #fetchShape(opts: {\n fetchUrl: URL\n requestAbortController: AbortController\n headers: Record<string, string>\n resumingFromPause?: boolean\n }): Promise<void> {\n // Store current fetch URL for shape key computation\n this.#currentFetchUrl = opts.fetchUrl\n\n // Check if we should enter replay mode (replaying cached responses)\n // This happens when we're starting fresh (offset=-1 or before first up-to-date)\n // and there's a recent up-to-date in localStorage (< 60s)\n if (!this.#syncState.isUpToDate && this.#syncState.canEnterReplayMode()) {\n const shapeKey = canonicalShapeKey(opts.fetchUrl)\n const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey)\n if (lastSeenCursor) {\n // Enter replay mode and store the last seen cursor\n this.#syncState = this.#syncState.enterReplayMode(lastSeenCursor)\n }\n }\n\n const useSse = this.options.liveSse ?? this.options.experimentalLiveSse\n if (\n this.#syncState.shouldUseSse({\n liveSseEnabled: !!useSse,\n isRefreshing: this.#isRefreshing,\n resumingFromPause: !!opts.resumingFromPause,\n })\n ) {\n opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`)\n opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`)\n return this.#requestShapeSSE(opts)\n }\n\n return this.#requestShapeLongPoll(opts)\n }\n\n async #withRequestTimeout<T>(\n promise: Promise<T>,\n requestAbortController: AbortController,\n fetchUrl: URL\n ): Promise<T> {\n const timeoutMs = this.#liveRequestTimeoutMs\n const isLiveRequest = fetchUrl.searchParams.get(LIVE_QUERY_PARAM) === `true`\n const isRefreshCatchUpRequest =\n this.#isRefreshing || this.#refreshCatchUpWatchdogActive\n\n if (timeoutMs === false || (!isLiveRequest && !isRefreshCatchUpRequest)) {\n return promise\n }\n\n let timeout: ReturnType<typeof setTimeout> | undefined\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n if (!requestAbortController.signal.aborted) {\n this.#restartAbortControllers.add(requestAbortController)\n requestAbortController.abort(LIVE_REQUEST_TIMEOUT)\n }\n reject(new FetchBackoffAbortError())\n }, timeoutMs)\n })\n\n try {\n return await Promise.race([promise, timeoutPromise])\n } finally {\n if (timeout) clearTimeout(timeout)\n }\n }\n\n async #requestShapeLongPoll(opts: {\n fetchUrl: URL\n requestAbortController: AbortController\n headers: Record<string, string>\n }): Promise<void> {\n const { fetchUrl, requestAbortController, headers } = opts\n const fetchUrlString = fetchUrl.toString()\n const rawResponse = await this.#withRequestTimeout(\n this.#sseFetchClient(fetchUrlString, {\n signal: requestAbortController.signal,\n headers,\n }),\n requestAbortController,\n fetchUrl\n )\n const response = await consumeResponseBody(\n rawResponse,\n fetchUrlString,\n requestAbortController.signal\n )\n\n this.#connected = true\n const shouldProcessBody = await this.#onInitialResponse(response)\n if (!shouldProcessBody) return\n\n const schema = this.#syncState.schema! // we know that it is not undefined because it is set by `this.#onInitialResponse`\n const res = await response.text()\n const messages = res || `[]`\n const batch = this.#messageParser.parse<Array<Message<T>>>(messages, schema)\n\n if (!Array.isArray(batch)) {\n const preview = bigintSafeStringify(batch)?.slice(0, 200)\n throw new FetchError(\n response.status,\n `Received non-array response body from shape endpoint. ` +\n `This may indicate a proxy or CDN is returning an unexpected response. ` +\n `Expected a JSON array, got ${typeof batch}: ${preview}`,\n undefined,\n Object.fromEntries(response.headers.entries()),\n fetchUrl.toString()\n )\n }\n\n await this.#onMessages(batch)\n }\n\n async #requestShapeSSE(opts: {\n fetchUrl: URL\n requestAbortController: AbortController\n headers: Record<string, string>\n }): Promise<void> {\n const { fetchUrl, requestAbortController, headers } = opts\n const fetch = this.#sseFetchClient\n\n // Track when the SSE connection starts\n this.#lastSseConnectionStartTime = Date.now()\n\n // Add Accept header for SSE requests\n const sseHeaders = {\n ...headers,\n Accept: `text/event-stream`,\n }\n\n let ignoredStaleResponse = false\n try {\n let buffer: Array<Message<T>> = []\n await fetchEventSource(fetchUrl.toString(), {\n headers: sseHeaders,\n fetch,\n onopen: async (response: Response) => {\n this.#connected = true\n const shouldProcessBody = await this.#onInitialResponse(response)\n if (!shouldProcessBody) {\n ignoredStaleResponse = true\n throw new Error(`stale response ignored`)\n }\n },\n onmessage: (event: EventSourceMessage) => {\n if (event.data) {\n // event.data is a single JSON object\n const schema = this.#syncState.schema! // we know that it is not undefined because it is set in onopen when we call this.#onInitialResponse\n const message = this.#messageParser.parse<Message<T>>(\n event.data,\n schema\n )\n buffer.push(message)\n\n if (isUpToDateMessage(message)) {\n // Flush the buffer on up-to-date message.\n // Ensures that we only process complete batches of operations.\n this.#onMessages(buffer, true)\n buffer = []\n }\n }\n },\n onerror: (error: Error) => {\n // rethrow to close the SSE connection\n throw error\n },\n signal: requestAbortController.signal,\n })\n } catch (error) {\n if (ignoredStaleResponse) {\n // Stale response was ignored in onopen — let the fetch loop retry\n return\n }\n if (requestAbortController.signal.aborted) {\n // An abort during SSE stream parsing produces a raw AbortError\n // instead of going through createFetchWithBackoff -- wrap it so\n // #start handles it correctly.\n throw new FetchBackoffAbortError()\n }\n // Re-throw known Electric errors so the caller can handle them\n // (e.g., 409 shape rotation, stale cache retry, missing headers).\n // Other errors (body parsing, SSE protocol failures, null body)\n // are SSE connection failures handled by the fallback mechanism\n // in the finally block below.\n if (\n error instanceof FetchError ||\n error instanceof StaleCacheError ||\n error instanceof MissingHeadersError\n ) {\n throw error\n }\n } finally {\n // Check if the SSE connection closed too quickly\n // This can happen when responses are cached or when the proxy/server\n // is misconfigured for SSE and closes the connection immediately\n const connectionDuration = Date.now() - this.#lastSseConnectionStartTime!\n const wasAborted = requestAbortController.signal.aborted\n\n const transition = this.#syncState.handleSseConnectionClosed({\n connectionDuration,\n wasAborted,\n minConnectionDuration: this.#minSseConnectionDuration,\n maxShortConnections: this.#maxShortSseConnections,\n })\n this.#syncState = transition.state\n\n if (transition.fellBackToLongPolling) {\n console.warn(\n `[Electric] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). ` +\n `Falling back to long polling. ` +\n `Your proxy must support streaming SSE responses (not buffer the complete response). ` +\n `Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy. ` +\n `Note: Do NOT disable caching entirely - Electric uses cache headers to enable request collapsing for efficiency.`,\n new Error(`stack trace`)\n )\n } else if (transition.wasShortConnection) {\n // Exponential backoff with full jitter: random(0, min(cap, base * 2^attempt))\n const maxDelay = Math.min(\n this.#sseBackoffMaxDelay,\n this.#sseBackoffBaseDelay *\n Math.pow(2, this.#syncState.consecutiveShortSseConnections)\n )\n const delayMs = Math.floor(Math.random() * maxDelay)\n await new Promise((resolve) => setTimeout(resolve, delayMs))\n }\n }\n }\n\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError: (error: Error) => void = () => {}\n ) {\n const subscriptionId = {}\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) {\n this.#start().catch(() => {\n // Errors from #start are handled internally via onError.\n // This catch prevents unhandled promise rejection in Node/Bun.\n })\n }\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n this.#unsubscribeFromVisibilityChanges?.()\n this.#unsubscribeFromWakeDetection?.()\n }\n\n /** Unix time at which we last synced. Undefined until first successful up-to-date. */\n lastSyncedAt(): number | undefined {\n return this.#syncState.lastSyncedAt\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n if (this.#syncState.lastSyncedAt === undefined) return Infinity\n return Date.now() - this.#syncState.lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#connected\n }\n\n /** True during initial fetch. False afterwards. */\n isLoading(): boolean {\n return !this.#syncState.isUpToDate\n }\n\n hasStarted(): boolean {\n return this.#started\n }\n\n isPaused(): boolean {\n return this.#pauseLock.isPaused\n }\n\n /** Await the next tick of the request loop */\n async #nextTick() {\n if (this.#pauseLock.isPaused) {\n throw new Error(\n `Cannot wait for next tick while PauseLock is held — this would deadlock because the request loop is paused`\n )\n }\n if (this.#tickPromise) {\n return this.#tickPromise\n }\n this.#tickPromise = new Promise((resolve, reject) => {\n this.#tickPromiseResolver = resolve\n this.#tickPromiseRejecter = reject\n })\n this.#tickPromise\n .finally(() => {\n this.#tickPromise = undefined\n this.#tickPromiseResolver = undefined\n this.#tickPromiseRejecter = undefined\n })\n .catch(() => {\n // The original tick promise is returned to callers; this chained promise\n // is only for cleanup, so consume rejections to avoid unhandled errors.\n })\n return this.#tickPromise\n }\n\n /**\n * Refreshes the shape stream.\n * This preemptively aborts any ongoing long poll and reconnects without\n * long polling, ensuring that the stream receives an up to date message with the\n * latest LSN from Postgres at that point in time.\n */\n async forceDisconnectAndRefresh(): Promise<void> {\n this.#refreshCount++\n this.#refreshCatchUpWatchdogActive = true\n try {\n const requestAbortController = this.#requestAbortController\n if (\n this.#syncState.isUpToDate &&\n requestAbortController &&\n !requestAbortController.signal.aborted\n ) {\n // If we are \"up to date\", any current request will be a \"live\" request\n // and needs to be aborted. Track restart intent ourselves instead of\n // relying only on AbortSignal.reason, which is missing in some React\n // Native runtimes.\n this.#restartAbortControllers.add(requestAbortController)\n requestAbortController.abort(FORCE_DISCONNECT_AND_REFRESH)\n }\n await this.#nextTick()\n } finally {\n this.#refreshCount--\n }\n }\n\n async #publish(\n messages: Message<T>[],\n opts: { allowReentrantBypass?: boolean } = {}\n ): Promise<void[]> {\n const deliver = () =>\n Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n\n // We process messages asynchronously but SSE's `onmessage` handler is\n // synchronous. Use a promise chain to ensure handlers execute sequentially\n // in the order messages were received. Only requestSnapshot's injected\n // snapshot batch is allowed to bypass the queue reentrantly; ordinary\n // stream batches (including SSE batches) must remain serialized.\n if (this.#isPublishing && opts.allowReentrantBypass) {\n return deliver()\n }\n\n this.#messageChain = this.#messageChain.then(async () => {\n this.#isPublishing = true\n try {\n return await deliver()\n } finally {\n this.#isPublishing = false\n }\n })\n\n return this.#messageChain\n }\n\n #sendErrorToSubscribers(error: Error) {\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n #hasBrowserVisibilityAPI(): boolean {\n return (\n typeof document === `object` &&\n typeof document.hidden === `boolean` &&\n typeof document.addEventListener === `function`\n )\n }\n\n #setVisibilityPaused(isHidden: boolean) {\n if (isHidden) {\n this.#pauseLock.acquire(`visibility`)\n } else if (this.#pauseLock.isHeldBy(`visibility`)) {\n this.#pauseLock.release(`visibility`)\n }\n }\n\n #subscribeToVisibilityChanges() {\n const runtimeVisibility =\n this.options.runtimeVisibility ?? getDefaultRuntimeVisibilityAdapter()\n\n if (runtimeVisibility) {\n this.#setVisibilityPaused(\n runtimeVisibility.getCurrentState?.() === `hidden`\n )\n\n const unsubscribe = runtimeVisibility.subscribe((state) => {\n this.#setVisibilityPaused(state === `hidden`)\n })\n\n this.#unsubscribeFromVisibilityChanges = () => {\n unsubscribe()\n this.#unsubscribeFromVisibilityChanges = undefined\n }\n return\n }\n\n if (this.#hasBrowserVisibilityAPI()) {\n const visibilityHandler = () => {\n this.#setVisibilityPaused(document.hidden)\n }\n\n visibilityHandler()\n document.addEventListener(`visibilitychange`, visibilityHandler)\n\n // Store cleanup function to remove the event listener\n this.#unsubscribeFromVisibilityChanges = () => {\n document.removeEventListener(`visibilitychange`, visibilityHandler)\n this.#unsubscribeFromVisibilityChanges = undefined\n }\n }\n }\n\n /**\n * Detects system wake from sleep using timer gap detection.\n * When the system sleeps, setInterval timers are paused. On wake,\n * the elapsed wall-clock time since the last tick will be much larger\n * than the interval period, indicating the system was asleep.\n *\n * Only active in non-browser environments (Bun, Node.js) where\n * `document.visibilitychange` is not available. In browsers,\n * `#subscribeToVisibilityChanges` handles this instead. Without wake\n * detection, in-flight HTTP requests (long-poll or SSE) may hang until\n * the OS TCP timeout.\n */\n #forceDisconnectAndRefreshFromWake() {\n const requestAbortController = this.#requestAbortController\n if (\n this.#pauseLock.isPaused ||\n !requestAbortController ||\n requestAbortController.signal.aborted ||\n this.options.signal?.aborted\n ) {\n return\n }\n\n this.#refreshCount++\n this.#refreshCatchUpWatchdogActive = true\n // Track restart intent ourselves instead of relying only on\n // AbortSignal.reason, which is missing in some React Native runtimes.\n this.#restartAbortControllers.add(requestAbortController)\n requestAbortController.abort(SYSTEM_WAKE)\n\n this.#nextTick()\n .catch(() => {\n // Teardown or user abort can reject the tick promise. Wake recovery runs\n // from a timer callback, so avoid surfacing that as an unhandled rejection.\n })\n .finally(() => {\n this.#refreshCount--\n })\n }\n\n #subscribeToWakeDetection() {\n if (this.#hasBrowserVisibilityAPI()) return\n if (this.#unsubscribeFromWakeDetection) return\n\n const INTERVAL_MS = 2_000\n const WAKE_THRESHOLD_MS = 4_000\n\n let lastTickTime = Date.now()\n\n const timer = setInterval(() => {\n const now = Date.now()\n const elapsed = now - lastTickTime\n lastTickTime = now\n\n if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {\n this.#forceDisconnectAndRefreshFromWake()\n }\n }, INTERVAL_MS)\n\n // Ensure the timer doesn't prevent the process from exiting\n if (typeof timer === `object` && `unref` in timer) {\n timer.unref()\n }\n\n this.#unsubscribeFromWakeDetection = () => {\n clearInterval(timer)\n this.#unsubscribeFromWakeDetection = undefined\n }\n }\n\n /**\n * Resets the state of the stream, optionally with a provided\n * shape handle\n */\n #reset(handle?: string) {\n this.#syncState = this.#syncState.markMustRefetch(handle)\n this.#connected = false\n // releaseAllMatching intentionally doesn't fire onReleased — every caller\n // (#requestShape's 409 handler, #checkFastLoop, and stale-retry\n // self-healing in #onInitialResponse) runs inside the active stream loop,\n // so the stream is already active and doesn't need a resume signal.\n this.#pauseLock.releaseAllMatching(`snapshot`)\n }\n\n /**\n * Request a snapshot for subset of data and inject it into the subscribed data stream.\n *\n * Only available when mode is `changes_only`.\n * Returns the insertion point & the data, but more importantly injects the data\n * into the subscribed data stream. Returned value is unlikely to be useful for the caller,\n * unless the caller has complicated additional logic.\n *\n * Data will be injected in a way that's also tracking further incoming changes, and it'll\n * skip the ones that are already in the snapshot.\n *\n * @param opts - The options for the snapshot request.\n * @returns The metadata and the data for the snapshot.\n */\n async requestSnapshot(opts: SubsetParams): Promise<{\n metadata: SnapshotMetadata\n data: Array<ChangeMessage<T>>\n }> {\n if (this.#mode === `full`) {\n throw new Error(\n `Snapshot requests are not supported in ${this.#mode} mode, as the consumer is guaranteed to observe all data`\n )\n }\n // Start the stream if not started — fire-and-forget like subscribe() does.\n // We must NOT await #start() because it runs the full request loop. The\n // PauseLock acquire below will abort the in-flight request, and the\n // re-check guard in #requestShape handles the race.\n if (!this.#started) {\n this.#start().catch(() => {})\n }\n\n const snapshotReason = `snapshot-${++this.#snapshotCounter}`\n\n this.#pauseLock.acquire(snapshotReason)\n\n // Warn if the snapshot holds the pause lock for too long — this likely\n // indicates a hung fetch or leaked lock. Visibility pauses are\n // intentionally long-lived so the warning lives here, not in PauseLock.\n const snapshotWarnTimer = setTimeout(() => {\n console.warn(\n `[Electric] Snapshot \"${snapshotReason}\" has held the pause lock for 30s — ` +\n `possible hung request or leaked lock. ` +\n `Current holders: ${[...new Set([snapshotReason])].join(`, `)}`,\n new Error(`stack trace`)\n )\n }, 30_000)\n\n try {\n const { metadata, data, responseOffset, responseHandle } =\n await this.fetchSnapshot(opts)\n\n const dataWithEndBoundary = (data as Array<Message<T>>).concat([\n { headers: { control: `snapshot-end`, ...metadata } },\n { headers: { control: `subset-end`, ...opts } },\n ])\n\n this.#snapshotTracker.addSnapshot(\n metadata,\n new Set(data.map((message) => message.key))\n )\n await this.#onMessages(dataWithEndBoundary, false, {\n allowReentrantPublishBypass: true,\n })\n\n // On cold start the stream's offset is still at \"now\". Advance it\n // to the snapshot's position so no updates are missed in between.\n if (responseOffset !== null || responseHandle !== null) {\n const transition = this.#syncState.handleResponseMetadata({\n status: 200,\n responseHandle,\n responseOffset,\n responseCursor: null,\n expiredHandle: null,\n now: Date.now(),\n maxStaleCacheRetries: this.#maxStaleCacheRetries,\n createCacheBuster,\n })\n if (transition.action === `accepted`) {\n this.#syncState = transition.state\n } else {\n console.warn(\n `[Electric] Snapshot response metadata was not accepted ` +\n `by state \"${this.#syncState.kind}\" (action: ${transition.action}). ` +\n `Stream offset was not advanced from snapshot.`,\n new Error(`stack trace`)\n )\n }\n }\n\n return {\n metadata,\n data,\n }\n } finally {\n clearTimeout(snapshotWarnTimer)\n this.#pauseLock.release(snapshotReason)\n }\n }\n\n /**\n * Fetch a snapshot for subset of data.\n * Returns the metadata and the data, but does not inject it into the subscribed data stream.\n *\n * By default, uses GET to send subset parameters as query parameters. This may hit URL length\n * limits (HTTP 414) with large WHERE clauses or many parameters. Set `method: 'POST'` or use\n * `subsetMethod: 'POST'` on the stream to send parameters in the request body instead.\n *\n * @param opts - The options for the snapshot request.\n * @returns The metadata, data, and the response's offset/handle for state advancement.\n */\n async fetchSnapshot(opts: SubsetParams): Promise<{\n metadata: SnapshotMetadata\n data: Array<ChangeMessage<T>>\n responseOffset: Offset | null\n responseHandle: string | null\n }> {\n return this.#fetchSnapshotWithRetry(opts, 0)\n }\n\n async #fetchSnapshotWithRetry(\n opts: SubsetParams,\n retryCount: number,\n cacheBuster?: string\n ): Promise<{\n metadata: SnapshotMetadata\n data: Array<ChangeMessage<T>>\n responseOffset: Offset | null\n responseHandle: string | null\n }> {\n const method = opts.method ?? this.options.subsetMethod ?? `GET`\n const usePost = method === `POST`\n\n let fetchUrl: URL\n let fetchOptions: RequestInit\n\n if (usePost) {\n const result = await this.#constructUrl(this.options.url, true)\n fetchUrl = result.fetchUrl\n fetchOptions = {\n method: `POST`,\n headers: {\n ...result.requestHeaders,\n 'Content-Type': `application/json`,\n },\n body: bigintSafeStringify(this.#buildSubsetBody(opts)),\n }\n } else {\n const result = await this.#constructUrl(this.options.url, true, opts)\n fetchUrl = result.fetchUrl\n fetchOptions = { headers: result.requestHeaders }\n }\n\n // Apply cache buster from same-handle 409 retry\n if (cacheBuster) {\n fetchUrl.searchParams.set(CACHE_BUSTER_QUERY_PARAM, cacheBuster)\n fetchUrl.searchParams.sort()\n }\n\n // Capture handle before fetch to avoid race conditions if it changes during the request\n const usedHandle = this.#syncState.handle\n\n let response: Response\n try {\n response = await this.#fetchClient(fetchUrl.toString(), fetchOptions)\n } catch (e) {\n // Handle 409 \"must-refetch\" - shape handle changed/expired.\n // The fetch wrapper throws FetchError for non-OK responses, so we catch here.\n // Unlike #requestShape, we don't call #reset() here as that would\n // clear the pause lock and break requestSnapshot's pause/resume logic.\n if (e instanceof FetchError && e.status === 409) {\n const nextRetryCount = retryCount + 1\n if (nextRetryCount > this.#maxSnapshotRetries) {\n throw new FetchError(\n 502,\n undefined,\n undefined,\n {},\n fetchUrl.toString(),\n `Snapshot request stuck in 409 retry loop after ${this.#maxSnapshotRetries} attempts. ` +\n `This indicates a proxy/CDN misconfiguration. ` +\n `For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`\n )\n }\n\n if (usedHandle) {\n const shapeKey = canonicalShapeKey(fetchUrl)\n expiredShapesCache.markExpired(shapeKey, usedHandle)\n }\n\n // For snapshot 409s, only update the handle — don't reset offset/schema/etc.\n // The main stream is paused and should not be disturbed.\n const nextHandle = e.headers[SHAPE_HANDLE_HEADER]\n if (nextHandle) {\n this.#syncState = this.#syncState.withHandle(nextHandle)\n } else {\n console.warn(\n `[Electric] Received 409 response without a shape handle header. ` +\n `This likely indicates a proxy or CDN stripping required headers.`\n )\n }\n const nextCacheBuster = createCacheBuster()\n\n return this.#fetchSnapshotWithRetry(\n opts,\n nextRetryCount,\n nextCacheBuster\n )\n }\n throw e\n }\n\n // Handle non-OK responses from custom fetch clients that bypass the wrapper chain\n if (!response.ok) {\n throw await FetchError.fromResponse(response, fetchUrl.toString())\n }\n\n const schema: Schema =\n this.#syncState.schema ??\n getSchemaFromHeaders(response.headers, {\n required: true,\n url: fetchUrl.toString(),\n })\n\n const { metadata, data: rawData } = await response.json()\n const data = this.#messageParser.parseSnapshotData<ChangeMessage<T>>(\n rawData,\n schema\n )\n\n const responseOffset =\n (response.headers.get(CHUNK_LAST_OFFSET_HEADER) as Offset) || null\n const responseHandle = response.headers.get(SHAPE_HANDLE_HEADER)\n\n return { metadata, data, responseOffset, responseHandle }\n }\n\n #buildSubsetBody(opts: SubsetParams): Record<string, unknown> {\n const body: Record<string, unknown> = {}\n\n if (opts.whereExpr) {\n body.where = compileExpression(\n opts.whereExpr,\n this.options.columnMapper?.encode\n )\n body.where_expr = opts.whereExpr\n } else if (opts.where && typeof opts.where === `string`) {\n body.where = encodeWhereClause(\n opts.where,\n this.options.columnMapper?.encode\n )\n }\n\n if (opts.params) {\n body.params = opts.params\n }\n\n if (opts.limit !== undefined) {\n body.limit = opts.limit\n }\n\n if (opts.offset !== undefined) {\n body.offset = opts.offset\n }\n\n if (opts.orderByExpr) {\n body.order_by = compileOrderBy(\n opts.orderByExpr,\n this.options.columnMapper?.encode\n )\n body.order_by_expr = opts.orderByExpr\n } else if (opts.orderBy && typeof opts.orderBy === `string`) {\n body.order_by = encodeWhereClause(\n opts.orderBy,\n this.options.columnMapper?.encode\n )\n }\n\n return body\n }\n}\n\n/**\n * Extracts the schema from response headers.\n * @param headers - The response headers\n * @param options - Options for schema extraction\n * @param options.required - If true, throws MissingHeadersError when header is missing. Defaults to false.\n * @param options.url - The URL to include in the error message if required is true\n * @returns The parsed schema, or an empty object if not required and header is missing\n * @throws {MissingHeadersError} if required is true and the header is missing\n */\nfunction getSchemaFromHeaders(\n headers: Headers,\n options?: { required?: boolean; url?: string }\n): Schema {\n const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER)\n if (!schemaHeader) {\n if (options?.required && options?.url) {\n throw new MissingHeadersError(options.url, [SHAPE_SCHEMA_HEADER])\n }\n return {}\n }\n return JSON.parse(schemaHeader)\n}\n\n/**\n * Validates that no reserved parameter names are used in the provided params object\n * @throws {ReservedParamError} if any reserved parameter names are found\n */\nfunction validateParams(params: Record<string, unknown> | undefined): void {\n if (!params) return\n\n const reservedParams = Object.keys(params).filter((key) =>\n RESERVED_PARAMS.has(key as ReservedParamKeys)\n )\n if (reservedParams.length > 0) {\n throw new ReservedParamError(reservedParams)\n }\n}\n\n// Module-level flag to ensure HTTP warning is only shown once per process/module\nlet didWarnOnHttp = false\n\n/**\n * Safely gets NODE_ENV without throwing ReferenceError in browser builds\n * where `process` may not be defined.\n */\nfunction getNodeEnvSafely(): string | undefined {\n return typeof process !== `undefined` ? process.env?.NODE_ENV : undefined\n}\n\n/**\n * Attempts to resolve a URL string, optionally with a base URL.\n * Returns undefined if the URL cannot be parsed.\n */\nfunction resolveUrlMaybe(url: string, base?: string): URL | undefined {\n try {\n return new URL(url, base)\n } catch {\n return undefined\n }\n}\n\n/**\n * Checks if we're in a browser environment (has window object).\n */\nfunction isBrowserEnvironment(): boolean {\n return typeof window !== `undefined`\n}\n\n/**\n * Gets the current page URL in browser environments.\n * Returns undefined in non-browser environments.\n */\nfunction getWindowLocationHref(): string | undefined {\n if (isBrowserEnvironment() && typeof window.location !== `undefined`) {\n return window.location.href\n }\n return undefined\n}\n\nfunction validateOptions<T>(options: Partial<ShapeStreamOptions<T>>): void {\n if (!options.url) {\n throw new MissingShapeUrlError()\n }\n if (options.signal && !(options.signal instanceof AbortSignal)) {\n throw new InvalidSignalError()\n }\n\n if (\n options.liveRequestTimeoutMs !== undefined &&\n options.liveRequestTimeoutMs !== false &&\n (!Number.isFinite(options.liveRequestTimeoutMs) ||\n options.liveRequestTimeoutMs <= 0)\n ) {\n throw new InvalidShapeOptionsError(\n `Invalid shape options: liveRequestTimeoutMs must be a positive finite number or false`\n )\n }\n\n if (\n options.offset !== undefined &&\n options.offset !== `-1` &&\n options.offset !== `now` &&\n !options.handle\n ) {\n throw new MissingShapeHandleError()\n }\n\n validateParams(options.params)\n\n // Warn about HTTP URLs in browser environments (unless disabled or in tests)\n // HTTP forces HTTP/1.1 which typically limits browsers to ~6 concurrent connections per origin\n const nodeEnv = getNodeEnvSafely()\n const warnOnHttp = options.warnOnHttp ?? nodeEnv !== `test`\n\n if (warnOnHttp && !didWarnOnHttp && isBrowserEnvironment()) {\n if (typeof console !== `undefined`) {\n // Try to resolve the URL, using window.location.href as base for relative URLs\n const baseUrl = getWindowLocationHref()\n const resolvedUrl = resolveUrlMaybe(options.url, baseUrl)\n\n // Check if the resolved URL (or the page itself for relative URLs) uses HTTP\n const isHttp = resolvedUrl?.protocol === `http:`\n\n if (isHttp) {\n didWarnOnHttp = true\n console.warn(\n `[Electric] Using HTTP (not HTTPS) typically limits browsers to ~6 concurrent connections per origin under HTTP/1.1. ` +\n `This can cause slow shapes and app freezes with multiple shapes. ` +\n `Use HTTPS for HTTP/2 support. See: https://electric-sql.com/r/electric-http2`\n )\n }\n }\n }\n\n return\n}\n\n/**\n * Resets the HTTP warning flag. Only for testing purposes.\n * @internal\n */\nexport function _resetHttpWarningForTesting(): void {\n didWarnOnHttp = false\n}\n\n// `unknown` being in the value is a bit of defensive programming if user doesn't use TS\nfunction setQueryParam(\n url: URL,\n key: string,\n value: Record<string, string> | string | unknown\n): void {\n if (value === undefined || value == null) {\n return\n } else if (typeof value === `string`) {\n url.searchParams.set(key, value)\n } else if (typeof value === `object`) {\n for (const [k, v] of Object.entries(value)) {\n url.searchParams.set(`${key}[${k}]`, v)\n }\n } else {\n url.searchParams.set(key, value.toString())\n }\n}\n\nfunction convertWhereParamsToObj(\n allPgParams: ExternalParamsRecord<Row>\n): ExternalParamsRecord<Row> {\n if (Array.isArray(allPgParams.params)) {\n return {\n ...allPgParams,\n params: Object.fromEntries(allPgParams.params.map((v, i) => [i + 1, v])),\n }\n }\n return allPgParams\n}\n","import { Message, Offset, Row } from './types'\nimport {\n isChangeMessage,\n isControlMessage,\n canonicalBigintSafeStringify,\n} from './helpers'\nimport { FetchError } from './error'\nimport { LogMode, ShapeStreamInterface } from './client'\n\nexport type ShapeData<T extends Row<unknown> = Row> = Map<string, T>\nexport type ShapeChangedCallback<T extends Row<unknown> = Row> = (data: {\n value: ShapeData<T>\n rows: T[]\n}) => void\n\ntype ShapeStatus = `syncing` | `up-to-date`\n\n/**\n * Shape subscriber notification semantics (see SPEC.md → \"Shape\n * notification semantics\" for the canonical contract):\n *\n * - **N1 (no notify while syncing).** Data messages that arrive while\n * `status === 'syncing'` apply to `#data` but do NOT call `#notify`.\n * A notification fires only when the shape transitions from\n * `syncing` to `up-to-date` via an up-to-date control message. This\n * guarantees that subscribers observing `rows` always see a\n * consistent snapshot of the shape AND can read a defined\n * `stream.lastSyncedAt()`.\n *\n * - **N2 (notify on change while up-to-date).** Once\n * `status === 'up-to-date'`, any data message (insert/update/delete,\n * subject to `mode === 'changes_only'` filtering) triggers a\n * notification; the status then transitions back to `syncing` until\n * the next up-to-date message.\n *\n * A `must-refetch` control message clears `#data` and transitions the\n * status back to `syncing`, which re-engages N1 — subscribers will\n * receive the post-rotation state on the next `up-to-date` without\n * ever observing an intermediate empty-rows notification.\n */\n\n/**\n * A Shape is an object that subscribes to a shape log,\n * keeps a materialised shape `.rows` in memory and\n * notifies subscribers when the value has changed.\n *\n * It can be used without a framework and as a primitive\n * to simplify developing framework hooks.\n *\n * @constructor\n * @param {ShapeStream<T extends Row>} - the underlying shape stream\n * @example\n * ```\n * const shapeStream = new ShapeStream<{ foo: number }>({\n * url: `http://localhost:3000/v1/shape`,\n * params: {\n * table: `foo`\n * }\n * })\n * const shape = new Shape(shapeStream)\n * ```\n *\n * `rows` returns a promise that resolves the Shape data once the Shape has been\n * fully loaded (and when resuming from being offline):\n *\n * const rows = await shape.rows\n *\n * `currentRows` returns the current data synchronously:\n *\n * const rows = shape.currentRows\n *\n * Subscribe to updates. Called whenever the shape updates in Postgres.\n *\n * shape.subscribe(({ rows }) => {\n * console.log(rows)\n * })\n */\nexport class Shape<T extends Row<unknown> = Row> {\n readonly stream: ShapeStreamInterface<T>\n\n readonly #data: ShapeData<T> = new Map()\n readonly #subscribers = new Map<object, ShapeChangedCallback<T>>()\n readonly #insertedKeys = new Set<string>()\n readonly #requestedSubSnapshots = new Set<string>()\n #reexecuteSnapshotsPending = false\n #status: ShapeStatus = `syncing`\n #error: FetchError | false = false\n\n constructor(stream: ShapeStreamInterface<T>) {\n this.stream = stream\n this.stream.subscribe(\n this.#process.bind(this),\n this.#handleError.bind(this)\n )\n }\n\n get isUpToDate(): boolean {\n return this.#status === `up-to-date`\n }\n\n get lastOffset(): Offset {\n return this.stream.lastOffset\n }\n\n get handle(): string | undefined {\n return this.stream.shapeHandle\n }\n\n get rows(): Promise<T[]> {\n return this.value.then((v) => Array.from(v.values()))\n }\n\n get currentRows(): T[] {\n return Array.from(this.currentValue.values())\n }\n\n get value(): Promise<ShapeData<T>> {\n return new Promise((resolve, reject) => {\n if (this.stream.isUpToDate) {\n resolve(this.currentValue)\n } else {\n const unsubscribe = this.subscribe(({ value }) => {\n unsubscribe()\n if (this.#error) reject(this.#error)\n resolve(value)\n })\n }\n })\n }\n\n get currentValue() {\n return this.#data\n }\n\n get error() {\n return this.#error\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.stream.lastSyncedAt()\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced() {\n return this.stream.lastSynced()\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading() {\n return this.stream.isLoading()\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.stream.isConnected()\n }\n\n /** Current log mode of the underlying stream */\n get mode(): LogMode {\n return this.stream.mode\n }\n\n /**\n * Request a snapshot for subset of data. Only available when mode is changes_only.\n * Returns void; data will be emitted via the stream and processed by this Shape.\n */\n async requestSnapshot(\n params: Parameters<ShapeStreamInterface<T>[`requestSnapshot`]>[0]\n ): Promise<void> {\n // Track this snapshot request for future re-execution on shape rotation.\n // Use canonical stringify so permutation-equivalent params dedup.\n const key = canonicalBigintSafeStringify(params)\n this.#requestedSubSnapshots.add(key)\n // Ensure the stream is up-to-date so schema is available for parsing\n await this.#awaitUpToDate()\n await this.stream.requestSnapshot(params)\n }\n\n subscribe(callback: ShapeChangedCallback<T>): () => void {\n const subscriptionId = {}\n\n this.#subscribers.set(subscriptionId, callback)\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n get numSubscribers() {\n return this.#subscribers.size\n }\n\n #process(messages: Message<T>[]): void {\n let shouldNotify = false\n\n messages.forEach((message) => {\n if (isChangeMessage(message)) {\n const wasUpToDate = this.#status === `up-to-date`\n this.#updateShapeStatus(`syncing`)\n if (this.mode === `full`) {\n switch (message.headers.operation) {\n case `insert`:\n this.#data.set(message.key, message.value)\n if (wasUpToDate) shouldNotify = true\n break\n case `update`:\n this.#data.set(message.key, {\n ...this.#data.get(message.key)!,\n ...message.value,\n })\n if (wasUpToDate) shouldNotify = true\n break\n case `delete`:\n this.#data.delete(message.key)\n if (wasUpToDate) shouldNotify = true\n break\n }\n } else {\n // changes_only: only apply updates/deletes for keys for which we observed an insert\n switch (message.headers.operation) {\n case `insert`:\n this.#insertedKeys.add(message.key)\n this.#data.set(message.key, message.value)\n if (wasUpToDate) shouldNotify = true\n break\n case `update`:\n if (this.#insertedKeys.has(message.key)) {\n this.#data.set(message.key, {\n ...this.#data.get(message.key)!,\n ...message.value,\n })\n if (wasUpToDate) shouldNotify = true\n }\n break\n case `delete`:\n if (this.#insertedKeys.has(message.key)) {\n this.#data.delete(message.key)\n this.#insertedKeys.delete(message.key)\n if (wasUpToDate) shouldNotify = true\n }\n break\n }\n }\n }\n\n if (isControlMessage(message)) {\n switch (message.headers.control) {\n case `up-to-date`:\n if (this.#updateShapeStatus(`up-to-date`)) shouldNotify = true\n if (this.#reexecuteSnapshotsPending) {\n this.#reexecuteSnapshotsPending = false\n void this.#reexecuteSnapshots()\n }\n break\n case `must-refetch`:\n this.#data.clear()\n this.#insertedKeys.clear()\n this.#error = false\n this.#updateShapeStatus(`syncing`)\n this.#reexecuteSnapshotsPending = true\n break\n }\n }\n })\n\n if (shouldNotify) this.#notify()\n }\n\n async #reexecuteSnapshots(): Promise<void> {\n try {\n await this.#awaitUpToDate()\n } catch (e) {\n this.#surfaceReexecuteError(e)\n return\n }\n\n const results = await Promise.all(\n Array.from(this.#requestedSubSnapshots).map(async (jsonParams) => {\n try {\n const snapshot = JSON.parse(jsonParams)\n await this.stream.requestSnapshot(snapshot)\n return undefined\n } catch (e) {\n return e\n }\n })\n )\n\n const firstError = results.find((e) => e !== undefined)\n if (firstError !== undefined) this.#surfaceReexecuteError(firstError)\n }\n\n #surfaceReexecuteError(e: unknown): void {\n if (e instanceof FetchError) {\n this.#error = e\n } else if (e instanceof Error) {\n this.#error = new FetchError(0, e.message, undefined, {}, ``, e.message)\n } else {\n this.#error = new FetchError(0, String(e), undefined, {}, ``, String(e))\n }\n this.#notify()\n }\n\n async #awaitUpToDate(): Promise<void> {\n if (this.#error) throw this.#error\n if (this.stream.isUpToDate) return\n if (this.stream.error) throw this.stream.error as Error\n await new Promise<void>((resolve, reject) => {\n let settled = false\n let interval: ReturnType<typeof setInterval>\n let unsub: (() => void) | undefined\n const done = (action: () => void) => {\n if (settled) return\n settled = true\n clearInterval(interval)\n unsub?.()\n action()\n }\n const check = () => {\n if (this.stream.isUpToDate) return done(resolve)\n const streamError = this.stream.error as Error | undefined\n if (streamError) return done(() => reject(streamError))\n if (this.#error) {\n const err = this.#error\n return done(() => reject(err))\n }\n }\n interval = setInterval(check, 10)\n unsub = this.stream.subscribe(\n () => check(),\n (err) => done(() => reject(err))\n )\n check()\n })\n }\n\n #updateShapeStatus(status: ShapeStatus): boolean {\n const stateChanged = this.#status !== status\n this.#status = status\n return stateChanged && status === `up-to-date`\n }\n\n #handleError(e: Error): void {\n if (e instanceof FetchError) {\n this.#error = e\n this.#notify()\n }\n }\n\n #notify(): void {\n this.#subscribers.forEach((callback) => {\n callback({ value: this.currentValue, rows: this.currentRows })\n })\n }\n}\n"],"mappings":"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","names":["FetchError","_FetchError","status","text","json","headers","url","message","response","contentType","FetchBackoffAbortError","InvalidShapeOptionsError","MissingShapeUrlError","InvalidSignalError","MissingShapeHandleError","ReservedParamError","reservedParams","ParserNullValueError","columnName","MissingHeadersError","url","missingHeaders","msg","h","StaleCacheError","message","parseNumber","value","parseBool","parseBigInt","parseJson","identityParser","v","defaultParser","pgArrayParser","parser","i","char","str","quoted","last","p","extractValue","x","start","end","val","loop","xs","MessageParser","transformer","__spreadValues","messages","schema","key","message","msg","row","_b","columnInfo","_a","typ","dimensions","additionalInfo","__objRest","typeParser","makeNullableParser","_","columnName","isNullable","ParserNullValueError","quoteIdentifier","identifier","snakeToCamel","str","_a","_b","_c","_d","leadingUnderscores","withoutLeading","trailingUnderscores","camelCased","match","letter","camelToSnake","createColumnMapper","mapping","reverseMapping","dbName","appName","dbColumnName","appColumnName","encodeWhereClause","whereClause","encode","sqlKeywords","quotedRanges","pos","ch","start","quoteChar","isInQuotedString","range","identifierPattern","_p1","offset","snakeCamelMapper","schema","dbColumn","isChangeMessage","message","isControlMessage","isUpToDateMessage","getOffset","lsn","bigintReplacer","_key","value","bigintSafeStringify","canonicalBigintSafeStringify","canonicalize","sorted","k","isVisibleInSnapshot","txid","snapshot","xid","xmin","xmax","xip","LIVE_CACHE_BUSTER_HEADER","SHAPE_HANDLE_HEADER","CHUNK_LAST_OFFSET_HEADER","SHAPE_SCHEMA_HEADER","CHUNK_UP_TO_DATE_HEADER","SNAPSHOT_HEADER","COLUMNS_QUERY_PARAM","LIVE_CACHE_BUSTER_QUERY_PARAM","EXPIRED_HANDLE_QUERY_PARAM","SHAPE_HANDLE_QUERY_PARAM","LIVE_QUERY_PARAM","OFFSET_QUERY_PARAM","TABLE_QUERY_PARAM","WHERE_QUERY_PARAM","REPLICA_PARAM","WHERE_PARAMS_PARAM","EXPERIMENTAL_LIVE_SSE_QUERY_PARAM","LIVE_SSE_QUERY_PARAM","FORCE_DISCONNECT_AND_REFRESH","PAUSE_STREAM","SYSTEM_WAKE","LIVE_REQUEST_TIMEOUT","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","ELECTRIC_PROTOCOL_QUERY_PARAMS","HTTP_RETRY_STATUS_CODES","BackoffDefaults","parseRetryAfterHeader","retryAfter","retryAfterSec","retryDate","deltaMs","abortableSleep","waitMs","signal","FetchBackoffAbortError","resolve","reject","settled","done","err","timer","onAbort","createFetchWithBackoff","fetchClient","backoffOptions","initialDelay","maxDelay","multiplier","debug","onFailedAttempt","maxRetries","args","_a","_b","url","options","delay","attempt","result","FetchError","e","serverMinimumMs","jitter","clientBackoffMs","source","NO_BODY_STATUS_CODES","consumeResponseBody","res","text","createFetchWithConsumedMessages","ChunkPrefetchDefaults","createFetchWithChunkBuffer","prefetchOptions","maxChunksToPrefetch","prefetchQueue","getRequestMethod","prefetchedRequest","response","nextUrl","getNextChunkUrl","PrefetchQueue","requiredElectricResponseHeaders","CHUNK_LAST_OFFSET_HEADER","SHAPE_HANDLE_HEADER","requiredLiveResponseHeaders","LIVE_CACHE_BUSTER_HEADER","requiredNonLiveResponseHeaders","SHAPE_SCHEMA_HEADER","createFetchWithResponseHeadersCheck","headers","missingHeaders","addMissingHeaders","requiredHeaders","h","urlString","SUBSET_PARAM_WHERE","SUBSET_PARAM_WHERE_PARAMS","SUBSET_PARAM_LIMIT","SUBSET_PARAM_OFFSET","SUBSET_PARAM_ORDER_BY","p","LIVE_QUERY_PARAM","MissingHeadersError","_fetchClient","_maxPrefetchedRequests","_prefetchQueue","_queueHeadUrl","_queueTailUrl","_PrefetchQueue_instances","prefetch_fn","__privateAdd","__privateSet","__privateGet","__privateMethod","_","aborter","entry","request","cleanup","chainAborter","__spreadProps","__spreadValues","shapeHandle","lastOffset","isUpToDate","CHUNK_UP_TO_DATE_HEADER","isSnapshot","SNAPSHOT_HEADER","expiredHandle","EXPIRED_HANDLE_QUERY_PARAM","SHAPE_HANDLE_QUERY_PARAM","OFFSET_QUERY_PARAM","sourceSignal","noop","abortParent","input","init","compileExpression","expr","columnMapper","mappedColumn","quoteIdentifier","compileFunction","_exhaustive","args","arg","a","compileOrderBy","clauses","clause","sql","getBytes","stream","onChunk","reader","result","getLines","onLine","buffer","position","fieldLength","discardTrailingNewline","arr","concat","bufLength","lineStart","lineEnd","getMessages","onId","onRetry","onMessage","message","newMessage","decoder","line","field","valueOffset","value","retry","a","b","res","EventStreamContentType","DefaultRetryInterval","LastEventId","fetchEventSource","input","_a","inputSignal","inputHeaders","inputOnOpen","onmessage","onclose","onerror","openWhenHidden","inputFetch","rest","__rest","resolve","reject","headers","curRequestController","onVisibilityChange","create","retryInterval","retryTimer","dispose","fetch","onopen","defaultOnOpen","sig","response","getLines","getMessages","id","retry","err","interval","innerErr","ExpiredShapesCache","shapeUrl","entry","handle","keys","oldest","min","k","e","stored","expiredShapesCache","UpToDateTracker","shapeKey","cursor","keys","oldest","min","k","now","timeSinceLastWrite","delay","entry","modified","key","e","stored","upToDateTracker","_SnapshotTracker_instances","detachFromReverseIndexes_fn","addToSet_fn","removeFromSet_fn","toXid8_fn","resolveLatestXid8_fn","SnapshotTracker","__privateAdd","metadata","keys","__privateMethod","xmax","databaseLsn","snapshotMark","message","txids","snapshots","snapshot","xid8","isVisibleInSnapshot","newDatabaseLsn","dbLsn","existing","map","key","value","set","xid","referenceXid8","xids","latest","ShapeStreamState","_cursor","_opts","_input","_url","_context","PausedState","error","ErrorState","handle","InitialState","_shared","ActiveState","shared","__privateAdd","__privateSet","__privateGet","url","OFFSET_QUERY_PARAM","SHAPE_HANDLE_QUERY_PARAM","input","_a","_b","_c","responseHandle","offset","liveCacheBuster","schema","lastSyncedAt","expiredHandle","retryCount","StaleRetryState","__spreadProps","__spreadValues","LiveState","FetchingState","staleResult","SyncingState","cursor","ReplayingState","_InitialState","_SyncingState","_staleCacheBuster","_staleCacheRetryCount","_StaleRetryState","fields","staleCacheBuster","staleCacheRetryCount","__objRest","context","CACHE_BUSTER_QUERY_PARAM","_consecutiveShortSseConnections","_sseFallbackToLongPolling","_LiveState","sseState","LIVE_CACHE_BUSTER_QUERY_PARAM","LIVE_QUERY_PARAM","opts","nextConsecutiveShort","nextFallback","fellBackToLongPolling","wasShortConnection","_replayCursor","_ReplayingState","replayCursor","suppressUpToDate","_PausedState","previousState","transition","_exhaustive","_ErrorState","createInitialState","_holders","_onAcquired","_onReleased","PauseLock","callbacks","__privateAdd","__privateSet","reason","__privateGet","wasUnlocked","prefix","defaultRuntimeVisibilityAdapterFactory","getDefaultRuntimeVisibilityAdapterFactory","defaultRuntimeVisibilityAdapterFactory","RESERVED_PARAMS","LIVE_CACHE_BUSTER_QUERY_PARAM","SHAPE_HANDLE_QUERY_PARAM","LIVE_QUERY_PARAM","OFFSET_QUERY_PARAM","CACHE_BUSTER_QUERY_PARAM","TROUBLESHOOTING_URL","createCacheBuster","resolveValue","value","toInternalParams","params","entries","resolvedEntries","key","resolvedValue","_","resolveHeaders","headers","reactNativeAppStateToVisibility","state","createReactNativeRuntimeVisibilityAdapter","AppState","callback","subscription","visibilityState","getDefaultRuntimeVisibilityAdapter","_a","getDefaultRuntimeVisibilityAdapterFactory","canonicalShapeKey","url","cleanUrl","ELECTRIC_PROTOCOL_QUERY_PARAMS","_error","_fetchClient","_sseFetchClient","_messageParser","_subscribers","_started","_syncState","_connected","_mode","_onError","_requestAbortController","_restartAbortControllers","_refreshCount","_refreshCatchUpWatchdogActive","_snapshotCounter","_ShapeStream_instances","isRefreshing_get","_tickPromise","_tickPromiseResolver","_tickPromiseRejecter","_messageChain","_isPublishing","_snapshotTracker","_pauseLock","_currentFetchUrl","_lastSseConnectionStartTime","_minSseConnectionDuration","_maxShortSseConnections","_sseBackoffBaseDelay","_sseBackoffMaxDelay","_liveRequestTimeoutMs","_unsubscribeFromVisibilityChanges","_unsubscribeFromWakeDetection","_maxStaleCacheRetries","_recentRequestEntries","_fastLoopWindowMs","_fastLoopThreshold","_fastLoopBackoffBaseMs","_fastLoopBackoffMaxMs","_fastLoopConsecutiveCount","_fastLoopMaxCount","_pendingRequestShapeCacheBuster","_maxSnapshotRetries","_expiredShapeRecoveryKey","_pendingSelfHealCheck","_consecutiveErrorRetries","_maxConsecutiveErrorRetries","_onErrorBackoff","start_fn","teardown_fn","backoffOnErrorRetry_fn","requestShape_fn","checkFastLoop_fn","constructUrl_fn","createAbortListener_fn","onInitialResponse_fn","onMessages_fn","fetchShape_fn","withRequestTimeout_fn","requestShapeLongPoll_fn","requestShapeSSE_fn","nextTick_fn","publish_fn","sendErrorToSubscribers_fn","hasBrowserVisibilityAPI_fn","setVisibilityPaused_fn","subscribeToVisibilityChanges_fn","forceDisconnectAndRefreshFromWake_fn","subscribeToWakeDetection_fn","reset_fn","fetchSnapshotWithRetry_fn","buildSubsetBody_fn","ShapeStream","options","__privateAdd","SnapshotTracker","_b","_c","_d","_e","__spreadValues","validateOptions","__privateSet","createInitialState","PauseLock","__privateGet","PAUSE_STREAM","__privateMethod","transformer","applyColumnMapper","row","result","dbKey","appKey","MessageParser","baseFetchClient","args","backOffOpts","__spreadProps","BackoffDefaults","fetchWithBackoffClient","createFetchWithBackoff","createFetchWithResponseHeadersCheck","createFetchWithChunkBuffer","createFetchWithConsumedMessages","onError","subscriptionId","__privateWrapper","requestAbortController","FORCE_DISCONNECT_AND_REFRESH","opts","snapshotReason","snapshotWarnTimer","metadata","data","responseOffset","responseHandle","dataWithEndBoundary","message","transition","err","retryOpts","isRetryable","MissingHeadersError","ErrorState","retryAttempt","initialDelay","maxDelay","multiplier","cappedDelay","delayMs","signal","resolve","settled","done","timer","requestShapeCacheBuster","activeCacheBuster","resumingFromPause","PausedState","fetchUrl","requestHeaders","abortListener","e","abortReason","isMarkedRestartAbort","isRestartAbort","SYSTEM_WAKE","LIVE_REQUEST_TIMEOUT","FetchError","FetchBackoffAbortError","StaleCacheError","shapeKey","expiredShapesCache","newShapeHandle","SHAPE_HANDLE_HEADER","nextRequestShapeCacheBuster","now","currentOffset","upToDateTracker","subsetParams","_f","convertWhereParamsToObj","validateParams","setQueryParam","TABLE_QUERY_PARAM","encodedWhere","encodeWhereClause","WHERE_QUERY_PARAM","originalColumns","encodedColumns","serializedColumns","quoteIdentifier","COLUMNS_QUERY_PARAM","REPLICA_PARAM","WHERE_PARAMS_PARAM","customParams","compiledWhere","compileExpression","SUBSET_PARAM_WHERE","SUBSET_PARAM_WHERE_EXPR","SUBSET_PARAM_WHERE_PARAMS","bigintSafeStringify","SUBSET_PARAM_LIMIT","SUBSET_PARAM_OFFSET","compiledOrderBy","compileOrderBy","SUBSET_PARAM_ORDER_BY","SUBSET_PARAM_ORDER_BY_EXPR","encodedOrderBy","LOG_MODE_QUERY_PARAM","expiredHandle","EXPIRED_HANDLE_QUERY_PARAM","response","status","shapeHandle","healedKey","staleHandle","CHUNK_LAST_OFFSET_HEADER","LIVE_CACHE_BUSTER_HEADER","getSchemaFromHeaders","batch","isSseMessage","lastMessage","hasUpToDateMessage","isUpToDateMessage","upToDateOffset","getOffset","messagesToProcess","isChangeMessage","changeLsn","lastSeenLsn","lastSeenCursor","useSse","EXPERIMENTAL_LIVE_SSE_QUERY_PARAM","LIVE_SSE_QUERY_PARAM","promise","timeoutMs","isLiveRequest","isRefreshCatchUpRequest","timeout","timeoutPromise","reject","fetchUrlString","rawResponse","consumeResponseBody","schema","messages","preview","fetch","sseHeaders","ignoredStaleResponse","buffer","fetchEventSource","event","error","connectionDuration","wasAborted","deliver","__","errorFn","isHidden","runtimeVisibility","unsubscribe","visibilityHandler","INTERVAL_MS","WAKE_THRESHOLD_MS","lastTickTime","elapsed","handle","retryCount","cacheBuster","usePost","fetchOptions","usedHandle","nextRetryCount","nextHandle","nextCacheBuster","rawData","body","schemaHeader","SHAPE_SCHEMA_HEADER","reservedParams","ReservedParamError","didWarnOnHttp","getNodeEnvSafely","resolveUrlMaybe","base","isBrowserEnvironment","getWindowLocationHref","MissingShapeUrlError","InvalidSignalError","InvalidShapeOptionsError","MissingShapeHandleError","nodeEnv","baseUrl","resolvedUrl","_resetHttpWarningForTesting","k","v","allPgParams","i","_data","_subscribers","_insertedKeys","_requestedSubSnapshots","_reexecuteSnapshotsPending","_status","_error","_Shape_instances","process_fn","reexecuteSnapshots_fn","surfaceReexecuteError_fn","awaitUpToDate_fn","updateShapeStatus_fn","handleError_fn","notify_fn","Shape","stream","__privateAdd","__privateMethod","__privateGet","v","resolve","reject","unsubscribe","value","params","key","canonicalBigintSafeStringify","callback","subscriptionId","messages","shouldNotify","message","isChangeMessage","wasUpToDate","__spreadValues","isControlMessage","__privateSet","e","firstError","jsonParams","snapshot","FetchError","settled","interval","unsub","done","action","check","streamError","err","status","stateChanged"]}