@steve02081504/fount-p2p 0.0.22 → 0.0.24

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 (71) hide show
  1. package/README.md +8 -6
  2. package/core/entity_id.mjs +5 -5
  3. package/core/logical_entity.mjs +1 -1
  4. package/core/object.mjs +9 -0
  5. package/core/partpath.mjs +10 -0
  6. package/core/random_id.mjs +4 -2
  7. package/crypto/crypto.mjs +26 -93
  8. package/dag/storage.mjs +0 -5
  9. package/dag/strip_extensions.mjs +1 -3
  10. package/{wire/part_query_cache.mjs → federation/part_query/cache.mjs} +7 -7
  11. package/{wire/part_query.mjs → federation/part_query/runtime.mjs} +36 -77
  12. package/files/assemble.mjs +5 -5
  13. package/files/assemble_stream.mjs +2 -2
  14. package/files/chunk/fetch.mjs +101 -0
  15. package/files/chunk/pending.mjs +59 -0
  16. package/files/{chunk_provider_registry.mjs → chunk/provider_registry.mjs} +4 -5
  17. package/files/chunk/responder.mjs +104 -0
  18. package/files/{chunk_store.mjs → chunk/store.mjs} +2 -2
  19. package/files/{chunk_fetch_verify.mjs → chunk/verify.mjs} +2 -2
  20. package/files/evfs.mjs +18 -19
  21. package/files/fed/fetch_shared.mjs +39 -0
  22. package/files/fed/responder.mjs +47 -0
  23. package/files/{acl.mjs → manifest/acl.mjs} +4 -4
  24. package/files/{manifest_acl_registry.mjs → manifest/acl_registry.mjs} +14 -17
  25. package/files/{manifest_fetch.mjs → manifest/fetch.mjs} +47 -52
  26. package/files/{manifest.mjs → manifest/normalize.mjs} +2 -2
  27. package/files/manifest/pending.mjs +53 -0
  28. package/files/{public_manifest.mjs → manifest/public.mjs} +12 -12
  29. package/files/transfer_key.mjs +2 -2
  30. package/files/transfer_key_registry.mjs +7 -7
  31. package/index.mjs +4 -4
  32. package/infra/service.mjs +1 -1
  33. package/link/handshake.mjs +0 -3
  34. package/mailbox/deliver_or_store.mjs +0 -2
  35. package/mailbox/parse.mjs +1 -1
  36. package/mailbox/store.mjs +0 -9
  37. package/mailbox/wire.mjs +24 -16
  38. package/node/reputation_sync.mjs +33 -29
  39. package/package.json +9 -8
  40. package/registries/inbound.mjs +19 -9
  41. package/registries/part_path.mjs +3 -8
  42. package/schemas/discovery.mjs +1 -1
  43. package/schemas/federation_pull.mjs +1 -1
  44. package/schemas/part_query.mjs +6 -5
  45. package/transport/group_link_set.mjs +2 -2
  46. package/transport/link_registry.mjs +2 -2
  47. package/transport/node_scope/features.mjs +166 -0
  48. package/transport/node_scope/wire.mjs +124 -0
  49. package/transport/remote_user_room.mjs +0 -1
  50. package/{rooms → transport}/scoped_link.mjs +1 -1
  51. package/transport/user_room.mjs +15 -28
  52. package/utils/fetch_wait.mjs +98 -0
  53. package/wire/adapter.mjs +13 -0
  54. package/wire/ingress.mjs +1 -8
  55. package/wire/part/fanout.mjs +99 -0
  56. package/wire/part/group.mjs +45 -0
  57. package/wire/{part_ingress.mjs → part/ingress.mjs} +58 -67
  58. package/wire/{part_invoke.mjs → part/invoke.mjs} +1 -22
  59. package/wire/part/pending.mjs +32 -0
  60. package/wire/part/query.mjs +91 -0
  61. package/wire/subscribe.mjs +13 -0
  62. package/federation/chunk_fetch_pending.mjs +0 -124
  63. package/federation/manifest_fetch_pending.mjs +0 -88
  64. package/files/chunk_fetch.mjs +0 -99
  65. package/files/chunk_responder.mjs +0 -142
  66. package/transport/advert_ingest.mjs +0 -17
  67. package/transport/node_scope.mjs +0 -289
  68. package/wire/group_part.mjs +0 -43
  69. package/wire/part_common.mjs +0 -55
  70. package/wire/part_fanout.mjs +0 -52
  71. /package/{wire → schemas}/part_query.tunables.json +0 -0
