@steve02081504/fount-p2p 0.0.4 → 0.0.6

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.
Files changed (64) hide show
  1. package/core/bytes_codec.mjs +0 -27
  2. package/core/constants.mjs +0 -31
  3. package/core/hexIds.mjs +0 -13
  4. package/crypto/key.mjs +14 -15
  5. package/dag/canonicalize_row.mjs +5 -5
  6. package/dag/index.mjs +0 -73
  7. package/dag/storage.mjs +0 -22
  8. package/discovery/mdns.mjs +4 -4
  9. package/discovery/nostr.mjs +3 -3
  10. package/federation/chunk_fetch_pending.mjs +3 -3
  11. package/federation/chunk_fetch_scheduler.mjs +3 -3
  12. package/federation/dag_order_cache.mjs +3 -3
  13. package/federation/entity_key_chain.mjs +0 -9
  14. package/federation/topo_order_memo.mjs +3 -18
  15. package/files/assemble.mjs +55 -39
  16. package/files/assemble_stream.mjs +96 -44
  17. package/files/chunk_provider_registry.mjs +0 -6
  18. package/files/chunk_responder.mjs +1 -1
  19. package/files/chunk_store.mjs +1 -11
  20. package/files/evfs.mjs +74 -47
  21. package/files/manifest.mjs +11 -2
  22. package/files/manifest_acl_registry.mjs +0 -6
  23. package/files/transfer_key.mjs +21 -15
  24. package/files/transfer_key_registry.mjs +9 -16
  25. package/governance/branch.mjs +0 -11
  26. package/governance/join_pow.mjs +17 -17
  27. package/link/channel_mux.mjs +9 -7
  28. package/link/frame.mjs +5 -5
  29. package/link/handshake.mjs +17 -17
  30. package/link/link.mjs +33 -33
  31. package/mailbox/deliver_or_store.mjs +13 -13
  32. package/mailbox/importance.mjs +0 -33
  33. package/mailbox/settings.mjs +0 -9
  34. package/mailbox/wire.mjs +4 -4
  35. package/node/denylist.mjs +0 -24
  36. package/node/network.mjs +0 -51
  37. package/node/personal_block.mjs +0 -35
  38. package/node/reputation_store.mjs +5 -14
  39. package/node/retention_policy.mjs +8 -23
  40. package/overlay/index.mjs +6 -6
  41. package/package.json +1 -1
  42. package/registries/event_type.mjs +0 -12
  43. package/registries/inbound.mjs +8 -30
  44. package/reputation/engine.mjs +0 -11
  45. package/rooms/scoped_link.mjs +18 -18
  46. package/schemas/part_query.mjs +156 -0
  47. package/timeline/append_core.mjs +3 -3
  48. package/transport/group_link_set.mjs +30 -30
  49. package/transport/ice_servers.mjs +0 -8
  50. package/transport/link_registry.mjs +13 -13
  51. package/transport/peer_identity_maps.mjs +0 -58
  52. package/transport/peer_pool.mjs +4 -4
  53. package/transport/remote_user_room.mjs +0 -12
  54. package/transport/rtc_connection_budget.mjs +2 -0
  55. package/transport/user_room.mjs +13 -24
  56. package/trust_graph/cache.mjs +0 -13
  57. package/trust_graph/engine.mjs +0 -24
  58. package/utils/async_mutex.mjs +0 -9
  59. package/wire/group_part.mjs +3 -3
  60. package/wire/part_fanout.mjs +0 -19
  61. package/wire/part_ingress.mjs +14 -14
  62. package/wire/part_query.mjs +486 -0
  63. package/wire/part_query.tunables.json +14 -0
  64. package/wire/part_query_cache.mjs +101 -0
