【免费下载链接】emulate
Local API emulation for CI and no-network sandboxes
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() |
| 状态结构会随版本演进 | 版本化快照 +stateVersion | snapshot()/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)值得新手留意:
- 临时文件 + rename:先写入
*.tmp再原子重命名,避免写一半的文件被读取; - 目录权限 0600:状态文件常含模拟密钥,默认只对属主可读写;
initialize原子创建或读取:用硬链接(link)保证并发的首次启动只落一份初始数据,已有文件则直接读取现值。
相关边界条件(父目录自动创建、临时文件清理、并发初始化)都有针对性测试覆盖,可阅读 persistence.test.ts 学习验收标准。⚡
进阶:编写 KV 存储适配器
persistence也可以传入任意实现了 PersistenceAdapter 接口的对象,接口只有三个异步方法:
load()→ 读取状态字符串,不存在时返回nullsave(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
相关推荐
为什么选择Gridster.js?探索这款jQuery插件的独特优势
为什么选择Gridster.js?探索这款jQuery插件的独特优势 Gridster.js是一个功能强大的jQuery插件,专门用于创建直观的可拖拽网格布局系
GitHub_Trending/chef5/chef快照管理:snapshot.client.ts与状态持久化方案
GitHub_Trending/chef5/chef快照管理:snapshot.client.ts与状态持久化方案 你是否曾因浏览器崩溃丢失数小时的开发成果?是
人工智能AI 应用AI Agent大模型代码生成前端后端构建零信任认证体系:Better Auth环境变量架构设计与安全实践
构建零信任认证体系:Better Auth环境变量架构设计与安全实践 在现代微服务架构和云原生环境中,认证系统的安全性直接关系到整个应用生态的稳定。Better
认证鉴权后端身份认证
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考