@@ -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
+ }
@@ -0,0 +1,45 @@
1
+ import { isPlainObject } from '../../core/object.mjs'
2
+
3
+ import { attachPartWire } from './ingress.mjs'
4
+
5
+ /** @typedef {import('../../wire/adapter.mjs').WireAdapter} WireAdapter */
6
+ /** @typedef {import('../../wire/adapter.mjs').WireContext} WireContext */
7
+
8
+ /**
9
+ * @param {unknown} data part 载荷
10
+ * @param {string} groupId 群 ID
11
+ * @returns {object | null} 校验通过后的载荷
12
+ */
13
+ function parseGroupContext(data, groupId) {
14
+ if (!isPlainObject(data)) return null
15
+ if (data.groupId !== groupId) return null
16
+ return data
17
+ }
18
+
19
+ /**
20
+ * @param {WireAdapter} wire 底层适配器
21
+ * @param {string} groupId 群 ID
22
+ * @returns {WireAdapter['on']} 注入 groupId 的 on 包装
23
+ */
24
+ function wrapWireOn(wire, groupId) {
25
+ return (name, handler) => wire.on(name, (data, peerId) => {
26
+ const payload = parseGroupContext(data, groupId)
27
+ if (!payload) return
28
+ handler(payload, peerId)
29
+ })
30
+ }
31
+
32
+ /**
33
+ * 群联邦房间挂载 part_wire(要求线载荷带 `groupId`)。
34
+ * @param {WireContext} wireContext 入站上下文
35
+ * @param {string} groupId 群 ID
36
+ * @param {WireAdapter} wire action 表
37
+ * @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
38
+ * @returns {() => void} 取消挂载的 dispose
39
+ */
40
+ export function attachGroupPartWire(wireContext, groupId, wire, options = {}) {
41
+ return attachPartWire(wireContext, {
42
+ send: wire.send.bind(wire),
43
+ on: wrapWireOn(wire, groupId),
44
+ }, options)
45
+ }
@@ -1,34 +1,23 @@
1
+ import { isPlainObject } from '../../core/object.mjs'
2
+ import { parsePartpath } from '../../core/partpath.mjs'
3
+ import { dispatchDeliveryInbound, dispatchRpcInbound } from '../../registries/inbound.mjs'
4
+ import { subscribeWire } from '../subscribe.mjs'
1
5
 
2
- import { dispatchDeliveryInbound, dispatchRpcInbound } from '../registries/inbound.mjs'
3
-
4
- import { isPlainObject } from './ingress.mjs'
5
- import { buildPartInvokePayload } from './part_common.mjs'
6
6
  import {
7
7
  isPartInvoke,
8
8
  isPartInvokeResponse,
9
- normalizePartpath,
10
9
  unwrapPartInvokeResult,
11
- } from './part_invoke.mjs'
12
-
13
- /** @typedef {import('./part_invoke.mjs').PartInvokeResponse} PartInvokeResponse */
14
-
15
- /** @type {Map<string, { responses: PartInvokeResponse[], finish: () => void, maxResponses: number, respondedPeers: Set<string> }>} */
16
- export const pendingPartInvoke = new Map()
17
-
18
- /**
19
- * @typedef {{
20
- * send: (name: string, payload: unknown, peerId: string | null) => void
21
- * on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void
22
- * }} PartWireAdapter
23
- */
10
+ } from './invoke.mjs'
11
+ import { handleIncomingPartInvokeResponse } from './pending.mjs'
24
12
 
