@optimystic/db-p2p 0.0.1

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 (189) hide show
  1. package/dist/index.min.js +52 -0
  2. package/dist/index.min.js.map +7 -0
  3. package/dist/src/cluster/client.d.ts +12 -0
  4. package/dist/src/cluster/client.d.ts.map +1 -0
  5. package/dist/src/cluster/client.js +65 -0
  6. package/dist/src/cluster/client.js.map +1 -0
  7. package/dist/src/cluster/cluster-repo.d.ts +79 -0
  8. package/dist/src/cluster/cluster-repo.d.ts.map +1 -0
  9. package/dist/src/cluster/cluster-repo.js +613 -0
  10. package/dist/src/cluster/cluster-repo.js.map +1 -0
  11. package/dist/src/cluster/partition-detector.d.ts +59 -0
  12. package/dist/src/cluster/partition-detector.d.ts.map +1 -0
  13. package/dist/src/cluster/partition-detector.js +129 -0
  14. package/dist/src/cluster/partition-detector.js.map +1 -0
  15. package/dist/src/cluster/service.d.ts +49 -0
  16. package/dist/src/cluster/service.d.ts.map +1 -0
  17. package/dist/src/cluster/service.js +107 -0
  18. package/dist/src/cluster/service.js.map +1 -0
  19. package/dist/src/index.d.ts +29 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +29 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/it-utility.d.ts +4 -0
  24. package/dist/src/it-utility.d.ts.map +1 -0
  25. package/dist/src/it-utility.js +32 -0
  26. package/dist/src/it-utility.js.map +1 -0
  27. package/dist/src/libp2p-key-network.d.ts +59 -0
  28. package/dist/src/libp2p-key-network.d.ts.map +1 -0
  29. package/dist/src/libp2p-key-network.js +278 -0
  30. package/dist/src/libp2p-key-network.js.map +1 -0
  31. package/dist/src/libp2p-node.d.ts +28 -0
  32. package/dist/src/libp2p-node.d.ts.map +1 -0
  33. package/dist/src/libp2p-node.js +270 -0
  34. package/dist/src/libp2p-node.js.map +1 -0
  35. package/dist/src/logger.d.ts +3 -0
  36. package/dist/src/logger.d.ts.map +1 -0
  37. package/dist/src/logger.js +6 -0
  38. package/dist/src/logger.js.map +1 -0
  39. package/dist/src/network/get-network-manager.d.ts +4 -0
  40. package/dist/src/network/get-network-manager.d.ts.map +1 -0
  41. package/dist/src/network/get-network-manager.js +17 -0
  42. package/dist/src/network/get-network-manager.js.map +1 -0
  43. package/dist/src/network/network-manager-service.d.ts +82 -0
  44. package/dist/src/network/network-manager-service.d.ts.map +1 -0
  45. package/dist/src/network/network-manager-service.js +283 -0
  46. package/dist/src/network/network-manager-service.js.map +1 -0
  47. package/dist/src/peer-utils.d.ts +2 -0
  48. package/dist/src/peer-utils.d.ts.map +1 -0
  49. package/dist/src/peer-utils.js +28 -0
  50. package/dist/src/peer-utils.js.map +1 -0
  51. package/dist/src/protocol-client.d.ts +12 -0
  52. package/dist/src/protocol-client.d.ts.map +1 -0
  53. package/dist/src/protocol-client.js +34 -0
  54. package/dist/src/protocol-client.js.map +1 -0
  55. package/dist/src/repo/client.d.ts +17 -0
  56. package/dist/src/repo/client.d.ts.map +1 -0
  57. package/dist/src/repo/client.js +82 -0
  58. package/dist/src/repo/client.js.map +1 -0
  59. package/dist/src/repo/cluster-coordinator.d.ts +59 -0
  60. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -0
  61. package/dist/src/repo/cluster-coordinator.js +539 -0
  62. package/dist/src/repo/cluster-coordinator.js.map +1 -0
  63. package/dist/src/repo/coordinator-repo.d.ts +29 -0
  64. package/dist/src/repo/coordinator-repo.d.ts.map +1 -0
  65. package/dist/src/repo/coordinator-repo.js +102 -0
  66. package/dist/src/repo/coordinator-repo.js.map +1 -0
  67. package/dist/src/repo/redirect.d.ts +14 -0
  68. package/dist/src/repo/redirect.d.ts.map +1 -0
  69. package/dist/src/repo/redirect.js +9 -0
  70. package/dist/src/repo/redirect.js.map +1 -0
  71. package/dist/src/repo/service.d.ts +52 -0
  72. package/dist/src/repo/service.d.ts.map +1 -0
  73. package/dist/src/repo/service.js +181 -0
  74. package/dist/src/repo/service.js.map +1 -0
  75. package/dist/src/repo/types.d.ts +7 -0
  76. package/dist/src/repo/types.d.ts.map +1 -0
  77. package/dist/src/repo/types.js +2 -0
  78. package/dist/src/repo/types.js.map +1 -0
  79. package/dist/src/routing/libp2p-known-peers.d.ts +4 -0
  80. package/dist/src/routing/libp2p-known-peers.d.ts.map +1 -0
  81. package/dist/src/routing/libp2p-known-peers.js +19 -0
  82. package/dist/src/routing/libp2p-known-peers.js.map +1 -0
  83. package/dist/src/routing/responsibility.d.ts +14 -0
  84. package/dist/src/routing/responsibility.d.ts.map +1 -0
  85. package/dist/src/routing/responsibility.js +45 -0
  86. package/dist/src/routing/responsibility.js.map +1 -0
  87. package/dist/src/routing/simple-cluster-coordinator.d.ts +23 -0
  88. package/dist/src/routing/simple-cluster-coordinator.d.ts.map +1 -0
  89. package/dist/src/routing/simple-cluster-coordinator.js +59 -0
  90. package/dist/src/routing/simple-cluster-coordinator.js.map +1 -0
  91. package/dist/src/storage/arachnode-fret-adapter.d.ts +65 -0
  92. package/dist/src/storage/arachnode-fret-adapter.d.ts.map +1 -0
  93. package/dist/src/storage/arachnode-fret-adapter.js +93 -0
  94. package/dist/src/storage/arachnode-fret-adapter.js.map +1 -0
  95. package/dist/src/storage/block-storage.d.ts +31 -0
  96. package/dist/src/storage/block-storage.d.ts.map +1 -0
  97. package/dist/src/storage/block-storage.js +154 -0
  98. package/dist/src/storage/block-storage.js.map +1 -0
  99. package/dist/src/storage/file-storage.d.ts +30 -0
  100. package/dist/src/storage/file-storage.d.ts.map +1 -0
  101. package/dist/src/storage/file-storage.js +127 -0
  102. package/dist/src/storage/file-storage.js.map +1 -0
  103. package/dist/src/storage/helpers.d.ts +3 -0
  104. package/dist/src/storage/helpers.d.ts.map +1 -0
  105. package/dist/src/storage/helpers.js +28 -0
  106. package/dist/src/storage/helpers.js.map +1 -0
  107. package/dist/src/storage/i-block-storage.d.ts +32 -0
  108. package/dist/src/storage/i-block-storage.d.ts.map +1 -0
  109. package/dist/src/storage/i-block-storage.js +2 -0
  110. package/dist/src/storage/i-block-storage.js.map +1 -0
  111. package/dist/src/storage/i-raw-storage.d.ts +20 -0
  112. package/dist/src/storage/i-raw-storage.d.ts.map +1 -0
  113. package/dist/src/storage/i-raw-storage.js +2 -0
  114. package/dist/src/storage/i-raw-storage.js.map +1 -0
  115. package/dist/src/storage/memory-storage.d.ts +27 -0
  116. package/dist/src/storage/memory-storage.d.ts.map +1 -0
  117. package/dist/src/storage/memory-storage.js +87 -0
  118. package/dist/src/storage/memory-storage.js.map +1 -0
  119. package/dist/src/storage/restoration-coordinator-v2.d.ts +63 -0
  120. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +1 -0
  121. package/dist/src/storage/restoration-coordinator-v2.js +157 -0
  122. package/dist/src/storage/restoration-coordinator-v2.js.map +1 -0
  123. package/dist/src/storage/ring-selector.d.ts +56 -0
  124. package/dist/src/storage/ring-selector.d.ts.map +1 -0
  125. package/dist/src/storage/ring-selector.js +118 -0
  126. package/dist/src/storage/ring-selector.js.map +1 -0
  127. package/dist/src/storage/storage-monitor.d.ts +23 -0
  128. package/dist/src/storage/storage-monitor.d.ts.map +1 -0
  129. package/dist/src/storage/storage-monitor.js +40 -0
  130. package/dist/src/storage/storage-monitor.js.map +1 -0
  131. package/dist/src/storage/storage-repo.d.ts +17 -0
  132. package/dist/src/storage/storage-repo.d.ts.map +1 -0
  133. package/dist/src/storage/storage-repo.js +267 -0
  134. package/dist/src/storage/storage-repo.js.map +1 -0
  135. package/dist/src/storage/struct.d.ts +29 -0
  136. package/dist/src/storage/struct.d.ts.map +1 -0
  137. package/dist/src/storage/struct.js +2 -0
  138. package/dist/src/storage/struct.js.map +1 -0
  139. package/dist/src/sync/client.d.ts +27 -0
  140. package/dist/src/sync/client.d.ts.map +1 -0
  141. package/dist/src/sync/client.js +32 -0
  142. package/dist/src/sync/client.js.map +1 -0
  143. package/dist/src/sync/protocol.d.ts +58 -0
  144. package/dist/src/sync/protocol.d.ts.map +1 -0
  145. package/dist/src/sync/protocol.js +12 -0
  146. package/dist/src/sync/protocol.js.map +1 -0
  147. package/dist/src/sync/service.d.ts +62 -0
  148. package/dist/src/sync/service.d.ts.map +1 -0
  149. package/dist/src/sync/service.js +168 -0
  150. package/dist/src/sync/service.js.map +1 -0
  151. package/package.json +73 -0
  152. package/readme.md +497 -0
  153. package/src/cluster/client.ts +63 -0
  154. package/src/cluster/cluster-repo.ts +711 -0
  155. package/src/cluster/partition-detector.ts +158 -0
  156. package/src/cluster/service.ts +156 -0
  157. package/src/index.ts +30 -0
  158. package/src/it-utility.ts +36 -0
  159. package/src/libp2p-key-network.ts +334 -0
  160. package/src/libp2p-node.ts +335 -0
  161. package/src/logger.ts +9 -0
  162. package/src/network/get-network-manager.ts +17 -0
  163. package/src/network/network-manager-service.ts +334 -0
  164. package/src/peer-utils.ts +24 -0
  165. package/src/protocol-client.ts +54 -0
  166. package/src/repo/client.ts +112 -0
  167. package/src/repo/cluster-coordinator.ts +592 -0
  168. package/src/repo/coordinator-repo.ts +137 -0
  169. package/src/repo/redirect.ts +17 -0
  170. package/src/repo/service.ts +219 -0
  171. package/src/repo/types.ts +7 -0
  172. package/src/routing/libp2p-known-peers.ts +26 -0
  173. package/src/routing/responsibility.ts +63 -0
  174. package/src/routing/simple-cluster-coordinator.ts +70 -0
  175. package/src/storage/arachnode-fret-adapter.ts +128 -0
  176. package/src/storage/block-storage.ts +182 -0
  177. package/src/storage/file-storage.ts +163 -0
  178. package/src/storage/helpers.ts +29 -0
  179. package/src/storage/i-block-storage.ts +40 -0
  180. package/src/storage/i-raw-storage.ts +30 -0
  181. package/src/storage/memory-storage.ts +108 -0
  182. package/src/storage/restoration-coordinator-v2.ts +191 -0
  183. package/src/storage/ring-selector.ts +155 -0
  184. package/src/storage/storage-monitor.ts +59 -0
  185. package/src/storage/storage-repo.ts +320 -0
  186. package/src/storage/struct.ts +34 -0
  187. package/src/sync/client.ts +42 -0
  188. package/src/sync/protocol.ts +71 -0
  189. package/src/sync/service.ts +229 -0
