@steve02081504/fount-p2p 0.0.4 → 0.0.5

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 (43) 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/index.mjs +0 -73
  6. package/dag/storage.mjs +0 -22
  7. package/federation/entity_key_chain.mjs +0 -9
  8. package/federation/topo_order_memo.mjs +0 -15
  9. package/files/assemble.mjs +55 -39
  10. package/files/assemble_stream.mjs +96 -44
  11. package/files/chunk_provider_registry.mjs +0 -6
  12. package/files/chunk_store.mjs +1 -11
  13. package/files/evfs.mjs +64 -37
  14. package/files/manifest.mjs +11 -2
  15. package/files/manifest_acl_registry.mjs +0 -6
  16. package/files/transfer_key.mjs +12 -6
  17. package/files/transfer_key_registry.mjs +0 -7
  18. package/governance/branch.mjs +0 -11
  19. package/link/channel_mux.mjs +2 -0
  20. package/mailbox/importance.mjs +0 -33
  21. package/mailbox/settings.mjs +0 -9
  22. package/node/denylist.mjs +0 -24
  23. package/node/network.mjs +0 -51
  24. package/node/personal_block.mjs +0 -35
  25. package/node/reputation_store.mjs +0 -9
  26. package/node/retention_policy.mjs +0 -15
  27. package/package.json +1 -1
  28. package/registries/event_type.mjs +0 -12
  29. package/registries/inbound.mjs +0 -22
  30. package/reputation/engine.mjs +0 -11
  31. package/schemas/part_query.mjs +156 -0
  32. package/transport/ice_servers.mjs +0 -8
  33. package/transport/peer_identity_maps.mjs +0 -58
  34. package/transport/remote_user_room.mjs +0 -12
  35. package/transport/rtc_connection_budget.mjs +2 -0
  36. package/transport/user_room.mjs +2 -11
  37. package/trust_graph/cache.mjs +0 -13
  38. package/trust_graph/engine.mjs +0 -24
  39. package/utils/async_mutex.mjs +0 -9
  40. package/wire/part_fanout.mjs +0 -19
  41. package/wire/part_query.mjs +487 -0
  42. package/wire/part_query.tunables.json +14 -0
  43. package/wire/part_query_cache.mjs +94 -0
@@ -104,41 +104,6 @@ export async function loadPersonalBlockEntries(viewerEntityHash) {
104
104
  return normalizePersonalListEntries(data?.blocked || [])
105
105
  }
106
106
 
107
- /**
108
- * @param {string} viewerEntityHash 观看者实体
109
- * @param {object} subject 待检主体
110
- * @returns {Promise<boolean>} 是否被隐藏
111
- */
112
- export async function isHiddenBy(viewerEntityHash, subject) {
113
- return matchesPersonalListEntries(await loadPersonalHideEntries(viewerEntityHash), subject)
114
- }
115
-
116
- /**
117
- * @param {string} viewerEntityHash 观看者实体
118
- * @param {object} subject 待检主体
119
- * @returns {Promise<boolean>} 是否被拉黑
120
- */
121
- export async function isBlockedBy(viewerEntityHash, subject) {
122
- return matchesPersonalListEntries(await loadPersonalBlockEntries(viewerEntityHash), subject)
123
- }
124
-
125
- /**
126
- * @param {string} viewerEntityHash 观看者实体
127
- * @param {object} subject 待检主体
128
- * @returns {Promise<boolean>} 是否应过滤
129
- */
130
- export async function isFilteredByPersonalLists(viewerEntityHash, subject) {
131
- if (!viewerEntityHash) return false
132
- const [blocked, hidden, muted] = await Promise.all([
133
- loadPersonalBlockEntries(viewerEntityHash),
134
- loadPersonalHideEntries(viewerEntityHash),
135
- loadPersonalMuteEntries(viewerEntityHash),
136
- ])
137
- return matchesPersonalListEntries(blocked, subject)
138
- || matchesPersonalListEntries(hidden, subject)
139
- || matchesPersonalListEntries(muted, subject)
140
- }
141
-
142
107
  /**
143
108
  * @param {string} viewerEntityHash 本地可写实体
144
109
  * @param {string} targetEntityHash 目标
@@ -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,
@@ -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}
@@ -3,21 +3,6 @@ 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(连通子图,不用拓扑下标切片)。
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.5",
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() {
@@ -41,22 +41,6 @@ export function registerDeliveryInboundHandler(type, handler) {
41
41
  deliveryHandlers.set(String(type || '').trim(), handler)
42
42
  }
43
43
 
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
44
  /**
61
45
  * @param {InboundContext} ctx 入站上下文
62
46
  * @param {object} message 已校验的线载荷(含 type)
@@ -82,9 +66,3 @@ export async function dispatchDeliveryInbound(ctx, message) {
82
66
  if (!handler) return
83
67
  await handler(ctx, message)
84
68
  }
85
-
86
- /** @returns {void} 测试用 */
87
- export function clearInboundHandlers() {
88
- rpcHandlers.clear()
89
- deliveryHandlers.clear()
90
- }
@@ -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
- }
@@ -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
+ }
@@ -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 请求体字段
@@ -34,61 +34,3 @@ export function pruneStaleRosterEntries(peerToNode, nodeToPeer, livePeerIds) {
34
34
  }