25
- /**
26
- * @typedef {{ replicaUsername?: string }} PartWireContext
27
- */
13
+ /** @typedef {import('../../wire/adapter.mjs').WireAdapter} WireAdapter */
14
+ /** @typedef {import('../../wire/adapter.mjs').WireContext} PartWireContext */
15
+ /** @typedef {import('./invoke.mjs').PartInvokeResponse} PartInvokeResponse */
16
+ /** @typedef {import('./invoke.mjs').PartInvoke} PartInvoke */
28
17
 
29
18
  /**
30
19
  * @param {object} data 入站 part_timeline_put 载荷
31
- * @param {string} partpath 已规范化 part 路径
20
+ * @param {string} partpath 已校验 part 路径
32
21
  * @returns {object | null} 白名单字段
33
22
  */
34
23
  function parsePartTimelinePut(data, partpath) {
@@ -44,25 +33,40 @@ function parsePartTimelinePut(data, partpath) {
44
33
  }
45
34
  }
46
35
 
36
+ /**
37
+ * @param {string} partpath part 路径
38
+ * @param {PartInvoke} invoke 调用体
39
+ * @param {string} [nodeHash] 来源节点
40
+ * @param {string} [groupId] 群上下文
41
+ * @returns {object} part_invoke 线载荷
42
+ */
43
+ function buildPartInvokePayload(partpath, invoke, nodeHash, groupId) {
44
+ return {
45
+ partpath,
46
+ invoke,
47
+ ...nodeHash ? { nodeHash } : {},
48
+ ...groupId ? { groupId } : {},
49
+ }
50
+ }
51
+
47
52
  /**
48
53
  * @param {PartWireContext} wireContext 入站上下文
49
- * @param {object} payload part_invoke 请求
54
+ * @param {object} payload part_invoke 请求(含已验证 peerId)
50
55
  * @returns {Promise<PartInvokeResponse | null>} RPC 处理器返回值
51
56
  */