@@ -7,7 +7,6 @@ import {
7
7
  bumpChunkStorageReputationPure,
8
8
  bumpReputationOnRelayPure,
9
9
  ensureReputationShape,
10
- isQuarantinedPure,
11
10
  observeBehaviorSamplePure,
12
11
  penalizeArchiveServeMismatchPure,
13
12
  penalizeChunkStorageFailurePure,
@@ -27,12 +26,12 @@ import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
27
26
 
28
27
  const DATA_NAME = 'reputation'
29
28
 
30
- /** @type {((opts: object) => Promise<boolean>) | null} */
29
+ /** @type {((options: object) => Promise<boolean>) | null} */
31
30
  let blockReputationHandler = null
32
31
 
33
32
  /**
34
33
  * Shell Load 时注册:公开 block/unblock → 信誉传导。
35
- * @param {(opts: object) => Promise<boolean>} handler block/unblock 信誉传导回调
34
+ * @param {(options: object) => Promise<boolean>} handler block/unblock 信誉传导回调
36
35
  * @returns {void}
37
36
  */
38
37
  export function registerBlockReputationHandler(handler) {
@@ -145,14 +144,6 @@ export async function observePeerBehavior(peerNodeHash, sample) {
145
144
  return anomaly
146
145
  }
147
146
 
148
- /**
149
- * @param {string} peerNodeHash 对端
150
- * @returns {boolean} 是否处于本地隔离
151
- */
152
- export function isPeerQuarantined(peerNodeHash) {
153
- return isQuarantinedPure(loadReputation(), peerNodeHash)
154
- }
155
-
156
147
  /**
157
148
  * @param {string} storagePeerKey 责任方
158
149
  * @returns {void}
@@ -317,10 +308,10 @@ export function pickNodeScore(nodeId) {
317
308
  }
318
309
 
319
310
  /**
320
- * @param {object} opts applyFollowedBlockSignal 参数
311
+ * @param {object} options applyFollowedBlockSignal 参数
321
312
  * @returns {Promise<boolean>} 是否已应用
322
313
  */
323
- export async function applyBlockReputationSignal(opts) {
314
+ export async function applyBlockReputationSignal(options) {
324
315
  if (!blockReputationHandler) return false
325
- return blockReputationHandler(opts)
316
+ return blockReputationHandler(options)
326
317
  }
@@ -3,36 +3,21 @@ import {
3
3
  authzFoldOrderIds,
4
4
  descendantClosureFromTip,
5
5
  } from '../governance/branch.mjs'
6
- import { getPermissionAnchorTypes } from '../registries/event_type.mjs'
7
-
8
- /**
9
- * @param {string[]} order 拓扑序 id 列表
10
- * @param {Map<string, object>} byId id → 事件
11
- * @returns {number} 最早须保留的事件下标
12
- */
13
- export function permissionAnchorStartIndex(order, byId) {
14
- let anchor = order.length
15
- for (let index = order.length - 1; index >= 0; index--) {
16
- const ev = byId.get(order[index])
17
- if (ev && getPermissionAnchorTypes().has(ev.type)) anchor = index
18
- }
19
- return anchor
20
- }
21
6
 
22
7
  /**
23
8
  * 在共识分支上计算须保留的事件 id(连通子图,不用拓扑下标切片)。
24
9
  * @param {string[]} order 规范拓扑序
25
10
  * @param {Map<string, object>} byId id → 事件
26
- * @param {object} opts 保留策略
27
- * @param {number} opts.maxDepth 分支上最大事件深度
28
- * @param {number} opts.cutoffWall 最早保留的 HLC wall
29
- * @param {Set<string>} opts.anchorTypes 权限锚点事件类型
30
- * @param {string | null} [opts.checkpointTipId] checkpoint 尖
31
- * @param {string | null} [opts.branchTipId] 共识分支尖
11
+ * @param {object} options 保留策略
12
+ * @param {number} options.maxDepth 分支上最大事件深度
13
+ * @param {number} options.cutoffWall 最早保留的 HLC wall
14
+ * @param {Set<string>} options.anchorTypes 权限锚点事件类型
15
+ * @param {string | null} [options.checkpointTipId] checkpoint 尖
16
+ * @param {string | null} [options.branchTipId] 共识分支尖
32
17
  * @returns {Set<string>} 保留 id
33
18
  */
34
- export function computeRetentionKeepIds(order, byId, opts) {
35
- const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } = opts
19
+ export function computeRetentionKeepIds(order, byId, options) {
20
+ const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } = options
36
21
  const branchOrder = authzFoldOrderIds(order, byId, branchTipId)
37
22
  const branchSet = new Set(branchOrder)
38
23
  if (!branchSet.size) return new Set()
package/overlay/index.mjs CHANGED
@@ -131,15 +131,15 @@ export function createOverlayRouter(registry, ttl = 3) {
131
131
  /**
132
132
  * 发现到目标节点的签名路由路径。
133
133
  * @param {string} targetNodeHash 目标节点 64 hex
134
- * @param {object} [opts] 选项
135
- * @param {number} [opts.ttl] 路由 TTL
136
- * @param {number} [opts.timeoutMs] 超时毫秒
134
+ * @param {object} [options] 选项
135
+ * @param {number} [options.ttl] 路由 TTL
136
+ * @param {number} [options.timeoutMs] 超时毫秒
137
137
  * @returns {Promise<string[]>} 从本节点到目标的 nodeHash 路径
138
138
  */
139
- async discoverRoute(targetNodeHash, opts = {}) {
139
+ async discoverRoute(targetNodeHash, options = {}) {
140
140
  const reqId = randomMsgIdHex()
141
- const maxTtl = Number(opts.ttl) || ttl
142
- const timeoutMs = Number(opts.timeoutMs) || 10_000
141
+ const maxTtl = Number(options.ttl) || ttl
142
+ const timeoutMs = Number(options.timeoutMs) || 10_000
143
143
  const promise = new Promise((resolve, reject) => {
144
144
  const timer = setTimeout(() => {
145
145
  pendingRoutes.delete(reqId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -56,18 +56,6 @@ export function getGovernanceAuthzTypes() {
56
56
  return typesWithFlag('governance')
57
57
  }
58
58
 
59
- /** 联邦入站/中继前须物化 ACL 门控的类型。 */
60
- /** @returns {Set<string>} ACL 门控事件 type 集合 */
61
- export function getFederationAclGatedEventTypes() {
62
- return typesWithFlag('aclGated')
63
- }
64
-
65
- /** §6.2 频道 GC 沉寂计时排除的类型。 */
66
- /** @returns {Set<string>} GC 排除事件 type 集合 */
67
- export function getChannelGcExcludedEventTypes() {
68
- return typesWithFlag('gcExclude')
69
- }
70
-
71
59
  /** 裁剪时不得早于最早一条权限锚点事件(§7.1)。 */
72
60
  /** @returns {Set<string>} 权限锚点事件 type 集合 */
73
61
  export function getPermissionAnchorTypes() {
@@ -10,11 +10,11 @@
10
10
  */
11
11
 
12
12
  /**
13
- * @typedef {(ctx: InboundContext, message: object) => Promise<PartInvokeResponse | null>} RpcInboundHandler
13
+ * @typedef {(inboundContext: InboundContext, message: object) => Promise<PartInvokeResponse | null>} RpcInboundHandler
14
14
  */
15
15
 
16
16
  /**
17
- * @typedef {(ctx: InboundContext, message: object) => Promise<void>} DeliveryInboundHandler
17
+ * @typedef {(inboundContext: InboundContext, message: object) => Promise<void>} DeliveryInboundHandler
18
18
  */
19
19
 
20
20
  /** @type {Map<string, RpcInboundHandler>} */
@@ -42,49 +42,27 @@ export function registerDeliveryInboundHandler(type, handler) {
42
42
  }
43
43
 
44
44
  /**
45
- * @param {string} type 入站类型
46
- * @returns {void}
47
- */
48
- export function unregisterRpcInboundHandler(type) {
49
- rpcHandlers.delete(String(type || '').trim())
50
- }
51
-
52
- /**
53
- * @param {string} type 入站类型
54
- * @returns {void}
55
- */
56
- export function unregisterDeliveryInboundHandler(type) {
57
- deliveryHandlers.delete(String(type || '').trim())
58
- }
59
-
60
- /**
61
- * @param {InboundContext} ctx 入站上下文
45
+ * @param {InboundContext} inboundContext 入站上下文
62
46
  * @param {object} message 已校验的线载荷(含 type)
63
47
  * @returns {Promise<PartInvokeResponse | null>} 处理器返回值
64
48
  */
65
- export async function dispatchRpcInbound(ctx, message) {
49
+ export async function dispatchRpcInbound(inboundContext, message) {
66
50
  const type = String(message?.type || '').trim()
67
51
  if (!type) return null
68
52
  const handler = rpcHandlers.get(type)
69
53
  if (!handler) return null
70
- return handler(ctx, message)
54
+ return handler(inboundContext, message)
71
55
  }
72
56
 
73
57
  /**
74
- * @param {InboundContext} ctx 入站上下文
58
+ * @param {InboundContext} inboundContext 入站上下文
75
59
  * @param {object} message 已校验的线载荷(含 type)
76
60
  * @returns {Promise<void>}
77
61
  */
78
- export async function dispatchDeliveryInbound(ctx, message) {
62
+ export async function dispatchDeliveryInbound(inboundContext, message) {
79
63
  const type = String(message?.type || '').trim()
80
64
  if (!type) return
81
65
  const handler = deliveryHandlers.get(type)
82
66
  if (!handler) return
83
- await handler(ctx, message)
84
- }
85
-
86
- /** @returns {void} 测试用 */
87
- export function clearInboundHandlers() {
88
- rpcHandlers.clear()
89
- deliveryHandlers.clear()
67
+ await handler(inboundContext, message)
90
68
  }
@@ -445,14 +445,3 @@ export function isQuarantinedPure(data, peerNodeHash, now = Date.now()) {
445
445
  const until = Number(data.byNodeHash?.[id]?.quarantinedUntil ?? 0)
446
446
  return Number.isFinite(until) && until > now
447
447
  }
448
-
449
- /**
450
- * @param {ReputationFile} data 信誉表
451
- * @param {string} peerNodeHash 对端
452
- * @param {number} [now] 当前时间
453
- * @param {typeof reputationTunables} [tunables] tunables
454
- * @returns {boolean} 是否检测到异常并触发隔离
455
- */
456
- export function detectAnomalyPure(data, peerNodeHash, now = Date.now(), tunables = reputationTunables) {
457
- return isQuarantinedPure(data, peerNodeHash, now)
458
- }
@@ -9,17 +9,17 @@ import {
9
9
 
10
10
  /**
11
11
  * 在指定 scope 与 roomSecret 下创建 link 层房间(discovery + registry 转发)。
12
- * @param {object} opts 房间选项
13
- * @param {string} opts.scope link registry scope(如 `group:{id}`)
14
- * @param {string} opts.roomSecret 房间 rendezvous 密钥
15
- * @param {(nodeHash: string) => boolean} [opts.allowNode] 是否允许与某 nodeHash 通信
16
- * @returns {{ start: () => Promise<void>, leave: () => Promise<void>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], onPeerJoin: (cb: (peerId: string) => void) => () => void, onPeerLeave: (cb: (peerId: string) => void) => () => void, getPeers: () => Record<string, true> }} 房间句柄
12
+ * @param {object} options 房间选项
13
+ * @param {string} options.scope link registry scope(如 `group:{id}`)
14
+ * @param {string} options.roomSecret 房间 rendezvous 密钥
15
+ * @param {(nodeHash: string) => boolean} [options.allowNode] 是否允许与某 nodeHash 通信
16
+ * @returns {{ start: () => Promise<void>, leave: () => Promise<void>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], onPeerJoin: (callback: (peerId: string) => void) => () => void, onPeerLeave: (callback: (peerId: string) => void) => () => void, getPeers: () => Record<string, true> }} 房间句柄
17
17
  */
18
- export function createScopedLinkRoom(opts) {
18
+ export function createScopedLinkRoom(options) {
19
19
  const registry = getLinkRegistry()
20
- const scope = String(opts.scope)
21
- const topic = groupRendezvousTopic(opts.roomSecret)
22
- const allowNode = typeof opts.allowNode === 'function' ? opts.allowNode : () => true
20
+ const scope = String(options.scope)
21
+ const topic = groupRendezvousTopic(options.roomSecret)
22
+ const allowNode = typeof options.allowNode === 'function' ? options.allowNode : () => true
23
23
  /** @type {Set<string>} */
24
24
  const discoveredPeers = new Set()
25
25
  /** @type {Set<string>} */
@@ -156,24 +156,24 @@ export function createScopedLinkRoom(opts) {
156
156
  ]
157
157
  },
158
158
  /**
159
- * @param {(peerId: string) => void} cb 新 peer 上线回调
159
+ * @param {(peerId: string) => void} callback 新 peer 上线回调
160
160
  * @returns {() => void} 取消订阅
161
161
  */
162
- onPeerJoin(cb) {
163
- joinListeners.add(cb)
162
+ onPeerJoin(callback) {
163
+ joinListeners.add(callback)
164
164
  for (const peerId of activePeerIds())
165
165
  announcedPeers.add(peerId)
166
166
  for (const peerId of announcedPeers)
167
- try { cb(peerId) } catch { /* ignore */ }
168
- return () => joinListeners.delete(cb)
167
+ try { callback(peerId) } catch { /* ignore */ }
168
+ return () => joinListeners.delete(callback)
169
169
  },
170
170
  /**
171
- * @param {(peerId: string) => void} cb peer 离线回调
171
+ * @param {(peerId: string) => void} callback peer 离线回调
172
172
  * @returns {() => void} 取消订阅
173
173
  */
174
- onPeerLeave(cb) {
175
- leaveListeners.add(cb)
176
- return () => leaveListeners.delete(cb)
174
+ onPeerLeave(callback) {
175
+ leaveListeners.add(callback)
176
+ return () => leaveListeners.delete(callback)
177
177
  },
178
178
  /**
179
179
  * @returns {Record<string, true>} 当前活跃 peer 的 nodeHash 集合
@@ -0,0 +1,156 @@
1
+ import { Buffer } from 'node:buffer'
2
+
3
+ import { canonicalStringify } from '../core/canonical_json.mjs'
4
+ import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
5
+ import { isPlainObject } from '../wire/ingress.mjs'
6
+ import { normalizePartpath } from '../wire/part_invoke.mjs'
7
+ import partQueryTunables from '../wire/part_query.tunables.json' with { type: 'json' }
8
+
9
+ /**
10
+ * @typedef {{
11
+ * requestId: string
12
+ * originNodeHash: string
13
+ * partpath: string
14
+ * kind: string
15
+ * query: unknown
16
+ * ttl: number
17
+ * budget: { maxHits: number }
18
+ * }} PartQueryReq
19
+ */
20
+
21
+ /**
22
+ * @typedef {{
23
+ * requestId: string
24
+ * fromNodeHash: string
25
+ * rows: unknown[]
26
+ * }} PartQueryRes
27
+ */
28
+
29
+ /**
30
+ * @param {unknown} value 任意 JSON
31
+ * @returns {number} UTF-8 序列化字节数;不可序列化时 Infinity
32
+ */
33
+ export function measureJsonBytes(value) {
34
+ try {
35
+ return Buffer.byteLength(JSON.stringify(value), 'utf8')
36
+ }
37
+ catch {
38
+ return Number.POSITIVE_INFINITY
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {unknown} budget 请求预算
44
+ * @param {number} [maxHits=partQueryTunables.maxHits] 上限
45
+ * @returns {{ maxHits: number }} 钳制后的预算
46
+ */
47
+ export function clampPartQueryBudget(budget, maxHits = partQueryTunables.maxHits) {
48
+ const cap = Math.max(1, Math.floor(Number(maxHits) || partQueryTunables.maxHits))
49
+ const raw = isPlainObject(budget) ? Number(budget.maxHits) : NaN
50
+ const hits = Number.isFinite(raw) ? Math.floor(raw) : cap
51
+ return { maxHits: Math.max(1, Math.min(cap, hits)) }
52
+ }
53
+
54
+ /**
55
+ * @param {unknown} ttl 跳数预算
56
+ * @param {number} [maxTtl=partQueryTunables.maxTtl] 上限
57
+ * @returns {number | null} 钳制后的 ttl;非法为 null
58
+ */
59
+ export function clampPartQueryTtl(ttl, maxTtl = partQueryTunables.maxTtl) {
60
+ const cap = Math.max(1, Math.floor(Number(maxTtl) || partQueryTunables.maxTtl))
61
+ const n = Math.floor(Number(ttl))
62
+ if (!Number.isFinite(n) || n < 1) return null
63
+ return Math.min(cap, n)
64
+ }
65
+
66
+ /**
67
+ * @param {unknown} rows 行数组
68
+ * @param {number} maxHits 条数上限
69
+ * @param {number} [maxRowsBytes=partQueryTunables.maxRowsBytes] 总尺寸上限
70
+ * @returns {unknown[] | null} 通过校验的 rows;失败 null
71
+ */
72
+ export function clampPartQueryRows(rows, maxHits, maxRowsBytes = partQueryTunables.maxRowsBytes) {
73
+ if (!Array.isArray(rows)) return null
74
+ const limited = rows.slice(0, Math.max(0, Math.floor(Number(maxHits) || 0)))
75
+ if (measureJsonBytes(limited) > maxRowsBytes) return null
76
+ return limited
77
+ }
78
+
79
+ /** requestId 上限(randomUUID 36 字符;防超长键灌爆 dedupe/pending 表) */
80
+ const REQUEST_ID_MAX_LENGTH = 128
81
+
82
+ /**
83
+ * @param {unknown} value 原始 requestId
84
+ * @returns {string | null} 修剪后的 requestId;非法 null
85
+ */
86
+ function normalizeRequestId(value) {
87
+ const requestId = String(value || '').trim()
88
+ if (!requestId || requestId.length > REQUEST_ID_MAX_LENGTH) return null
89
+ return requestId
90
+ }
91
+
92
+ /**
93
+ * @param {unknown} value 入站 req
94
+ * @param {typeof partQueryTunables} [tunables] 可调参数
95
+ * @returns {PartQueryReq | null} 校验通过的 req
96
+ */
97
+ export function parsePartQueryReq(value, tunables = partQueryTunables) {
98
+ if (!isPlainObject(value)) return null
99
+ const requestId = normalizeRequestId(value.requestId)
100
+ if (!requestId) return null
101
+ const originNodeHash = normalizeHex64(value.originNodeHash)
102
+ if (!isHex64(originNodeHash)) return null
103
+ const partpath = normalizePartpath(value.partpath)
104
+ if (!partpath) return null
105
+ const kind = String(value.kind || '').trim()
106
+ if (!kind) return null
107
+ if (!Object.prototype.hasOwnProperty.call(value, 'query')) return null
108
+ if (measureJsonBytes(value.query) > (tunables.maxQueryBytes ?? 2048)) return null
109
+ const ttl = clampPartQueryTtl(value.ttl, tunables.maxTtl)
110
+ if (ttl == null) return null
111
+ const budget = clampPartQueryBudget(value.budget, tunables.maxHits)
112
+ return {
113
+ requestId,
114
+ originNodeHash,
115
+ partpath,
116
+ kind,
117
+ query: value.query,
118
+ ttl,
119
+ budget,
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @param {unknown} value 入站 res
125
+ * @param {typeof partQueryTunables} [tunables] 可调参数
126
+ * @returns {PartQueryRes | null} 校验通过的 res
127
+ */
128
+ export function parsePartQueryRes(value, tunables = partQueryTunables) {
129
+ if (!isPlainObject(value)) return null
130
+ const requestId = normalizeRequestId(value.requestId)
131
+ if (!requestId) return null
132
+ const fromNodeHash = normalizeHex64(value.fromNodeHash)
133
+ if (!isHex64(fromNodeHash)) return null
134
+ const rows = clampPartQueryRows(value.rows, tunables.maxHits, tunables.maxRowsBytes)
135
+ if (!rows) return null
136
+ return { requestId, fromNodeHash, rows }
137
+ }
138
+
139
+ /**
140
+ * @param {unknown} partpath part 路径
141
+ * @param {unknown} kind 查询标签
142
+ * @param {unknown} query 不透明查询
143
+ * @returns {string | null} 规范化三元组的缓存材料;非法 null
144
+ */
145
+ export function normalizePartQueryCacheMaterial(partpath, kind, query) {
146
+ const path = normalizePartpath(partpath)
147
+ const k = String(kind || '').trim()
148
+ if (!path || !k) return null
149
+ if (measureJsonBytes(query) > partQueryTunables.maxQueryBytes) return null
150
+ try {
151
+ return canonicalStringify({ partpath: path, kind: k, query })
152
+ }
153
+ catch {
154
+ return null
155
+ }
156
+ }
@@ -14,17 +14,17 @@ import { computeDagTipIdsFromEvents } from '../governance/branch.mjs'
14
14
  * 为追加事件计算 HLC 与 DAG 前驱(chat 群与 social 时间线共用)。
15
15
  * @param {object[]} previous 已有事件
16
16
  * @param {object} event 待追加事件
17
- * @param {{ multiTip?: boolean }} [opts] chat 在多 tip 时连接全部 tip
17
+ * @param {{ multiTip?: boolean }} [options] chat 在多 tip 时连接全部 tip
18
18
  * @returns {{ hlc: object, prev_event_ids: string[], last: object | undefined }} HLC、前驱与末条事件
19
19
  */
20
- export function computeAppendHlcAndPrev(previous, event, opts = {}) {
20
+ export function computeAppendHlcAndPrev(previous, event, options = {}) {
21
21
  const last = previous[previous.length - 1]
22
22
  const hlc = nextHlc(last?.hlc, event.timestamp ?? Date.now())
23
23
  const tips = computeDagTipIdsFromEvents(previous)
24
24
  let prev_event_ids
25
25
  if (event.prev_event_ids?.length)
26
26
  prev_event_ids = sortedPrevEventIds(event.prev_event_ids)
27
- else if (opts.multiTip && tips.length > 1)
27
+ else if (options.multiTip && tips.length > 1)
28
28
  prev_event_ids = sortedPrevEventIds(tips)
29
29
  else if (tips.length)
30
30
  prev_event_ids = sortedPrevEventIds(tips)
@@ -14,22 +14,22 @@ import { resolveFederationPoolLimits, selectLinkTargetsFromMembers } from './pee
14
14
 
15
15
  /**
16
16
  * 创建基于 link registry 的群组联邦房间。
17
- * @param {object} opts 选项
18
- * @param {string} opts.groupId 群组 id
19
- * @param {string} opts.roomSecret 房间密钥(用于 rendezvous topic)
20
- * @param {string[]} opts.members 初始成员 nodeHash 列表
21
- * @param {object} [opts.groupSettings] 群设置(连接预算:trustedPeerSlots/explorePeerSlots/maxPeers 等)
22
- * @returns {{ groupId: string, scope: string, start: () => Promise<void>, leave: () => Promise<void>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>, send: (actionName: string, payload: unknown, peerId?: string | null) => Promise<number>, onEnvelope: (cb: (senderNodeHash: string, envelope: object) => void) => () => void, onPeerJoin: (cb: (peerId: string) => void) => () => void, onPeerLeave: (cb: (peerId: string) => void) => () => void, getPeers: () => Record<string, true>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], registerCleanup: (fn: () => void) => void, isActive: () => boolean }} 群组 link set 接口
17
+ * @param {object} options 选项
18
+ * @param {string} options.groupId 群组 id
19
+ * @param {string} options.roomSecret 房间密钥(用于 rendezvous topic)
20
+ * @param {string[]} options.members 初始成员 nodeHash 列表
21
+ * @param {object} [options.groupSettings] 群设置(连接预算:trustedPeerSlots/explorePeerSlots/maxPeers 等)
22
+ * @returns {{ groupId: string, scope: string, start: () => Promise<void>, leave: () => Promise<void>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>, send: (actionName: string, payload: unknown, peerId?: string | null) => Promise<number>, onEnvelope: (callback: (senderNodeHash: string, envelope: object) => void) => () => void, onPeerJoin: (callback: (peerId: string) => void) => () => void, onPeerLeave: (callback: (peerId: string) => void) => () => void, getPeers: () => Record<string, true>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], registerCleanup: (cleanup: () => void) => void, isActive: () => boolean }} 群组 link set 接口
23
23
  */
24
- export function createGroupLinkSet(opts) {
25
- const registry = opts.registry ?? getLinkRegistry()
26
- const autoconnect = opts.autoconnect !== false
27
- const groupId = String(opts.groupId)
24
+ export function createGroupLinkSet(options) {
25
+ const registry = options.registry ?? getLinkRegistry()
26
+ const autoconnect = options.autoconnect !== false
27
+ const groupId = String(options.groupId)
28
28
  const scope = `group:${groupId}`
29
- const topic = groupRendezvousTopic(opts.roomSecret)
30
- const members = new Set((Array.isArray(opts.members) ? opts.members : []).map(String))
29
+ const topic = groupRendezvousTopic(options.roomSecret)
30
+ const members = new Set((Array.isArray(options.members) ? options.members : []).map(String))
31
31
  const selfNodeHash = registry.localIdentity.nodeHash
32
- const groupSettings = opts.groupSettings ?? {}
32
+ const groupSettings = options.groupSettings ?? {}
33
33
  // 初始成员是调用方明确知道的引导集合(如 introducer/creator/seed),作为必连锚点保证引导期连通。
34
34
  const initialAnchors = new Set(members)
35
35
  /** @type {ReturnType<typeof setTimeout> | null} */
@@ -50,12 +50,12 @@ export function createGroupLinkSet(opts) {
50
50
 
51
51
  /**
52
52
  * 注册 leave 时执行的清理回调。
53
- * @param {() => void} fn 清理函数
53
+ * @param {() => void} cleanup 清理函数
54
54
  * @returns {void}
55
55
  */
56
- function registerCleanup(fn) {
57
- if (typeof fn !== 'function') return
58
- cleanups.add(fn)
56
+ function registerCleanup(cleanup) {
57
+ if (typeof cleanup !== 'function') return
58
+ cleanups.add(cleanup)
59
59
  }
60
60
 
61
61
  /**
@@ -255,34 +255,34 @@ export function createGroupLinkSet(opts) {
255
255
  },
256
256
  /**
257
257
  * 订阅入站 envelope。
258
- * @param {(senderNodeHash: string, envelope: object) => void} cb 回调
258
+ * @param {(senderNodeHash: string, envelope: object) => void} callback 回调
259
259
  * @returns {() => void} 取消订阅函数
260
260
  */
261
- onEnvelope(cb) {
262
- envelopeListeners.add(cb)
263
- return () => envelopeListeners.delete(cb)
261
+ onEnvelope(callback) {
262
+ envelopeListeners.add(callback)
263
+ return () => envelopeListeners.delete(callback)
264
264
  },
265
265
  /**
266
266
  * 订阅 peer 加入事件(含当前已在线 peer 的即时回调)。
267
- * @param {(peerId: string) => void} cb 回调
267
+ * @param {(peerId: string) => void} callback 回调
268
268
  * @returns {() => void} 取消订阅函数
269
269
  */
270
- onPeerJoin(cb) {
271
- peerJoinListeners.add(cb)
270
+ onPeerJoin(callback) {
271
+ peerJoinListeners.add(callback)
272
272
  for (const { peerId } of activeRoster())
273
273
  if (peerId) announcedPeers.add(peerId)
274
274
  for (const peerId of announcedPeers)
275
- try { cb(peerId) } catch { /* ignore */ }
276
- return () => peerJoinListeners.delete(cb)
275
+ try { callback(peerId) } catch { /* ignore */ }
276
+ return () => peerJoinListeners.delete(callback)
277
277
  },
278
278
  /**
279
279
  * 订阅 peer 离开事件。
280
- * @param {(peerId: string) => void} cb 回调
280
+ * @param {(peerId: string) => void} callback 回调
281
281
  * @returns {() => void} 取消订阅函数
282
282
  */
283
- onPeerLeave(cb) {
284
- peerLeaveListeners.add(cb)
285
- return () => peerLeaveListeners.delete(cb)
283
+ onPeerLeave(callback) {
284
+ peerLeaveListeners.add(callback)
285
+ return () => peerLeaveListeners.delete(callback)
286
286
  },
287
287
  /**
288
288
  * 返回当前在线 peer 的 Record 映射。
@@ -47,14 +47,6 @@ export function resolveIceServers(groupSettings) {
47
47
  return out.length ? out : [...DEFAULT_ICE_SERVERS]
48
48
  }
49
49
 
50
- /**
51
- * @param {unknown} groupSettings 物化群设置
52
- * @returns {{ iceServers: ReturnType<typeof resolveIceServers> }} Trystero rtcConfig 片段
53
- */
54
- export function resolveIceServersForTrystero(groupSettings) {
55
- return { iceServers: resolveIceServers(groupSettings) }
56
- }
57
-
58
50
  /**
59
51
  * 校验并规范化待写入 DAG 的 iceServers 数组。
60
52
  * @param {unknown} raw 请求体字段
@@ -187,20 +187,20 @@ function subscribeBucket(buckets, key, listener) {
187
187
 
188
188
  /**
189
189
  * 创建 P2P 链路注册表(discovery、信令、直连与 overlay relay)。
190
- * @param {object} [opts] 选项
191
- * @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array }} [opts.localIdentity] 本地身份
192
- * @param {typeof createLink} [opts.createLink] 链路工厂(可注入 mock)
193
- * @param {RTCConfiguration['iceServers']} [opts.iceServers] ICE 服务器列表
194
- * @param {number} [opts.maxActive] 最大并发活跃链路数
195
- * @param {boolean} [opts.autoRegisterDiscoveryProviders] 是否自动注册 discovery provider
190
+ * @param {object} [options] 选项
191
+ * @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array }} [options.localIdentity] 本地身份
192
+ * @param {typeof createLink} [options.createLink] 链路工厂(可注入 mock)
193
+ * @param {RTCConfiguration['iceServers']} [options.iceServers] ICE 服务器列表
194
+ * @param {number} [options.maxActive] 最大并发活跃链路数
195
+ * @param {boolean} [options.autoRegisterDiscoveryProviders] 是否自动注册 discovery provider
196
196
  * @returns {object} link registry 接口
197
197
  */
198
- export function createLinkRegistry(opts = {}) {
199
- const localIdentity = resolveLocalIdentity(opts.localIdentity)
200
- const createLinkImpl = opts.createLink ?? createLink
201
- const iceServers = opts.iceServers?.length ? opts.iceServers : DEFAULT_ICE_SERVERS
202
- const maxActive = Math.max(4, Number(opts.maxActive) || 32)
203
- const autoRegisterDiscoveryProviders = opts.autoRegisterDiscoveryProviders !== false
198
+ export function createLinkRegistry(options = {}) {
199
+ const localIdentity = resolveLocalIdentity(options.localIdentity)
200
+ const createLinkImpl = options.createLink ?? createLink
201
+ const iceServers = options.iceServers?.length ? options.iceServers : DEFAULT_ICE_SERVERS
202
+ const maxActive = Math.max(4, Number(options.maxActive) || 32)
203
+ const autoRegisterDiscoveryProviders = options.autoRegisterDiscoveryProviders !== false
204
204
  const selfTopic = nodeRendezvousTopic(localIdentity.nodeHash)
205
205
  /** @type {Map<string, Awaited<ReturnType<typeof createLinkImpl>>>} */
206
206
  const links = new Map()
@@ -348,7 +348,7 @@ export function createLinkRegistry(opts = {}) {
348
348
  /**
349
349
  * 为一条 connId 会话建 PC 并走 glare 择一:建 link → 绑 onDown 清 session → ready → registerResolvedLink。
350
350
  * initiator 侧建链前先 trimToBudget 腾预算。出错则清理该 connId 会话。
351
- * @param {{ remoteNodeHash: string, connId: string, session: ReturnType<typeof createBufferedSignalSession>, initiator: boolean }} opts 建链参数
351
+ * @param {{ remoteNodeHash: string, connId: string, session: ReturnType<typeof createBufferedSignalSession>, initiator: boolean }} options 建链参数
352
352
  * @returns {Promise<Awaited<ReturnType<typeof createLinkImpl>> | null>} 当前规范链(本条可能因 glare 被关,取 links 现值);失败 null
353
353
  */
354
354
  async function buildConnLink({ remoteNodeHash, connId, session, initiator }) {