@agentunion/fastaun 0.5.0 → 0.5.1
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/CHANGELOG.md +59 -0
- package/_packed_docs/CHANGELOG-validators.md +144 -0
- package/_packed_docs/CHANGELOG.md +59 -0
- package/_packed_docs/INDEX.md +198 -181
- package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -1
- package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
- package/_packed_docs/protocol/README.md +2 -1
- package/_packed_docs/protocol/index.md +8 -3
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
- package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
- package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
- package/_packed_docs/sdk/INDEX.md +14 -14
- package/dist/client/delivery.d.ts +9 -0
- package/dist/client/delivery.js +157 -24
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +33 -11
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.js +127 -40
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +6 -1
- package/dist/client/v2-e2ee.js +199 -130
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +8 -1
- package/dist/client.js +227 -31
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +8 -0
- package/dist/collab/client.js +12 -0
- package/dist/collab/client.js.map +1 -1
- package/dist/errors.d.ts +0 -20
- package/dist/errors.js +8 -53
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +9 -4
- package/dist/facades.js +137 -15
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +26 -9
- package/dist/group-fs.js +84 -84
- package/dist/group-fs.js.map +1 -1
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.js +41 -63
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +1 -0
- package/dist/keystore/aid-db.js +43 -43
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/local-identity-store.d.ts +16 -0
- package/dist/keystore/local-identity-store.js +190 -18
- package/dist/keystore/local-identity-store.js.map +1 -1
- package/dist/keystore/local-token-store.d.ts +0 -13
- package/dist/keystore/local-token-store.js +0 -105
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +9 -1
- package/dist/storage/lowlevel.js +12 -1
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/types.d.ts +0 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/storage/vfs.d.ts +22 -3
- package/dist/storage/vfs.js +89 -54
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +336 -49
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +52 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +38 -0
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.js +53 -23
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.js +4 -2
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +32 -0
- package/dist/validators.js +128 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
### POSIX VFS 方法
|
|
61
61
|
|
|
62
|
-
> 在对象存储之上提供 Linux 文件系统语义(`ls/find/df/stat/mkdir/rm/mv/cp/mount`)。节点类型:`file` / `dir` / `symlink` / `mount`。
|
|
62
|
+
> 在对象存储之上提供 Linux 文件系统语义(`ls/find/df/stat/lstat/mkdir/touch/rm/mv/cp/mount`)。节点类型:`file` / `dir` / `symlink` / `mount`。
|
|
63
63
|
|
|
64
64
|
| 方法 | 说明 |
|
|
65
65
|
|------|------|
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
| [storage.fs.stat](#storagefsstat) | 查节点(stat,跟随末级软链) |
|
|
70
70
|
| [storage.fs.lstat](#storagefslstat) | 查节点(lstat,不跟随软链) |
|
|
71
71
|
| [storage.fs.mkdir](#storagefsmkdir) | 建目录(mkdir) |
|
|
72
|
+
| [storage.fs.touch](#storagefstouch) | 创建空文件或更新时间戳(touch) |
|
|
72
73
|
| [storage.fs.remove](#storagefsremove) | 删除文件/目录/软链(rm) |
|
|
73
74
|
| [storage.fs.rename](#storagefsrename) | 同 owner 内移动/改名(mv) |
|
|
74
75
|
| [storage.fs.copy](#storagefscopy) | 复制对象或软链(cp) |
|
|
@@ -91,6 +92,8 @@
|
|
|
91
92
|
### ACL / 权限方法
|
|
92
93
|
|
|
93
94
|
> 统一权限求值顺序(硬顺序):公开位 → token → ACL(最近祖先前缀)→ 角色 → owner → 拒绝。
|
|
95
|
+
>
|
|
96
|
+
> AID storage 的 `storage.set_acl/remove_acl` 面向具体 AID,当前主要用于写/删除授权;直接读授权不通过 ACL 下发,读访问应使用 `storage.create_share_link` / `storage.get_by_share`,撤销分享使用 `storage.revoke_share_link`。`role:*` 伪主体只允许可信 group 内部门面管理,客户端不得直接对 `group_aid` 空间设置角色 ACL;群自有区 admin 写授权使用 `group.fs.set_acl/remove_acl`。
|
|
94
97
|
|
|
95
98
|
| 方法 | 说明 |
|
|
96
99
|
|------|------|
|
|
@@ -113,15 +116,15 @@
|
|
|
113
116
|
|
|
114
117
|
---
|
|
115
118
|
|
|
116
|
-
> `object_key` 当前仅支持 ASCII 安全字符集合 `[A-Za-z0-9._/-]`,且不允许空路径段、`..`、反斜杠转义后的非法段。
|
|
117
|
-
|
|
118
|
-
## SDK 封装状态
|
|
119
|
-
|
|
120
|
-
Python / Go / TypeScript / JavaScript SDK 均提供 storage low-level 与 VFS 门面。普通应用优先使用 SDK VFS:`write_bytes` / `upload_file` 会先 `check_upload`,小对象走 `put_object`,大对象走 `create_upload_session` → HTTP PUT → `complete_upload`,秒传路径用 `complete_upload(skip_blob=true)`;`read_bytes` / `download_file` 优先尝试 inline `get_object`,超限时回退 `create_download_ticket`。需要精确控制 ACL、token、软链、卷、批量操作或 URL 字段时,再直接调用本手册中的 `storage.*` RPC。
|
|
121
|
-
|
|
122
|
-
## storage.put_object
|
|
123
|
-
|
|
124
|
-
上传小对象(内容 base64 编码通过 RPC 传输)。
|
|
119
|
+
> `object_key` 当前仅支持 ASCII 安全字符集合 `[A-Za-z0-9._/-]`,且不允许空路径段、`..`、反斜杠转义后的非法段。
|
|
120
|
+
|
|
121
|
+
## SDK 封装状态
|
|
122
|
+
|
|
123
|
+
Python / Go / TypeScript / JavaScript SDK 均提供 storage low-level 与 VFS 门面。普通应用优先使用 SDK VFS:`write_bytes` / `upload_file` 会先 `check_upload`,小对象走 `put_object`,大对象走 `create_upload_session` → HTTP PUT → `complete_upload`,秒传路径用 `complete_upload(skip_blob=true)`;`read_bytes` / `download_file` 优先尝试 inline `get_object`,超限时回退 `create_download_ticket`;`touch` 直接封装 `storage.fs.touch`。需要精确控制 ACL、token、软链、卷、批量操作或 URL 字段时,再直接调用本手册中的 `storage.*` RPC。
|
|
124
|
+
|
|
125
|
+
## storage.put_object
|
|
126
|
+
|
|
127
|
+
上传小对象(内容 base64 编码通过 RPC 传输)。
|
|
125
128
|
|
|
126
129
|
### 参数
|
|
127
130
|
|
|
@@ -885,7 +888,7 @@ share_url = result["url"] # https://alice.agentid.pub/s/Ab3xK9mZ2q
|
|
|
885
888
|
|
|
886
889
|
## storage.fs.list
|
|
887
890
|
|
|
888
|
-
列目录(POSIX `ls`)。混合返回子目录/对象/软链/可用挂载点,排序 dir < file < symlink < mount。群 owner 路径回退到 Group FS 子节点。
|
|
891
|
+
列目录(POSIX `ls`)。混合返回子目录/对象/软链/可用挂载点,排序 dir < file < symlink < mount。群 owner 路径回退到 Group FS 子节点。
|
|
889
892
|
|
|
890
893
|
### 参数
|
|
891
894
|
|
|
@@ -915,7 +918,7 @@ share_url = result["url"] # https://alice.agentid.pub/s/Ab3xK9mZ2q
|
|
|
915
918
|
|
|
916
919
|
## storage.fs.find
|
|
917
920
|
|
|
918
|
-
递归查找(POSIX `find`),支持 name/type/size/mtime 过滤与分页。Group FS 与 `.collab` 注册表有回退。
|
|
921
|
+
递归查找(POSIX `find`),支持 name/type/size/mtime 过滤与分页。Group FS 与 `.collab` 注册表有回退。
|
|
919
922
|
|
|
920
923
|
### 参数
|
|
921
924
|
|
|
@@ -1014,6 +1017,36 @@ fs 目录节点视图。
|
|
|
1014
1017
|
|
|
1015
1018
|
---
|
|
1016
1019
|
|
|
1020
|
+
## storage.fs.touch
|
|
1021
|
+
|
|
1022
|
+
创建空文件或刷新已有节点的修改时间(POSIX `touch`)。已存在的文件、目录或软链会更新时间戳;不存在时默认创建 0 字节私有文件;`no_create=true` 时不存在不创建并返回 `{touched:false, created:false}`。
|
|
1023
|
+
|
|
1024
|
+
### 参数
|
|
1025
|
+
|
|
1026
|
+
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
|
1027
|
+
|------|------|------|------|------|
|
|
1028
|
+
| `path` | string | 是 | — | 目标路径 |
|
|
1029
|
+
| `owner_aid` | string | 否 | 当前用户 | 所有者 AID |
|
|
1030
|
+
| `bucket` | string | 否 | `"default"` | 存储桶 |
|
|
1031
|
+
| `parents` | boolean | 否 | `false` | 创建文件时是否递归创建父目录 |
|
|
1032
|
+
| `no_create` / `noCreate` | boolean | 否 | `false` | 目标不存在时不创建 |
|
|
1033
|
+
| `mtime` | integer | 否 | 当前时间 | Unix 秒或毫秒;小于 `10000000000` 按秒解释 |
|
|
1034
|
+
| `follow_symlinks` / `followSymlinks` | boolean | 否 | `false` | 目标为软链时是否跟随末级软链 |
|
|
1035
|
+
| `content_type` | string | 否 | `"application/octet-stream"` | 新建空文件的 MIME 类型 |
|
|
1036
|
+
| `metadata` | object | 否 | `{}` | 新建空文件的元数据 |
|
|
1037
|
+
| `expire_in_seconds` | integer | 否 | `0` | 新建空文件的过期秒数 |
|
|
1038
|
+
|
|
1039
|
+
### 响应
|
|
1040
|
+
|
|
1041
|
+
成功返回 fs 节点视图,并额外包含:
|
|
1042
|
+
|
|
1043
|
+
| 字段 | 类型 | 说明 |
|
|
1044
|
+
|------|------|------|
|
|
1045
|
+
| `touched` | boolean | 是否实际更新或创建 |
|
|
1046
|
+
| `created` | boolean | 是否新建了空文件 |
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1017
1050
|
## storage.fs.remove
|
|
1018
1051
|
|
|
1019
1052
|
删除文件/目录/软链(POSIX `rm`,目录用 `recursive`)。**拒绝删除挂载点**(须先 unmount)。
|
|
@@ -1107,6 +1140,8 @@ fs 目录节点视图。
|
|
|
1107
1140
|
| `mount` | object | 挂载视图 |
|
|
1108
1141
|
| `status` | string | `active`(直接生效)/ `pending`(待批准) |
|
|
1109
1142
|
|
|
1143
|
+
> `status` 字段仅在直接调用 lowlevel RPC(`fs_mount`/`FSMount`/`fsMount` 原始返回)时可见。四语言 SDK 的高层 VFS 门面(`vfs.mount()`/`StorageVFS.Mount()`/`vfs.mount()`)会把响应解析为通用 NodeView,该视图不保留顶层 `status` 字段;需要判断是否 `pending` 时请改用 lowlevel 接口读取原始响应,或检查服务端在 `pending` 场景下额外写入的 `request_id`。
|
|
1144
|
+
|
|
1110
1145
|
---
|
|
1111
1146
|
|
|
1112
1147
|
## storage.fs.approve
|
|
@@ -1219,7 +1254,7 @@ fs 目录节点视图。
|
|
|
1219
1254
|
|
|
1220
1255
|
## storage.atomic_repoint
|
|
1221
1256
|
|
|
1222
|
-
原子重指软链 target(乐观锁 CAS)。**collab commit / tag 并发正确性的底层核心**。
|
|
1257
|
+
原子重指软链 target(乐观锁 CAS)。**collab commit / tag 并发正确性的底层核心**。
|
|
1223
1258
|
|
|
1224
1259
|
### 参数
|
|
1225
1260
|
|
|
@@ -1280,14 +1315,16 @@ fs 目录节点视图。
|
|
|
1280
1315
|
|
|
1281
1316
|
## storage.set_acl
|
|
1282
1317
|
|
|
1283
|
-
授予路径前缀 ACL grant
|
|
1318
|
+
授予路径前缀 ACL grant。普通 AID storage 的 `grantee_aid` 必须是具体 AID,主要用于授予写/删除权限;子路径默认继承(最近祖先覆盖)。读取不通过 AID ACL 直接授权,应用应改用 `storage.create_share_link` 并在需要时用 `storage.revoke_share_link` 撤销。
|
|
1319
|
+
|
|
1320
|
+
`role:*` 伪主体只允许可信 group 内部门面写入。客户端不能直接对 `group_aid` 空间调用 `storage.set_acl` 设置 `role:admin`;群自有区角色写授权统一使用 `group.fs.set_acl`。
|
|
1284
1321
|
|
|
1285
1322
|
### 参数
|
|
1286
1323
|
|
|
1287
1324
|
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
|
1288
1325
|
|------|------|------|------|------|
|
|
1289
1326
|
| `path` | string | 是 | — | 授权路径前缀 |
|
|
1290
|
-
| `grantee_aid` | string | 是 | — | 被授权 AID
|
|
1327
|
+
| `grantee_aid` | string | 是 | — | 被授权 AID;`role:*` 仅限 group 内部调用 |
|
|
1291
1328
|
| `perms` | string | 是 | — | 权限位:`r`/`w`/`rw`/`rwx`(`rwx` 含删除) |
|
|
1292
1329
|
| `owner_aid` | string | 否 | 当前用户 | 所有者 AID |
|
|
1293
1330
|
| `bucket` | string | 否 | `"default"` | 存储桶 |
|
|
@@ -1302,7 +1339,7 @@ ACL 视图。
|
|
|
1302
1339
|
|
|
1303
1340
|
## storage.remove_acl
|
|
1304
1341
|
|
|
1305
|
-
移除路径 ACL
|
|
1342
|
+
移除路径 ACL 授权。对 AID storage,这是撤销写/删除授权的入口;读分享的撤销入口是 `storage.revoke_share_link`。群自有区角色写授权撤销使用 `group.fs.remove_acl`,不要由客户端直接调用 `storage.remove_acl` 操作 `role:*`。
|
|
1306
1343
|
|
|
1307
1344
|
### 参数
|
|
1308
1345
|
|
|
@@ -1339,7 +1376,7 @@ ACL 视图。
|
|
|
1339
1376
|
|
|
1340
1377
|
## storage.set_visibility
|
|
1341
1378
|
|
|
1342
|
-
切换对象或目录的公开/私有(软链不支持)。`allow_roles`
|
|
1379
|
+
切换对象或目录的公开/私有(软链不支持)。`allow_roles` 是低层兼容字段,不能作为群自有区 admin 写授权入口;群自有区角色写授权必须使用 `group.fs.set_acl/remove_acl`。
|
|
1343
1380
|
|
|
1344
1381
|
### 参数
|
|
1345
1382
|
|
|
@@ -1349,7 +1386,7 @@ ACL 视图。
|
|
|
1349
1386
|
| `visibility` | string | 是 | — | `public` / `private` |
|
|
1350
1387
|
| `owner_aid` | string | 否 | 当前用户 | 所有者 AID |
|
|
1351
1388
|
| `bucket` | string | 否 | `"default"` | 存储桶 |
|
|
1352
|
-
| `allow_roles` | array | 否 | — |
|
|
1389
|
+
| `allow_roles` | array | 否 | — | 低层兼容字段,仅表示可见性读角色;不能用于群自有区写授权 |
|
|
1353
1390
|
|
|
1354
1391
|
### 响应
|
|
1355
1392
|
|
|
@@ -1502,4 +1539,4 @@ token 列表(hash 摘要 + 元数据)。
|
|
|
1502
1539
|
| -32004 | 权限拒绝(requester 不是对象 owner,或非公开对象的读权限不足) |
|
|
1503
1540
|
| -32008 | 对象不存在(`ErrNotFound`) |
|
|
1504
1541
|
| -32009 | 版本冲突(`ErrVersionConflict`,`expected_version` 与实际版本不匹配) |
|
|
1505
|
-
| -32000 | 通用错误(参数校验失败、配额不足、base64 解码失败、文件大小超限等) |
|
|
1542
|
+
| -32000 | 通用错误(参数校验失败、配额不足、base64 解码失败、文件大小超限等) |
|
|
@@ -11,10 +11,10 @@ AUN SDK 文档位于 `docs/sdk/`,索引文件 `docs/sdk/INDEX.md` 分三层:
|
|
|
11
11
|
- `AUNClient`:认证、连接、状态机、事件和 RPC。
|
|
12
12
|
- `ServiceProxyClient`:Python SDK 的 Service Proxy provider 侧客户端,负责 Gateway 控制面注册和 proxy-server 数据面隧道注册。
|
|
13
13
|
|
|
14
|
-
业务 RPC 的底层参数见 `09-*-rpc-manual.md`,可通过 `client.call(method, params)` 调用;Storage、Collab、Group FS、Service Proxy 等具备高层门面的能力以对应专项文档为准。`message.send`、`message.thought.put`、`group.send`、`group.thought.put` 的业务 payload 见 `09-payload-reference.md`。在线轻量通知和跨域 federation notify 方案见 `Notify通知方案.md`。
|
|
15
|
-
|
|
16
|
-
Storage 的 SDK VFS、控制面/数据面分离、direct backend 上传下载、类 Linux 权限、mount/symlink 和服务端分层见 `AUN Storage架构设计.md`。
|
|
17
|
-
Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI、`cp/mv`
|
|
14
|
+
业务 RPC 的底层参数见 `09-*-rpc-manual.md`,可通过 `client.call(method, params)` 调用;Storage、Collab、Group FS、Service Proxy 等具备高层门面的能力以对应专项文档为准。`message.send`、`message.thought.put`、`group.send`、`group.thought.put` 的业务 payload 见 `09-payload-reference.md`。在线轻量通知和跨域 federation notify 方案见 `Notify通知方案.md`。
|
|
15
|
+
|
|
16
|
+
Storage 的 SDK VFS、控制面/数据面分离、direct backend 上传下载、类 Linux 权限、mount/symlink 和服务端分层见 `AUN Storage架构设计.md`。
|
|
17
|
+
Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI、`cp/mv` 语义、群自有区角色 ACL 授权/撤销/查询与 `group_aid` 签名、`parents` 语义、JS string 差异和 memberdata 服务端映射见 `09-group-rpc-manual.md`。Collab 的 `gc`、`reflog`、`revert`、`clone` 及版本化文档/标签语义见 `09-collab-rpc-manual.md`。
|
|
18
18
|
|
|
19
19
|
## 渐进式查阅流程
|
|
20
20
|
|
|
@@ -29,10 +29,10 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
|
|
|
29
29
|
- 身份与认证:AIDStore / AID / 注册 / 加载 / 证书
|
|
30
30
|
- 连接与状态:AUNClient / 九态状态机 / Gateway / 重连
|
|
31
31
|
- E2EE:默认加密、ProtectedHeaders、P2P / Group V2
|
|
32
|
-
- RPC 与事件:`client.call()`、`client.on()`、`client.notify()`、RPC 手册
|
|
33
|
-
- Storage:SDK VFS、direct backend 数据面、`storage.*` low-level RPC、类 Linux 权限、mount/symlink、Storage Service 分层
|
|
34
|
-
- 群文件系统:`group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv`
|
|
35
|
-
- 协作(collab):`collab.*` 版本化文档、乐观锁 CAS commit、三方合并、标签、GC、reflog、revert(见 `09-collab-rpc-manual.md`)
|
|
32
|
+
- RPC 与事件:`client.call()`、`client.on()`、`client.notify()`、RPC 手册
|
|
33
|
+
- Storage:SDK VFS、direct backend 数据面、`storage.*` low-level RPC、类 Linux 权限、mount/symlink、Storage Service 分层
|
|
34
|
+
- 群文件系统:`group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` 上传下载、群自有区角色 ACL 与 `group_aid` 签名、`parents`、JS string 差异和 memberdata 服务端映射(见 `09-group-rpc-manual.md`)
|
|
35
|
+
- 协作(collab):`collab.*` 版本化文档、乐观锁 CAS commit、三方合并、标签、GC、reflog、revert(见 `09-collab-rpc-manual.md`)
|
|
36
36
|
- Service Proxy:`ServiceProxyClient`、`proxy.register_services`、proxy-server `register_services`
|
|
37
37
|
- agent.md:`AIDStore.upload_agent_md()`、`AIDStore.download_agent_md()`、`AIDStore.check_agent_md()`
|
|
38
38
|
- 错误处理:Result、异常、错误码、重试
|
|
@@ -68,11 +68,11 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
|
|
|
68
68
|
| 发布、下载或检查 agent.md | [04-连接与认证](04-连接与认证.md)、[06-API手册](06-API手册.md) |
|
|
69
69
|
| 状态机和重连 | [03-核心概念](03-核心概念.md)、[04-连接与认证](04-连接与认证.md) |
|
|
70
70
|
| 查方法签名 | [06-API手册](06-API手册.md) |
|
|
71
|
-
| 查消息或群组 RPC | 对应 `09-*-rpc-manual.md` |
|
|
72
|
-
| 查群文件系统 RPC / SDK / CLI
|
|
73
|
-
| 查 Storage 架构、SDK VFS、权限模型、mount/symlink 和上传下载链路 | [AUN Storage架构设计.md](<AUN Storage架构设计.md>) |
|
|
74
|
-
| 查 Storage RPC 参数 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) |
|
|
75
|
-
| 查协作(版本化文档/乐观锁/标签/GC/reflog/revert)RPC | [09-collab-rpc-manual.md](09-collab-rpc-manual.md) |
|
|
71
|
+
| 查消息或群组 RPC | 对应 `09-*-rpc-manual.md` |
|
|
72
|
+
| 查群文件系统 RPC / SDK / CLI 入口、群自有区角色 ACL 授权/撤销/查询、`parents` 和 JS string 差异 | [09-group-rpc-manual.md](09-group-rpc-manual.md)、[06-API手册](06-API手册.md) |
|
|
73
|
+
| 查 Storage 架构、SDK VFS、权限模型、mount/symlink 和上传下载链路 | [AUN Storage架构设计.md](<AUN Storage架构设计.md>) |
|
|
74
|
+
| 查 Storage RPC 参数 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) |
|
|
75
|
+
| 查协作(版本化文档/乐观锁/标签/GC/reflog/revert)RPC | [09-collab-rpc-manual.md](09-collab-rpc-manual.md) |
|
|
76
76
|
| 查 notify 在线轻量通知 / 跨域 federation | [Notify通知方案.md](Notify通知方案.md) |
|
|
77
77
|
| 查 Service Proxy 服务注册和路由 | [06-API手册](06-API手册.md)、[09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) |
|
|
78
78
|
| 查 payload 格式 | [09-payload-reference.md](09-payload-reference.md) |
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
| [08-最佳实践](08-最佳实践.md) | 幂等连接 · 多 AID · 资源清理 · 测试数据保护 |
|
|
20
20
|
| [09-payload-reference](09-payload-reference.md) | message / group / thought payload 格式 |
|
|
21
21
|
| [09-message-rpc-manual](09-message-rpc-manual.md) | P2P 消息 RPC |
|
|
22
|
-
| [09-group-rpc-manual](09-group-rpc-manual.md) | 群组 RPC(含 `group.fs.*` 群文件系统 RPC
|
|
23
|
-
| [09-storage-rpc-manual](09-storage-rpc-manual.md) | 存储 RPC(对象 + POSIX VFS / 卷 / 软链 / ACL / token) |
|
|
24
|
-
| [09-collab-rpc-manual](09-collab-rpc-manual.md) | 协作 RPC(版本化文档 / 乐观锁 / 三方合并 / 标签 / GC / reflog / revert) |
|
|
25
|
-
| [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md) |
|
|
26
|
-
| [group.fs POSIX 化分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md) | group.fs 重构的 IPO、Step-by-step、用例集、Checklist、验收和回滚 |
|
|
27
|
-
| [AUN Storage架构设计](<AUN Storage架构设计.md>) | Storage SDK VFS、控制面/数据面分离、服务端分层、类 Linux 权限、mount/symlink 与关键时序 |
|
|
22
|
+
| [09-group-rpc-manual](09-group-rpc-manual.md) | 群组 RPC(含 `group.fs.*` 群文件系统 RPC、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义) |
|
|
23
|
+
| [09-storage-rpc-manual](09-storage-rpc-manual.md) | 存储 RPC(对象 + POSIX VFS / 卷 / 软链 / ACL / token) |
|
|
24
|
+
| [09-collab-rpc-manual](09-collab-rpc-manual.md) | 协作 RPC(版本化文档 / 乐观锁 / 三方合并 / 标签 / GC / reflog / revert) |
|
|
25
|
+
| [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md) | `client.group.fs.*` / `aun group fs` / `group.fs.*` 群文件系统设计,含群自有区角色 ACL、`parents` 和 JS string 语义 |
|
|
26
|
+
| [group.fs POSIX 化分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md) | group.fs 重构的 IPO、Step-by-step、用例集、Checklist、验收和回滚 |
|
|
27
|
+
| [AUN Storage架构设计](<AUN Storage架构设计.md>) | Storage SDK VFS、控制面/数据面分离、服务端分层、类 Linux 权限、mount/symlink 与关键时序 |
|
|
28
28
|
| [09-meta-rpc-manual](09-meta-rpc-manual.md) | meta RPC 和信任根 |
|
|
29
29
|
| [09-stream-rpc-manual](09-stream-rpc-manual.md) | stream RPC |
|
|
30
30
|
| [09-proxy-rpc-manual](09-proxy-rpc-manual.md) | Service Proxy 控制面 RPC 和数据面隧道注册 |
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
|
|
63
63
|
- `client.call()` / `client.on()` → [04-连接与认证](04-连接与认证.md)、[06-API手册](06-API手册.md)
|
|
64
64
|
- `client.notify()` 在线轻量通知、跨域 federation、在线/离线边界 → [Notify通知方案](Notify通知方案.md)
|
|
65
|
-
- Message RPC → [09-message-rpc-manual](09-message-rpc-manual.md)
|
|
66
|
-
- Group RPC 与群文件系统 `group.fs.*`
|
|
67
|
-
- 下一版群文件系统 `group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` POSIX 化语义和 JS string 差异 → [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md)、[分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md)
|
|
68
|
-
- Storage 架构、SDK VFS、控制面/数据面分离、类 Linux 权限和 mount/symlink → [AUN Storage架构设计](<AUN Storage架构设计.md>)
|
|
69
|
-
- Storage RPC(对象 + `storage.fs.*` / `storage.volume.*` / 软链 / ACL / token)→ [09-storage-rpc-manual](09-storage-rpc-manual.md)
|
|
70
|
-
- Collab RPC(`collab.*` 版本化文档、乐观锁、标签、GC、reflog、revert)→ [09-collab-rpc-manual](09-collab-rpc-manual.md)
|
|
65
|
+
- Message RPC → [09-message-rpc-manual](09-message-rpc-manual.md)
|
|
66
|
+
- Group RPC 与群文件系统 `group.fs.*` 控制面、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义 → [09-group-rpc-manual](09-group-rpc-manual.md)
|
|
67
|
+
- 下一版群文件系统 `group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` POSIX 化语义和 JS string 差异 → [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md)、[分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md)
|
|
68
|
+
- Storage 架构、SDK VFS、控制面/数据面分离、类 Linux 权限和 mount/symlink → [AUN Storage架构设计](<AUN Storage架构设计.md>)
|
|
69
|
+
- Storage RPC(对象 + `storage.fs.*` / `storage.volume.*` / `storage.fs.touch` / 软链 / ACL / token)→ [09-storage-rpc-manual](09-storage-rpc-manual.md)
|
|
70
|
+
- Collab RPC(`collab.*` 版本化文档、乐观锁、标签、GC、reflog、revert)→ [09-collab-rpc-manual](09-collab-rpc-manual.md)
|
|
71
71
|
- Meta RPC → [09-meta-rpc-manual](09-meta-rpc-manual.md)
|
|
72
72
|
- Stream RPC → [09-stream-rpc-manual](09-stream-rpc-manual.md)
|
|
73
73
|
- Service Proxy RPC 和隧道注册 → [09-proxy-rpc-manual](09-proxy-rpc-manual.md)
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
|
|
119
119
|
### 09-*-rpc-manual
|
|
120
120
|
|
|
121
|
-
各业务服务的 RPC 参数、响应和错误语义。Storage、Collab、Group FS、Service Proxy 等明确提供高层门面的能力应优先使用门面;需要精确控制底层参数时再通过 `client.call()` 调用业务 RPC。
|
|
121
|
+
各业务服务的 RPC 参数、响应和错误语义。Storage、Collab、Group FS、Service Proxy 等明确提供高层门面的能力应优先使用门面;需要精确控制底层参数时再通过 `client.call()` 调用业务 RPC。
|
|
122
122
|
|
|
123
123
|
### AUN Storage架构设计
|
|
124
124
|
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
|
|
127
127
|
### 09-collab-rpc-manual
|
|
128
128
|
|
|
129
|
-
定义协作层 `collab.*` RPC(共 21 个):文档版本线(ls-files/create/show/commit/merge/log/diff/revert/prune)、运维备份迁移(gc/reflog/clone)、目录级标签(tag.create/list/show/diff/restore/rm/prune)、群内发现(ls-remote/unregister)。collab 编排并入 storage 服务进程,以调用者身份直调 storage 原语,授权下沉到 storage ACL。涵盖乐观锁 CAS commit 撞版本响应、三方合并冲突标记、forward-only 标签回滚、垃圾扫描和审计日志语义。
|
|
129
|
+
定义协作层 `collab.*` RPC(共 21 个):文档版本线(ls-files/create/show/commit/merge/log/diff/revert/prune)、运维备份迁移(gc/reflog/clone)、目录级标签(tag.create/list/show/diff/restore/rm/prune)、群内发现(ls-remote/unregister)。collab 编排并入 storage 服务进程,以调用者身份直调 storage 原语,授权下沉到 storage ACL。涵盖乐观锁 CAS commit 撞版本响应、三方合并冲突标记、forward-only 标签回滚、垃圾扫描和审计日志语义。
|
|
130
130
|
|
|
131
131
|
### 09-proxy-rpc-manual
|
|
132
132
|
|
|
@@ -59,6 +59,15 @@ export declare class MessageDeliveryEngine {
|
|
|
59
59
|
restoreSeqTrackerState(): void;
|
|
60
60
|
migrateSeqStateGroupIds(state: Record<string, number>): Record<string, number>;
|
|
61
61
|
saveSeqTrackerState(): void;
|
|
62
|
+
persistSeq(ns: string, forceSeq?: number): void;
|
|
63
|
+
flushSeqTrackerPending(): void;
|
|
64
|
+
private dropSeqTrackerPending;
|
|
65
|
+
private currentSeqTrackerContext;
|
|
66
|
+
private seqTrackerPending;
|
|
67
|
+
private mergeSeqTrackerPending;
|
|
68
|
+
private scheduleSeqTrackerFlush;
|
|
69
|
+
private cancelSeqTrackerFlushTimer;
|
|
70
|
+
private writeSeqTrackerState;
|
|
62
71
|
persistRepairedSeq(ns: string): void;
|
|
63
72
|
clampAckSeq(method: string, field: string, ns: string, seq: number): number;
|
|
64
73
|
clampAckParams(method: string, params: RpcParams): RpcParams;
|
package/dist/client/delivery.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { slotIsolationKey } from '../config.js';
|
|
2
2
|
import { normalizeGroupId } from '../group-id.js';
|
|
3
|
-
import { isJsonObject } from '../types.js';
|
|
3
|
+
import { ConnectionState, isJsonObject } from '../types.js';
|
|
4
4
|
const PUSHED_SEQS_LIMIT = 50_000;
|
|
5
5
|
const PENDING_ORDERED_LIMIT = 50_000;
|
|
6
6
|
const GROUP_RECALL_SEEN_LIMIT = 10_000;
|
|
7
|
+
const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
|
|
7
8
|
const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
8
9
|
'module_id', 'message_type', 'type', 'kind', 'version',
|
|
9
10
|
'from', 'from_aid', 'sender_aid', 'to', 'to_aid', 'group_id',
|
|
@@ -95,7 +96,8 @@ export class MessageDeliveryEngine {
|
|
|
95
96
|
const eventPayload = payload;
|
|
96
97
|
client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
|
|
97
98
|
if (eventPayload.action === 'dissolved') {
|
|
98
|
-
const
|
|
99
|
+
const rawGroupId = eventPayload.group_aid ?? eventPayload.group_id ?? '';
|
|
100
|
+
const groupId = normalizeGroupId(String(rawGroupId)) || String(rawGroupId).trim();
|
|
99
101
|
if (groupId)
|
|
100
102
|
client._cleanupDissolvedGroup?.(groupId);
|
|
101
103
|
}
|
|
@@ -495,7 +497,7 @@ export class MessageDeliveryEngine {
|
|
|
495
497
|
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
496
498
|
}
|
|
497
499
|
if (contig !== contigBefore)
|
|
498
|
-
this.
|
|
500
|
+
this.persistSeq(ns);
|
|
499
501
|
}
|
|
500
502
|
publishAppEvent(event, payload, source = 'direct') {
|
|
501
503
|
const client = this.runtime.client;
|
|
@@ -565,6 +567,8 @@ export class MessageDeliveryEngine {
|
|
|
565
567
|
}
|
|
566
568
|
logMessageDebug(stage, source, event, message, opts = {}) {
|
|
567
569
|
const client = this.runtime.client;
|
|
570
|
+
if (client._isMessageDebugEnabled?.() === false)
|
|
571
|
+
return;
|
|
568
572
|
const record = {
|
|
569
573
|
stage,
|
|
570
574
|
source,
|
|
@@ -666,7 +670,7 @@ export class MessageDeliveryEngine {
|
|
|
666
670
|
.catch((e) => { client._clientLog.debug(`P2P auto-ack failed: ${formatDeliveryError(e)}`); });
|
|
667
671
|
}
|
|
668
672
|
if (contigAfter !== contigBefore)
|
|
669
|
-
this.
|
|
673
|
+
this.persistSeq(ns);
|
|
670
674
|
if (encryptedPush)
|
|
671
675
|
return;
|
|
672
676
|
}
|
|
@@ -743,7 +747,7 @@ export class MessageDeliveryEngine {
|
|
|
743
747
|
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
744
748
|
}
|
|
745
749
|
if (contigAfter !== contigBefore)
|
|
746
|
-
this.
|
|
750
|
+
this.persistSeq(ns);
|
|
747
751
|
void published;
|
|
748
752
|
return;
|
|
749
753
|
}
|
|
@@ -766,7 +770,7 @@ export class MessageDeliveryEngine {
|
|
|
766
770
|
.catch((e) => { client._clientLog.debug(`group message auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
767
771
|
}
|
|
768
772
|
if (contigAfter !== contigBefore)
|
|
769
|
-
this.
|
|
773
|
+
this.persistSeq(ns);
|
|
770
774
|
if (encryptedPush)
|
|
771
775
|
return;
|
|
772
776
|
}
|
|
@@ -822,7 +826,7 @@ export class MessageDeliveryEngine {
|
|
|
822
826
|
this.prunePushedSeqs(ns);
|
|
823
827
|
if (client._seqTracker.getContiguousSeq(ns) !== afterSeq) {
|
|
824
828
|
await this.drainOrderedMessages(ns, undefined, true);
|
|
825
|
-
this.
|
|
829
|
+
this.persistSeq(ns);
|
|
826
830
|
}
|
|
827
831
|
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
828
832
|
if (contig > 0 && contig !== afterSeq) {
|
|
@@ -836,9 +840,6 @@ export class MessageDeliveryEngine {
|
|
|
836
840
|
finally {
|
|
837
841
|
client._gapFillDone.delete(dedupKey);
|
|
838
842
|
client._releasePullGate(ns, token);
|
|
839
|
-
if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
840
|
-
void this.fillP2pGap();
|
|
841
|
-
}
|
|
842
843
|
}
|
|
843
844
|
}
|
|
844
845
|
async fillGroupGap(groupId) {
|
|
@@ -865,7 +866,7 @@ export class MessageDeliveryEngine {
|
|
|
865
866
|
this.prunePushedSeqs(ns);
|
|
866
867
|
if (client._seqTracker.getContiguousSeq(ns) !== afterSeq) {
|
|
867
868
|
await this.drainOrderedMessages(ns, undefined, true);
|
|
868
|
-
this.
|
|
869
|
+
this.persistSeq(ns);
|
|
869
870
|
}
|
|
870
871
|
client._clientLog.debug(`group message gap fill done: group=${groupId}, after_seq=${afterSeq}, filled=${filled}`);
|
|
871
872
|
}
|
|
@@ -963,7 +964,7 @@ export class MessageDeliveryEngine {
|
|
|
963
964
|
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
964
965
|
await this.drainOrderedMessages(ns);
|
|
965
966
|
if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
|
|
966
|
-
this.
|
|
967
|
+
this.persistSeq(ns);
|
|
967
968
|
}
|
|
968
969
|
if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
|
|
969
970
|
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
|
|
@@ -999,6 +1000,7 @@ export class MessageDeliveryEngine {
|
|
|
999
1000
|
}
|
|
1000
1001
|
async handleGroupChangedEventSeq(data, groupId) {
|
|
1001
1002
|
const client = this.runtime.client;
|
|
1003
|
+
groupId = normalizeGroupId(groupId) || String(groupId ?? '').trim();
|
|
1002
1004
|
let needPull = false;
|
|
1003
1005
|
const rawEventSeq = data.event_seq;
|
|
1004
1006
|
const eventSeq = Number(rawEventSeq);
|
|
@@ -1027,7 +1029,7 @@ export class MessageDeliveryEngine {
|
|
|
1027
1029
|
await this.drainOrderedMessages(ns);
|
|
1028
1030
|
if (ackContig > 0 && ackContig !== contigBefore) {
|
|
1029
1031
|
if (data.action !== 'dissolved')
|
|
1030
|
-
this.
|
|
1032
|
+
this.persistSeq(ns);
|
|
1031
1033
|
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
|
|
1032
1034
|
client._transport.call('group.ack_events', {
|
|
1033
1035
|
group_id: groupId,
|
|
@@ -1132,7 +1134,7 @@ export class MessageDeliveryEngine {
|
|
|
1132
1134
|
const newContig = client._seqTracker.getContiguousSeq(ns);
|
|
1133
1135
|
const needPull = pushSeq > newContig && !published;
|
|
1134
1136
|
if (newContig !== contigBefore) {
|
|
1135
|
-
|
|
1137
|
+
this.persistSeq(ns);
|
|
1136
1138
|
}
|
|
1137
1139
|
if (newContig > 0 && newContig !== contigBefore) {
|
|
1138
1140
|
const ackSeq = this.clampAckSeq('message.v2.ack', 'up_to_seq', ns, newContig);
|
|
@@ -1204,7 +1206,7 @@ export class MessageDeliveryEngine {
|
|
|
1204
1206
|
this.runtime.delivery.setOnlineUnreadHintDrainActive(true);
|
|
1205
1207
|
try {
|
|
1206
1208
|
while (client._onlineUnreadHintQueue.size > 0) {
|
|
1207
|
-
if (client.state !==
|
|
1209
|
+
if (client.state !== ConnectionState.READY)
|
|
1208
1210
|
return;
|
|
1209
1211
|
if (client._sessionOptions?.background_sync === false)
|
|
1210
1212
|
return;
|
|
@@ -1377,23 +1379,132 @@ export class MessageDeliveryEngine {
|
|
|
1377
1379
|
}
|
|
1378
1380
|
saveSeqTrackerState() {
|
|
1379
1381
|
const client = this.runtime.client;
|
|
1380
|
-
|
|
1382
|
+
const context = this.currentSeqTrackerContext();
|
|
1383
|
+
if (!context) {
|
|
1384
|
+
this.flushSeqTrackerPending();
|
|
1381
1385
|
return;
|
|
1386
|
+
}
|
|
1382
1387
|
const state = client._seqTracker.exportState();
|
|
1383
|
-
|
|
1388
|
+
const pending = this.seqTrackerPending();
|
|
1389
|
+
if (pending.size > 0 && client._seqTrackerPendingPersistContext === context) {
|
|
1390
|
+
const liveNamespaces = new Set(Object.keys(state));
|
|
1391
|
+
for (const ns of [...pending.keys()]) {
|
|
1392
|
+
if (!liveNamespaces.has(ns))
|
|
1393
|
+
pending.delete(ns);
|
|
1394
|
+
}
|
|
1395
|
+
if (pending.size === 0)
|
|
1396
|
+
client._seqTrackerPendingPersistContext = null;
|
|
1397
|
+
}
|
|
1398
|
+
if (Object.keys(state).length > 0)
|
|
1399
|
+
this.mergeSeqTrackerPending(context, state);
|
|
1400
|
+
this.flushSeqTrackerPending();
|
|
1401
|
+
}
|
|
1402
|
+
persistSeq(ns, forceSeq) {
|
|
1403
|
+
const client = this.runtime.client;
|
|
1404
|
+
const context = this.currentSeqTrackerContext();
|
|
1405
|
+
if (!context) {
|
|
1406
|
+
this.flushSeqTrackerPending();
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const seq = forceSeq ?? client._seqTracker.getContiguousSeq(ns);
|
|
1410
|
+
if (!ns || !Number.isFinite(seq) || seq <= 0)
|
|
1411
|
+
return;
|
|
1412
|
+
if (forceSeq !== undefined) {
|
|
1413
|
+
client._seqTracker.forceContiguousSeq(ns, seq);
|
|
1414
|
+
}
|
|
1415
|
+
this.mergeSeqTrackerPending(context, { [ns]: seq });
|
|
1416
|
+
this.scheduleSeqTrackerFlush();
|
|
1417
|
+
}
|
|
1418
|
+
flushSeqTrackerPending() {
|
|
1419
|
+
const client = this.runtime.client;
|
|
1420
|
+
this.cancelSeqTrackerFlushTimer();
|
|
1421
|
+
const context = client._seqTrackerPendingPersistContext;
|
|
1422
|
+
const pending = this.seqTrackerPending();
|
|
1423
|
+
if (!context || pending.size === 0) {
|
|
1424
|
+
client._seqTrackerPendingPersistContext = null;
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
const [aid, deviceId, slotId] = JSON.parse(context);
|
|
1428
|
+
const state = Object.fromEntries(pending.entries());
|
|
1429
|
+
pending.clear();
|
|
1430
|
+
client._seqTrackerPendingPersistContext = null;
|
|
1431
|
+
this.writeSeqTrackerState(aid, deviceId, slotId, state);
|
|
1432
|
+
}
|
|
1433
|
+
dropSeqTrackerPending(ns) {
|
|
1434
|
+
const client = this.runtime.client;
|
|
1435
|
+
const pending = this.seqTrackerPending();
|
|
1436
|
+
pending.delete(ns);
|
|
1437
|
+
if (pending.size === 0) {
|
|
1438
|
+
client._seqTrackerPendingPersistContext = null;
|
|
1439
|
+
this.cancelSeqTrackerFlushTimer();
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
currentSeqTrackerContext() {
|
|
1443
|
+
const client = this.runtime.client;
|
|
1444
|
+
if (!client._aid)
|
|
1445
|
+
return null;
|
|
1446
|
+
return JSON.stringify([client._aid, client._deviceId, client._slotId]);
|
|
1447
|
+
}
|
|
1448
|
+
seqTrackerPending() {
|
|
1449
|
+
const client = this.runtime.client;
|
|
1450
|
+
if (!(client._seqTrackerPendingPersist instanceof Map)) {
|
|
1451
|
+
client._seqTrackerPendingPersist = new Map();
|
|
1452
|
+
}
|
|
1453
|
+
return client._seqTrackerPendingPersist;
|
|
1454
|
+
}
|
|
1455
|
+
mergeSeqTrackerPending(context, state) {
|
|
1456
|
+
const client = this.runtime.client;
|
|
1457
|
+
const pending = this.seqTrackerPending();
|
|
1458
|
+
const pendingContext = client._seqTrackerPendingPersistContext;
|
|
1459
|
+
if (pendingContext && pendingContext !== context && pending.size > 0) {
|
|
1460
|
+
this.flushSeqTrackerPending();
|
|
1461
|
+
}
|
|
1462
|
+
client._seqTrackerPendingPersistContext = context;
|
|
1463
|
+
for (const [ns, seq] of Object.entries(state)) {
|
|
1464
|
+
const seqNum = Number(seq);
|
|
1465
|
+
if (ns && Number.isFinite(seqNum) && seqNum > 0) {
|
|
1466
|
+
pending.set(ns, seqNum);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
scheduleSeqTrackerFlush() {
|
|
1471
|
+
const client = this.runtime.client;
|
|
1472
|
+
const pending = this.seqTrackerPending();
|
|
1473
|
+
if (pending.size === 0)
|
|
1474
|
+
return;
|
|
1475
|
+
if (client._seqTrackerFlushTimer)
|
|
1476
|
+
return;
|
|
1477
|
+
client._seqTrackerFlushTimer = setTimeout(() => {
|
|
1478
|
+
client._seqTrackerFlushTimer = null;
|
|
1479
|
+
this.flushSeqTrackerPending();
|
|
1480
|
+
}, SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS);
|
|
1481
|
+
}
|
|
1482
|
+
cancelSeqTrackerFlushTimer() {
|
|
1483
|
+
const client = this.runtime.client;
|
|
1484
|
+
if (client._seqTrackerFlushTimer) {
|
|
1485
|
+
clearTimeout(client._seqTrackerFlushTimer);
|
|
1486
|
+
client._seqTrackerFlushTimer = null;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
writeSeqTrackerState(aid, deviceId, slotId, state) {
|
|
1490
|
+
const client = this.runtime.client;
|
|
1491
|
+
if (!aid || Object.keys(state).length === 0)
|
|
1384
1492
|
return;
|
|
1385
1493
|
try {
|
|
1386
1494
|
const saveFn = client._tokenStore.saveSeq;
|
|
1387
1495
|
if (typeof saveFn === 'function') {
|
|
1388
1496
|
for (const [ns, seq] of Object.entries(state)) {
|
|
1389
|
-
saveFn.call(client._tokenStore,
|
|
1497
|
+
saveFn.call(client._tokenStore, aid, deviceId, slotId, ns, seq);
|
|
1390
1498
|
}
|
|
1391
1499
|
return;
|
|
1392
1500
|
}
|
|
1393
1501
|
const updater = client._tokenStore.updateInstanceState;
|
|
1394
1502
|
if (typeof updater === 'function') {
|
|
1395
|
-
updater.call(client._tokenStore,
|
|
1396
|
-
metadata.seq_tracker_state
|
|
1503
|
+
updater.call(client._tokenStore, aid, deviceId, slotId, (metadata) => {
|
|
1504
|
+
const existing = isJsonObject(metadata.seq_tracker_state)
|
|
1505
|
+
? { ...metadata.seq_tracker_state }
|
|
1506
|
+
: {};
|
|
1507
|
+
metadata.seq_tracker_state = { ...existing, ...state };
|
|
1397
1508
|
return metadata;
|
|
1398
1509
|
});
|
|
1399
1510
|
}
|
|
@@ -1403,9 +1514,9 @@ export class MessageDeliveryEngine {
|
|
|
1403
1514
|
client._clientLog.warn(`save SeqTracker state failed: ${error}`);
|
|
1404
1515
|
client._dispatcher.publish('seq_tracker.persist_error', {
|
|
1405
1516
|
phase: 'save',
|
|
1406
|
-
aid
|
|
1407
|
-
device_id:
|
|
1408
|
-
slot_id:
|
|
1517
|
+
aid,
|
|
1518
|
+
device_id: deviceId,
|
|
1519
|
+
slot_id: slotId,
|
|
1409
1520
|
error: String(error),
|
|
1410
1521
|
}).catch(() => { });
|
|
1411
1522
|
}
|
|
@@ -1415,6 +1526,7 @@ export class MessageDeliveryEngine {
|
|
|
1415
1526
|
if (!client._aid || !ns)
|
|
1416
1527
|
return;
|
|
1417
1528
|
const seq = client._seqTracker.getContiguousSeq(ns);
|
|
1529
|
+
this.dropSeqTrackerPending(ns);
|
|
1418
1530
|
try {
|
|
1419
1531
|
if (seq > 0 && typeof client._tokenStore.saveSeq === 'function') {
|
|
1420
1532
|
client._tokenStore.saveSeq(client._aid, client._deviceId, client._slotId, ns, seq);
|
|
@@ -1425,6 +1537,18 @@ export class MessageDeliveryEngine {
|
|
|
1425
1537
|
deleteSeq.call(client._tokenStore, client._aid, client._deviceId, client._slotId, ns);
|
|
1426
1538
|
return;
|
|
1427
1539
|
}
|
|
1540
|
+
const updater = client._tokenStore.updateInstanceState;
|
|
1541
|
+
if (seq <= 0 && typeof updater === 'function') {
|
|
1542
|
+
updater.call(client._tokenStore, client._aid, client._deviceId, client._slotId, (metadata) => {
|
|
1543
|
+
if (isJsonObject(metadata.seq_tracker_state)) {
|
|
1544
|
+
const next = { ...metadata.seq_tracker_state };
|
|
1545
|
+
delete next[ns];
|
|
1546
|
+
metadata.seq_tracker_state = next;
|
|
1547
|
+
}
|
|
1548
|
+
return metadata;
|
|
1549
|
+
});
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1428
1552
|
if (seq > 0) {
|
|
1429
1553
|
this.saveSeqTrackerState();
|
|
1430
1554
|
}
|
|
@@ -1484,6 +1608,7 @@ export class MessageDeliveryEngine {
|
|
|
1484
1608
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
1485
1609
|
if (!queue || queue.size === 0)
|
|
1486
1610
|
return;
|
|
1611
|
+
let delivered = false;
|
|
1487
1612
|
while (true) {
|
|
1488
1613
|
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1489
1614
|
const ready = [...queue.keys()]
|
|
@@ -1512,10 +1637,14 @@ export class MessageDeliveryEngine {
|
|
|
1512
1637
|
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, 'ordered-drain', pullResponse);
|
|
1513
1638
|
this.markPublishedSeq(ns, seq);
|
|
1514
1639
|
client._markOrderedSeqDelivered(ns, seq);
|
|
1640
|
+
delivered = true;
|
|
1515
1641
|
client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
1516
1642
|
}
|
|
1517
|
-
if (queue.size === 0)
|
|
1643
|
+
if (queue.size === 0) {
|
|
1518
1644
|
client._pendingOrderedMsgs.delete(ns);
|
|
1645
|
+
if (delivered)
|
|
1646
|
+
this.saveSeqTrackerState();
|
|
1647
|
+
}
|
|
1519
1648
|
}
|
|
1520
1649
|
async publishOrderedMessage(event, ns, seq, payload) {
|
|
1521
1650
|
const client = this.runtime.client;
|
|
@@ -1561,6 +1690,8 @@ export class MessageDeliveryEngine {
|
|
|
1561
1690
|
client._markOrderedSeqDelivered(ns, seqNum);
|
|
1562
1691
|
client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
1563
1692
|
await this.drainOrderedMessages(ns);
|
|
1693
|
+
if (!client._pendingOrderedMsgs.get(ns))
|
|
1694
|
+
this.saveSeqTrackerState();
|
|
1564
1695
|
return true;
|
|
1565
1696
|
}
|
|
1566
1697
|
async publishOrderedGroupRecall(ns, seq, message) {
|
|
@@ -1580,6 +1711,8 @@ export class MessageDeliveryEngine {
|
|
|
1580
1711
|
this.markPublishedSeq(ns, seqNum);
|
|
1581
1712
|
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
1582
1713
|
await this.drainOrderedMessages(ns);
|
|
1714
|
+
if (!client._pendingOrderedMsgs.get(ns))
|
|
1715
|
+
this.saveSeqTrackerState();
|
|
1583
1716
|
return true;
|
|
1584
1717
|
}
|
|
1585
1718
|
async publishPulledMessage(event, ns, seq, payload) {
|