@electric-sql/client 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +125 -7
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +65 -1
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.legacy-esm.js +123 -7
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +123 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +59 -3
- package/src/constants.ts +4 -0
- package/src/expression-compiler.ts +132 -0
- package/src/fetch.ts +16 -0
- package/src/index.ts +1 -0
- package/src/types.ts +28 -0
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/error.ts","../../src/parser.ts","../../src/column-mapper.ts","../../src/helpers.ts","../../src/constants.ts","../../src/fetch.ts","../../src/client.ts","../../src/expired-shapes-cache.ts","../../src/up-to-date-tracker.ts","../../src/snapshot-tracker.ts","../../src/shape.ts"],"sourcesContent":["export * from './client'\nexport * from './shape'\nexport * from './types'\nexport {\n isChangeMessage,\n isControlMessage,\n isVisibleInSnapshot,\n} from './helpers'\nexport { FetchError } from './error'\nexport { type BackoffOptions, BackoffDefaults } from './fetch'\nexport { ELECTRIC_PROTOCOL_QUERY_PARAMS } from './constants'\nexport {\n type ColumnMapper,\n createColumnMapper,\n snakeCamelMapper,\n snakeToCamel,\n camelToSnake,\n} from './column-mapper'\n","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","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 * - Collapses multiple underscores: `user__id` → `userId`\n * - Normalizes to lowercase first: `user_Column` → `userColumn`\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') // 'userId'\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 (handling multiple underscores)\n const camelCased = normalized.replace(/_+([a-z])/g, (_, letter) =>\n 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 lowercase letters\n // e.g., userId -> 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 `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 !isChangeMessage(message)\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\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 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 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`\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 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]\n","import {\n CHUNK_LAST_OFFSET_HEADER,\n CHUNK_UP_TO_DATE_HEADER,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_HANDLE_HEADER,\n SHAPE_HANDLE_QUERY_PARAM,\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: 100,\n maxDelay: 60_000, // Cap at 60s - reasonable for long-lived connections\n multiplier: 1.3,\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\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\n await new Promise((resolve) => setTimeout(resolve, waitMs))\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\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 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 (args[1]?.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.toString(),\n err instanceof Error\n ? err.message\n : typeof err === `string`\n ? err\n : `failed to read body`\n )\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\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 `electric-offset`,\n `electric-handle`,\n]\n\nexport const requiredLiveResponseHeaders = [`electric-cursor`]\n\nexport const requiredNonLiveResponseHeaders = [`electric-schema`]\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\n // only prefetch if shape handle and offset for next chunk are available, and\n // response is not already up-to-date\n if (!shapeHandle || !lastOffset || isUpToDate) 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 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","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 { getOffset, isUpToDateMessage, isChangeMessage } from './helpers'\nimport {\n FetchError,\n FetchBackoffAbortError,\n MissingShapeUrlError,\n InvalidSignalError,\n MissingShapeHandleError,\n ReservedParamError,\n MissingHeadersError,\n} from './error'\nimport {\n BackoffDefaults,\n BackoffOptions,\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 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} from './constants'\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'\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])\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 | `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 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 backoffOptions?: BackoffOptions\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 * 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\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 */\nfunction 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 for (const [key, value] of url.searchParams) {\n if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {\n cleanUrl.searchParams.set(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 number,\n [\n (messages: Message<T>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n #started = false\n #state = `active` as `active` | `pause-requested` | `paused`\n #lastOffset: Offset\n #liveCacheBuster: string // Seconds since our Electric Epoch 😎\n #lastSyncedAt?: number // unix time\n #isUpToDate: boolean = false\n #isMidStream: boolean = true\n #connected: boolean = false\n #shapeHandle?: string\n #mode: LogMode\n #schema?: Schema\n #onError?: ShapeStreamErrorHandler\n #requestAbortController?: AbortController\n #isRefreshing = false\n #tickPromise?: Promise<void>\n #tickPromiseResolver?: () => void\n #tickPromiseRejecter?: (reason?: unknown) => void\n #messageChain = Promise.resolve<void[]>([]) // promise chain for incoming messages\n #snapshotTracker = new SnapshotTracker()\n #activeSnapshotRequests = 0 // counter for concurrent snapshot requests\n #midStreamPromise?: Promise<void>\n #midStreamPromiseResolver?: () => void\n #lastSeenCursor?: string // Last seen cursor from previous session (used to detect cached responses)\n #currentFetchUrl?: URL // Current fetch URL for computing shape key\n #lastSseConnectionStartTime?: number\n #minSseConnectionDuration = 1000 // Minimum expected SSE connection duration (1 second)\n #consecutiveShortSseConnections = 0\n #maxShortSseConnections = 3 // Fall back to long polling after this many short connections\n #sseFallbackToLongPolling = false\n #sseBackoffBaseDelay = 100 // Base delay for exponential backoff (ms)\n #sseBackoffMaxDelay = 5000 // Maximum delay cap (ms)\n #unsubscribeFromVisibilityChanges?: () => void\n\n // Derived state: we're in replay mode if we have a last seen cursor\n get #replayMode(): boolean {\n return this.#lastSeenCursor !== undefined\n }\n\n constructor(options: ShapeStreamOptions<GetExtensions<T>>) {\n this.options = { subscribe: true, ...options }\n validateOptions(this.options)\n this.#lastOffset = this.options.offset ?? `-1`\n this.#liveCacheBuster = ``\n this.#shapeHandle = this.options.handle\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\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 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.#shapeHandle\n }\n\n get error() {\n return this.#error\n }\n\n get isUpToDate() {\n return this.#isUpToDate\n }\n\n get lastOffset() {\n return this.#lastOffset\n }\n\n get mode() {\n return this.#mode\n }\n\n async #start(): Promise<void> {\n this.#started = true\n\n try {\n await this.#requestShape()\n } catch (err) {\n this.#error = err\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 if (retryOpts && typeof retryOpts === `object`) {\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 // Clear the error since we're retrying\n this.#error = null\n\n // Restart from current offset\n this.#started = false\n await this.#start()\n return\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.#connected = false\n this.#tickPromiseRejecter?.()\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.#connected = false\n this.#tickPromiseRejecter?.()\n throw err\n }\n\n // Normal completion, clean up\n this.#connected = false\n this.#tickPromiseRejecter?.()\n }\n\n async #requestShape(): Promise<void> {\n if (this.#state === `pause-requested`) {\n this.#state = `paused`\n return\n }\n\n if (\n !this.options.subscribe &&\n (this.options.signal?.aborted || this.#isUpToDate)\n ) {\n return\n }\n\n const resumingFromPause = this.#state === `paused`\n this.#state = `active`\n\n const { url, signal } = this.options\n const { fetchUrl, requestHeaders } = await this.#constructUrl(\n url,\n resumingFromPause\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 try {\n await this.#fetchShape({\n fetchUrl,\n requestAbortController,\n headers: requestHeaders,\n resumingFromPause,\n })\n } catch (e) {\n // Handle abort error triggered by refresh\n if (\n (e instanceof FetchError || e instanceof FetchBackoffAbortError) &&\n requestAbortController.signal.aborted &&\n requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH\n ) {\n // Start a new request\n return this.#requestShape()\n }\n\n if (e instanceof FetchBackoffAbortError) {\n // Check current state - it may have changed due to concurrent pause/resume calls\n // from the visibility change handler during the async fetch operation.\n // TypeScript's flow analysis doesn't account for concurrent state changes.\n const currentState = this.#state as\n | `active`\n | `pause-requested`\n | `paused`\n if (\n requestAbortController.signal.aborted &&\n requestAbortController.signal.reason === PAUSE_STREAM &&\n currentState === `pause-requested`\n ) {\n this.#state = `paused`\n }\n return // interrupted\n }\n if (!(e instanceof FetchError)) throw e // should never happen\n\n if (e.status == 409) {\n // Upon receiving a 409, we should start from scratch\n // with the newly provided shape handle, or a fallback\n // pseudo-handle based on the current one to act as a\n // consistent cache buster\n\n // Store the current shape URL as expired to avoid future 409s\n if (this.#shapeHandle) {\n const shapeKey = canonicalShapeKey(fetchUrl)\n expiredShapesCache.markExpired(shapeKey, this.#shapeHandle)\n }\n\n const newShapeHandle =\n e.headers[SHAPE_HANDLE_HEADER] || `${this.#shapeHandle!}-next`\n this.#reset(newShapeHandle)\n\n // must refetch control message might be in a list or not depending\n // on whether it came from an SSE request or long poll - handle both\n // cases for safety here but worth revisiting 409 handling\n await this.#publish(\n (Array.isArray(e.json) ? e.json : [e.json]) as Message<T>[]\n )\n return this.#requestShape()\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 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 if (subsetParams.where && typeof subsetParams.where === `string`) {\n const encodedWhere = encodeWhereClause(\n subsetParams.where,\n this.options.columnMapper?.encode\n )\n setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, encodedWhere)\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 JSON.stringify(subsetParams.params)\n )\n if (subsetParams.limit)\n setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit)\n if (subsetParams.offset)\n setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset)\n if (subsetParams.orderBy && typeof subsetParams.orderBy === `string`) {\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 Electric's internal parameters\n fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, this.#lastOffset)\n fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, this.#mode)\n\n // Snapshot requests (with subsetParams) should never use live polling\n const isSnapshotRequest = subsetParams !== undefined\n\n if (this.#isUpToDate && !isSnapshotRequest) {\n // If we are resuming from a paused state, we don't want to perform a live request\n // because it could be a long poll that holds for 20sec\n // and during all that time `isConnected` will be false\n if (!this.#isRefreshing && !resumingFromPause) {\n fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`)\n }\n fetchUrl.searchParams.set(\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n this.#liveCacheBuster\n )\n }\n\n if (this.#shapeHandle) {\n // This should probably be a header for better cache breaking?\n fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, this.#shapeHandle!)\n }\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 async #onInitialResponse(response: Response) {\n const { headers, status } = response\n const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)\n if (shapeHandle) {\n this.#shapeHandle = shapeHandle\n }\n\n const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)\n if (lastOffset) {\n this.#lastOffset = lastOffset as Offset\n }\n\n const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER)\n if (liveCacheBuster) {\n this.#liveCacheBuster = liveCacheBuster\n }\n\n this.#schema = this.#schema ?? getSchemaFromHeaders(headers)\n\n // NOTE: 204s are deprecated, the Electric server should not\n // send these in latest versions but this is here for backwards\n // compatibility\n if (status === 204) {\n // There's no content so we are live and up to date\n this.#lastSyncedAt = Date.now()\n }\n }\n\n async #onMessages(batch: Array<Message<T>>, isSseMessage = false) {\n // Update isUpToDate\n if (batch.length > 0) {\n // Set isMidStream to true when we receive any data\n this.#isMidStream = true\n\n const lastMessage = batch[batch.length - 1]\n if (isUpToDateMessage(lastMessage)) {\n if (isSseMessage) {\n // Only use the offset from the up-to-date message if this was an SSE message.\n // If we would use this offset from a regular fetch, then it will be wrong\n // and we will get an \"offset is out of bounds for this shape\" error\n const offset = getOffset(lastMessage)\n if (offset) {\n this.#lastOffset = offset\n }\n }\n this.#lastSyncedAt = Date.now()\n this.#isUpToDate = true\n // Set isMidStream to false when we see an up-to-date message\n this.#isMidStream = false\n // Resolve the promise waiting for mid-stream to end\n this.#midStreamPromiseResolver?.()\n\n // Check if we should suppress this up-to-date notification\n // to prevent multiple renders from cached responses\n if (this.#replayMode && !isSseMessage) {\n // We're in replay mode (replaying cached responses during initial sync).\n // Check if the cursor has changed - cursors are time-based and always\n // increment, so a new cursor means fresh data from the server.\n const currentCursor = this.#liveCacheBuster\n\n if (currentCursor === this.#lastSeenCursor) {\n // Same cursor = still replaying cached responses\n // Suppress this up-to-date notification\n return\n }\n }\n\n // We're either:\n // 1. Not in replay mode (normal operation), or\n // 2. This is a live/SSE message (always fresh), or\n // 3. Cursor has changed (exited replay mode with fresh data)\n // In all cases, notify subscribers and record the up-to-date.\n this.#lastSeenCursor = undefined // Exit replay mode\n\n if (this.#currentFetchUrl) {\n const shapeKey = canonicalShapeKey(this.#currentFetchUrl)\n upToDateTracker.recordUpToDate(shapeKey, this.#liveCacheBuster)\n }\n }\n\n // Filter messages using snapshot tracker\n const messagesToProcess = batch.filter((message) => {\n if (isChangeMessage(message)) {\n return !this.#snapshotTracker.shouldRejectMessage(message)\n }\n return true // Always process control messages\n })\n\n await this.#publish(messagesToProcess)\n }\n }\n\n /**\n * Fetches the shape from the server using either long polling or SSE.\n * Upon receiving a successfull 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.#isUpToDate && !this.#replayMode) {\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.#lastSeenCursor = lastSeenCursor\n }\n }\n\n const useSse = this.options.liveSse ?? this.options.experimentalLiveSse\n if (\n this.#isUpToDate &&\n useSse &&\n !this.#isRefreshing &&\n !opts.resumingFromPause &&\n !this.#sseFallbackToLongPolling\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 #requestShapeLongPoll(opts: {\n fetchUrl: URL\n requestAbortController: AbortController\n headers: Record<string, string>\n }): Promise<void> {\n const { fetchUrl, requestAbortController, headers } = opts\n const response = await this.#fetchClient(fetchUrl.toString(), {\n signal: requestAbortController.signal,\n headers,\n })\n\n this.#connected = true\n await this.#onInitialResponse(response)\n\n const schema = this.#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 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 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 await this.#onInitialResponse(response)\n },\n onmessage: (event: EventSourceMessage) => {\n if (event.data) {\n // event.data is a single JSON object\n const schema = this.#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 (requestAbortController.signal.aborted) {\n // During an SSE request, the fetch might have succeeded\n // and we are parsing the incoming stream.\n // If the abort happens while we're parsing the stream,\n // then it won't be caught by our `createFetchWithBackoff` wrapper\n // and instead we will get a raw AbortError here\n // which we need to turn into a `FetchBackoffAbortError`\n // such that #start handles it correctly.`\n throw new FetchBackoffAbortError()\n }\n throw error\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 if (connectionDuration < this.#minSseConnectionDuration && !wasAborted) {\n // Connection was too short - likely a cached response or misconfiguration\n this.#consecutiveShortSseConnections++\n\n if (\n this.#consecutiveShortSseConnections >= this.#maxShortSseConnections\n ) {\n // Too many short connections - fall back to long polling\n this.#sseFallbackToLongPolling = true\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 )\n } else {\n // Add exponential backoff with full jitter to prevent tight infinite loop\n // Formula: random(0, min(cap, base * 2^attempt))\n const maxDelay = Math.min(\n this.#sseBackoffMaxDelay,\n this.#sseBackoffBaseDelay *\n Math.pow(2, this.#consecutiveShortSseConnections)\n )\n const delayMs = Math.floor(Math.random() * maxDelay)\n await new Promise((resolve) => setTimeout(resolve, delayMs))\n }\n } else if (connectionDuration >= this.#minSseConnectionDuration) {\n // Connection was healthy - reset counter\n this.#consecutiveShortSseConnections = 0\n }\n }\n }\n\n #pause() {\n if (this.#started && this.#state === `active`) {\n this.#state = `pause-requested`\n this.#requestAbortController?.abort(PAUSE_STREAM)\n }\n }\n\n #resume() {\n if (\n this.#started &&\n (this.#state === `paused` || this.#state === `pause-requested`)\n ) {\n // Don't resume if the user's signal is already aborted\n // This can happen if the signal was aborted while we were paused\n // (e.g., TanStack DB collection was GC'd)\n if (this.options.signal?.aborted) {\n return\n }\n\n // If we're resuming from pause-requested state, we need to set state back to active\n // to prevent the pause from completing\n if (this.#state === `pause-requested`) {\n this.#state = `active`\n }\n this.#start()\n }\n }\n\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError: (error: Error) => void = () => {}\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n this.#unsubscribeFromVisibilityChanges?.()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.#lastSyncedAt\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n if (this.#lastSyncedAt === undefined) return Infinity\n return Date.now() - this.#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 afterwise. */\n isLoading(): boolean {\n return !this.#isUpToDate\n }\n\n hasStarted(): boolean {\n return this.#started\n }\n\n isPaused(): boolean {\n return this.#state === `paused`\n }\n\n /** Await the next tick of the request loop */\n async #nextTick() {\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.finally(() => {\n this.#tickPromise = undefined\n this.#tickPromiseResolver = undefined\n this.#tickPromiseRejecter = undefined\n })\n return this.#tickPromise\n }\n\n /** Await until we're not in the middle of a stream (i.e., until we see an up-to-date message) */\n async #waitForStreamEnd() {\n if (!this.#isMidStream) {\n return\n }\n if (this.#midStreamPromise) {\n return this.#midStreamPromise\n }\n this.#midStreamPromise = new Promise((resolve) => {\n this.#midStreamPromiseResolver = resolve\n })\n this.#midStreamPromise.finally(() => {\n this.#midStreamPromise = undefined\n this.#midStreamPromiseResolver = undefined\n })\n return this.#midStreamPromise\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.#isRefreshing = true\n if (this.#isUpToDate && !this.#requestAbortController?.signal.aborted) {\n // If we are \"up to date\", any current request will be a \"live\" request\n // and needs to be aborted\n this.#requestAbortController?.abort(FORCE_DISCONNECT_AND_REFRESH)\n }\n await this.#nextTick()\n this.#isRefreshing = false\n }\n\n async #publish(messages: Message<T>[]): Promise<void[]> {\n // We process messages asynchronously\n // but SSE's `onmessage` handler is synchronous.\n // We use a promise chain to ensure that the handlers\n // execute sequentially in the order the messages were received.\n this.#messageChain = this.#messageChain.then(() =>\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\n return this.#messageChain\n }\n\n #sendErrorToSubscribers(error: Error) {\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n #subscribeToVisibilityChanges() {\n if (\n typeof document === `object` &&\n typeof document.hidden === `boolean` &&\n typeof document.addEventListener === `function`\n ) {\n const visibilityHandler = () => {\n if (document.hidden) {\n this.#pause()\n } else {\n this.#resume()\n }\n }\n\n document.addEventListener(`visibilitychange`, visibilityHandler)\n\n // Store cleanup function to remove the event listener\n this.#unsubscribeFromVisibilityChanges = () => {\n document.removeEventListener(`visibilitychange`, visibilityHandler)\n }\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.#lastOffset = `-1`\n this.#liveCacheBuster = ``\n this.#shapeHandle = handle\n this.#isUpToDate = false\n this.#isMidStream = true\n this.#connected = false\n this.#schema = undefined\n this.#activeSnapshotRequests = 0\n // Reset SSE fallback state to try SSE again after reset\n this.#consecutiveShortSseConnections = 0\n this.#sseFallbackToLongPolling = false\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 // We shouldn't be getting a snapshot on a shape that's not started\n if (!this.#started) await this.#start()\n\n // Wait until we're not mid-stream before pausing\n // This ensures we don't pause in the middle of a transaction\n await this.#waitForStreamEnd()\n\n // Pause the stream if this is the first snapshot request\n this.#activeSnapshotRequests++\n\n try {\n if (this.#activeSnapshotRequests === 1) {\n // Currently this cannot throw, but in case it can later it's in this try block to not have a stuck counter\n this.#pause()\n }\n\n const { metadata, data } = 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 this.#onMessages(dataWithEndBoundary, false)\n\n return {\n metadata,\n data,\n }\n } finally {\n // Resume the stream if this was the last snapshot request\n this.#activeSnapshotRequests--\n if (this.#activeSnapshotRequests === 0) {\n this.#resume()\n }\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 * @param opts - The options for the snapshot request.\n * @returns The metadata and the data for the snapshot.\n */\n async fetchSnapshot(opts: SubsetParams): Promise<{\n metadata: SnapshotMetadata\n data: Array<ChangeMessage<T>>\n }> {\n const { fetchUrl, requestHeaders } = await this.#constructUrl(\n this.options.url,\n true,\n opts\n )\n\n const response = await this.#fetchClient(fetchUrl.toString(), {\n headers: requestHeaders,\n })\n\n if (!response.ok) {\n throw new FetchError(\n response.status,\n undefined,\n undefined,\n Object.fromEntries([...response.headers.entries()]),\n fetchUrl.toString()\n )\n }\n\n // Use schema from stream if available, otherwise extract from response header\n const schema: Schema =\n this.#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 return {\n metadata,\n data,\n }\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\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.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 return\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","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\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\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 { xmin: bigint; xmax: bigint; xip_list: bigint[]; keys: Set<string> }\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 this.activeSnapshots.set(metadata.snapshot_mark, {\n xmin: BigInt(metadata.xmin),\n xmax: BigInt(metadata.xmax),\n xip_list: metadata.xip_list.map(BigInt),\n keys,\n })\n const xmaxSet =\n this.xmaxSnapshots\n .get(BigInt(metadata.xmax))\n ?.add(metadata.snapshot_mark) ?? new Set([metadata.snapshot_mark])\n this.xmaxSnapshots.set(BigInt(metadata.xmax), xmaxSet)\n const databaseLsnSet =\n this.snapshotsByDatabaseLsn\n .get(BigInt(metadata.database_lsn))\n ?.add(metadata.snapshot_mark) ?? new Set([metadata.snapshot_mark])\n this.snapshotsByDatabaseLsn.set(\n BigInt(metadata.database_lsn),\n databaseLsnSet\n )\n }\n\n /**\n * Remove a snapshot from tracking\n */\n removeSnapshot(snapshotMark: number): void {\n this.activeSnapshots.delete(snapshotMark)\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 const xid = Math.max(...txids) // Use the maximum transaction ID\n\n for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {\n if (xid >= xmax) {\n for (const snapshot of snapshots) {\n this.removeSnapshot(snapshot)\n }\n }\n }\n\n return [...this.activeSnapshots.values()].some(\n (x) => x.keys.has(message.key) && isVisibleInSnapshot(xid, x)\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","import { Message, Offset, Row } from './types'\nimport { isChangeMessage, isControlMessage } 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 * 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<number, 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 const key = JSON.stringify(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 = Math.random()\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 shouldNotify = 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 break\n case `update`:\n this.#data.set(message.key, {\n ...this.#data.get(message.key)!,\n ...message.value,\n })\n break\n case `delete`:\n this.#data.delete(message.key)\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 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 }\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 }\n break\n }\n }\n }\n\n if (isControlMessage(message)) {\n switch (message.headers.control) {\n case `up-to-date`:\n shouldNotify = this.#updateShapeStatus(`up-to-date`)\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 shouldNotify = this.#updateShapeStatus(`syncing`)\n // Flag to re-execute sub-snapshots once the new shape is up-to-date\n this.#reexecuteSnapshotsPending = true\n break\n }\n }\n })\n\n if (shouldNotify) this.#notify()\n }\n\n async #reexecuteSnapshots(): Promise<void> {\n // Wait until stream is up-to-date again (ensures schema is available)\n await this.#awaitUpToDate()\n\n // Re-execute all snapshots concurrently\n 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 } catch (_) {\n // Ignore and continue; errors will be surfaced via stream onError\n }\n })\n )\n }\n\n async #awaitUpToDate(): Promise<void> {\n if (this.stream.isUpToDate) return\n await new Promise<void>((resolve) => {\n const check = () => {\n if (this.stream.isUpToDate) {\n clearInterval(interval)\n unsub()\n resolve()\n }\n }\n const interval = setInterval(check, 10)\n const unsub = this.stream.subscribe(\n () => check(),\n () => check()\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":["value","pos","_fetchClient","_a","_b","fetch","_subscribers","_error"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/error.ts","../../src/parser.ts","../../src/column-mapper.ts","../../src/helpers.ts","../../src/constants.ts","../../src/fetch.ts","../../src/expression-compiler.ts","../../src/client.ts","../../src/expired-shapes-cache.ts","../../src/up-to-date-tracker.ts","../../src/snapshot-tracker.ts","../../src/shape.ts"],"sourcesContent":["export * from './client'\nexport * from './shape'\nexport * from './types'\nexport {\n isChangeMessage,\n isControlMessage,\n isVisibleInSnapshot,\n} from './helpers'\nexport { FetchError } from './error'\nexport { type BackoffOptions, BackoffDefaults } from './fetch'\nexport { ELECTRIC_PROTOCOL_QUERY_PARAMS } from './constants'\nexport {\n type ColumnMapper,\n createColumnMapper,\n snakeCamelMapper,\n snakeToCamel,\n camelToSnake,\n} from './column-mapper'\nexport { compileExpression, compileOrderBy } from './expression-compiler'\n","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","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 * - Collapses multiple underscores: `user__id` → `userId`\n * - Normalizes to lowercase first: `user_Column` → `userColumn`\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') // 'userId'\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 (handling multiple underscores)\n const camelCased = normalized.replace(/_+([a-z])/g, (_, letter) =>\n 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 lowercase letters\n // e.g., userId -> 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 `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 !isChangeMessage(message)\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\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 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 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`\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 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]\n","import {\n CHUNK_LAST_OFFSET_HEADER,\n CHUNK_UP_TO_DATE_HEADER,\n EXPIRED_HANDLE_QUERY_PARAM,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_HANDLE_HEADER,\n SHAPE_HANDLE_QUERY_PARAM,\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: 100,\n maxDelay: 60_000, // Cap at 60s - reasonable for long-lived connections\n multiplier: 1.3,\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\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\n await new Promise((resolve) => setTimeout(resolve, waitMs))\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\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 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 (args[1]?.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.toString(),\n err instanceof Error\n ? err.message\n : typeof err === `string`\n ? err\n : `failed to read body`\n )\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\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 `electric-offset`,\n `electric-handle`,\n]\n\nexport const requiredLiveResponseHeaders = [`electric-cursor`]\n\nexport const requiredNonLiveResponseHeaders = [`electric-schema`]\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\n // only prefetch if shape handle and offset for next chunk are available, and\n // response is not already up-to-date\n if (!shapeHandle || !lastOffset || isUpToDate) 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","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","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 { getOffset, isUpToDateMessage, isChangeMessage } from './helpers'\nimport {\n FetchError,\n FetchBackoffAbortError,\n MissingShapeUrlError,\n InvalidSignalError,\n MissingShapeHandleError,\n ReservedParamError,\n MissingHeadersError,\n} from './error'\nimport {\n BackoffDefaults,\n BackoffOptions,\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 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} 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'\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])\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 | `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 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 backoffOptions?: BackoffOptions\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 * 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\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 */\nfunction 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 for (const [key, value] of url.searchParams) {\n if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {\n cleanUrl.searchParams.set(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 number,\n [\n (messages: Message<T>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n #started = false\n #state = `active` as `active` | `pause-requested` | `paused`\n #lastOffset: Offset\n #liveCacheBuster: string // Seconds since our Electric Epoch 😎\n #lastSyncedAt?: number // unix time\n #isUpToDate: boolean = false\n #isMidStream: boolean = true\n #connected: boolean = false\n #shapeHandle?: string\n #mode: LogMode\n #schema?: Schema\n #onError?: ShapeStreamErrorHandler\n #requestAbortController?: AbortController\n #isRefreshing = false\n #tickPromise?: Promise<void>\n #tickPromiseResolver?: () => void\n #tickPromiseRejecter?: (reason?: unknown) => void\n #messageChain = Promise.resolve<void[]>([]) // promise chain for incoming messages\n #snapshotTracker = new SnapshotTracker()\n #activeSnapshotRequests = 0 // counter for concurrent snapshot requests\n #midStreamPromise?: Promise<void>\n #midStreamPromiseResolver?: () => void\n #lastSeenCursor?: string // Last seen cursor from previous session (used to detect cached responses)\n #currentFetchUrl?: URL // Current fetch URL for computing shape key\n #lastSseConnectionStartTime?: number\n #minSseConnectionDuration = 1000 // Minimum expected SSE connection duration (1 second)\n #consecutiveShortSseConnections = 0\n #maxShortSseConnections = 3 // Fall back to long polling after this many short connections\n #sseFallbackToLongPolling = false\n #sseBackoffBaseDelay = 100 // Base delay for exponential backoff (ms)\n #sseBackoffMaxDelay = 5000 // Maximum delay cap (ms)\n #unsubscribeFromVisibilityChanges?: () => void\n\n // Derived state: we're in replay mode if we have a last seen cursor\n get #replayMode(): boolean {\n return this.#lastSeenCursor !== undefined\n }\n\n constructor(options: ShapeStreamOptions<GetExtensions<T>>) {\n this.options = { subscribe: true, ...options }\n validateOptions(this.options)\n this.#lastOffset = this.options.offset ?? `-1`\n this.#liveCacheBuster = ``\n this.#shapeHandle = this.options.handle\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\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 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.#shapeHandle\n }\n\n get error() {\n return this.#error\n }\n\n get isUpToDate() {\n return this.#isUpToDate\n }\n\n get lastOffset() {\n return this.#lastOffset\n }\n\n get mode() {\n return this.#mode\n }\n\n async #start(): Promise<void> {\n this.#started = true\n\n try {\n await this.#requestShape()\n } catch (err) {\n this.#error = err\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 if (retryOpts && typeof retryOpts === `object`) {\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 // Clear the error since we're retrying\n this.#error = null\n\n // Restart from current offset\n this.#started = false\n await this.#start()\n return\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.#connected = false\n this.#tickPromiseRejecter?.()\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.#connected = false\n this.#tickPromiseRejecter?.()\n throw err\n }\n\n // Normal completion, clean up\n this.#connected = false\n this.#tickPromiseRejecter?.()\n }\n\n async #requestShape(): Promise<void> {\n if (this.#state === `pause-requested`) {\n this.#state = `paused`\n return\n }\n\n if (\n !this.options.subscribe &&\n (this.options.signal?.aborted || this.#isUpToDate)\n ) {\n return\n }\n\n const resumingFromPause = this.#state === `paused`\n this.#state = `active`\n\n const { url, signal } = this.options\n const { fetchUrl, requestHeaders } = await this.#constructUrl(\n url,\n resumingFromPause\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 try {\n await this.#fetchShape({\n fetchUrl,\n requestAbortController,\n headers: requestHeaders,\n resumingFromPause,\n })\n } catch (e) {\n // Handle abort error triggered by refresh\n if (\n (e instanceof FetchError || e instanceof FetchBackoffAbortError) &&\n requestAbortController.signal.aborted &&\n requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH\n ) {\n // Start a new request\n return this.#requestShape()\n }\n\n if (e instanceof FetchBackoffAbortError) {\n // Check current state - it may have changed due to concurrent pause/resume calls\n // from the visibility change handler during the async fetch operation.\n // TypeScript's flow analysis doesn't account for concurrent state changes.\n const currentState = this.#state as\n | `active`\n | `pause-requested`\n | `paused`\n if (\n requestAbortController.signal.aborted &&\n requestAbortController.signal.reason === PAUSE_STREAM &&\n currentState === `pause-requested`\n ) {\n this.#state = `paused`\n }\n return // interrupted\n }\n if (!(e instanceof FetchError)) throw e // should never happen\n\n if (e.status == 409) {\n // Upon receiving a 409, we should start from scratch\n // with the newly provided shape handle, or a fallback\n // pseudo-handle based on the current one to act as a\n // consistent cache buster\n\n // Store the current shape URL as expired to avoid future 409s\n if (this.#shapeHandle) {\n const shapeKey = canonicalShapeKey(fetchUrl)\n expiredShapesCache.markExpired(shapeKey, this.#shapeHandle)\n }\n\n const newShapeHandle =\n e.headers[SHAPE_HANDLE_HEADER] || `${this.#shapeHandle!}-next`\n this.#reset(newShapeHandle)\n\n // must refetch control message might be in a list or not depending\n // on whether it came from an SSE request or long poll - handle both\n // cases for safety here but worth revisiting 409 handling\n await this.#publish(\n (Array.isArray(e.json) ? e.json : [e.json]) as Message<T>[]\n )\n return this.#requestShape()\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 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 JSON.stringify(subsetParams.params)\n )\n if (subsetParams.limit)\n setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit)\n if (subsetParams.offset)\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 Electric's internal parameters\n fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, this.#lastOffset)\n fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, this.#mode)\n\n // Snapshot requests (with subsetParams) should never use live polling\n const isSnapshotRequest = subsetParams !== undefined\n\n if (this.#isUpToDate && !isSnapshotRequest) {\n // If we are resuming from a paused state, we don't want to perform a live request\n // because it could be a long poll that holds for 20sec\n // and during all that time `isConnected` will be false\n if (!this.#isRefreshing && !resumingFromPause) {\n fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`)\n }\n fetchUrl.searchParams.set(\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n this.#liveCacheBuster\n )\n }\n\n if (this.#shapeHandle) {\n // This should probably be a header for better cache breaking?\n fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, this.#shapeHandle!)\n }\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 async #onInitialResponse(response: Response) {\n const { headers, status } = response\n const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)\n if (shapeHandle) {\n // Don't accept a handle we know is expired - this can happen if a\n // proxy serves a stale cached response despite the expired_handle\n // cache buster parameter\n const shapeKey = this.#currentFetchUrl\n ? canonicalShapeKey(this.#currentFetchUrl)\n : null\n const expiredHandle = shapeKey\n ? expiredShapesCache.getExpiredHandle(shapeKey)\n : null\n if (shapeHandle !== expiredHandle) {\n this.#shapeHandle = shapeHandle\n } else {\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 `Ignoring the stale handle and continuing with handle \"${this.#shapeHandle}\".`\n )\n }\n }\n\n const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)\n if (lastOffset) {\n this.#lastOffset = lastOffset as Offset\n }\n\n const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER)\n if (liveCacheBuster) {\n this.#liveCacheBuster = liveCacheBuster\n }\n\n this.#schema = this.#schema ?? getSchemaFromHeaders(headers)\n\n // NOTE: 204s are deprecated, the Electric server should not\n // send these in latest versions but this is here for backwards\n // compatibility\n if (status === 204) {\n // There's no content so we are live and up to date\n this.#lastSyncedAt = Date.now()\n }\n }\n\n async #onMessages(batch: Array<Message<T>>, isSseMessage = false) {\n // Update isUpToDate\n if (batch.length > 0) {\n // Set isMidStream to true when we receive any data\n this.#isMidStream = true\n\n const lastMessage = batch[batch.length - 1]\n if (isUpToDateMessage(lastMessage)) {\n if (isSseMessage) {\n // Only use the offset from the up-to-date message if this was an SSE message.\n // If we would use this offset from a regular fetch, then it will be wrong\n // and we will get an \"offset is out of bounds for this shape\" error\n const offset = getOffset(lastMessage)\n if (offset) {\n this.#lastOffset = offset\n }\n }\n this.#lastSyncedAt = Date.now()\n this.#isUpToDate = true\n // Set isMidStream to false when we see an up-to-date message\n this.#isMidStream = false\n // Resolve the promise waiting for mid-stream to end\n this.#midStreamPromiseResolver?.()\n\n // Check if we should suppress this up-to-date notification\n // to prevent multiple renders from cached responses\n if (this.#replayMode && !isSseMessage) {\n // We're in replay mode (replaying cached responses during initial sync).\n // Check if the cursor has changed - cursors are time-based and always\n // increment, so a new cursor means fresh data from the server.\n const currentCursor = this.#liveCacheBuster\n\n if (currentCursor === this.#lastSeenCursor) {\n // Same cursor = still replaying cached responses\n // Suppress this up-to-date notification\n return\n }\n }\n\n // We're either:\n // 1. Not in replay mode (normal operation), or\n // 2. This is a live/SSE message (always fresh), or\n // 3. Cursor has changed (exited replay mode with fresh data)\n // In all cases, notify subscribers and record the up-to-date.\n this.#lastSeenCursor = undefined // Exit replay mode\n\n if (this.#currentFetchUrl) {\n const shapeKey = canonicalShapeKey(this.#currentFetchUrl)\n upToDateTracker.recordUpToDate(shapeKey, this.#liveCacheBuster)\n }\n }\n\n // Filter messages using snapshot tracker\n const messagesToProcess = batch.filter((message) => {\n if (isChangeMessage(message)) {\n return !this.#snapshotTracker.shouldRejectMessage(message)\n }\n return true // Always process control messages\n })\n\n await this.#publish(messagesToProcess)\n }\n }\n\n /**\n * Fetches the shape from the server using either long polling or SSE.\n * Upon receiving a successfull 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.#isUpToDate && !this.#replayMode) {\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.#lastSeenCursor = lastSeenCursor\n }\n }\n\n const useSse = this.options.liveSse ?? this.options.experimentalLiveSse\n if (\n this.#isUpToDate &&\n useSse &&\n !this.#isRefreshing &&\n !opts.resumingFromPause &&\n !this.#sseFallbackToLongPolling\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 #requestShapeLongPoll(opts: {\n fetchUrl: URL\n requestAbortController: AbortController\n headers: Record<string, string>\n }): Promise<void> {\n const { fetchUrl, requestAbortController, headers } = opts\n const response = await this.#fetchClient(fetchUrl.toString(), {\n signal: requestAbortController.signal,\n headers,\n })\n\n this.#connected = true\n await this.#onInitialResponse(response)\n\n const schema = this.#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 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 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 await this.#onInitialResponse(response)\n },\n onmessage: (event: EventSourceMessage) => {\n if (event.data) {\n // event.data is a single JSON object\n const schema = this.#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 (requestAbortController.signal.aborted) {\n // During an SSE request, the fetch might have succeeded\n // and we are parsing the incoming stream.\n // If the abort happens while we're parsing the stream,\n // then it won't be caught by our `createFetchWithBackoff` wrapper\n // and instead we will get a raw AbortError here\n // which we need to turn into a `FetchBackoffAbortError`\n // such that #start handles it correctly.`\n throw new FetchBackoffAbortError()\n }\n throw error\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 if (connectionDuration < this.#minSseConnectionDuration && !wasAborted) {\n // Connection was too short - likely a cached response or misconfiguration\n this.#consecutiveShortSseConnections++\n\n if (\n this.#consecutiveShortSseConnections >= this.#maxShortSseConnections\n ) {\n // Too many short connections - fall back to long polling\n this.#sseFallbackToLongPolling = true\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 )\n } else {\n // Add exponential backoff with full jitter to prevent tight infinite loop\n // Formula: random(0, min(cap, base * 2^attempt))\n const maxDelay = Math.min(\n this.#sseBackoffMaxDelay,\n this.#sseBackoffBaseDelay *\n Math.pow(2, this.#consecutiveShortSseConnections)\n )\n const delayMs = Math.floor(Math.random() * maxDelay)\n await new Promise((resolve) => setTimeout(resolve, delayMs))\n }\n } else if (connectionDuration >= this.#minSseConnectionDuration) {\n // Connection was healthy - reset counter\n this.#consecutiveShortSseConnections = 0\n }\n }\n }\n\n #pause() {\n if (this.#started && this.#state === `active`) {\n this.#state = `pause-requested`\n this.#requestAbortController?.abort(PAUSE_STREAM)\n }\n }\n\n #resume() {\n if (\n this.#started &&\n (this.#state === `paused` || this.#state === `pause-requested`)\n ) {\n // Don't resume if the user's signal is already aborted\n // This can happen if the signal was aborted while we were paused\n // (e.g., TanStack DB collection was GC'd)\n if (this.options.signal?.aborted) {\n return\n }\n\n // If we're resuming from pause-requested state, we need to set state back to active\n // to prevent the pause from completing\n if (this.#state === `pause-requested`) {\n this.#state = `active`\n }\n this.#start()\n }\n }\n\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError: (error: Error) => void = () => {}\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n this.#unsubscribeFromVisibilityChanges?.()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.#lastSyncedAt\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n if (this.#lastSyncedAt === undefined) return Infinity\n return Date.now() - this.#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 afterwise. */\n isLoading(): boolean {\n return !this.#isUpToDate\n }\n\n hasStarted(): boolean {\n return this.#started\n }\n\n isPaused(): boolean {\n return this.#state === `paused`\n }\n\n /** Await the next tick of the request loop */\n async #nextTick() {\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.finally(() => {\n this.#tickPromise = undefined\n this.#tickPromiseResolver = undefined\n this.#tickPromiseRejecter = undefined\n })\n return this.#tickPromise\n }\n\n /** Await until we're not in the middle of a stream (i.e., until we see an up-to-date message) */\n async #waitForStreamEnd() {\n if (!this.#isMidStream) {\n return\n }\n if (this.#midStreamPromise) {\n return this.#midStreamPromise\n }\n this.#midStreamPromise = new Promise((resolve) => {\n this.#midStreamPromiseResolver = resolve\n })\n this.#midStreamPromise.finally(() => {\n this.#midStreamPromise = undefined\n this.#midStreamPromiseResolver = undefined\n })\n return this.#midStreamPromise\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.#isRefreshing = true\n if (this.#isUpToDate && !this.#requestAbortController?.signal.aborted) {\n // If we are \"up to date\", any current request will be a \"live\" request\n // and needs to be aborted\n this.#requestAbortController?.abort(FORCE_DISCONNECT_AND_REFRESH)\n }\n await this.#nextTick()\n this.#isRefreshing = false\n }\n\n async #publish(messages: Message<T>[]): Promise<void[]> {\n // We process messages asynchronously\n // but SSE's `onmessage` handler is synchronous.\n // We use a promise chain to ensure that the handlers\n // execute sequentially in the order the messages were received.\n this.#messageChain = this.#messageChain.then(() =>\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\n return this.#messageChain\n }\n\n #sendErrorToSubscribers(error: Error) {\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n #subscribeToVisibilityChanges() {\n if (\n typeof document === `object` &&\n typeof document.hidden === `boolean` &&\n typeof document.addEventListener === `function`\n ) {\n const visibilityHandler = () => {\n if (document.hidden) {\n this.#pause()\n } else {\n this.#resume()\n }\n }\n\n document.addEventListener(`visibilitychange`, visibilityHandler)\n\n // Store cleanup function to remove the event listener\n this.#unsubscribeFromVisibilityChanges = () => {\n document.removeEventListener(`visibilitychange`, visibilityHandler)\n }\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.#lastOffset = `-1`\n this.#liveCacheBuster = ``\n this.#shapeHandle = handle\n this.#isUpToDate = false\n this.#isMidStream = true\n this.#connected = false\n this.#schema = undefined\n this.#activeSnapshotRequests = 0\n // Reset SSE fallback state to try SSE again after reset\n this.#consecutiveShortSseConnections = 0\n this.#sseFallbackToLongPolling = false\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 // We shouldn't be getting a snapshot on a shape that's not started\n if (!this.#started) await this.#start()\n\n // Wait until we're not mid-stream before pausing\n // This ensures we don't pause in the middle of a transaction\n await this.#waitForStreamEnd()\n\n // Pause the stream if this is the first snapshot request\n this.#activeSnapshotRequests++\n\n try {\n if (this.#activeSnapshotRequests === 1) {\n // Currently this cannot throw, but in case it can later it's in this try block to not have a stuck counter\n this.#pause()\n }\n\n const { metadata, data } = 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 this.#onMessages(dataWithEndBoundary, false)\n\n return {\n metadata,\n data,\n }\n } finally {\n // Resume the stream if this was the last snapshot request\n this.#activeSnapshotRequests--\n if (this.#activeSnapshotRequests === 0) {\n this.#resume()\n }\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 * @param opts - The options for the snapshot request.\n * @returns The metadata and the data for the snapshot.\n */\n async fetchSnapshot(opts: SubsetParams): Promise<{\n metadata: SnapshotMetadata\n data: Array<ChangeMessage<T>>\n }> {\n const { fetchUrl, requestHeaders } = await this.#constructUrl(\n this.options.url,\n true,\n opts\n )\n\n const response = await this.#fetchClient(fetchUrl.toString(), {\n headers: requestHeaders,\n })\n\n if (!response.ok) {\n throw new FetchError(\n response.status,\n undefined,\n undefined,\n Object.fromEntries([...response.headers.entries()]),\n fetchUrl.toString()\n )\n }\n\n // Use schema from stream if available, otherwise extract from response header\n const schema: Schema =\n this.#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 return {\n metadata,\n data,\n }\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\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.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 return\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","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\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\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 { xmin: bigint; xmax: bigint; xip_list: bigint[]; keys: Set<string> }\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 this.activeSnapshots.set(metadata.snapshot_mark, {\n xmin: BigInt(metadata.xmin),\n xmax: BigInt(metadata.xmax),\n xip_list: metadata.xip_list.map(BigInt),\n keys,\n })\n const xmaxSet =\n this.xmaxSnapshots\n .get(BigInt(metadata.xmax))\n ?.add(metadata.snapshot_mark) ?? new Set([metadata.snapshot_mark])\n this.xmaxSnapshots.set(BigInt(metadata.xmax), xmaxSet)\n const databaseLsnSet =\n this.snapshotsByDatabaseLsn\n .get(BigInt(metadata.database_lsn))\n ?.add(metadata.snapshot_mark) ?? new Set([metadata.snapshot_mark])\n this.snapshotsByDatabaseLsn.set(\n BigInt(metadata.database_lsn),\n databaseLsnSet\n )\n }\n\n /**\n * Remove a snapshot from tracking\n */\n removeSnapshot(snapshotMark: number): void {\n this.activeSnapshots.delete(snapshotMark)\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 const xid = Math.max(...txids) // Use the maximum transaction ID\n\n for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {\n if (xid >= xmax) {\n for (const snapshot of snapshots) {\n this.removeSnapshot(snapshot)\n }\n }\n }\n\n return [...this.activeSnapshots.values()].some(\n (x) => x.keys.has(message.key) && isVisibleInSnapshot(xid, x)\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","import { Message, Offset, Row } from './types'\nimport { isChangeMessage, isControlMessage } 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 * 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<number, 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 const key = JSON.stringify(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 = Math.random()\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 shouldNotify = 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 break\n case `update`:\n this.#data.set(message.key, {\n ...this.#data.get(message.key)!,\n ...message.value,\n })\n break\n case `delete`:\n this.#data.delete(message.key)\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 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 }\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 }\n break\n }\n }\n }\n\n if (isControlMessage(message)) {\n switch (message.headers.control) {\n case `up-to-date`:\n shouldNotify = this.#updateShapeStatus(`up-to-date`)\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 shouldNotify = this.#updateShapeStatus(`syncing`)\n // Flag to re-execute sub-snapshots once the new shape is up-to-date\n this.#reexecuteSnapshotsPending = true\n break\n }\n }\n })\n\n if (shouldNotify) this.#notify()\n }\n\n async #reexecuteSnapshots(): Promise<void> {\n // Wait until stream is up-to-date again (ensures schema is available)\n await this.#awaitUpToDate()\n\n // Re-execute all snapshots concurrently\n 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 } catch (_) {\n // Ignore and continue; errors will be surfaced via stream onError\n }\n })\n )\n }\n\n async #awaitUpToDate(): Promise<void> {\n if (this.stream.isUpToDate) return\n await new Promise<void>((resolve) => {\n const check = () => {\n if (this.stream.isUpToDate) {\n clearInterval(interval)\n unsub()\n resolve()\n }\n }\n const interval = setInterval(check, 10)\n const unsub = this.stream.subscribe(\n () => check(),\n () => check()\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":["value","pos","_fetchClient","_a","_b","fetch","_subscribers","_error"]}
|