52
57
  async function dispatchPartInvoke(wireContext, payload) {
53
- const partpath = normalizePartpath(payload?.partpath)
58
+ const partpath = parsePartpath(payload?.partpath)
54
59
  const invoke = payload?.invoke
55
60
  if (!partpath || !isPlainObject(invoke)) return null
56
61
  return dispatchRpcInbound({
57
62
  replicaUsername: wireContext.replicaUsername,
58
- requesterNodeHash: payload.nodeHash ? String(payload.nodeHash).trim() : null,
63
+ requesterNodeHash: payload.peerId ? String(payload.peerId).trim() : null,
59
64
  groupId: payload.groupId ? String(payload.groupId).trim() : undefined,
60
65
  peerId: payload.peerId,
61
66
  }, {
62
67
  type: 'part_invoke',
63
68
  partpath,
64
69
  invoke,
65
- nodeHash: payload.nodeHash,
66
70
  groupId: payload.groupId,
67
71
  requestId: payload.requestId,
68
72
  })
@@ -71,24 +75,32 @@ async function dispatchPartInvoke(wireContext, payload) {
71
75
  /**
72
76
  * 挂载 part_timeline_put / part_invoke / part_invoke_response。
73
77
  * @param {PartWireContext} wireContext 入站上下文
74
- * @param {PartWireAdapter} wire Trystero 适配器
78
+ * @param {WireAdapter} wire action 表
75
79
  * @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
76
80
  * @returns {() => void} 取消挂载的 dispose
77
81
  */
78
82
  export function attachPartWire(wireContext, wire, options = {}) {
79
- const offs = [
80
- wire.on('part_timeline_put', data => {
83
+ return subscribeWire(wire, {
84
+ /**
85
+ * @param {unknown} data part_timeline_put 载荷
86
+ * @param {string} peerId 对端 nodeHash
87
+ */
88
+ part_timeline_put(data, peerId) {
81
89
  if (!isPlainObject(data)) return
82
- const partpath = normalizePartpath(data.partpath)
90
+ const partpath = parsePartpath(data.partpath)
83
91
  if (!partpath) return
84
92
  const message = parsePartTimelinePut(data, partpath)
85
93
  if (!message) return
86
94
  void dispatchDeliveryInbound({
87
95
  replicaUsername: wireContext.replicaUsername,
88
- requesterNodeHash: data.nodeHash ? String(data.nodeHash).trim() : null,
96
+ requesterNodeHash: peerId ? String(peerId).trim() : null,
89
97
  }, message)
90
- }),
91
- wire.on('part_invoke', (data, peerId) => {
98
+ },
99
+ /**
100
+ * @param {unknown} data part_invoke 载荷
101
+ * @param {string} peerId 对端 nodeHash
102
+ */
103
+ part_invoke(data, peerId) {
92
104
  if (!isPlainObject(data)) return
93
105
  if (options.allowPartInvoke?.(data) === false) return
94
106
  const payload = { ...data, peerId }
@@ -96,27 +108,27 @@ export function attachPartWire(wireContext, wire, options = {}) {
96
108
  void handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId)
97
109
  else
98
110
  void handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId)
99
- }),
100
- wire.on('part_invoke_response', (data, peerId) => {
111
+ },
112
+ /**
113
+ * @param {unknown} data part_invoke_response 载荷
114
+ * @param {string} peerId 对端 nodeHash
115
+ */
116
+ part_invoke_response(data, peerId) {
101
117
  if (!isPlainObject(data)) return
102
118
  handleIncomingPartInvokeResponse(data, peerId)
103
- }),
104
- ]
105
- return () => {
106
- for (const off of offs)
107
- try { off?.() } catch { /* ignore */ }
108
- }
119
+ },
120
+ })
109
121
  }
110
122
 
111
123
  /**
112
124
  * @param {PartWireContext} wireContext 入站上下文
113
125
  * @param {object} payload part_invoke 请求(含 requestId)
114
- * @param {PartWireAdapter} wire 发送适配器
126
+ * @param {WireAdapter} wire 发送适配器
115
127
  * @param {string} peerId 对端
116
128
  * @returns {Promise<void>}
117
129
  */
118
130
  export async function handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId) {
119
- const partpath = normalizePartpath(payload?.partpath)
131
+ const partpath = parsePartpath(payload?.partpath)
120
132
  if (!partpath || !payload.requestId) return
121
133
 
122
134
  const response = await dispatchPartInvoke(wireContext, { ...payload, peerId })
@@ -135,40 +147,19 @@ export async function handleIncomingPartInvokeRequest(wireContext, payload, wire
135
147
  /**
136
148
  * @param {PartWireContext} wireContext 入站上下文
137
149
  * @param {object} payload part_invoke 请求(无 requestId)
138
- * @param {PartWireAdapter} wire 发送适配器
150
+ * @param {WireAdapter} wire 发送适配器
139
151
  * @param {string} peerId 对端
140
152
  * @returns {Promise<void>}
141
153
  */
142
154
  export async function handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId) {
143
- const partpath = normalizePartpath(payload?.partpath)
155
+ const partpath = parsePartpath(payload?.partpath)
144
156
  if (!partpath) return
145
157
 
146
158
  const response = await dispatchPartInvoke(wireContext, { ...payload, peerId })
147
159
  const followUp = unwrapPartInvokeResult(response)
148
160
  if (!isPartInvoke(followUp)) return
149
161
  try {
150
- wire.send('part_invoke', buildPartInvokePayload({
151
- partpath,
152
- invoke: followUp,
153
- nodeHash: payload.nodeHash,
154
- groupId: payload.groupId,
155
- }), peerId)
162
+ wire.send('part_invoke', buildPartInvokePayload(partpath, followUp, peerId, payload.groupId), peerId)
156
163
  }
157
164
  catch { /* disconnected */ }
158
165
  }
159
-
160
- /**
161
- * @param {object} payload 响应
162
- * @param {string} [peerId] Trystero 对端 id,用于同 peer 去重
163
- * @returns {void}
164
- */
165
- export function handleIncomingPartInvokeResponse(payload, peerId = '') {
166
- const pending = pendingPartInvoke.get(payload.requestId)
167
- if (!pending || !isPartInvokeResponse(payload.response)) return
168
- if (peerId) {
169
- if (pending.respondedPeers.has(peerId)) return
170
- pending.respondedPeers.add(peerId)
171
- }
172
- pending.responses.push(payload.response)
173
- if (pending.responses.length >= pending.maxResponses) pending.finish()
174
- }
@@ -1,6 +1,4 @@
1
- import { isPlainObject } from './ingress.mjs'
2
-
3
- const PARTPATH_RE = /^[\w-]+(?:\/[\w-]+)*$/
1
+ import { isPlainObject } from '../../core/object.mjs'
4
2
 
5
3
  /** @typedef {string} PartInvokeKind */
6
4
 
@@ -22,16 +20,6 @@ const PARTPATH_RE = /^[\w-]+(?:\/[\w-]+)*$/
22
20
 
23
21
  /** @typedef {PartInvokeResultResponse | PartInvokeErrorResponse} PartInvokeResponse */
24
22
 
25
- /**
26
- * @typedef {object} SocialRpcPartInvokeResponse
27
- * @property {object} result RPC 响应体
28
- */
29
-
30
- /**
31
- * @typedef {object} TimelinePutPartInvokeResponse
32
- * @property {{ ok: boolean }} result timeline ingest 结果
33
- */
34
-
35
23
  /**
36
24
  * @param {unknown} value 候选响应
37
25
  * @returns {value is PartInvokeResponse} 是否为 part_invoke 响应体
@@ -61,15 +49,6 @@ export function unwrapPartInvokeResult(response) {
61
49
  return response.result ?? null
62
50
  }
63
51
 
64
- /**
65
- * @param {unknown} value partpath 字符串
66
- * @returns {string | null} 规范化 partpath
67
- */
68
- export function normalizePartpath(value) {
69
- const path = String(value || '').trim().replace(/^\/+|\/+$/g, '')
70
- return PARTPATH_RE.test(path) ? path : null
71
- }
72
-
73
52
  /**
74
53
  * @param {unknown} value invoke 体
75
54
  * @returns {value is PartInvoke} 是否含已知 kind
@@ -0,0 +1,32 @@
1
+ import { isPartInvokeResponse } from './invoke.mjs'
2
+
3
+ /** @typedef {import('./invoke.mjs').PartInvokeResponse} PartInvokeResponse */
4
+
5
+ /**
6
+ * @typedef {{
7
+ * responses: PartInvokeResponse[]
8
+ * finish: () => void
9
+ * maxResponses: number
10
+ * respondedPeers: Set<string>
11
+ * }} PendingPartInvoke
12
+ */
13
+
14
+ /** @type {Map<string, PendingPartInvoke>} */
15
+ export const pendingPartInvoke = new Map()
16
+
17
+ /**
18
+ * 入站 part_invoke_response:写入对应 pending 槽。
19
+ * @param {object} payload 响应
20
+ * @param {string} [peerId] 对端 id,用于同 peer 去重
21
+ * @returns {void}
22
+ */
23
+ export function handleIncomingPartInvokeResponse(payload, peerId = '') {
24
+ const pending = pendingPartInvoke.get(payload.requestId)
25
+ if (!pending || !isPartInvokeResponse(payload.response)) return
26
+ if (peerId) {
27
+ if (pending.respondedPeers.has(peerId)) return
28
+ pending.respondedPeers.add(peerId)
29
+ }
30
+ pending.responses.push(payload.response)
31
+ if (pending.responses.length >= pending.maxResponses) pending.finish()
32
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ handleIncomingPartQueryResponse,
3
+ processIncomingPartQueryRequest,
4
+ queryNetwork as queryNetworkCore,
5
+ resolvePartQueryState,
6
+ } from '../../federation/part_query/runtime.mjs'
7
+ import {
8
+ parsePartQueryReq,
9
+ parsePartQueryRes,
10
+ } from '../../schemas/part_query.mjs'
11
+ import partQueryTunables from '../../schemas/part_query.tunables.json' with { type: 'json' }
12
+ import { sendToNodeLink } from '../../transport/link_registry.mjs'
13
+ import { consumeWireRateBucket } from '../rate_bucket.mjs'
14
+ import { subscribeWire } from '../subscribe.mjs'
15
+
16
+ /** @typedef {import('../adapter.mjs').WireAdapter} WireAdapter */
17
+ /** @typedef {import('../adapter.mjs').WireContext} WireContext */
18
+ /** @typedef {import('../../federation/part_query/runtime.mjs').PartQueryDependencies} PartQueryDependencies */
19
+
20
+ /**
21
+ * @param {PartQueryDependencies} [dependencies] 可注入依赖
22
+ * @returns {PartQueryDependencies} 补上默认 deliver 后的依赖
23
+ */
24
+ function withDefaultDeliver(dependencies = {}) {
25
+ return {
26
+ ...dependencies,
27
+ deliver: dependencies.deliver || ((nodeHash, action, payload) =>
28
+ sendToNodeLink(nodeHash, { scope: 'node', action, payload })),
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 挂载 part_query_req / part_query_res(入站 parse / dedupe / rate;运行时在 federation)。
34
+ * @param {WireContext} wireContext 入站上下文
35
+ * @param {WireAdapter} wire wire
36
+ * @param {PartQueryDependencies} [dependencies] 可注入依赖(含 per-node state)
37
+ * @returns {() => void} 取消挂载的 dispose
38
+ */
39
+ export function attachPartQueryWire(wireContext, wire, dependencies = {}) {
40
+ const deps = withDefaultDeliver(dependencies)
41
+ const state = resolvePartQueryState(deps)
42
+ return subscribeWire(wire, {
43
+ /**
44
+ * @param {unknown} data part_query_req 载荷
45
+ * @param {string} peerId 对端 nodeHash
46
+ */
47
+ part_query_req(data, peerId) {
48
+ const request = parsePartQueryReq(data)
49
+ if (!request) return
50
+ if (!state.takeDedupe(request.requestId)) return
51
+ const source = String(peerId || request.originNodeHash || '').trim().toLowerCase()
52
+ if (source && !consumeWireRateBucket(`part_query:${source}`, {
53
+ maxCount: partQueryTunables.ratePerSourcePerMin,
54
+ })) return
55
+ void processIncomingPartQueryRequest(wireContext, wire, request, String(peerId || ''), deps)
56
+ },
57
+ /**
58
+ * @param {unknown} data part_query_res 载荷
59
+ * @param {string} peerId 对端 nodeHash
60
+ */
61
+ part_query_res(data, peerId) {
62
+ const response = parsePartQueryRes(data)
63
+ if (!response) return
64
+ handleIncomingPartQueryResponse(response, String(peerId || ''), deps)
65
+ },
66
+ })
67
+ }
68
+
69
+ /**
70
+ * 多跳查询(默认经 link_registry 投递;可注入 deliver)。
71
+ * @param {string} username trust graph 上下文
72
+ * @param {string} partpath part 路径
73
+ * @param {string} kind 查询标签
74
+ * @param {unknown} query 不透明查询
75
+ * @param {Parameters<typeof queryNetworkCore>[4]} [options] 选项
76
+ * @returns {Promise<unknown[]>} 合并后的 rows
77
+ */
78
+ export function queryNetwork(username, partpath, kind, query, options = {}) {
79
+ return queryNetworkCore(username, partpath, kind, query, withDefaultDeliver(options))
80
+ }
81
+
82
+ /**
83
+ * Part query 运行时(state、入站 handler、hop timeout、测试重置)。
84
+ */
85
+ export {
86
+ createPartQueryNodeState,
87
+ registerQueryInboundHandler,
88
+ resetPartQueryStateForTests,
89
+ resolvePartQueryHopTimeoutMs,
90
+ resolvePartQueryState,
91
+ } from '../../federation/part_query/runtime.mjs'
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 批量注册 wire action,返回统一 dispose。
3
+ * @param {import('./adapter.mjs').WireAdapter} wire action 表
4
+ * @param {Record<string, (payload: unknown, peerId: string) => void>} handlers action → handler
5
+ * @returns {() => void} 取消全部注册
6
+ */
7
+ export function subscribeWire(wire, handlers) {
8
+ const disposers = Object.entries(handlers).map(([name, handler]) => wire.on(name, handler))
9
+ return () => {
10
+ for (const dispose of disposers)
11
+ dispose()
12
+ }
13
+ }