☰
emulate状态持久化实战:KV适配器、filePersistence与版本化快照快速上手
2026/10/11 19:55:39 网站建设 项目流程

【免费下载链接】emulate

Local API emulation for CI and no-network sandboxes

项目地址:https://gitcode.com/gh_mirrors/emul/emulate
点击查看免费下载

emulate 是一个本地 API 模拟(local API emulation)工具,帮助开发者在 CI 和无网络沙箱中模拟 GitHub、Stripe、Slack 等服务的接口。它的状态默认保存在内存中,重启即清空——而emulate 状态持久化机制让模拟数据在重启后依然保留。本文带你快速上手三种持久化方案:filePersistence本地文件适配器、KV 存储适配器,以及带stateVersion的版本化快照。

为什么模拟状态默认是"易失"的?

emulate 的设计哲学是"快、可重置、适合 CI":全部状态由内存中的Store持有,启动时从 seed 配置填充,测试结束随时可重置。这意味着每次冷启动都会丢失上一轮运行产生的数据——比如 OAuth 回调创建的会话、下单产生的订单记录。

架构说明可参考:apps/web/app/docs/architecture/page.mdx

当你的场景需要"像真实服务一样记住状态"(本地联调、跨重启复现 Bug),就需要开启持久化。💾

场景推荐方式关键 API
CLI 启动自定义 API,保存到本地文件配置中直接写文件路径persistence: './.emulate/xxx.json'
编程式 / Next.js / Nuxt 嵌入内置文件适配器或自定义 KV 适配器filePersistence(path)、load()/save()
状态结构会随版本演进版本化快照 +stateVersionsnapshot()/restore()

一键上手:CLI 里用文件路径开启持久化

对自定义 API,只要在 emulate.config.ts 的服务条目中加上一个persistence文件路径即可(相对路径会基于配置文件所在目录解析,见 config-loader.ts):

services: { inventory: { emulator: inventory, persistence: './.emulate/inventory.json', }, }

运行时的语义非常清晰:

  • 冷启动:自动load()恢复已保存的状态,seed 仅作为 reset 基线;
  • 变更之后:每次状态被修改(包括"会改状态的读请求"和流式响应结束/取消时)都会序列化save(),保存操作经过串行化,不会写坏文件;
  • 重置与关闭:reset()、restore()和受控关闭同样会持久化当前状态;
  • 无跨进程锁:同一状态仍由单进程持有,多进程场景需自行规避。

完整生命周期表格见官方文档 custom-apis 指南,完整可运行示例在 examples/custom-api/emulate.config.ts 与 examples/custom-api/emulators/inventory.ts。

编程式接入:filePersistence 的原子写细节

在代码中启动模拟器(测试、Next.js/Nuxt 适配器)时,@emulators/core内置了filePersistence,一个路径就能获得生产级的文件适配器:

import { filePersistence } from '@emulators/core' persistence: filePersistence('.emulate/state.json'),

它的实现(persistence.ts)值得新手留意:

  1. 临时文件 + rename:先写入*.tmp再原子重命名,避免写一半的文件被读取;
  2. 目录权限 0600:状态文件常含模拟密钥,默认只对属主可读写;
  3. initialize原子创建或读取:用硬链接(link)保证并发的首次启动只落一份初始数据,已有文件则直接读取现值。

相关边界条件(父目录自动创建、临时文件清理、并发初始化)都有针对性测试覆盖,可阅读 persistence.test.ts 学习验收标准。⚡

进阶:编写 KV 存储适配器

persistence也可以传入任意实现了 PersistenceAdapter 接口的对象,接口只有三个异步方法:

  • load()→ 读取状态字符串,不存在时返回null
  • save(data)→ 写入状态字符串
  • initialize?(data)→ 原子地"创建或读取",供需要生成身份信息的内置服务使用

Next.js 接入示例(对接 KV 数据库,见 README 持久化章节):

const kvAdapter = { async load() { return await kv.get('emulate-state') }, async save(data: string) { await kv.set('emulate-state', data) }, } export const { GET, POST } = createEmulateHandler({ services: { github: { emulator: github } }, persistence: kvAdapter, })

Nuxt 接入可改用useStorage,示例见 README Nuxt 持久化章节,适配器用法分别在 adapter-next/README.md 与 adapter-nuxt/README.md。CLI 路径下,字符串会被自动包装成filePersistence,并在保存前做一层"有变更才写"的缓冲,见 project-runner.ts。

版本化快照:stateVersion 让状态可以"升级"

内置服务(GitHub、Stripe 等)的状态由Store管理,snapshot()会导出所有集合与键值数据,Map/Set等特殊类型会被安全序列化/反序列化(store.ts、serialize 逻辑),恢复时还会删除快照中不存在的集合。

自定义 API 的快照则带有三件套元数据(custom.ts):

  • formatVersion:快照格式版本
  • definition:属于哪个模拟器定义
  • stateVersion:状态结构的版本,在定义中声明,修改持久化形状时递增

恢复时若stateVersion不匹配,emulate 会显式报错而不是静默产生脏数据:Incompatible snapshot for xxx. Supply a matching stateVersion or migrate the saved snapshot.(校验逻辑)。快照本身是脱离副本,restore()只替换当前状态,不破坏原始 reset 基线——这正是"版本化快照"的价值:数据结构变更时,你可以先snapshot()导出、手动迁移、再恢复,而不会丢失现场。🔁

最佳实践清单

  • ✅seed 是基线不是初始值:有持久化文件后,启动时恢复的是已保存状态;seed 只作为 reset 的回退基线。
  • ✅watch 重载会重建基线:成功的 watch 重载会捕获新基线并重置本轮运行,持久化文件不受影响。
  • ✅快照里含生成的密钥:内置服务的持久化快照会包含生成的身份密钥,请把.emulate/目录加入.gitignore并保持后端私有。
  • ⚠️无跨进程锁:load/save不提供多进程互斥,同一实例只应被一个进程使用。
  • ⚠️后台变更不在自动边界内:任意后台任务做的状态变更不自动落盘,请在请求处理或显式保存点完成变更。

相关资源

  • 官方文档(持久化与生命周期):apps/web/app/docs/custom-apis/page.mdx
  • 架构总览(内存状态与插件系统):apps/web/app/docs/architecture/page.mdx
  • 内置文件适配器源码:packages/@emulators/core/src/persistence.ts
  • 核心 Store 快照实现:packages/@emulators/core/src/store.ts
  • 完整自定义 API 示例:examples/custom-api/
  • 持久化行为测试:packages/@emulators/core/src/tests/persistence.test.ts

【免费下载链接】emulate

Local API emulation for CI and no-network sandboxes

项目地址:https://gitcode.com/gh_mirrors/emul/emulate
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询