35
35
  return stale
36
36
  }
37
-
38
- /**
39
- * 创建 peer id 与 nodeHash 双向映射及 roster 查询辅助。
40
- * @param {object} [opts] 选项
41
- * @param {() => Iterable<string>} [opts.getLivePeerIds] 获取当前在线 peer id 的回调
42
- * @param {(stale: Array<{ peerId: string, remoteNodeHash?: string }>) => void} [opts.onStalePruned] stale 条目被清理时的回调
43
- * @returns {{ peerToNode: Map<string, string>, nodeToPeer: Map<string, string>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string | undefined }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, onPeerLeave: (peerId: string) => void }} 映射与查询接口
44
- */
45
- export function createPeerIdentityMaps(opts = {}) {
46
- /** @type {Map<string, string>} */
47
- const peerToNode = new Map()
48
- /** @type {Map<string, string>} */
49
- const nodeToPeer = new Map()
50
- const getLivePeerIds = typeof opts.getLivePeerIds === 'function' ? opts.getLivePeerIds : null
51
- const onStalePruned = typeof opts.onStalePruned === 'function' ? opts.onStalePruned : null
52
-
53
- /**
54
- * 根据 live peer 集合清理 stale 映射。
55
- * @returns {void}
56
- */
57
- const reconcile = () => {
58
- if (!getLivePeerIds) return
59
- const stale = pruneStaleRosterEntries(peerToNode, nodeToPeer, getLivePeerIds())
60
- if (stale.length && onStalePruned) onStalePruned(stale)
61
- }
62
-
63
- return {
64
- peerToNode,
65
- nodeToPeer,
66
- /**
67
- * 返回当前 roster(会先 reconcile stale 条目)。
68
- * @returns {Array<{ peerId: string, remoteNodeHash: string | undefined }>} roster 列表
69
- */
70
- getRoster() {
71
- reconcile()
72
- return [...peerToNode.entries()].map(([peerId, remoteNodeHash]) => ({ peerId, remoteNodeHash }))
73
- },
74
- /**
75
- * 按 nodeHash 查找对应 peer id。
76
- * @param {string} targetNodeHash 目标节点 64 hex
77
- * @returns {string | null} 对端 id;无映射时为 null
78
- */
79
- getPeerIdByNodeHash(targetNodeHash) {
80
- reconcile()
81
- return nodeToPeer.get(String(targetNodeHash).trim().toLowerCase()) || null
82
- },
83
- /**
84
- * peer 离线时从映射中移除。
85
- * @param {string} peerId 离线的 peer id
86
- * @returns {void}
87
- */
88
- onPeerLeave(peerId) {
89
- const remote = peerToNode.get(peerId)
90
- if (remote) nodeToPeer.delete(remote)
91
- peerToNode.delete(peerId)
92
- },
93
- }
94
- }
@@ -95,15 +95,3 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
95
95
  inflights.set(key, task)
96
96
  return await task
97
97
  }
