@omni-api/plugin-idempotency
v0.0.2
Published
Idempotency-Key middleware for OmniAPI: prevent duplicate writes from client retries
Downloads
250
Readme
@omni/plugin-idempotency
OmniAPI 幂等键中间件。让客户端重试不会重复扣款 / 重复下单。
用法
Procedure 上挂中间件
import { idempotent } from '@omni/plugin-idempotency';
defineProcedure({
name: 'order.create',
middleware: [auth(), idempotent({ ttlMs: 5 * 60_000 })],
// ...
});HTTP Adapter 注入 key
HTTP 客户端通过 Idempotency-Key: <random-uuid> header 传 key。Adapter 把它写到 ctx.state.idempotencyKey:
createHttpAdapter({
registry,
// 在 authenticate 之后或自定义 hook 中注入:
fastify: {
onRequest: [(req, _reply, done) => {
const k = req.headers['idempotency-key'];
if (typeof k === 'string') (req as any).idempotencyKey = k;
done();
}],
},
});
// 然后在你的 buildHttpContext wrapper 里把它转到 ctx.state客户端
curl -X POST /orders \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"sku":"SKU001","qty":2}'
# 网络抖动重试:用同一个 key 再试
curl -X POST /orders -H "Idempotency-Key: <same>" ... # → 返回上次结果,不重复创建行为
| 场景 | 结果 |
|---|---|
| 无 key(默认) | 放行,不保护 |
| 无 key + required: true | 409 IDEMPOTENCY_KEY_REQUIRED |
| 同 key 第二次(已完成) | 直接返回缓存结果 |
| 同 key 第二次(处理中) | 409 IDEMPOTENCY_IN_PROGRESS |
| 同 key 并发竞争 | 一个赢,其他 409 IDEMPOTENCY_RACE |
| handler 抛错 | 释放 key,可重试 |
后端
createMemoryStore():单进程默认- 自己实现
IdempotencyStore接口接 Redis(用SET key NX PX ttl实现原子 acquire)
注意
- TTL 默认 5 分钟。重试窗口大于 TTL 后会"二次执行" —— 这是约定权衡(避免 key 永驻存储)。
- 不同 procedure 的同名 key 自动隔离。
