@steve02081504/fount-p2p 0.0.43 → 0.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -66,6 +66,7 @@ Deno / native / BT: [runtime.md](docs/runtime.md).
66
66
  ### Node / network
67
67
 
68
68
  - **Node data:** `initNode({ nodeDir, entityStore? })` — `node.json`, `network.json`, `denylist.json`, `reputation.json`, `mailbox/`, `chunks/`. Default EntityStore: `{nodeDir}/entities/`. No `FOUNT_*` env knobs — subprocess IPC uses argv.
69
+ - **Storage vs runtime:** local identity (`ensureNodeSeed` / `getNodeHash` / `resolveLocalEntityHashFromRecoveryPubKeyHex`, all `node/storage` JSON helpers) must not require `initNode`. Configure the dir with `configureNodeStorage({ nodeDir })` (idempotent; cannot switch while running — `closeNode()` first) and only call `initNode` when the network should run. Hosts configure storage unconditionally so entities have a local identity with P2P disabled. [runtime.md](docs/runtime.md)
69
70
  - **fount network:** shells use `startNode` / `ensureLinkToNode` / `sendToNodeLink` / rooms — never import `link/` internals or pick a transport. Providers: `registerLinkProvider` from `./link` or facade.
70
71
  - **Link `level` vs discovery `priority`:** descending `level` picks data transport (`nostr` = −∞ last resort); ascending `priority` orders handshake/presence media only. [transports.md](docs/transports.md)
71
72
  - **Mesh first / no versioning:** ≥N links (K acquaintances + N−K explore); discovery API is `listVisibleNodeHashes` + `connectToNode` only; no topic on the fount-network surface; no version/compat fields. [mesh.md](docs/mesh.md)
package/docs/runtime.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  `ensureRuntime`, startup/shutdown budgets, and Bluetooth hardware probe. Day-to-day shell rules: [AGENTS.md](../AGENTS.md). Providers: [transports.md](transports.md). Mesh N/K: [mesh.md](mesh.md).
4
4
 
5
+ ## Offline local identity (storage without a running node)
6
+
7
+ Three independent stages — do not couple them:
8
+
9
+ 1. **Storage** — `configureNodeStorage({ nodeDir })` records where `node.json` / identity seed live. No runtime, no disk I/O, no discovery.
10
+ 2. **Local identity** — `ensureNodeSeed` / `getNodeHash` / `resolveLocalEntityHashFromRecoveryPubKeyHex` (and `node/storage` JSON helpers) only need stage 1, so a local `entityHash` can be created while federation is off.
11
+ 3. **Network** — `initNode({ nodeDir, entityStore })` builds the runtime and link graph. Same `nodeDir` as stage 1 reuses the persisted seed and hashes.
12
+
13
+ ```javascript
14
+ import { configureNodeStorage } from '@steve02081504/fount-p2p/node/instance'
15
+ import { resolveLocalEntityHashFromRecoveryPubKeyHex } from '@steve02081504/fount-p2p/node/identity'
16
+
17
+ configureNodeStorage({ nodeDir: '/data/p2p/node' })
18
+ const entityHash = resolveLocalEntityHashFromRecoveryPubKeyHex(recoveryPubKeyHex) // works with P2P disabled
19
+ ```
20
+
21
+ `configureNodeStorage` is idempotent for the same dir; switching dirs while a node is running is rejected (call `closeNode()` first). `closeNode()` clears the storage config, so re-configure after closing. Consumer hosts (fount, subfount) call `configureNodeStorage` unconditionally at startup and only call `initNode` when P2P is enabled.
22
+
5
23
  ## `ensureRuntime` contract
6
24
 
7
25
  Returns after registering lan / nostr discovery providers (subject to the signaling `channels` whitelist), registering all enabled link providers (including `ble_gatt`), and scheduling background warm — does **not** await lan_tcp listen, Nostr relays, or BT availability.
package/index.mjs CHANGED
@@ -13,6 +13,7 @@ import { registerLinkProvider } from './link/providers/index.mjs'
13
13
  import { ensureNodeDefaults, getNodeHash } from './node/identity.mjs'