@@ -0,0 +1,191 @@
1
+ import type { BlockId } from '@optimystic/db-core';
2
+ import { hashKey } from 'p2p-fret';
3
+ import { peerIdFromString } from '@libp2p/peer-id';
4
+ import type { BlockArchive, RestoreCallback } from './struct.js';
5
+ import { SyncClient } from '../sync/client.js';
6
+ import type { IPeerNetwork } from '@optimystic/db-core';
7
+ import type { ArachnodeFretAdapter } from './arachnode-fret-adapter.js';
8
+ import { createLogger } from '../logger.js';
9
+
10
+ /**
11
+ * Coordinates block restoration across discovered Arachnode storage rings.
12
+ *
13
+ * Queries rings in order of broader coverage (inner rings first):
14
+ * 1. Transaction ring peers (my ring)
15
+ * 2. Inner storage rings (Ring N-1, N-2, ..., Ring 0)
16
+ *
17
+ * Each ring is discovered dynamically via FRET neighbor snapshots.
18
+ */
19
+ export class RestorationCoordinator {
20
+ private readonly metrics = {
21
+ totalRequests: 0,
22
+ successByRing: new Map<number, number>(),
23
+ failureByRing: new Map<number, number>(),
24
+ averageDurationMs: 0
25
+ };
26
+
27
+ constructor(
28
+ private readonly fretAdapter: ArachnodeFretAdapter,
29
+ private readonly peerNetwork: IPeerNetwork,
30
+ private readonly protocolPrefix: string
31
+ ) {}
32
+
33
+ private readonly log = createLogger('storage:restoration')
34
+
35
+ /**
36
+ * Restore a block by querying discovered storage rings.
37
+ */
38
+ async restore(blockId: BlockId, rev?: number): Promise<BlockArchive | undefined> {
39
+ const startTime = Date.now();
40
+ this.metrics.totalRequests++;
41
+
42
+ // 1. Try my transaction ring peers first
43
+ const myPeers = await this.getMyRingPeers(blockId);
44
+ const myRingDepth = this.getMyRingDepth();
45
+
46
+ for (const peerId of myPeers) {
47
+ const archive = await this.queryPeer(peerId, blockId, rev);
48
+ if (archive) {
49
+ this.recordSuccess(myRingDepth, blockId, Date.now() - startTime);
50
+ return archive;
51
+ }
52
+ }
53
+
54
+ // 2. Try inner storage rings (broader coverage)
55
+ for (let ringDepth = myRingDepth - 1; ringDepth >= 0; ringDepth--) {
56
+ const storagePeers = this.fretAdapter.findPeersAtRing(ringDepth);
57
+
58
+ // Filter to peers responsible for this block's partition
59
+ const responsiblePeers = this.filterByPartition(storagePeers, blockId, ringDepth);
60
+
61
+ for (const peerIdStr of responsiblePeers) {
62
+ const archive = await this.queryPeer(peerIdStr, blockId, rev);
63
+ if (archive) {
64
+ this.recordSuccess(ringDepth, blockId, Date.now() - startTime);
65
+ return archive;
66
+ }
67
+ }
68
+ }
69
+
70
+ // No ring had the data
71
+ const duration = Date.now() - startTime;
72
+ this.log('restore failed for block %s after %dms', blockId, duration)
73
+ return undefined;
74
+ }
75
+
76
+ /**
77
+ * Create a RestoreCallback function that uses this coordinator.
78
+ */
79
+ createRestoreCallback(): RestoreCallback {
80
+ return async (blockId: BlockId, rev?: number) => {
81
+ return await this.restore(blockId, rev);
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Get peers in my transaction ring for a given block.
87
+ */
88
+ private async getMyRingPeers(blockId: BlockId): Promise<string[]> {
89
+ const blockIdBytes = new TextEncoder().encode(blockId);
90
+ const coord = await hashKey(blockIdBytes);
91
+ return this.fretAdapter.getFret().assembleCohort(coord, 10);
92
+ }
93
+
94
+ /**
95
+ * Get my own ring depth from Arachnode info.
96
+ */
97
+ private getMyRingDepth(): number {
98
+ const myInfo = this.fretAdapter.getMyArachnodeInfo();
99
+ return myInfo?.ringDepth ?? 8; // Default to Ring 8
100
+ }
101
+
102
+ /**
103
+ * Filter peers by partition responsibility.
104
+ */
105
+ private filterByPartition(peers: string[], blockId: BlockId, ringDepth: number): string[] {
106
+ if (ringDepth === 0) {
107
+ return peers; // Ring 0 covers all blocks
108
+ }
109
+
110
+ const blockPrefix = this.extractBlockPrefix(blockId, ringDepth);
111
+
112
+ return peers.filter(peerId => {
113
+ const info = this.fretAdapter.getArachnodeInfo(peerId);
114
+ if (!info || !info.partition) return false;
115
+ return info.partition.prefixValue === blockPrefix;
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Extract prefix bits from block ID for partition matching.
121
+ */
122
+ private extractBlockPrefix(blockId: BlockId, bits: number): number {
123
+ const bytes = new TextEncoder().encode(blockId);
124
+ // Hash the block ID to get uniform distribution
125
+ const hash = new Uint8Array(32);
126
+ for (let i = 0; i < Math.min(bytes.length, hash.length); i++) {
127
+ hash[i] = bytes[i]!;
128
+ }
129
+
130
+ // Extract first N bits
131
+ let value = 0;
132
+ for (let i = 0; i < bits; i++) {
133
+ const byteIndex = Math.floor(i / 8);
134
+ const bitIndex = 7 - (i % 8);
135
+ const bit = (hash[byteIndex]! >> bitIndex) & 1;
136
+ value = (value << 1) | bit;
137
+ }
138
+ return value;
139
+ }
140
+
141
+ /**
142
+ * Query a specific peer for a block.
143
+ */
144
+ private async queryPeer(peerIdStr: string, blockId: BlockId, rev?: number): Promise<BlockArchive | undefined> {
145
+ try {
146
+ const peerId = peerIdFromString(peerIdStr);
147
+ const client = new SyncClient(
148
+ peerId,
149
+ this.peerNetwork,
150
+ this.protocolPrefix
151
+ );
152
+
153
+ const response = await client.requestBlock({ blockId, rev });
154
+ return response.success ? response.archive : undefined;
155
+ } catch (error) {
156
+ this.log('queryPeer failed for %s - %o', peerIdStr, error)
157
+ return undefined;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Record successful restoration from a ring.
163
+ */
164
+ private recordSuccess(ringDepth: number, blockId: BlockId, durationMs: number): void {
165
+ const count = this.metrics.successByRing.get(ringDepth) ?? 0;
166
+ this.metrics.successByRing.set(ringDepth, count + 1);
167
+
168
+ // Update rolling average duration
169
+ const totalSuccesses = Array.from(this.metrics.successByRing.values())
170
+ .reduce((sum, c) => sum + c, 0);
171
+ const prevTotal = this.metrics.averageDurationMs * (totalSuccesses - 1);
172
+ this.metrics.averageDurationMs = (prevTotal + durationMs) / totalSuccesses;
173
+
174
+ console.log(
175
+ `[Ring ${ringDepth}] Successfully restored block ${blockId} in ${durationMs}ms`
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Get restoration metrics for monitoring.
181
+ */
182
+ getMetrics(): {
183
+ totalRequests: number;
184
+ successByRing: Map<number, number>;
185
+ failureByRing: Map<number, number>;
186
+ averageDurationMs: number;
187
+ } {
188
+ return { ...this.metrics };
189
+ }
190
+ }
191
+
@@ -0,0 +1,155 @@
1
+ import { hashPeerId } from 'p2p-fret';
2
+ import type { StorageMonitor } from './storage-monitor.js';
3
+ import type { ArachnodeInfo, ArachnodeFretAdapter } from './arachnode-fret-adapter.js';
4
+
5
+ export interface RingSelectorConfig {
6
+ /** Minimum storage capacity in bytes */
7
+ minCapacity: number;
8
+
9
+ /** Thresholds for ring transitions */
10
+ thresholds: {
11
+ /** Move to outer ring when used > this % */
12
+ moveOut: number;
13
+ /** Move to inner ring when used < this % */
14
+ moveIn: number;
15
+ };
16
+ }
17
+
18
+ /**
19
+ * Determines appropriate ring depth based on storage capacity and network demand.
20
+ *
21
+ * Ring depth represents keyspace partitioning:
22
+ * - Ring 0: Full keyspace (1 partition)
23
+ * - Ring N: 2^N partitions
24
+ *
25
+ * A node selects its ring based on: available_capacity / estimated_neighborhood_demand
26
+ */
27
+ export class RingSelector {
28
+ constructor(
29
+ private readonly fretAdapter: ArachnodeFretAdapter,
30
+ private readonly storageMonitor: StorageMonitor,
31
+ private readonly config: RingSelectorConfig
32
+ ) {}
33
+
34
+ /**
35
+ * Determine appropriate ring depth based on capacity and demand.
36
+ */
37
+ async determineRing(): Promise<number> {
38
+ const capacity = await this.storageMonitor.getCapacity();
39
+
40
+ if (capacity.available < this.config.minCapacity) {
41
+ // Not enough capacity for any ring
42
+ return -1;
43
+ }
44
+
45
+ // Estimate total network size from FRET
46
+ // We use a simple heuristic: assume average block size and typical data distribution
47
+ const avgBlockSize = 100 * 1024; // 100KB typical block
48
+ const estimatedTotalBlocks = 1000; // Conservative estimate
49
+ const estimatedTotalData = estimatedTotalBlocks * avgBlockSize;
50
+
51
+ // Calculate what fraction of keyspace we can cover
52
+ const coverage = capacity.available / estimatedTotalData;
53
+
54
+ // Ring depth: 0 = full keyspace, N = 2^N partitions
55
+ // If coverage = 0.01 (1%), we need ~100 partitions ≈ Ring 7
56
+ // If coverage = 1.0 (100%), we can handle full keyspace = Ring 0
57
+ const ringDepth = Math.max(0, Math.ceil(-Math.log2(Math.max(0.001, coverage))));
58
+
59
+ return Math.min(ringDepth, 16); // Cap at Ring 16 (65536 partitions)
60
+ }
61
+
62
+ /**
63
+ * Calculate partition for a given ring depth and peer ID.
64
+ */
65
+ async calculatePartition(
66
+ ringDepth: number,
67
+ peerId: string
68
+ ): Promise<{ prefixBits: number, prefixValue: number } | undefined> {
69
+ if (ringDepth === 0) {
70
+ return undefined; // Full keyspace, no partition
71
+ }
72
+
73
+ // Hash peer ID to get coordinate
74
+ const coord = await hashPeerId({ toString: () => peerId } as any);
75
+
76
+ // Extract prefix bits from coordinate
77
+ const prefixBits = ringDepth;
78
+ const prefixValue = this.extractPrefix(coord, prefixBits);
79
+
80
+ return { prefixBits, prefixValue };
81
+ }
82
+
83
+ /**
84
+ * Create Arachnode info for this node.
85
+ */
86
+ async createArachnodeInfo(peerId: string): Promise<ArachnodeInfo> {
87
+ const capacity = await this.storageMonitor.getCapacity();
88
+ const ringDepth = await this.determineRing();
89
+ const partition = ringDepth >= 0
90
+ ? await this.calculatePartition(ringDepth, peerId)
91
+ : undefined;
92
+
93
+ return {
94
+ ringDepth: Math.max(0, ringDepth),
95
+ partition,
96
+ capacity: {
97
+ total: capacity.total,
98
+ used: capacity.used,
99
+ available: capacity.available
100
+ },
101
+ status: 'active'
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Monitor capacity and determine if ring transition is needed.
107
+ */
108
+ async shouldTransition(): Promise<{
109
+ shouldMove: boolean;
110
+ direction?: 'in' | 'out';
111
+ newRingDepth?: number
112
+ }> {
113
+ const capacity = await this.storageMonitor.getCapacity();
114
+ const usedPercent = capacity.used / capacity.total;
115
+
116
+ if (usedPercent > this.config.thresholds.moveOut) {
117
+ // Move to outer ring (more granular partition)
118
+ const currentRingDepth = await this.determineRing();
119
+ return {
120
+ shouldMove: true,
121
+ direction: 'out',
122
+ newRingDepth: currentRingDepth + 1
123
+ };
124
+ }
125
+
126
+ if (usedPercent < this.config.thresholds.moveIn) {
127
+ // Move to inner ring (broader coverage)
128
+ const currentRingDepth = await this.determineRing();
129
+ if (currentRingDepth > 0) {
130
+ return {
131
+ shouldMove: true,
132
+ direction: 'in',
133
+ newRingDepth: currentRingDepth - 1
134
+ };
135
+ }
136
+ }
137
+
138
+ return { shouldMove: false };
139
+ }
140
+
141
+ /**
142
+ * Extract first N bits from byte array as a number.
143
+ */
144
+ private extractPrefix(bytes: Uint8Array, bits: number): number {
145
+ let value = 0;
146
+ for (let i = 0; i < bits; i++) {
147
+ const byteIndex = Math.floor(i / 8);
148
+ const bitIndex = 7 - (i % 8);
149
+ const bit = (bytes[byteIndex]! >> bitIndex) & 1;
150
+ value = (value << 1) | bit;
151
+ }
152
+ return value;
153
+ }
154
+ }
155
+
@@ -0,0 +1,59 @@
1
+ import type { IRawStorage } from './i-raw-storage.js';
2
+
3
+ export interface StorageCapacity {
4
+ total: number;
5
+ used: number;
6
+ available: number;
7
+ }
8
+
9
+ export interface StorageMonitorConfig {
10
+ totalBytes?: number;
11
+ usedBytes?: number;
12
+ availableBytes?: number;
13
+ }
14
+
15
+ /**
16
+ * Monitors storage capacity for ring selection.
17
+ * Provides estimates based on storage backend or supplied overrides.
18
+ */
19
+ export class StorageMonitor {
20
+ constructor(
21
+ private readonly storage: IRawStorage,
22
+ private readonly config: StorageMonitorConfig = {}
23
+ ) {}
24
+
25
+ async getCapacity(): Promise<StorageCapacity> {
26
+ const defaultTotal = 10 * 1024 * 1024 * 1024; // 10GB default
27
+ const total = this.config.totalBytes ?? defaultTotal;
28
+ const usedOverride = this.config.usedBytes;
29
+ const availableOverride = this.config.availableBytes;
30
+
31
+ if (
32
+ usedOverride !== undefined ||
33
+ availableOverride !== undefined ||
34
+ this.config.totalBytes !== undefined
35
+ ) {
36
+ const used = usedOverride ?? Math.max(0, total - (availableOverride ?? total));
37
+ const available = availableOverride ?? Math.max(0, total - used);
38
+ return {
39
+ total,
40
+ used: Math.min(total, Math.max(0, used)),
41
+ available: Math.max(0, Math.min(total, available))
42
+ };
43
+ }
44
+
45
+ const used = await this.estimateUsedSpace();
46
+ const available = Math.max(0, total - used);
47
+
48
+ return {
49
+ total,
50
+ used,
51
+ available
52
+ };
53
+ }
54
+
55
+ private async estimateUsedSpace(): Promise<number> {
56
+ return 0; // TODO: Implement actual space calculation
57
+ }
58
+ }
59
+