@electric-sql/client 演进全解析:ShapeStream 状态机、缓存失效防御与错误重试体系
【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric
本篇文章以@electric-sql/client(Electric 官方 TypeScript 客户端)的完整 CHANGELOG 为骨架,结合仓库内的 README、SPEC.md 与源码实现,系统梳理该客户端从 0.0.2 到 1.5.27 的架构演进脉络。你将掌握其双 API 设计(ShapeStream / Shape)、七状态状态机、错误重试契约、CDN 缓存失效防御、子集快照、列名映射、SSE 实时传输与移动端生命周期适配等核心机制,并了解每个机制背后对应的源码与测试依据,可直接用于二次开发、排障与深入理解 Electric 的 HTTP 同步协议。
一、客户端定位:把 Postgres 变成实时数据库的 HTTP 接入层
Electric 通过 HTTP 接口向海量客户端暴露 Postgres 数据的实时子集(称为 Shape)。@electric-sql/client正是面向浏览器、Edge Function、Node/Bun/Deno 等 JavaScript 运行时的官方接入客户端。根据 README 的描述,它同时支持细粒度与粗粒度两种响应式订阅模式:既可以逐行订阅变更,也可以在整个 Shape 变化时一次性拿到全量数据。
客户端围绕两个核心类展开:
ShapeStream:以行级粒度消费 Shape 的变更流(insert/update/delete),订阅回调收到的是消息数组;Shape:在ShapeStream之上做物化(materialize),订阅回调收到的是整个 Shape 的最新rows。
// ShapeStream:逐行订阅 import { ShapeStream } from '@electric-sql/client' const stream = new ShapeStream({ url: `http://localhost:3000/v1/shape`, params: { table: `foo` }, headers: { Authorization: `Bearer token` }, }) stream.subscribe((messages) => { // messages 是包含一行或多行更新的数组 // 客户端会等待所有订阅者处理完再继续推进 }) // Shape:全量物化订阅 import { ShapeStream, Shape } from '@electric-sql/client' const shape = new Shape(new ShapeStream({ url: `http://localhost:3000/v1/shape`, params: { table: `foo` }, })) await shape.rows // 返回最新 Shape 数据 shape.subscribe(({ rows }) => { // rows 是 Shape 内每行最新值的数组 })二、配置模型的演进:从平铺选项到params子键
CHANGELOG 记录了客户端 API 在 0.x 阶段的两次重要破坏性重构,理解它们有助于你读懂当前代码中的接口设计。
2.1 v0.9.0:协议级选项与数据源选项分离
早期版本把table、where、columns、replica等 PostgreSQL 专属选项直接平铺在ShapeStreamOptions顶层。由于 Electric 计划未来支持多种数据源,CHANGELOG 在 0.9.0 中移除了databaseId选项,并将 PostgreSQL 专属选项统一迁移到params子键下,形成了PostgresParams类型:
// Before (0.9.0 之前) const stream = new ShapeStream({ url: 'http://localhost:3000/v1/shape', table: 'users', where: 'id > 100', columns: ['id', 'name'], replica: 'full', }) // After (0.9.0 之后) const stream = new ShapeStream({ url: 'http://localhost:3000/v1/shape', params: { table: 'users', where: 'id > 100', columns: ['id', 'name'], replica: 'full', }, })这一设计延续至今,可见于 client.ts 中的PostgresParams接口定义:table(根表,代理场景下可省略)、columns(列子集,须包含主键)、where(WHERE 子句)、params(位置参数$1/$2的取值)、replica(default只发送变更列,full发送整行并附带old_value,但带宽开销更高,默认不建议)。
2.2 v1.0.0:函数式参数与函数式 Header
客户端支持把params与headers声明为同步或异步函数,在请求发出前并行解析。这在认证令牌刷新、多租户上下文切换等场景下非常有用:
const stream = new ShapeStream({ url: 'http://localhost:3000/v1/shape', params: { table: 'items', userId: () => getCurrentUserId(), filter: async () => await getUserPreferences(), }, headers: { Authorization: async () => `Bearer ${await getAccessToken()}`, }, })与此同时,CHANGELOG 还强调所有官方客户端都会对查询参数排序,保证 Shape URL 缓存一致(0.9.0 的af0c0bf)。
三、七状态状态机:ShapeStream 的核心骨架
3.1 为什么需要显式状态机
在 v1.5.3 之前,ShapeStream 的隐式同步状态保存在一个"扁平上下文袋"中——所有字段无论当前状态如何都同时存在。v1.5.3 用 OOP 状态模式将其重构为显式状态机:每个状态(Initial、Syncing、Live、Replaying、StaleRetry、Paused、Error)是独立类,只携带与自身相关的字段,状态迁移产生新的不可变状态对象。源码 shape-stream-state.ts 开头的类层次注释完整描述了这一结构:
ShapeStreamState (abstract base) ├── ActiveState (abstract — shared field storage & helpers) │ ├── FetchingState (abstract) │ │ ├── InitialState │ │ ├── SyncingState │ │ └── StaleRetryState │ ├── LiveState │ └── ReplayingState ├── PausedState (delegates to previousState) └── ErrorState (delegates to previousState)3.2 状态、事件与迁移规则
SPEC.md 是该状态机的形式化规范,被官方指定为"行为的唯一事实来源",测试全部由此派生。七个状态分为三组:
| 分组 | 状态 | kind | 说明 |
|---|---|---|---|
| Fetching | InitialState | initial | 尚无数据,等待首个响应 |
| Fetching | SyncingState | syncing | 收到首个响应,追赶最新位置 |
| Fetching | StaleRetryState | stale-retry | 响应过期,携带 cache-buster 重试 |
| Active | LiveState | live | 已追上最新位置,实时流式更新 |
| Active | ReplayingState | replaying | 恢复后从缓存重新抓取 |
| Delegate | PausedState | paused | 暂停,委托 previousState |
| Delegate | ErrorState | error | 失败,委托 previousState + error |
作用于任意状态的十个事件为:response、messages、sseClose、pause、resume、error、retry、markMustRefetch、withHandle、enterReplayMode。核心迁移路径如下:
Initial ──response──► Syncing ──up-to-date──► Live │ │ └──stale──► StaleRetry │ │ │ Syncing ◄──response──┘ │ │ Any ──────pause──────► Paused ───resume───► (previous) Any ──────error──────► Error ───retry────► (previous) Any ──markMustRefetch─► Initial (offset = -1)SPEC 同时规定了完整的 70 个组合(7 状态 × 10 事件)迁移真值表,类型为Record<ShapeStreamStateKind, Record<EventType, ExpectedBehavior>>——没有Partial,因此 TypeScript 在编译期就强制完整性,见 state-transition-table.ts。
3.3 关键不变量(Invariants)
SPEC.md 定义了 13 条不变量(I0–I12),其中几条对理解客户端行为至关重要:
- I1:
isUpToDate === true当且仅当 LiveState 在委托链上(即状态本身是 LiveState,或经由 Paused/Error 的previousState可达); - I2:迁移总是产生新对象,绝不原地修改(字段全部
readonly),no-op 迁移才返回this; - I3:
state.pause().resume() === state(引用相等)作为代数性质测试覆盖全部 7 个状态; - I4:
state.toErrorState(err).retry() === state,错误重试保持身份; - I10:任何状态的
markMustRefetch(handle)都重置为offset === '-1'、schema === undefined的 InitialState; - I12:不允许同类委托状态嵌套——
Paused(Paused(X))收敛为Paused(X),Error(Error(X))收敛为Error(X)(新错误覆盖旧错误),但交叉嵌套如Paused(Error(X))被保留,因为它有语义意义。
这些不变量由 state-machine-dsl.ts 中的assertStateInvariants()与assertReachableInvariants()在运行时自动校验。
四、错误处理与重试体系
4.1 错误类型层次
客户端在 v0.8.0 引入了一批具名错误类,集中在 error.ts,可分为两组:
配置期错误:
MissingShapeUrlError:缺少必需的url参数;InvalidSignalError:signal不是合法的AbortSignal实例;ReservedParamError:自定义参数与协议保留参数名冲突;ShapeStreamAlreadyRunningError:流已在运行;InvalidShapeOptionsError:选项校验失败。
运行期错误:
FetchError:携带status、text、json、headers、url的 HTTP 错误;FetchBackoffAbortError:等待退避期间被 AbortSignal 中止(v1.5.21 起从包入口正式导出);MissingShapeHandleError:非初始抓取(offset > -1)却缺少 shape handle;ParserNullValueError:非空列收到NULL值;MissingHeadersError:响应缺少electric-*必需头(常由代理 CORS 配置错误导致,v1.4.1 起被视为不可重试错误);StaleCacheError:响应中的 handle 已被标记过期,说明数据来自错误的缓存层。
4.2 onError 重试契约
README 详细规定了流级onError的语义——返回值决定是否继续同步:
- 返回对象(哪怕是空
{})→ 继续重试:{}用原参数重试、{ params }用修改后的参数、{ headers }用修改后的 Header、二者皆可; - 返回
void/undefined→ 永久停止流。
import { ShapeStream, FetchError } from '@electric-sql/client' const stream = new ShapeStream({ url: `http://localhost:3000/v1/shape`, params: { table: `foo` }, onError: (error) => { if (error instanceof FetchError) { if (error.status === 401) { // 刷新令牌后重试 return { headers: { Authorization: `Bearer ${getRefreshedToken()}` } } } if (error.status === 403) { // 切换用户上下文后重试 return { params: { table: `foo`, where: `user_id = $1`, params: [fallbackUserId] } } } } // 其他错误:返回 void 停止同步 }, })值得注意:5xx、网络错误与 429 限流会自动以指数退避重试,onError只在自动重试耗尽或遇到不可重试的 4xx 时才被调用。订阅者也可以传第二个回调处理订阅级错误,但它不能控制重试行为。
4.3 退避参数与重试风暴治理
CHANGELOG 记录了一条清晰的退避参数演进线:
- v1.5.8(
e172d4b):默认退避参数向行业标准(gRPC、AWS)靠拢——initialDelay100ms → 1s,multiplier1.3 → 2,maxDelay60s → 32s,5 次重试即到达上限(此前约需 25 次); - v1.5.15/v1.5.18:将
onError连续重试上限绑定为 50 次,防止"永远返回重试指令"的损坏 handler 导致无限重试与内存增长;计数器在成功数据(非空消息批次或 204)时重置;v1.5.18 进一步为onError触发的重试加入带抖动(jitter)的指数退避; - v1.5.23:新增请求看门狗(request watchdog),
liveRequestTimeoutMs默认 45s(设为false可关闭),超时后以内部原因live-request-timeout中止并重启请求循环,即使平台 fetch promise 永不 settle 也能恢复,解决了移动端网络切换或生命周期转换时 fetch 挂死的问题。
SPEC.md 的"Client Fetch Loop Paths"章节枚举了 client.ts 中六个回环请求点(L1–L6),并强调"任何回环路径都必须改变下一个请求 URL,否则会死循环",其中 L5(onError重试)由#maxConsecutiveErrorRetries(50)与中止感知的全抖动退避共同守护。
五、缓存失效与 CDN/代理防御:一条持续三个大版本的主线
CHANGELOG 中最反复出现的主题,是对抗错误缓存的 CDN/代理。这类问题的共同诱因是:代理或浏览器 HTTP 缓存返回了带过期 shape handle 的旧响应,客户端若接受它就会在错误的位置继续推进 offset,甚至陷入高速死循环。
5.1 从expired_handle到cache-buster
- v1.0.10:409 时在 localStorage 记录过期 handle,后续请求附带
expired_handle参数,避免重复 409 并降低加载延迟(见 expired-shapes-cache.ts); - v1.3.1:当代理忽略
expired_handle仍返回含旧 handle 的缓存时,客户端不再接受与过期缓存匹配的 handle,prefetch 也不抓取 handle 等于expired_handle的下一个 chunk,并输出 console 警告帮助排查代理配置; - v1.5.11:修复代理剥离 handle 头时 409 重试 URL 无限膨胀的问题——不再对 handle 追加
-next,改为随机cache-buster查询参数保证重试 URL 唯一; - v1.5.13:CDN 提供陈旧缓存导致每秒钟数百次同 URL 重试的问题,陈旧响应一律进入
stale-retry并附加 cache-buster,同时加入重复 URL 守卫与状态机警告堆栈; - v1.5.15:409 响应无条件新建 cache-buster,保证 409 后的 URL 与 409 前的 URL 必然不同,从根上防止"缓存 409 被 CDN 无限循环"。
SPEC.md 将"无条件 409 cache buster"列为不变量,由静态分析规则conditional-409-cache-buster、model-based.test.ts 中的Respond409SameHandleCmd/Respond409NoHandleCmd以及 pbt-micro.test.ts 中的#fetchSnapshotWithRetry 409 loop PBT(严格约束#maxSnapshotRetries = 5)共同守护。
5.2 快速循环检测与自愈恢复
- v1.5.8:
#checkFastLoop检测"快速请求却停留在同一 offset"(典型于客户端缓存或代理/CDN 配置错误),先清除该 shape 的缓存状态并从头抓取,若循环持续则指数退避,最终抛出带诊断信息的错误; - v1.5.15(
690e25a):当陈旧缓存重试 3 次耗尽后,客户端清除 localStorage 中的过期条目并不带expired_handle重试一次。由于服务端从不复用 handle(SPEC 的 S0 假设),全新响应必然携带新 handle,从而绕过陈旧检测——这一自愈机制避免了"代理剥离 cache-buster 参数导致 shape 永久无法加载"的僵局。
SPEC.md 的 S0 假设直接解释了为何这一策略可行:服务端 handle 形如{phash2_hash}-{microsecond_timestamp},唯一性由单调时间戳、SQLiteUNIQUE INDEX与 ETSinsert_new检查三重保障,因此"响应包含过期 handle 必然来自缓存层而非服务端"。
5.3 协议查询参数常量
客户端把协议相关查询参数集中在 constants.ts,并通过ELECTRIC_PROTOCOL_QUERY_PARAMS导出(v1.0.8 起),供代理配置白名单透传使用。v1.2.0 还修复了subset__params的序列化方式:从 deepObject 风格(subset__params[1])改为 JSON 序列化(subset__params={"1":"value1"}),让代理可以按常量参数名subset__params匹配而无需动态模式匹配。
六、子集快照与按需加载模式
6.1 v1.0.11 的三件套
CHANGELOG 在 v1.0.11 一次性引入了三个互补能力:
changes_only模式:服务端不生成初始快照,客户端直接接收增量变更,适合"不保留历史状态、重载后从头开始"的无状态客户端;- 子集快照(subset snapshots):服务端接受
subset__*参数,返回特殊形式的子集快照响应(含如何在流中定位的信息),客户端新增requestSnapshot方法发出请求并把快照注入订阅消息流的正确位置,以snapshot-end控制消息定界; offset=now特殊值:客户端收到立即的 up-to-date 响应与最新可续 offset,跳过全部历史数据直接"从零开始",与changes_only、子集快照搭配最佳。
相关协议常量(subset__where、subset__limit、subset__offset、subset__order_by、subset__params等)都在 constants.ts 中定义。
6.2 后续迭代
- v1.3.0:在注入的子集快照末尾追加额外消息标记结束;
- v1.5.0:子集快照支持 POST——将 WHERE、排序、分页参数放进请求体而非 URL 查询参数,避免复杂查询或大型 IN 列表触发 HTTP 414;types.ts 的
SubsetParams允许按请求覆盖subsetMethod(GET默认、POST推荐),并注明 Electric 2.0 将弃用 GET; - v1.4.0:支持结构化子集参数(
whereExpr、orderByExpr),当 TanStack DB 等调用方发送结构化表达式数据时,客户端可在生成最终 SQL 前正确应用列名变换; - v1.5.7:修复 int8 列解析出的 BigInt 传给
requestSnapshot/fetchSnapshot参数时JSON.stringify抛 "Do not know how to serialize a BigInt" 的问题; - v1.5.26:修复 PostgreSQL 事务 ID 回绕(wraparound)后的子集快照过滤问题,并在流越过各快照的数据库 LSN 后退役过滤器。
6.3 相关修复
- v1.5.20:
requestSnapshot()现在保证在注入的快照批次已投递给订阅者(含异步与可重入订阅者路径)之后才 resolve; - v1.5.9:修复
changes_only模式下暂停/恢复后唤醒检测定时器未重新武装的问题; - v1.5.7(
858e13d):修复按需模式(offset: "now")冷启动requestSnapshot()后流不推进 offset/handle 的问题——现在流会从快照位置继续,而不是停留在陈旧的"now"offset,避免快照与下一次实时轮询之间的更新被遗漏。
七、列名映射:snake_case ↔ camelCase 的双向通道
v1.2.0 引入columnMapper选项:encode(应用名 → 数据库名,作用于 WHERE 子句与查询参数)与decode(数据库名 → 应用名,作用于结果列名)双向映射,并内置snakeCamelMapper()自动转换与createColumnMapper()自定义映射。旧有的transformer不再承担列改名职责,但仍可用于值变换(如加密)。
实现细节集中在 column-mapper.ts:
snakeToCamel/camelToSnake:刻意设计为可逆的注入式变换——保留前导/尾随下划线、连续下划线计数(user__id→user_Id),避免不同数据库列名在应用层碰撞;encodeWhereClause:基于正则的 WHERE 子句列名编码,跳过引号字符串、SQL 关键字(AND/OR/IN/NULL等)与$n占位符;源码注释明确提示正则方案对复杂嵌套表达式、引号标识符(如"user-id")支持有限,复杂查询建议直接用数据库列名或显式映射;quoteIdentifier:对标识符做双引号包裹并转义内部双引号,保证含特殊字符的列名安全进入查询参数;createColumnMapper(mapping):基于显式映射表构建,适用于列名不符合 snake/camel 规则、或需要精确控制(如id→identifier)的场景。
v1.2.2 修复了columnMapper对子集加载的支持(columns参数现在会先从应用列名编码为数据库列名再传给服务端);v1.4.0 又让结构化子集表达式(whereExpr)在生成 SQL 前应用同样的列名变换。相关的回归测试见 column-mapper.test.ts。
八、实时传输:从长轮询到 SSE
实时数据通道经历了清晰的三阶段演进:
- v1.0.5(
c59000f):实验性 SSE 支持; - v1.1.0(
37242f6):废弃experimental_live_sse,引入正式的live_sse标志以 Server-Sent Events 发送实时更新,并让 409 must-refetch 在 SSE 与长轮询两条路径上都被正确处理;live_sse随之加入ELECTRIC_PROTOCOL_QUERY_PARAMS(v1.1.3); - v1.5.4(
186b8f8):正确打包修补过的fetch-event-source。此前 liveSse 模式引入的fetch-event-source(比内置EventSource功能更强)带有对 document/window 存在性的假设与 abort 相关缺陷,虽然源码打了补丁,但构建产物未包含补丁。仓库根目录的 patches/@microsoft__fetch-event-source.patch 正是这一修补的存档。
SPEC.md 还规定了 SSE 专属约束:
- C6:SSE 的 up-to-date 消息通过
upToDateOffset更新 offset,非 SSE 的 up-to-date 消息保留现有 offset; - C8:SSE 状态(
sseFallbackToLongPolling、consecutiveShortSseConnections)是 LiveState 的私有字段,通过 LiveState 自身迁移保持,从非 Live 状态回到 Live 时重置为默认值; - v1.5.15 将
EXPERIMENTAL_LIVE_SSE_QUERY_PARAM加入ELECTRIC_PROTOCOL_QUERY_PARAMS,使canonicalShapeKey能剥离该参数——此前 SSE 与长轮询两条代码路径对同一 shape 会生成不同的缓存键。
此外 v1.0.2 起实时响应统一由 204 改为 200(204 仅表示"无新内容"),v1.0.3 保留对旧 204 的向后兼容;v1.5.6 进一步修复了对旧服务器的 204 处理——此前 204 只更新lastSyncedAt而不进入 live 状态,isUpToDate永远为 false、live=true永远不附加到 URL,订阅者永远等不到 up-to-date 信号(对现代服务端惰性无害,但对旧服务端会造成无限追赶轮询)。
九、移动端与运行环境适配
客户端在移动端(React Native / Expo)与服务器运行时(Node/Bun/Deno)上的健壮性是一条持续投入的线:
- v1.0.4:基于页面可见性(visibilitychange)暂停/恢复流;
- v1.1.4:修复快速切换标签页(尤其 Firefox)时 pause/resume 状态机的竞态——
#pause()先进入中间态pause-requested,若#resume()只检查paused状态就会卡死;同时修复可见性监听器泄漏; - v1.5.2:修复非浏览器环境(Bun、Node)系统休眠后流挂死——wake 时自动 abort 陈旧的 in-flight HTTP 请求并重连,不必等 TCP 超时;
- v1.5.3:引入
PauseLock协调可见性变化与快照请求之间的暂停/恢复,避免某个子系统的 resume 覆盖另一个的 pause;v1.5.3 还修复了"恢复会话尚无 schema 时收到陈旧缓存响应"导致schema!解引用崩溃的问题; - v1.5.22:修复不保留
AbortSignal.reason的运行时(部分 React Native fetch/AbortController 实现)中 wake 重连失败的问题; - v1.5.23:自动探测 React Native
AppState,应用进入后台时暂停请求、回到前台后以非 live 追赶模式恢复;runtimeVisibility适配器钩子保留给其他非浏览器运行时; - v1.5.24:改用 React Native 包导出(package export)在 Metro/Expo 构建中接线 AppState 生命周期,替代脆弱的运行时
require('react-native')自动探测,runtimeVisibility仍作为显式逃生口保留。
对应源码包括 pause-lock.ts、runtime-visibility.ts 与 react-native.ts,相关测试见 pause-lock.test.ts 与 wake-detection.test.ts。
十、测试方法论:从状态机 DSL 到属性测试与变异测试
v1.5.10 与 v1.5.15 在测试基建上的投入值得单独说明,因为它直接驱动了多个生产缺陷的修复:
- 多层级测试 DSL:面向 ShapeStream 状态机的流式场景构建器(fluent scenario builder)、70 单元迁移真值表、代数性质测试(algebraic property tests)、带种子的模糊测试(seeded fuzz testing)与变异测试(mutation testing),见 state-machine-dsl.ts;
- 基于模型的属性测试(model-based PBT):在 model-based.test.ts 中模拟服务端响应序列,发现并修复了:409 后未无条件重建 cache-buster 导致的 CDN 无限循环、
ShapeStream#start中等待永不 settle 的 live fetch 导致的调用栈帧泄漏、onError无限重试循环、#requestShape在 409 时发布原始响应体导致订阅者保留陈旧数据等问题; - 微目标 PBT(micro-target PBT):在 pbt-micro.test.ts 中针对单一函数验证,修复了
canonicalShapeKey重复参数折叠、Shape#process在[up-to-date, insert]批次中吞掉通知、subset__limit=0/subset__offset=0因真值判断被丢弃、snakeToCamel多下划线列名碰撞、SnapshotTracker反向索引残留等缺陷; - 静态分析:在 static-analysis.test.ts 中对源码做 AST 检查,覆盖"无界重试循环、非条件 409 cache-buster、尾位置 await、错误路径
#publish调用"等规则,从源头阻断回归。
此外,SPEC.md 的 "Bidirectional Enforcement Checklist" 用两张表把"文档 → 代码"(每条不变量/约束由哪种测试强制执行)与"代码 → 文档"(每个测试文件对应规范哪一节)双向对齐,形成可审计的闭环。
十一、Shape 通知语义与数据一致性
SPEC.md 还单独定义了Shape类的通知语义(与 ShapeStream 状态机分离):
- N1:
syncing状态下到达的数据消息会应用到#data但不触发通知;首次订阅者通知发生在up-to-date控制消息使状态从syncing转为up-to-date时。原因是同步服务可能返回不含 up-to-date 的响应(如offset === -1的初始响应),若此时通知订阅者,他们会看到部分视图且流的lastSyncedAt()仍为undefined; - N2:一旦进入
up-to-date,任何数据消息都触发通知并回到syncing,直到下一个 up-to-date——这保证了[up-to-date, insert]同一批次场景下 insert 能正确触发通知。
v1.5.15 明确修复了"Shape 在syncing期间收到数据消息也通知订阅者"的违规行为,并让must-refetch不再触发一次中间的空行通知——状态直接回到syncing,订阅者在下一个 up-to-date 收到轮换后的完整状态,与仓库中长期存在的should resync from scratch on a shape rotation集成测试(client.test.ts)保持一致。v1.5.27(最新补丁)则修复了重放(replay)期间抑制重复 up-to-date 通知时误丢数据消息的问题,与 SPEC 的 C9 约束遥相呼应。
十二、其他值得注意的演进
- wire protocol:v1.0.0 从
offset演进为显式lsn头——唯一合法 offset 是响应头中携带的值;v0.7.0 将shape_id查询参数更名为handle;v0.6.0 移除自定义 HTTP 头的x-前缀。当前协议头常量见 constants.ts:electric-handle、electric-offset、electric-cursor、electric-schema、electric-up-to-date、electric-snapshot; - 类型解析:v0.2.x 起逐步把 int2/int4/int8/float4/float8/bool/json 及数组解析为 JS 原生值,v0.3.x 支持 null 可空性,v1.0.7 修复文本
"NULL"被误解析为NULL; - move-in 事件:v1.5.14 为客户端增加 move-in 事件支持,
MoveOutPattern更名为MovePattern(保留废弃别名),EventMessage同时接受move-out与move-in,ChangeMessage头新增active_conditions字段(对应 types.ts 中的MoveTag复合标签语义); - 子查询:v1.5.25 起 Shape WHERE 子句中的子查询正式 GA,
allow_subqueries与tagged_subqueries两个特性开关被移除,不再需要设置ELECTRIC_FEATURE_FLAGS; - HTTP 警告:v1.5.17 在浏览器环境使用 HTTP URL 时输出警告——HTTP/1.1 下浏览器对同一主机仅允许 6 个并发连接,可能拖慢流甚至冻结应用,可用
warnOnHttp: false关闭; - TanStack Intent:v1.5.12 附带 9 个面向 AI Agent 的 TanStack Intent skills(shapes、proxy auth、schema design、debugging、deployment 等),位于 skills 目录。
结语
从 CHANGELOG 可以清晰看到,@electric-sql/client的演进主线始终围绕三个目标:协议正确性(状态机与通知语义的形式化)、生产环境健壮性(对抗错误缓存、挂死 fetch 与网络切换)与多运行时适配(浏览器、React Native、Node/Bun/Deno)。而 SPEC.md、状态机 DSL 与多层属性测试构成的"规范—测试—实现"三角,使其在每个新版本中都能把复杂度保持在可控范围。对于希望深入 Electric 同步协议或借鉴实时客户端工程实践的开发者,这份 CHANGELOG 连同 README 与源码,是一份难得的完整教材。
【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考