14
14
  import {
15
15
  closeNode,
16
+ configureNodeStorage,
16
17
  getNodeDir,
17
18
  getP2PFeatures,
18
19
  initNode,
@@ -65,6 +66,7 @@ export {
65
66
  attachNodeScopeDefaultFeatures,
66
67
  closeNode,
67
68
  configureLinkRegistry,
69
+ configureNodeStorage,
68
70
  createGroupLinkSet,
69
71
  createScopedLinkRoom,
70
72
  ensureChannelAvailable,
package/node/identity.mjs CHANGED
@@ -42,6 +42,8 @@ function saveNodeFile(patch) {
42
42
  }
43
43
 
44
44
  /**
45
+ * 确保本地节点 seed 已持久化并返回。
46
+ * 只依赖本地存储目录(`configureNodeStorage` 或 `initNode` 提供),不要求运行中的节点。
45
47
  * @returns {string} 64 位十六进制 节点种子
46
48
  */
47
49
  export function ensureNodeSeed() {
@@ -54,6 +56,8 @@ export function ensureNodeSeed() {
54
56
  }
55
57
 
56
58
  /**
59
+ * 由本地 seed 派生本节点 nodeHash。
60
+ * 只依赖本地存储目录,不要求运行中的节点。
57
61
  * @returns {string} 本节点 64 hex nodeHash
58
62
  */
59
63
  export function getNodeHash() {
@@ -111,6 +115,8 @@ export function entityHashFromKeys(nodeHash, recoveryPubKeyHex) {
111
115
  }
112
116
 
113
117
  /**
118
+ * 由本地 nodeHash 与 recovery 公钥派生本节点 entityHash。
119
+ * 只依赖本地存储目录,不要求运行中的节点(联邦未启动时也可创建本地身份)。
114
120
  * @param {string} recoveryPubKeyHex 64 位十六进制 recovery 公钥
115
121
  * @returns {string | null} 本节点 entityHash
116
122
  */
package/node/instance.mjs CHANGED
@@ -22,6 +22,13 @@ import { defaultSignalingRuntimeConfig, resolveSignalingRuntimeConfig } from './
22
22
  /** @type {NodeRuntime | null} */
23
23
  let runtime = null
24
24
 
25
+ /**
26
+ * 本地节点数据目录。与 `runtime` 解耦:身份 seed 等纯本地持久化只依赖存储目录,
27
+ * 不要求 `initNode()`(联邦未启动时也要能派生本地身份)。
28
+ * @type {string | null}
29
+ */
30
+ let nodeStorageDir = null
31
+
25
32
  /** @type {Set<(event: string, payload?: unknown) => void>} */
26
33
  const changeListeners = new Set()
27
34
 
@@ -35,6 +42,28 @@ export function getRtcPolyfillCacheEpoch() {
35
42
  return rtcPolyfillCacheEpoch
36
43
  }
37
44
 
45
+ /**
46
+ * 配置本地节点数据目录(`node.json` / 身份 seed 等纯本地持久化位置),
47
+ * 不初始化节点运行时、不创建 disk 资源、不启动发现或连接。
48
+ *
49
+ * 配置后 `getNodeDir` / `ensureNodeSeed` / `getNodeHash` /
50
+ * `resolveLocalEntityHashFromRecoveryPubKeyHex` 可在无运行节点时使用;
51
+ * 之后以同一目录调用 `initNode` 会复用已持久化的 seed 与身份。
52
+ *
53
+ * 同目录重复配置为幂等;运行中的节点不允许切换到不同目录,须先 `closeNode()`。
54
+ * @param {{ nodeDir: string }} options - 本地节点数据目录
55
+ * @returns {string} 规范化为绝对路径后的目录
56
+ */
57
+ export function configureNodeStorage(options) {
58
+ const rawDir = options?.nodeDir
59
+ if (typeof rawDir !== 'string' || !rawDir) throw new Error('p2p: configureNodeStorage requires nodeDir')
60
+ const nodeDir = path.resolve(rawDir)
61
+ if (runtime && runtime.nodeDir !== nodeDir)
62
+ throw new Error('p2p: running node uses another nodeDir — call closeNode() before reconfiguring storage')
63
+ nodeStorageDir = nodeDir
64
+ return nodeDir
65
+ }
66
+
38
67
  /**
39
68
  * @param {{ nodeDir: string, entityStore?: import('./entity_store.mjs').EntityStore }} options - 节点目录与可选 entity store
40
69
  * @returns {NodeRuntime} 初始化后的运行时
@@ -43,7 +72,7 @@ export function initNode(options) {
43
72
  if (runtime) throw new Error('p2p: initNode already called — use setNodeLogger / setSignalingRuntimeConfig or closeNode')
44
73
  if (options?.logger !== undefined || options?.signaling !== undefined)
45
74
  throw new Error('p2p: initNode only accepts nodeDir/entityStore — use setNodeLogger / setSignalingRuntimeConfig')
46
- const nodeDir = path.resolve(options.nodeDir)
75
+ const nodeDir = options?.nodeDir == null ? nodeStorageDir : configureNodeStorage({ nodeDir: options.nodeDir })
47
76
  if (!nodeDir) throw new Error('p2p: initNode requires nodeDir')
48
77
  const entityStore = options.entityStore ?? createFsEntityStore(path.join(nodeDir, 'entities'))
49
78
  runtime = {
@@ -123,10 +152,12 @@ export function getP2PFeatures() {
123
152
  }
124
153
 
125
154
  /**
126
- * @returns {string} 节点数据目录绝对路径
155
+ * @returns {string} 节点数据目录绝对路径;未配置且节点未初始化时抛错
127
156
  */
128
157
  export function getNodeDir() {
129
- return getNode().nodeDir
158
+ if (runtime) return runtime.nodeDir
159
+ if (nodeStorageDir) return nodeStorageDir
160
+ throw new Error('p2p: node storage not configured — call configureNodeStorage() or initNode() first')
130
161
  }
131
162
 
132
163
  /**
@@ -164,12 +195,13 @@ export function onNodeChange(listener) {
164
195
  }
165
196
 
166
197
  /**
167
- * 关闭节点:释放全部文件句柄(chunk 读/写流等)并清空运行时与监听器。
168
- * 之后可用 initNode 重新引导。
198
+ * 关闭节点:释放全部文件句柄(chunk 读/写流等)并清空运行时、存储目录配置与监听器。
199
+ * 之后可用 initNode / configureNodeStorage 重新引导。
169
200
  * @returns {Promise<void>}
170
201
  */
171
202
  export async function closeNode() {
172
203
  runtime = null
204
+ nodeStorageDir = null
173
205
  changeListeners.clear()
174
206
  rtcPolyfillCacheEpoch++
175
207
  await closeAllFileStreams()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -15,7 +15,7 @@
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/steve02081504/fount-p2p.git",
18
- "directory": "."
18
+ "directory": "js"
19
19
  },
20
20
  "engines": {
21
21
  "node": ">=20"