@steve02081504/fount-p2p 0.0.23 → 0.0.25

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 (67) hide show
  1. package/README.md +8 -6
  2. package/core/object.mjs +9 -0
  3. package/core/partpath.mjs +10 -0
  4. package/dag/storage.mjs +0 -5
  5. package/dag/strip_extensions.mjs +1 -3
  6. package/{wire/part_query_cache.mjs → federation/part_query/cache.mjs} +7 -7
  7. package/{wire/part_query.mjs → federation/part_query/runtime.mjs} +36 -77
  8. package/files/assemble.mjs +5 -5
  9. package/files/assemble_stream.mjs +2 -2
  10. package/files/chunk/fetch.mjs +101 -0
  11. package/files/chunk/pending.mjs +59 -0
  12. package/files/{chunk_provider_registry.mjs → chunk/provider_registry.mjs} +4 -5
  13. package/files/chunk/responder.mjs +104 -0
  14. package/files/{chunk_store.mjs → chunk/store.mjs} +2 -2
  15. package/files/{chunk_fetch_verify.mjs → chunk/verify.mjs} +2 -2
  16. package/files/evfs.mjs +18 -19
  17. package/files/fed/fetch_shared.mjs +39 -0
  18. package/files/fed/responder.mjs +47 -0
  19. package/files/{acl.mjs → manifest/acl.mjs} +4 -4
  20. package/files/{manifest_acl_registry.mjs → manifest/acl_registry.mjs} +14 -17
  21. package/files/{manifest_fetch.mjs → manifest/fetch.mjs} +47 -52
  22. package/files/{manifest.mjs → manifest/normalize.mjs} +2 -2
  23. package/files/manifest/pending.mjs +53 -0
  24. package/files/{public_manifest.mjs → manifest/public.mjs} +12 -12
  25. package/files/transfer_key.mjs +2 -2
  26. package/files/transfer_key_registry.mjs +7 -7
  27. package/index.mjs +4 -4
  28. package/infra/service.mjs +1 -1
  29. package/link/handshake.mjs +0 -3
  30. package/mailbox/deliver_or_store.mjs +0 -2
  31. package/mailbox/parse.mjs +1 -1
  32. package/mailbox/store.mjs +0 -9
  33. package/mailbox/wire.mjs +24 -16
  34. package/node/reputation_sync.mjs +33 -29
  35. package/package.json +10 -10
  36. package/registries/inbound.mjs +19 -9
  37. package/registries/part_path.mjs +3 -8
  38. package/schemas/discovery.mjs +1 -1
  39. package/schemas/federation_pull.mjs +1 -1
  40. package/schemas/part_query.mjs +6 -5
  41. package/transport/group_link_set.mjs +2 -2
  42. package/transport/link_registry.mjs +2 -2
  43. package/transport/node_scope/features.mjs +166 -0
  44. package/transport/node_scope/wire.mjs +143 -0
  45. package/transport/remote_user_room.mjs +0 -1
  46. package/{rooms → transport}/scoped_link.mjs +1 -1
  47. package/transport/user_room.mjs +15 -28
  48. package/utils/fetch_wait.mjs +98 -0
  49. package/wire/adapter.mjs +13 -0
  50. package/wire/ingress.mjs +1 -8
  51. package/wire/part/fanout.mjs +99 -0
  52. package/wire/part/group.mjs +45 -0
  53. package/wire/{part_ingress.mjs → part/ingress.mjs} +58 -67
  54. package/wire/{part_invoke.mjs → part/invoke.mjs} +1 -22
  55. package/wire/part/pending.mjs +32 -0
  56. package/wire/part/query.mjs +91 -0
  57. package/wire/subscribe.mjs +13 -0
  58. package/federation/chunk_fetch_pending.mjs +0 -124
  59. package/federation/manifest_fetch_pending.mjs +0 -88
  60. package/files/chunk_fetch.mjs +0 -99
  61. package/files/chunk_responder.mjs +0 -142
  62. package/transport/advert_ingest.mjs +0 -17
  63. package/transport/node_scope.mjs +0 -289
  64. package/wire/group_part.mjs +0 -43
  65. package/wire/part_common.mjs +0 -55
  66. package/wire/part_fanout.mjs +0 -52
  67. /package/{wire → schemas}/part_query.tunables.json +0 -0
