十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

@electric-sql/client 演进全解析:ShapeStream 状态机、缓存失效防御与错误重试体系

@electric-sql/client 演进全解析:ShapeStream 状态机、缓存失效防御与错误重试体系 electric-sql/client 演进全解析ShapeStream 状态机、缓存失效防御与错误重试体系【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric本篇文章以electric-sql/clientElectric 官方 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列子集须包含主键、whereWHERE 子句、params位置参数$1/$2的取值、replicadefault只发送变更列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说明FetchingInitialStateinitial尚无数据等待首个响应FetchingSyncingStatesyncing收到首个响应追赶最新位置FetchingStaleRetryStatestale-retry响应过期携带 cache-buster 重试ActiveLiveStatelive已追上最新位置实时流式更新ActiveReplayingStatereplaying恢复后从缓存重新抓取DelegatePausedStatepaused暂停委托 previousStateDelegateErrorStateerror失败委托 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 事件迁移真值表类型为RecordShapeStreamStateKind, RecordEventType, ExpectedBehavior——没有Partial因此 TypeScript 在编译期就强制完整性见 state-transition-table.ts。3.3 关键不变量InvariantsSPEC.md 定义了 13 条不变量I0–I12其中几条对理解客户端行为至关重要I1isUpToDate true当且仅当 LiveState 在委托链上即状态本身是 LiveState或经由 Paused/Error 的previousState可达I2迁移总是产生新对象绝不原地修改字段全部readonlyno-op 迁移才返回thisI3state.pause().resume() state引用相等作为代数性质测试覆盖全部 7 个状态I4state.toErrorState(err).retry() state错误重试保持身份I10任何状态的markMustRefetch(handle)都重置为offset -1、schema undefined的 InitialStateI12不允许同类委托状态嵌套——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参数InvalidSignalErrorsignal不是合法的AbortSignal实例ReservedParamError自定义参数与协议保留参数名冲突ShapeStreamAlreadyRunningError流已在运行InvalidShapeOptionsError选项校验失败。运行期错误FetchError携带status、text、json、headers、url的 HTTP 错误FetchBackoffAbortError等待退避期间被 AbortSignal 中止v1.5.21 起从包入口正式导出MissingShapeHandleError非初始抓取offset -1却缺少 shape handleParserNullValueError非空列收到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.8e172d4b默认退避参数向行业标准gRPC、AWS靠拢——initialDelay100ms → 1smultiplier1.3 → 2maxDelay60s → 32s5 次重试即到达上限此前约需 25 次v1.5.15/v1.5.18将onError连续重试上限绑定为 50 次防止永远返回重试指令的损坏 handler 导致无限重试与内存增长计数器在成功数据非空消息批次或 204时重置v1.5.18 进一步为onError触发的重试加入带抖动jitter的指数退避v1.5.23新增请求看门狗request watchdogliveRequestTimeoutMs默认 45s设为false可关闭超时后以内部原因live-request-timeout中止并重启请求循环即使平台 fetch promise 永不 settle 也能恢复解决了移动端网络切换或生命周期转换时 fetch 挂死的问题。SPEC.md 的Client Fetch Loop Paths章节枚举了 client.ts 中六个回环请求点L1–L6并强调任何回环路径都必须改变下一个请求 URL否则会死循环其中 L5onError重试由#maxConsecutiveErrorRetries50与中止感知的全抖动退避共同守护。五、缓存失效与 CDN/代理防御一条持续三个大版本的主线CHANGELOG 中最反复出现的主题是对抗错误缓存的 CDN/代理。这类问题的共同诱因是代理或浏览器 HTTP 缓存返回了带过期 shape handle 的旧响应客户端若接受它就会在错误的位置继续推进 offset甚至陷入高速死循环。5.1 从expired_handle到cache-busterv1.0.10409 时在 localStorage 记录过期 handle后续请求附带expired_handle参数避免重复 409 并降低加载延迟见 expired-shapes-cache.tsv1.3.1当代理忽略expired_handle仍返回含旧 handle 的缓存时客户端不再接受与过期缓存匹配的 handleprefetch 也不抓取 handle 等于expired_handle的下一个 chunk并输出 console 警告帮助排查代理配置v1.5.11修复代理剥离 handle 头时 409 重试 URL 无限膨胀的问题——不再对 handle 追加-next改为随机cache-buster查询参数保证重试 URL 唯一v1.5.13CDN 提供陈旧缓存导致每秒钟数百次同 URL 重试的问题陈旧响应一律进入stale-retry并附加 cache-buster同时加入重复 URL 守卫与状态机警告堆栈v1.5.15409 响应无条件新建 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.15690e25a当陈旧缓存重试 3 次耗尽后客户端清除 localStorage 中的过期条目并不带expired_handle重试一次。由于服务端从不复用 handleSPEC 的 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控制消息定界offsetnow特殊值客户端收到立即的 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 414types.ts 的SubsetParams允许按请求覆盖subsetMethodGET默认、POST推荐并注明 Electric 2.0 将弃用 GETv1.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.20requestSnapshot()现在保证在注入的快照批次已投递给订阅者含异步与可重入订阅者路径之后才 resolvev1.5.9修复changes_only模式下暂停/恢复后唤醒检测定时器未重新武装的问题v1.5.7858e13d修复按需模式offset: now冷启动requestSnapshot()后流不推进 offset/handle 的问题——现在流会从快照位置继续而不是停留在陈旧的nowoffset避免快照与下一次实时轮询之间的更新被遗漏。七、列名映射snake_case ↔ camelCase 的双向通道v1.2.0 引入columnMapper选项encode应用名 → 数据库名作用于 WHERE 子句与查询参数与decode数据库名 → 应用名作用于结果列名双向映射并内置snakeCamelMapper()自动转换与createColumnMapper()自定义映射。旧有的transformer不再承担列改名职责但仍可用于值变换如加密。实现细节集中在 column-mapper.tssnakeToCamel/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.5c59000f实验性 SSE 支持v1.1.037242f6废弃experimental_live_sse引入正式的live_sse标志以 Server-Sent Events 发送实时更新并让 409 must-refetch 在 SSE 与长轮询两条路径上都被正确处理live_sse随之加入ELECTRIC_PROTOCOL_QUERY_PARAMSv1.1.3v1.5.4186b8f8正确打包修补过的fetch-event-source。此前 liveSse 模式引入的fetch-event-source比内置EventSource功能更强带有对 document/window 存在性的假设与 abort 相关缺陷虽然源码打了补丁但构建产物未包含补丁。仓库根目录的 patches/microsoft__fetch-event-source.patch 正是这一修补的存档。SPEC.md 还规定了 SSE 专属约束C6SSE 的 up-to-date 消息通过upToDateOffset更新 offset非 SSE 的 up-to-date 消息保留现有 offsetC8SSE 状态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 改为 200204 仅表示无新内容v1.0.3 保留对旧 204 的向后兼容v1.5.6 进一步修复了对旧服务器的 204 处理——此前 204 只更新lastSyncedAt而不进入 live 状态isUpToDate永远为 false、livetrue永远不附加到 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 覆盖另一个的 pausev1.5.3 还修复了恢复会话尚无 schema 时收到陈旧缓存响应导致schema!解引用崩溃的问题v1.5.22修复不保留AbortSignal.reason的运行时部分 React Native fetch/AbortController 实现中 wake 重连失败的问题v1.5.23自动探测 React NativeAppState应用进入后台时暂停请求、回到前台后以非 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 时发布原始响应体导致订阅者保留陈旧数据等问题微目标 PBTmicro-target PBT在 pbt-micro.test.ts 中针对单一函数验证修复了canonicalShapeKey重复参数折叠、Shape#process在[up-to-date, insert]批次中吞掉通知、subset__limit0/subset__offset0因真值判断被丢弃、snakeToCamel多下划线列名碰撞、SnapshotTracker反向索引残留等缺陷静态分析在 static-analysis.test.ts 中对源码做 AST 检查覆盖无界重试循环、非条件 409 cache-buster、尾位置 await、错误路径#publish调用等规则从源头阻断回归。此外SPEC.md 的 Bidirectional Enforcement Checklist 用两张表把文档 → 代码每条不变量/约束由哪种测试强制执行与代码 → 文档每个测试文件对应规范哪一节双向对齐形成可审计的闭环。十一、Shape 通知语义与数据一致性SPEC.md 还单独定义了Shape类的通知语义与 ShapeStream 状态机分离N1syncing状态下到达的数据消息会应用到#data但不触发通知首次订阅者通知发生在up-to-date控制消息使状态从syncing转为up-to-date时。原因是同步服务可能返回不含 up-to-date 的响应如offset -1的初始响应若此时通知订阅者他们会看到部分视图且流的lastSyncedAt()仍为undefinedN2一旦进入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 protocolv1.0.0 从offset演进为显式lsn头——唯一合法 offset 是响应头中携带的值v0.7.0 将shape_id查询参数更名为handlev0.6.0 移除自定义 HTTP 头的x-前缀。当前协议头常量见 constants.tselectric-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被误解析为NULLmove-in 事件v1.5.14 为客户端增加 move-in 事件支持MoveOutPattern更名为MovePattern保留废弃别名EventMessage同时接受move-out与move-inChangeMessage头新增active_conditions字段对应 types.ts 中的MoveTag复合标签语义子查询v1.5.25 起 Shape WHERE 子句中的子查询正式 GAallow_subqueries与tagged_subqueries两个特性开关被移除不再需要设置ELECTRIC_FEATURE_FLAGSHTTP 警告v1.5.17 在浏览器环境使用 HTTP URL 时输出警告——HTTP/1.1 下浏览器对同一主机仅允许 6 个并发连接可能拖慢流甚至冻结应用可用warnOnHttp: false关闭TanStack Intentv1.5.12 附带 9 个面向 AI Agent 的 TanStack Intent skillsshapes、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),仅供参考
返回列表