@runko/persist-mongo 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ const db = client.db("myapp");
17
17
 
18
18
  await migrate(db); // 建索引,幂等
19
19
 
20
- const runtime = createAgentRuntime(agent, { persistence: mongoPersistence(db) });
20
+ const runtime = createAgentRuntime({ agent, prepareTurn, persistence: mongoPersistence(db) });
21
21
  ```
22
22
 
23
23
  **吃 `Db` 而不是 `MongoClient`**:选哪个 database 是你的决定(多租户可能一租户一个 db),
@@ -35,7 +35,7 @@ SQLite / PostgreSQL / MySQL 那三个包底下共用 `@runko/persist-kysely`。K
35
35
  | 准则 | 结论 |
36
36
  | --- | --- |
37
37
  | 不假设事务能跨接口 | ✅ 需要原子的只有 `dequeue`,Mongo 的 `findOneAndDelete` 原生原子 |
38
- | 不要求 CAS | ✅ 一次都没用上 |
38
+ | 不要求 CAS | ✅ 一次都没用上(租约版仲裁用 CAS,但那是**另一个接口**) |
39
39
  | 不支持跨会话查询 | ✅ 每个查询都以 `conversationId` 打头,正好是索引前缀 |
40
40
 
41
41
  三条全成立——接口**没有漏掉关系型假设**。
@@ -46,7 +46,10 @@ Mongo 的 `findOneAndDelete({...}, {sort})` **是数据库直接给的**。
46
46
 
47
47
  ## 它存什么
48
48
 
49
- 三个集合:`agent_ledger`(账本)· `agent_decisions`(人工裁决留底)· `agent_queue`(待发队列)。
49
+ 六个集合:`agent_ledger`(账本)· `agent_decisions`(人工裁决留底)· `agent_queue`(待发队列)·
50
+ `agent_tool_tails`([工具收尾](../../docs/terms.md))· `agent_leases`(租约,**只有多副本才会用到**,
51
+ [交接预留 / 待接手](../../docs/terms.md)也存在这份文档上,不另开集合)·
52
+ `agent_nodes`([节点登记表](../../docs/terms.md),同样只有多副本才用到)。
50
53
 
51
54
  集合名固定,不提供前缀开关——**要隔离请用另一个 database**,那在 Mongo 里是一等公民,
52
55
  比集合名前缀干净得多。
@@ -57,6 +60,42 @@ Mongo 的 `findOneAndDelete({...}, {sort})` **是数据库直接给的**。
57
60
  **它不存你的东西。** runko 只认一个不透明的 `conversationId`,会话叫什么、属于谁,
58
61
  全归你自己存。
59
62
 
63
+ ## 多副本怎么配
64
+
65
+ 跑不止一个进程时,光换持久化不够——还得把[归属仲裁机制](../../docs/terms.md)换成**租约版**,
66
+ 否则同一份对话会被两个副本同时推进。同一个 `Db` 装两次就行:
67
+
68
+ ```ts
69
+ createAgentRuntime({
70
+ agent,
71
+ prepareTurn,
72
+ persistence: mongoPersistence(db),
73
+ arbitration: mongoArbitration(db, { holder: process.env.RUNKO_NODE_URL }),
74
+ // 想让[指定交接](../../docs/terms.md)挑到接手副本,再装这一行——**可选**,
75
+ // 不装的话交权时挑不到接手节点,对话一律标成待接手。
76
+ handover: {
77
+ node: process.env.RUNKO_NODE_URL, // 与上面的 holder 相同
78
+ nodes: mongoNodeRegistry(db),
79
+ // 请对方节点执行 runtime.takeOver(ids)——HTTP、RPC 都行,由你来写
80
+ requestTakeover: (node, conversationIds) => askPeerToTakeOver(node, conversationIds),
81
+ },
82
+ });
83
+ ```
84
+
85
+ `holder` 是**本副本的可达地址**,框架原样存、原样传、不解释它:别的副本抢不到归属时会拿到它,
86
+ 由接入代码决定把请求转给谁——**转发是你写的,不是框架做的**。
87
+
88
+ `migrate()` 仍然只调一次,租约那个索引已经在里面了。
89
+
90
+ 单进程别装它:`@runko/agent` 内置的内存版更快,[独占](../../docs/terms.md)还是**真保证**,
91
+ 而租约版只能做到**尽力 + 可检测**。完整取舍见
92
+ [多副本部署](../../docs/host/node/tech/multi-replica.md)。
93
+
94
+ > **这一份是重写的,不是薄壳。** SQL 那三个包的租约版底下共用 `@runko/persist-kysely`;
95
+ > 本包直接落在 Mongo 驱动上,条件写只要**一次往返**(`findOneAndUpdate` 直接返回更新后的
96
+ > 文档),不必像 SQL 版那样「条件 UPDATE + 读回来比对令牌」两步——那两步是为了绕开 MySQL
97
+ > 把「匹配到了但值没变」也报成 0 行。
98
+
60
99
  ## 要求 MongoDB 5.0+
61
100
 
62
101
  工具入参是**任意 JSON**,键里可能有 `.` 或 `$`——5.0 之前的 MongoDB 不接受这种字段名。
@@ -83,7 +122,8 @@ Mongo 在「匹配到但新值与旧值完全相同」时报 `matched=1, modifie
83
122
  RUNKO_TEST_MONGO_URL=mongodb://127.0.0.1:27018 pnpm --filter @runko/persist-mongo test
84
123
  ```
85
124
 
86
- 跟其余三个持久化包**跑同一套一致性用例**。不给连接串时整档跳过并说明原因——
125
+ 跟其余三个持久化包**跑同一套一致性用例**,租约版另跑[仲裁一致性套件](../conformance/README.md)的
126
+ 四组(通用 / 多节点 / 超时接管 / 报 takeover)。不给连接串时整档跳过并说明原因——
87
127
  Mongo 没有 pglite 那样的进程内替身(`mongodb-memory-server` 是下载一个真 mongod 来跑)。
88
128
 
89
129
  ## 不是只有这一条路