98
-
99
- /**
100
- * 释放目标节点的远端用户房间连接。
101
- * @param {string} targetNodeHash 目标节点 64 hex
102
- * @returns {void}
103
- */
104
- export function releaseRemoteUserRoom(targetNodeHash) {
105
- const key = targetNodeHash.toLowerCase()
106
- const slot = slots.get(key)
107
- if (slot) void Promise.resolve(slot.leave()).catch(() => { })
108
- slots.delete(key)
109
- }
@@ -162,6 +162,8 @@ const NON_CRITICAL_FED_ACTIONS = new Set([
162
162
  'fed_manifest_get',
163
163
  'fed_manifest_data',
164
164
  'part_invoke',
165
+ 'part_query_req',
166
+ 'part_query_res',
165
167
  'discovery_announce',
166
168
  'discovery_query',
167
169
  'char_rpc',
@@ -4,6 +4,7 @@ import { attachMailboxWire } from '../mailbox/wire.mjs'
4
4
  import { ensureNodeDefaults, getNodeHash } from '../node/identity.mjs'
5
5
  import { registerFederationRoomProvider } from '../registries/room_provider.mjs'
6
6
  import { attachPartWire } from '../wire/part_ingress.mjs'
7
+ import { attachPartQueryWire } from '../wire/part_query.mjs'
7
8
 
8
9
  import { listLinks, sendToNodeLink, subscribeScope, getLinkRegistry } from './link_registry.mjs'
9
10
  import { USER_ROOM_SCOPE } from './room_scopes.mjs'
@@ -105,6 +106,7 @@ async function ensureNodeScopeRuntime(ctx) {
105
106
  const wire = createNodeScopeWire()
106
107
  nodeScopeWire = wire
107
108
  attachPartWire({ replicaUsername: ctx.replicaUsername }, wire)
109
+ attachPartQueryWire({ replicaUsername: ctx.replicaUsername }, wire)
108
110
  attachMailboxWire({ replicaUsername: ctx.replicaUsername }, wire)
109
111
  attachUserRoomChunkHandlers(ctx.replicaUsername || '', wire)
110
112
  for (const hook of nodeScopeWireHooks)
@@ -217,17 +219,6 @@ export async function ensureUserRoom(ctx = {}) {
217
219
  return await userRoomInflight
218
220
  }
219
221
 
220
- /**
221
- * @returns {void}
222
- */
223
- export function invalidateUserRoom() {
224
- userRoomSlot = null
225
- userRoomInflight = null
226
- nodeScopeWire = null
227
- nodeScopeReplicaUsername = ''
228
- nodeScopeCleanup = null
229
- }
230
-
231
222
  /**
232
223
  * @param {string} username 副本用户名
233
224
  * @param {string} actionName 节点 scope action
@@ -4,13 +4,6 @@ let revision = 0
4
4
 
5
5
  const DEFAULT_TTL_MS = 30_000
6
6
 
7
- /**
8
- * @returns {number} 当前 revision
9
- */
10
- export function getTrustGraphRevision() {
11
- return revision
12
- }
13
-
14
7
  /**
15
8
  * @returns {void}
16
9
  */
@@ -36,9 +29,3 @@ export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_M
36
29
  cacheByUsername.set(key, { graph, builtAt: now, revision })
37
30
  return graph
38
31
  }
39
-
40
- /** @returns {void} 测试用 */
41
- export function clearTrustGraphCache() {
42
- cacheByUsername.clear()
43
- revision = 0
44
- }
@@ -17,30 +17,6 @@ import trustGraphTunables from './tunables.json' with { type: 'json' }
17
17
  * }} TrustGraphInputs
18
18
  */
19
19
 
20
- /**
21
- * @returns {typeof trustGraphTunables} 默认 tunables
22
- */
23
- export function defaultTrustGraphTunables() {
24
- return trustGraphTunables
25
- }
26
-
27
- /**
28
- * @param {Map<string, TrustNode>} byNode 累积图
29
- * @param {string} scopeId scope 标识
30
- * @param {string} nodeHash 64 位十六进制
31
- * @param {number} score 信誉分
32
- */
33
- export function mergeTrustNode(byNode, scopeId, nodeHash, score) {
34
- const previous = byNode.get(nodeHash)
35
- if (previous) {
36
- const seenCount = previous.scopeIds.length
37
- previous.score = (previous.score * seenCount + score) / (seenCount + 1)
38
- if (!previous.scopeIds.includes(scopeId)) previous.scopeIds.push(scopeId)
39
- return
40
- }
41
- byNode.set(nodeHash, { nodeHash, score, scopeIds: [scopeId] })
42
- }
43
-
44
20
  /**
45
21
  * @param {TrustGraphInputs} inputs 图输入
46
22
  * @param {typeof trustGraphTunables} [tunables] tunables
@@ -58,12 +58,3 @@ export async function withAsyncMutex(key, fn) {
58
58
  }
59
59
  })
60
60
  }
61
-
62
- /**
63
- * @param {string} keyPrefix 前缀
64
- * @returns {(key: string, fn: () => Promise<T>) => Promise<T>} 带前缀的 mutex 函数
65
- * @template T
66
- */
67
- export function asyncMutexForPrefix(keyPrefix) {
68
- return (key, fn) => withAsyncMutex(`${keyPrefix}:${key}`, fn)
69
- }
@@ -50,22 +50,3 @@ export async function collectPartInvokeResponses(username, partpath, invoke, tim
50
50
 
51
51
  return waitForResponses
52
52
  }
53
-
54
- /**
55
- * 单向 part_invoke fanout(mailbox put 等)。
56
- * @param {string} username 用户
57
- * @param {string} partpath part 路径
58
- * @param {import('./part_invoke.mjs').PartInvoke} invoke 调用体
59
- * @param {number} limit fanout 上限
60
- * @param {string} [nodeHash] 来源节点
61
- * @param {string} [groupId] 群上下文
62
- * @returns {Promise<number>} 发送次数
63
- */
64
- export async function fanoutPartInvoke(username, partpath, invoke, limit, nodeHash, groupId) {
65
- return requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).fanoutToTopNodes(username, 'part_invoke', buildPartInvokePayload({
66
- partpath,
67
- invoke,
68
- nodeHash,
69
- groupId,
70
- }), limit)
71
- }