@@ -0,0 +1,166 @@
1
+ import { attachNodeScopeFedResponder } from '../../files/fed/responder.mjs'
2
+ import { attachMailboxWire } from '../../mailbox/wire.mjs'
3
+ import { attachPartWire } from '../../wire/part/ingress.mjs'
4
+ import { attachPartQueryWire } from '../../wire/part/query.mjs'
5
+
6
+ import {
7
+ clearNodeScopeSubscribe,
8
+ ensureNodeScope,
9
+ getNodeScopeContext,
10
+ getNodeScopeWire,
11
+ } from './wire.mjs'
12
+
13
+ /** @typedef {import('./wire.mjs').NodeScopeWire} NodeScopeWire */
14
+ /** @typedef {import('./wire.mjs').NodeScopeContext} NodeScopeContext */
15
+
16
+ /** @type {Set<() => void>} */
17
+ const nodeScopeFeatureDisposers = new Set()
18
+
19
+ /** @type {Map<string, { count: number, disposeCore: () => void }>} */
20
+ const featureAttachRefs = new Map()
21
+
22
+ /**
23
+ * @param {() => void} dispose - feature 卸载函数
24
+ * @returns {() => void} 包装后的 dispose(同时从跟踪集移除)
25
+ */
26
+ function trackFeatureDisposer(dispose) {
27
+ nodeScopeFeatureDisposers.add(dispose)
28
+ return () => {
29
+ dispose()
30
+ nodeScopeFeatureDisposers.delete(dispose)
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 同一 feature 多次 attach 共享一份 wire;dispose 引用计数归零才卸。
36
+ * @param {string} key - feature 去重键
37
+ * @param {() => () => void} attachCore - 首次 attach 时执行,返回核心 dispose
38
+ * @returns {() => void} 引用计数包装的 dispose
39
+ */
40
+ function attachFeatureRefCounted(key, attachCore) {
41
+ let entry = featureAttachRefs.get(key)
42
+ if (!entry) {
43
+ entry = { count: 0, disposeCore: attachCore() }
44
+ featureAttachRefs.set(key, entry)
45
+ }
46
+ entry.count++
47
+ return trackFeatureDisposer(() => {
48
+ const current = featureAttachRefs.get(key)
49
+ if (!current) return
50
+ current.count--
51
+ if (current.count > 0) return
52
+ current.disposeCore()
53
+ featureAttachRefs.delete(key)
54
+ })
55
+ }
56
+
57
+ /**
58
+ * @param {string} key - feature 去重键
59
+ * @param {(wire: NodeScopeWire, context: NodeScopeContext) => () => void} attachCore - 首次 attach
60
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
61
+ * @returns {() => void} 引用计数包装的 dispose
62
+ */
63
+ export function attachNodeScopeFeature(key, attachCore, options = {}) {
64
+ ensureNodeScope(options)
65
+ return attachFeatureRefCounted(key, () => attachCore(getNodeScopeWire(), getNodeScopeContext()))
66
+ }
67
+
68
+ /** @type {Record<string, (wire: NodeScopeWire, context: NodeScopeContext) => () => void>} */
69
+ const builtinFeatures = {
70
+ /**
71
+ * @param {NodeScopeWire} wire node scope wire
72
+ * @param {NodeScopeContext} context 入站上下文
73
+ * @returns {() => void} 取消挂载的 dispose
74
+ */
75
+ mailbox: (wire, context) => attachMailboxWire(context, wire),
76
+ /**
77
+ * @param {NodeScopeWire} wire node scope wire
78
+ * @param {NodeScopeContext} context 入站上下文
79
+ * @returns {() => void} 取消挂载的 dispose
80
+ */
81
+ part: (wire, context) => attachPartWire(context, wire),
82
+ /**
83
+ * @param {NodeScopeWire} wire node scope wire
84
+ * @param {NodeScopeContext} context 入站上下文
85
+ * @returns {() => void} 取消挂载的 dispose
86
+ */
87
+ partQuery: (wire, context) => attachPartQueryWire(context, wire),
88
+ /**
89
+ * @param {NodeScopeWire} wire node scope wire
90
+ * @returns {() => void} 取消挂载的 dispose
91
+ */
92
+ chunks: wire => attachNodeScopeFedResponder(wire),
93
+ }
94
+
95
+ /**
96
+ * @param {keyof typeof builtinFeatures} key - 内置 feature 名
97
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
98
+ * @returns {() => void} 引用计数包装的 dispose
99
+ */
100
+ function attachBuiltin(key, options = {}) {
101
+ return attachNodeScopeFeature(key, builtinFeatures[key], options)
102
+ }
103
+
104
+ /**
105
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
106
+ * @returns {() => void} 取消挂载的 dispose
107
+ */
108
+ export function attachNodeScopeMailbox(options = {}) {
109
+ return attachBuiltin('mailbox', options)
110
+ }
111
+
112
+ /**
113
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
114
+ * @returns {() => void} 取消挂载的 dispose
115
+ */
116
+ export function attachNodeScopePart(options = {}) {
117
+ return attachBuiltin('part', options)
118
+ }
119
+
120
+ /**
121
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
122
+ * @returns {() => void} 取消挂载的 dispose
123
+ */
124
+ export function attachNodeScopePartQuery(options = {}) {
125
+ return attachBuiltin('partQuery', options)
126
+ }
127
+
128
+ /**
129
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
130
+ * @returns {() => void} 取消挂载的 dispose
131
+ */
132
+ export function attachNodeScopeChunks(options = {}) {
133
+ return attachBuiltin('chunks', options)
134
+ }
135
+
136
+ /**
137
+ * 全业务 preset(part / partQuery / mailbox / chunks)。
138
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
139
+ * @returns {() => void} 取消全部 preset 挂载的 dispose
140
+ */
141
+ export function attachNodeScopeDefaultFeatures(options = {}) {
142
+ const disposers = [
143
+ attachNodeScopePart(options),
144
+ attachNodeScopePartQuery(options),
145
+ attachNodeScopeMailbox(options),
146
+ attachNodeScopeChunks(options),
147
+ ]
148
+ return () => {
149
+ for (const dispose of disposers) dispose()
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 卸掉 feature 挂载;可选保留 node scope 订阅。
155
+ * @param {{ keepSubscribe?: boolean }} [options] - true 时保留 node scope 订阅
156
+ * @returns {void}
157
+ */
158
+ export function stopNodeScopeRuntime(options = {}) {
159
+ for (const dispose of [...nodeScopeFeatureDisposers]) dispose()
160
+ nodeScopeFeatureDisposers.clear()
161
+ for (const entry of featureAttachRefs.values())
162
+ try { entry.disposeCore() } catch { /* ignore */ }
163
+ featureAttachRefs.clear()
164
+ if (options.keepSubscribe) return
165
+ clearNodeScopeSubscribe()
166
+ }
@@ -0,0 +1,143 @@
1
+ import { subscribeScope, sendToNodeLink } from '../link_registry.mjs'
2
+
3
+ /** @typedef {import('../../wire/adapter.mjs').WireAdapter} NodeScopeWire */
4
+ /** @typedef {{ replicaUsername: string }} NodeScopeContext */
5
+
6
+ /** @type {Map<string, Set<(payload: unknown, peerId: string) => void>>} */
7
+ const nodeActionHandlers = new Map()
8
+
9
+ /** @type {Set<(context: NodeScopeContext, wire: NodeScopeWire) => void>} */
10
+ const nodeScopeWireHooks = new Set()
11
+
12
+ /** @type {NodeScopeContext} */
13
+ const nodeScopeContext = { replicaUsername: '' }
14
+
15
+ /** @type {NodeScopeWire | null} */
16
+ let nodeScopeWire = null
17
+
18
+ /** @type {(() => void) | null} */
19
+ let nodeScopeSubscribeCleanup = null
20
+
21
+ /**
22
+ * @returns {NodeScopeWire} node scope 的 wire 适配器
23
+ */
24
+ function createNodeScopeWire() {
25
+ return {
26
+ /**
27
+ * @param {string} name action 名
28
+ * @param {(payload: unknown, peerId: string) => void} handler 回调
29
+ * @returns {() => void} 取消订阅
30
+ */
31
+ on(name, handler) {
32
+ if (!nodeActionHandlers.has(name)) nodeActionHandlers.set(name, new Set())
33
+ nodeActionHandlers.get(name).add(handler)
34
+ return () => {
35
+ const set = nodeActionHandlers.get(name)
36
+ if (!set) return
37
+ set.delete(handler)
38
+ if (!set.size) nodeActionHandlers.delete(name)
39
+ }
40
+ },
41
+ /**
42
+ * @param {string} name action 名
43
+ * @param {unknown} payload 载荷
44
+ * @param {string} peerId 目标 peer
45
+ * @returns {void}
46
+ */
47
+ send(name, payload, peerId) {
48
+ if (!peerId) return
49
+ void sendToNodeLink(peerId, { scope: 'node', action: name, payload }).catch(() => { })
50
+ },
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @param {string} action - action 名
56
+ * @returns {boolean} 是否已挂载处理器
57
+ */
58
+ export function hasNodeScopeAction(action) {
59
+ return (nodeActionHandlers.get(action)?.size ?? 0) > 0
60
+ }
61
+
62
+ /**
63
+ * @param {string} action - action 名
64
+ * @returns {number} 已注册的处理器数量
65
+ */
66
+ export function countNodeScopeActionHandlers(action) {
67
+ return nodeActionHandlers.get(action)?.size ?? 0
68
+ }
69
+
70
+ /**
71
+ * 测试/调试:直接派发已挂载的 node action。
72
+ * @param {string} action - action 名
73
+ * @param {unknown} payload - 载荷
74
+ * @param {string} peerId - 发送方 nodeHash
75
+ * @returns {boolean} 是否有处理器被调用
76
+ */
77
+ export function dispatchNodeScopeAction(action, payload, peerId) {
78
+ const handlers = nodeActionHandlers.get(action)
79
+ if (!handlers?.size) return false
80
+ for (const handler of handlers)
81
+ try { handler(payload, peerId) } catch { /* ignore */ }
82
+ return true
83
+ }
84
+
85
+ /**
86
+ * @returns {NodeScopeWire | null} 当前 wire,未 ensure 时为 null
87
+ */
88
+ export function getNodeScopeWire() {
89
+ return nodeScopeWire
90
+ }
91
+
92
+ /**
93
+ * @returns {NodeScopeContext} 可变 node scope 上下文
94
+ */
95
+ export function getNodeScopeContext() {
96
+ return nodeScopeContext
97
+ }
98
+
99
+ /**
100
+ * 只订阅 node scope 派发,不挂任何 feature。
101
+ * @param {{ replicaUsername?: string }} [options] - 可选副本用户名
102
+ * @returns {() => void} 取消订阅的 dispose
103
+ */
104
+ export function ensureNodeScope(options = {}) {
105
+ if (options.replicaUsername != null)
106
+ nodeScopeContext.replicaUsername = String(options.replicaUsername)
107
+ if (nodeScopeSubscribeCleanup) return nodeScopeSubscribeCleanup
108
+ nodeScopeSubscribeCleanup = subscribeScope('node', (senderNodeHash, envelope) => {
109
+ const handlers = nodeActionHandlers.get(envelope.action)
110
+ if (!handlers?.size) return
111
+ for (const handler of handlers)
112
+ try { handler(envelope.payload, senderNodeHash) } catch { /* ignore */ }
113
+ })
114
+ if (!nodeScopeWire) {
115
+ nodeScopeWire = createNodeScopeWire()
116
+ for (const hook of nodeScopeWireHooks)
117
+ try { hook(nodeScopeContext, nodeScopeWire) } catch { /* ignore */ }
118
+ }
119
+ return nodeScopeSubscribeCleanup
120
+ }
121
+
122
+ /**
123
+ * 在 node scope wire 创建时(或已存在时立即)回调;shell 用此挂自定义 action。
124
+ * @param {(context: NodeScopeContext, wire: NodeScopeWire) => void} hook - wire 就绪回调
125
+ * @returns {() => void} 取消注册的 dispose
126
+ */
127
+ export function registerNodeScopeWireHook(hook) {
128
+ nodeScopeWireHooks.add(hook)
129
+ if (nodeScopeWire)
130
+ try { hook(nodeScopeContext, nodeScopeWire) } catch { /* ignore */ }
131
+ return () => nodeScopeWireHooks.delete(hook)
132
+ }
133
+
134
+ /**
135
+ * 卸掉 scope 订阅与 wire(feature 须先卸)。
136
+ * @returns {void}
137
+ */
138
+ export function clearNodeScopeSubscribe() {
139
+ nodeScopeSubscribeCleanup?.()
140
+ nodeScopeSubscribeCleanup = null
141
+ nodeScopeWire = null
142
+ nodeActionHandlers.clear()
143
+ }
@@ -28,7 +28,6 @@ registerFederationRoomProvider('remote-user-room', () => {
28
28
  * @returns {Promise<RemoteUserRoomSlot>} 房间槽
29
29
  */
30
30
  export async function ensureRemoteUserRoom(username, targetNodeHash) {
31
- void username
32
31
  const key = targetNodeHash.toLowerCase()
33
32
  const existing = slots.get(key)
34
33
  if (existing) return existing
@@ -1,4 +1,4 @@
1
- import { createGroupLinkSet } from '../transport/group_link_set.mjs'
1
+ import { createGroupLinkSet } from './group_link_set.mjs'
2
2
 
3
3
  /**
4
4
  * scoped 房间:group_link_set 的薄预设(任意 scope + allowNode + 发现即拨)。
@@ -5,21 +5,12 @@ import { registerFederationRoomProvider } from '../registries/room_provider.mjs'
5
5
  import { shuffleInPlace } from '../utils/shuffle.mjs'
6
6
 
7
7
  import { getLinkRegistry, listLinks, sendToNodeLink } from './link_registry.mjs'
8
- import {
9
- attachUserRoomDefaultWires,
10
- ensureNodeScope,
11
- } from './node_scope.mjs'
8
+ import { attachNodeScopeDefaultFeatures } from './node_scope/features.mjs'
9
+ import { ensureNodeScope } from './node_scope/wire.mjs'
12
10
  import { USER_ROOM_SCOPE } from './room_scopes.mjs'
13
11
 
14
- /**
15
- * 经 node scope 向对端发 action。
16
- * @param {string} peerId 对端 nodeHash
17
- * @param {string} action 动作名
18
- * @param {unknown} payload 载荷
19
- * @returns {Promise<boolean>} 是否发出
20
- */
21
- const sendNodeAction = (peerId, action, payload) =>
22
- sendToNodeLink(peerId, { scope: 'node', action, payload })
12
+ /** User Room 随机 peer 转发默认上限 */
13
+ const USER_ROOM_PEER_FANOUT_DEFAULT = 6
23
14
 
24
15
  /** @type {Promise<UserRoomSlot> | null} */
25
16
  let userRoomInflight = null
@@ -50,7 +41,7 @@ export function activeLinkRoster() {
50
41
  * roomId: string
51
42
  * roomSecret: string
52
43
  * room: object | null
53
- * sendToPeer: (peerId: string, actionName: string, payload: unknown) => void
44
+ * sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>
54
45
  * getRoster: () => Array<{ peerId: string, remoteNodeHash: string | undefined }>
55
46
  * getPeerIdByNodeHash: (nodeHash: string) => string | null
56
47
  * }} UserRoomSlot
@@ -74,7 +65,7 @@ registerFederationRoomProvider('user-room', () => {
74
65
  * @param {string} peerId - 目标 peer
75
66
  * @param {string} actionName - node scope action 名
76
67
  * @param {unknown} payload - 载荷
77
- * @returns {void}
68
+ * @returns {Promise<boolean>} 是否发出
78
69
  */
79
70
  sendToPeer: (peerId, actionName, payload) => slot.sendToPeer(peerId, actionName, payload),
80
71
  }]
@@ -95,7 +86,7 @@ export function resolveUserRoomCredentials() {
95
86
  }
96
87
 
97
88
  /**
98
- * 用户房间槽 + runtime;默认不挂业务 wire(用 `attachUserRoomDefaultWires` / `attachDefaultWires: true`)。
89
+ * 用户房间槽 + runtime;默认不挂业务 wire(用 `attachNodeScopeDefaultFeatures` / `attachDefaultWires: true`)。
99
90
  * @param {{ replicaUsername?: string, attachDefaultWires?: boolean }} [options] - 副本用户名与是否挂载默认 wire
100
91
  * @returns {Promise<UserRoomSlot>} 用户房间槽
101
92
  */
@@ -105,7 +96,7 @@ export async function ensureUserRoom(options = {}) {
105
96
  ensureNodeScope({ replicaUsername: options.replicaUsername })
106
97
  if (userRoomSlot) {
107
98
  if (attachDefaultWires && !userRoomDefaultWiresDispose)
108
- userRoomDefaultWiresDispose = attachUserRoomDefaultWires({ replicaUsername: options.replicaUsername })
99
+ userRoomDefaultWiresDispose = attachNodeScopeDefaultFeatures({ replicaUsername: options.replicaUsername })
109
100
  return userRoomSlot
110
101
  }
111
102
  if (userRoomInflight) return await userRoomInflight
@@ -115,7 +106,7 @@ export async function ensureUserRoom(options = {}) {
115
106
  await getLinkRegistry().ensureRuntime()
116
107
  ensureNodeScope({ replicaUsername: options.replicaUsername })
117
108
  if (attachDefaultWires && !userRoomDefaultWiresDispose)
118
- userRoomDefaultWiresDispose = attachUserRoomDefaultWires({ replicaUsername: options.replicaUsername })
109
+ userRoomDefaultWiresDispose = attachNodeScopeDefaultFeatures({ replicaUsername: options.replicaUsername })
119
110
  const creds = resolveUserRoomCredentials()
120
111
  userRoomSlot = {
121
112
  roomId: creds.roomId,
@@ -125,10 +116,10 @@ export async function ensureUserRoom(options = {}) {
125
116
  * @param {string} peerId - 目标 peer
126
117
  * @param {string} actionName - node scope action 名
127
118
  * @param {unknown} payload - 载荷
128
- * @returns {void}
119
+ * @returns {Promise<boolean>} 是否发出
129
120
  */
130
121
  sendToPeer(peerId, actionName, payload) {
131
- void sendNodeAction(peerId, actionName, payload).catch(() => { })
122
+ return sendToNodeLink(peerId, { scope: 'node', action: actionName, payload }).catch(() => false)
132
123
  },
133
124
  /**
134
125
  * @returns {Array<{ peerId: string, remoteNodeHash: string }>} 当前活跃链路 roster
@@ -162,16 +153,15 @@ export async function ensureUserRoom(options = {}) {
162
153
  * @returns {Promise<number>} 成功转发的 peer 数
163
154
  */
164
155
  export async function deliverToUserRoomPeers(username, actionName, payload, exceptPeerId = null, limit) {
165
- void username
166
- const { USER_ROOM_PEER_FANOUT_DEFAULT } = await import('../wire/part_common.mjs')
167
156
  const fanoutLimit = limit ?? USER_ROOM_PEER_FANOUT_DEFAULT
168
- const body = { ...payload, nodeHash: getNodeHash() }
157
+ if (fanoutLimit <= 0) return 0
158
+ const slot = await ensureUserRoom({ replicaUsername: username })
169
159
  let sent = 0
170
- const peers = shuffleInPlace(activeLinkRoster()
160
+ const peers = shuffleInPlace(slot.getRoster()
171
161
  .filter(({ peerId }) => peerId && peerId !== exceptPeerId))
172
162
  for (const { peerId } of peers)
173
163
  try {
174
- if (await sendNodeAction(peerId, actionName, body))
164
+ if (await slot.sendToPeer(peerId, actionName, { ...payload, nodeHash: getNodeHash() }))
175
165
  sent++
176
166
  if (sent >= fanoutLimit) break
177
167
  }
@@ -179,6 +169,3 @@ export async function deliverToUserRoomPeers(username, actionName, payload, exce
179
169
 
180
170
  return sent
181
171
  }
182
-
183
- /** 再导出:node-scope 订阅与默认 wires(见 `node_scope.mjs`)。 */
184
- export { attachUserRoomDefaultWires, ensureNodeScope } from './node_scope.mjs'
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 有界 pending fetch 等待表(chunk / manifest 等入站响应槽)。
3
+ * @template T
4
+ * @param {{ maxSize: number }} options 容量
5
+ * @returns {{
6
+ * pending: Map<string, { expectedKey: string, timer: ReturnType<typeof setTimeout>, finish: (value: T | null | Error) => void, handle: { done: Promise<T | null>, cancel: () => void } }>,
7
+ * register: (key: string, expectedKey: string, timeoutMs: number, options?: { rejectOnTimeout?: boolean }) => { done: Promise<T | null>, cancel: () => void },
8
+ * peek: (key: string) => { expectedKey: string, timer: ReturnType<typeof setTimeout>, finish: (value: T | null | Error) => void, handle: { done: Promise<T | null>, cancel: () => void } } | undefined,
9
+ * settle: (key: string, value: T | null | Error) => boolean,
10
+ * }} 等待表 API
11
+ */
12
+ export function createFetchWaitTable({ maxSize }) {
13
+ /** @type {Map<string, { expectedKey: string, timer: ReturnType<typeof setTimeout>, finish: (value: T | null | Error) => void, handle: { done: Promise<T | null>, cancel: () => void } }>} */
14
+ const pending = new Map()
15
+
16
+ /**
17
+ * @param {string} key 等待键
18
+ * @param {T | null | Error} value 结果或错误
19
+ * @returns {boolean} 是否命中并完成
20
+ */
21
+ function settle(key, value) {
22
+ const entry = pending.get(key)
23
+ if (!entry) return false
24
+ clearTimeout(entry.timer)
25
+ pending.delete(key)
26
+ entry.finish(value)
27
+ return true
28
+ }
29
+
30
+ return {
31
+ pending,
32
+
33
+ /**
34
+ * @param {string} key 唯一等待键
35
+ * @param {string} expectedKey 期望匹配键
36
+ * @param {number} timeoutMs 超时
37
+ * @param {{ rejectOnTimeout?: boolean }} [options] 超时是否 reject
38
+ * @returns {{ done: Promise<T | null>, cancel: () => void }} 等待句柄
39
+ */
40
+ register(key, expectedKey, timeoutMs, options = {}) {
41
+ if (!key)
42
+ return {
43
+ done: Promise.resolve(null),
44
+ /** @returns {void} */
45
+ cancel: () => { },
46
+ }
47
+
48
+ const existing = pending.get(key)
49
+ if (existing) return existing.handle
50
+
51
+ if (pending.size >= maxSize)
52
+ return {
53
+ done: Promise.resolve(null),
54
+ /** @returns {void} */
55
+ cancel: () => { },
56
+ }
57
+
58
+ /** @type {(value: T | null | Error) => void} */
59
+ let finish
60
+ const done = new Promise((resolve, reject) => {
61
+ /**
62
+ * @param {T | null | Error} value 完成值或错误
63
+ * @returns {void}
64
+ */
65
+ finish = value => {
66
+ if (value instanceof Error) reject(value)
67
+ else resolve(value)
68
+ }
69
+ })
70
+
71
+ const timer = setTimeout(() => {
72
+ pending.delete(key)
73
+ if (options.rejectOnTimeout) finish(new Error('fetch timeout'))
74
+ else finish(null)
75
+ }, timeoutMs)
76
+
77
+ /** @type {{ done: Promise<T | null>, cancel: () => void }} */
78
+ const handle = {
79
+ done,
80
+ /** @returns {void} 取消等待 */
81
+ cancel: () => settle(key, null),
82
+ }
83
+ pending.set(key, { expectedKey, timer, finish, handle })
84
+ return handle
85
+ },
86
+
87
+ /**
88
+ * 只读查看,不移除。
89
+ * @param {string} key 等待键
90
+ * @returns {{ expectedKey: string, timer: ReturnType<typeof setTimeout>, finish: (value: T | null | Error) => void, handle: { done: Promise<T | null>, cancel: () => void } } | undefined} 等待条目
91
+ */
92
+ peek(key) {
93
+ return pending.get(key)
94
+ },
95
+
96
+ settle,
97
+ }
98
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @typedef {{
3
+ * on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void
4
+ * send: (name: string, payload: unknown, peerId: string | null) => void
5
+ * }} WireAdapter
6
+ */
7
+
8
+ /**
9
+ * @typedef {{ replicaUsername?: string }} WireContext
10
+ */
11
+
12
+ /** Wire 适配器与上下文的类型导出(运行时代码见各 wire 模块)。 */
13
+ export {}
package/wire/ingress.mjs CHANGED
@@ -4,14 +4,7 @@
4
4
  import { Buffer } from 'node:buffer'
5
5
 
6
6
  import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
7
-
8
- /**
9
- * @param {unknown} value 待判定值
10
- * @returns {value is Record<string, unknown>} 是否为非 null 的普通对象(非数组)
11
- */
12
- export function isPlainObject(value) {
13
- return value != null && !Array.isArray(value) && typeof value === 'object'
14
- }
7
+ import { isPlainObject } from '../core/object.mjs'
15
8
 
16
9
  /**
17
10
  * 解析 WebSocket / Trystero 入站 JSON 帧。
@@ -0,0 +1,99 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
3
+ import { getNodeHash } from '../../node/identity.mjs'
4
+ import { DEFAULT_TRUST_GRAPH_OWNER, requireTrustGraphProvider } from '../../trust_graph/registry.mjs'
5
+
6
+ import { unwrapPartInvokeResult } from './invoke.mjs'
7
+ import { pendingPartInvoke } from './pending.mjs'
8
+
9
+ /** @typedef {import('./invoke.mjs').PartInvokeResponse} PartInvokeResponse */
10
+ /** @typedef {import('./invoke.mjs').PartInvoke} PartInvoke */
11
+
12
+ /** 时间线 part_timeline_put fanout 上限 */
13
+ export const TIMELINE_FANOUT_LIMIT = 8
14
+ /** part_invoke RPC collect 默认响应数 */
15
+ export const PART_INVOKE_FANOUT_DEFAULT = 6
16
+
17
+ /**
18
+ * @param {string} partpath part 路径
19
+ * @param {PartInvoke} invoke 调用体
20
+ * @param {string} [nodeHash] 来源节点
21
+ * @param {string} [requestId] RPC 请求 id
22
+ * @returns {object} part_invoke 线载荷
23
+ */
24
+ function buildPartInvokePayload(partpath, invoke, nodeHash, requestId) {
25
+ return {
26
+ partpath,
27
+ invoke,
28
+ ...nodeHash ? { nodeHash } : {},
29
+ ...requestId ? { requestId } : {},
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @param {PartInvokeResponse[]} results collect 原始结果
35
+ * @returns {object[]} 仅含成功 result 的载荷
36
+ */
37
+ export function partInvokeDataRows(results) {
38
+ /** @type {object[]} */
39
+ const rows = []
40
+ for (const row of results) {
41
+ const data = unwrapPartInvokeResult(row)
42
+ if (data != null) rows.push(/** @type {object} */ data)
43
+ }
44
+ return rows
45
+ }
46
+
47
+ /**
48
+ * @param {PartInvokeResponse[]} results collect 原始结果
49
+ * @returns {string[]} 邻居返回的错误信息
50
+ */
51
+ export function partInvokeErrorMessages(results) {
52
+ /** @type {string[]} */
53
+ const errors = []
54
+ for (const row of results) {
55
+ const message = row?.error?.message
56
+ if (message) errors.push(message)
57
+ }
58
+ return errors
59
+ }
60
+
61
+ /**
62
+ * 向 trust-graph top-K 扇出 part_invoke,收集 part_invoke_response。
63
+ * 调用方须已挂载 `attachPartWire`(否则收不到 response)。
64
+ * @param {string} username trust graph 上下文
65
+ * @param {string} partpath part 路径
66
+ * @param {PartInvoke} invoke 调用体
67
+ * @param {number} [timeoutMs=2500] 超时
68
+ * @param {number} [maxResponses=6] 最多响应数
69
+ * @returns {Promise<PartInvokeResponse[]>} 邻居 PartInvokeResponse(含 error)
70
+ */
71
+ export async function collectPartInvokeResponses(username, partpath, invoke, timeoutMs = 2500, maxResponses = PART_INVOKE_FANOUT_DEFAULT) {
72
+ const requestId = randomUUID()
73
+ const nodeHash = getNodeHash()
74
+ /** @type {PartInvokeResponse[]} */
75
+ const responses = []
76
+
77
+ const waitForResponses = new Promise(resolve => {
78
+ /** 超时或收满响应后清理 pending 并完成 Promise。 */
79
+ const finish = () => {
80
+ clearTimeout(timer)
81
+ pendingPartInvoke.delete(requestId)
82
+ resolve(responses)
83
+ }
84
+ const timer = setTimeout(finish, timeoutMs)
85
+ pendingPartInvoke.set(requestId, { responses, finish, maxResponses, respondedPeers: new Set() })
86
+ })
87
+
88
+ const sent = await requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).fanoutToTopNodes(
89
+ username,
90
+ 'part_invoke',
91
+ buildPartInvokePayload(partpath, invoke, nodeHash, requestId),
92
+ maxResponses,
93
+ )
94
+
95
+ const pending = pendingPartInvoke.get(requestId)
96
+ if (pending && sent === 0) pending.finish()
97
+
98
+ return waitForResponses
99
+ }