@hashtree/worker 0.1.26 → 0.2.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 (258) hide show
  1. package/README.md +16 -16
  2. package/package.json +23 -19
  3. package/src/app-runtime.ts +393 -0
  4. package/src/capabilities/blossomBandwidthTracker.ts +74 -0
  5. package/src/capabilities/blossomTransport.ts +179 -0
  6. package/src/capabilities/connectivity.ts +54 -0
  7. package/src/capabilities/idbStorage.ts +94 -0
  8. package/src/capabilities/meshRouterStore.ts +426 -0
  9. package/src/capabilities/rootResolver.ts +497 -0
  10. package/src/client-id.ts +137 -0
  11. package/src/client.ts +501 -0
  12. package/{dist/entry.js → src/entry.ts} +1 -1
  13. package/src/htree-path.ts +53 -0
  14. package/src/htree-url.ts +156 -0
  15. package/src/index.ts +76 -0
  16. package/src/mediaStreaming.ts +64 -0
  17. package/src/p2p/boundedQueue.ts +168 -0
  18. package/src/p2p/errorMessage.ts +6 -0
  19. package/src/p2p/index.ts +48 -0
  20. package/src/p2p/lruCache.ts +78 -0
  21. package/src/p2p/meshQueryRouter.ts +361 -0
  22. package/src/p2p/protocol.ts +11 -0
  23. package/src/p2p/queryForwardingMachine.ts +197 -0
  24. package/src/p2p/signaling.ts +284 -0
  25. package/src/p2p/uploadRateLimiter.ts +85 -0
  26. package/src/p2p/webrtcController.ts +1168 -0
  27. package/src/p2p/webrtcProxy.ts +519 -0
  28. package/src/privacyGuards.ts +31 -0
  29. package/src/protocol.ts +124 -0
  30. package/src/relay/identity.ts +86 -0
  31. package/src/relay/mediaHandler.ts +1633 -0
  32. package/src/relay/ndk.ts +590 -0
  33. package/{dist/iris/nostr-wasm.js → src/relay/nostr-wasm.ts} +4 -1
  34. package/src/relay/nostr.ts +249 -0
  35. package/src/relay/protocol.ts +361 -0
  36. package/src/relay/publicAssetUrl.ts +25 -0
  37. package/src/relay/rootPathResolver.ts +50 -0
  38. package/src/relay/shims.d.ts +17 -0
  39. package/src/relay/signing.ts +332 -0
  40. package/src/relay/treeRootCache.ts +354 -0
  41. package/src/relay/treeRootSubscription.ts +577 -0
  42. package/src/relay/utils/constants.ts +139 -0
  43. package/src/relay/utils/errorMessage.ts +7 -0
  44. package/src/relay/utils/lruCache.ts +79 -0
  45. package/src/relay/webrtc.ts +5 -0
  46. package/src/relay/webrtcSignaling.ts +108 -0
  47. package/src/relay/worker.ts +1787 -0
  48. package/src/relay-client.ts +265 -0
  49. package/src/relay-entry.ts +1 -0
  50. package/src/runtime-network.ts +134 -0
  51. package/src/runtime.ts +153 -0
  52. package/src/transferableBytes.ts +5 -0
  53. package/src/tree-root.ts +851 -0
  54. package/src/types.ts +8 -0
  55. package/src/worker.ts +975 -0
  56. package/LICENSE +0 -21
  57. package/dist/app-runtime.d.ts +0 -60
  58. package/dist/app-runtime.d.ts.map +0 -1
  59. package/dist/app-runtime.js +0 -271
  60. package/dist/app-runtime.js.map +0 -1
  61. package/dist/capabilities/blossomBandwidthTracker.d.ts +0 -26
  62. package/dist/capabilities/blossomBandwidthTracker.d.ts.map +0 -1
  63. package/dist/capabilities/blossomBandwidthTracker.js +0 -53
  64. package/dist/capabilities/blossomBandwidthTracker.js.map +0 -1
  65. package/dist/capabilities/blossomTransport.d.ts +0 -22
  66. package/dist/capabilities/blossomTransport.d.ts.map +0 -1
  67. package/dist/capabilities/blossomTransport.js +0 -144
  68. package/dist/capabilities/blossomTransport.js.map +0 -1
  69. package/dist/capabilities/connectivity.d.ts +0 -3
  70. package/dist/capabilities/connectivity.d.ts.map +0 -1
  71. package/dist/capabilities/connectivity.js +0 -49
  72. package/dist/capabilities/connectivity.js.map +0 -1
  73. package/dist/capabilities/idbStorage.d.ts +0 -25
  74. package/dist/capabilities/idbStorage.d.ts.map +0 -1
  75. package/dist/capabilities/idbStorage.js +0 -73
  76. package/dist/capabilities/idbStorage.js.map +0 -1
  77. package/dist/capabilities/meshRouterStore.d.ts +0 -71
  78. package/dist/capabilities/meshRouterStore.d.ts.map +0 -1
  79. package/dist/capabilities/meshRouterStore.js +0 -316
  80. package/dist/capabilities/meshRouterStore.js.map +0 -1
  81. package/dist/capabilities/rootResolver.d.ts +0 -10
  82. package/dist/capabilities/rootResolver.d.ts.map +0 -1
  83. package/dist/capabilities/rootResolver.js +0 -393
  84. package/dist/capabilities/rootResolver.js.map +0 -1
  85. package/dist/client-id.d.ts +0 -18
  86. package/dist/client-id.d.ts.map +0 -1
  87. package/dist/client-id.js +0 -98
  88. package/dist/client-id.js.map +0 -1
  89. package/dist/client.d.ts +0 -61
  90. package/dist/client.d.ts.map +0 -1
  91. package/dist/client.js +0 -417
  92. package/dist/client.js.map +0 -1
  93. package/dist/entry.d.ts +0 -2
  94. package/dist/entry.d.ts.map +0 -1
  95. package/dist/entry.js.map +0 -1
  96. package/dist/htree-path.d.ts +0 -13
  97. package/dist/htree-path.d.ts.map +0 -1
  98. package/dist/htree-path.js +0 -38
  99. package/dist/htree-path.js.map +0 -1
  100. package/dist/htree-url.d.ts +0 -22
  101. package/dist/htree-url.d.ts.map +0 -1
  102. package/dist/htree-url.js +0 -118
  103. package/dist/htree-url.js.map +0 -1
  104. package/dist/index.d.ts +0 -17
  105. package/dist/index.d.ts.map +0 -1
  106. package/dist/index.js +0 -8
  107. package/dist/index.js.map +0 -1
  108. package/dist/iris/identity.d.ts +0 -36
  109. package/dist/iris/identity.d.ts.map +0 -1
  110. package/dist/iris/identity.js +0 -78
  111. package/dist/iris/identity.js.map +0 -1
  112. package/dist/iris/mediaHandler.d.ts +0 -64
  113. package/dist/iris/mediaHandler.d.ts.map +0 -1
  114. package/dist/iris/mediaHandler.js +0 -1285
  115. package/dist/iris/mediaHandler.js.map +0 -1
  116. package/dist/iris/ndk.d.ts +0 -96
  117. package/dist/iris/ndk.d.ts.map +0 -1
  118. package/dist/iris/ndk.js +0 -502
  119. package/dist/iris/ndk.js.map +0 -1
  120. package/dist/iris/nostr-wasm.d.ts +0 -14
  121. package/dist/iris/nostr-wasm.d.ts.map +0 -1
  122. package/dist/iris/nostr-wasm.js.map +0 -1
  123. package/dist/iris/nostr.d.ts +0 -60
  124. package/dist/iris/nostr.d.ts.map +0 -1
  125. package/dist/iris/nostr.js +0 -207
  126. package/dist/iris/nostr.js.map +0 -1
  127. package/dist/iris/protocol.d.ts +0 -583
  128. package/dist/iris/protocol.d.ts.map +0 -1
  129. package/dist/iris/protocol.js +0 -16
  130. package/dist/iris/protocol.js.map +0 -1
  131. package/dist/iris/publicAssetUrl.d.ts +0 -6
  132. package/dist/iris/publicAssetUrl.d.ts.map +0 -1
  133. package/dist/iris/publicAssetUrl.js +0 -14
  134. package/dist/iris/publicAssetUrl.js.map +0 -1
  135. package/dist/iris/rootPathResolver.d.ts +0 -9
  136. package/dist/iris/rootPathResolver.d.ts.map +0 -1
  137. package/dist/iris/rootPathResolver.js +0 -32
  138. package/dist/iris/rootPathResolver.js.map +0 -1
  139. package/dist/iris/signing.d.ts +0 -50
  140. package/dist/iris/signing.d.ts.map +0 -1
  141. package/dist/iris/signing.js +0 -299
  142. package/dist/iris/signing.js.map +0 -1
  143. package/dist/iris/treeRootCache.d.ts +0 -86
  144. package/dist/iris/treeRootCache.d.ts.map +0 -1
  145. package/dist/iris/treeRootCache.js +0 -269
  146. package/dist/iris/treeRootCache.js.map +0 -1
  147. package/dist/iris/treeRootSubscription.d.ts +0 -55
  148. package/dist/iris/treeRootSubscription.d.ts.map +0 -1
  149. package/dist/iris/treeRootSubscription.js +0 -479
  150. package/dist/iris/treeRootSubscription.js.map +0 -1
  151. package/dist/iris/utils/constants.d.ts +0 -76
  152. package/dist/iris/utils/constants.d.ts.map +0 -1
  153. package/dist/iris/utils/constants.js +0 -113
  154. package/dist/iris/utils/constants.js.map +0 -1
  155. package/dist/iris/utils/errorMessage.d.ts +0 -5
  156. package/dist/iris/utils/errorMessage.d.ts.map +0 -1
  157. package/dist/iris/utils/errorMessage.js +0 -8
  158. package/dist/iris/utils/errorMessage.js.map +0 -1
  159. package/dist/iris/utils/lruCache.d.ts +0 -26
  160. package/dist/iris/utils/lruCache.d.ts.map +0 -1
  161. package/dist/iris/utils/lruCache.js +0 -66
  162. package/dist/iris/utils/lruCache.js.map +0 -1
  163. package/dist/iris/webrtc.d.ts +0 -2
  164. package/dist/iris/webrtc.d.ts.map +0 -1
  165. package/dist/iris/webrtc.js +0 -3
  166. package/dist/iris/webrtc.js.map +0 -1
  167. package/dist/iris/webrtcSignaling.d.ts +0 -37
  168. package/dist/iris/webrtcSignaling.d.ts.map +0 -1
  169. package/dist/iris/webrtcSignaling.js +0 -86
  170. package/dist/iris/webrtcSignaling.js.map +0 -1
  171. package/dist/iris/worker.d.ts +0 -12
  172. package/dist/iris/worker.d.ts.map +0 -1
  173. package/dist/iris/worker.js +0 -1529
  174. package/dist/iris/worker.js.map +0 -1
  175. package/dist/iris-client.d.ts +0 -31
  176. package/dist/iris-client.d.ts.map +0 -1
  177. package/dist/iris-client.js +0 -197
  178. package/dist/iris-client.js.map +0 -1
  179. package/dist/iris-entry.d.ts +0 -2
  180. package/dist/iris-entry.d.ts.map +0 -1
  181. package/dist/iris-entry.js +0 -2
  182. package/dist/iris-entry.js.map +0 -1
  183. package/dist/mediaStreaming.d.ts +0 -7
  184. package/dist/mediaStreaming.d.ts.map +0 -1
  185. package/dist/mediaStreaming.js +0 -48
  186. package/dist/mediaStreaming.js.map +0 -1
  187. package/dist/p2p/boundedQueue.d.ts +0 -79
  188. package/dist/p2p/boundedQueue.d.ts.map +0 -1
  189. package/dist/p2p/boundedQueue.js +0 -134
  190. package/dist/p2p/boundedQueue.js.map +0 -1
  191. package/dist/p2p/errorMessage.d.ts +0 -5
  192. package/dist/p2p/errorMessage.d.ts.map +0 -1
  193. package/dist/p2p/errorMessage.js +0 -7
  194. package/dist/p2p/errorMessage.js.map +0 -1
  195. package/dist/p2p/index.d.ts +0 -8
  196. package/dist/p2p/index.d.ts.map +0 -1
  197. package/dist/p2p/index.js +0 -6
  198. package/dist/p2p/index.js.map +0 -1
  199. package/dist/p2p/lruCache.d.ts +0 -26
  200. package/dist/p2p/lruCache.d.ts.map +0 -1
  201. package/dist/p2p/lruCache.js +0 -65
  202. package/dist/p2p/lruCache.js.map +0 -1
  203. package/dist/p2p/meshQueryRouter.d.ts +0 -44
  204. package/dist/p2p/meshQueryRouter.d.ts.map +0 -1
  205. package/dist/p2p/meshQueryRouter.js +0 -228
  206. package/dist/p2p/meshQueryRouter.js.map +0 -1
  207. package/dist/p2p/protocol.d.ts +0 -10
  208. package/dist/p2p/protocol.d.ts.map +0 -1
  209. package/dist/p2p/protocol.js +0 -2
  210. package/dist/p2p/protocol.js.map +0 -1
  211. package/dist/p2p/queryForwardingMachine.d.ts +0 -46
  212. package/dist/p2p/queryForwardingMachine.d.ts.map +0 -1
  213. package/dist/p2p/queryForwardingMachine.js +0 -144
  214. package/dist/p2p/queryForwardingMachine.js.map +0 -1
  215. package/dist/p2p/signaling.d.ts +0 -63
  216. package/dist/p2p/signaling.d.ts.map +0 -1
  217. package/dist/p2p/signaling.js +0 -185
  218. package/dist/p2p/signaling.js.map +0 -1
  219. package/dist/p2p/uploadRateLimiter.d.ts +0 -21
  220. package/dist/p2p/uploadRateLimiter.d.ts.map +0 -1
  221. package/dist/p2p/uploadRateLimiter.js +0 -62
  222. package/dist/p2p/uploadRateLimiter.js.map +0 -1
  223. package/dist/p2p/webrtcController.d.ts +0 -168
  224. package/dist/p2p/webrtcController.d.ts.map +0 -1
  225. package/dist/p2p/webrtcController.js +0 -902
  226. package/dist/p2p/webrtcController.js.map +0 -1
  227. package/dist/p2p/webrtcProxy.d.ts +0 -62
  228. package/dist/p2p/webrtcProxy.d.ts.map +0 -1
  229. package/dist/p2p/webrtcProxy.js +0 -447
  230. package/dist/p2p/webrtcProxy.js.map +0 -1
  231. package/dist/privacyGuards.d.ts +0 -14
  232. package/dist/privacyGuards.d.ts.map +0 -1
  233. package/dist/privacyGuards.js +0 -27
  234. package/dist/privacyGuards.js.map +0 -1
  235. package/dist/protocol.d.ts +0 -225
  236. package/dist/protocol.d.ts.map +0 -1
  237. package/dist/protocol.js +0 -2
  238. package/dist/protocol.js.map +0 -1
  239. package/dist/runtime-network.d.ts +0 -23
  240. package/dist/runtime-network.d.ts.map +0 -1
  241. package/dist/runtime-network.js +0 -105
  242. package/dist/runtime-network.js.map +0 -1
  243. package/dist/runtime.d.ts +0 -23
  244. package/dist/runtime.d.ts.map +0 -1
  245. package/dist/runtime.js +0 -122
  246. package/dist/runtime.js.map +0 -1
  247. package/dist/tree-root.d.ts +0 -201
  248. package/dist/tree-root.d.ts.map +0 -1
  249. package/dist/tree-root.js +0 -632
  250. package/dist/tree-root.js.map +0 -1
  251. package/dist/types.d.ts +0 -2
  252. package/dist/types.d.ts.map +0 -1
  253. package/dist/types.js +0 -2
  254. package/dist/types.js.map +0 -1
  255. package/dist/worker.d.ts +0 -9
  256. package/dist/worker.d.ts.map +0 -1
  257. package/dist/worker.js +0 -797
  258. package/dist/worker.js.map +0 -1
@@ -0,0 +1,179 @@
1
+ import {
2
+ BlossomStore,
3
+ type BlossomSigner,
4
+ type BlossomUploadCallback,
5
+ fromHex,
6
+ } from '@hashtree/core';
7
+ import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure';
8
+ import type { BlossomServerConfig } from '../protocol.js';
9
+ import {
10
+ BlossomBandwidthTracker,
11
+ type BlossomBandwidthStats,
12
+ type BlossomBandwidthUpdateHandler,
13
+ } from './blossomBandwidthTracker.js';
14
+
15
+ export const DEFAULT_BLOSSOM_SERVERS: BlossomServerConfig[] = [];
16
+
17
+ const MAX_CONCURRENT_READ_FETCHES = 12;
18
+
19
+ let activeReadFetches = 0;
20
+ const pendingReadFetchWaiters: Array<() => void> = [];
21
+
22
+ export type {
23
+ BlossomBandwidthServerStats,
24
+ BlossomBandwidthStats,
25
+ BlossomBandwidthUpdateHandler,
26
+ } from './blossomBandwidthTracker.js';
27
+
28
+ function normalizeServerUrl(url: string): string {
29
+ return url.replace(/\/+$/, '');
30
+ }
31
+
32
+ function normalizeServers(servers: BlossomServerConfig[] | undefined): BlossomServerConfig[] {
33
+ const source = servers && servers.length > 0 ? servers : DEFAULT_BLOSSOM_SERVERS;
34
+ const unique = new Map<string, BlossomServerConfig>();
35
+ for (const server of source) {
36
+ const url = normalizeServerUrl(server.url.trim());
37
+ if (!url) continue;
38
+ unique.set(url, {
39
+ url,
40
+ read: server.read ?? true,
41
+ write: server.write ?? false,
42
+ });
43
+ }
44
+ return Array.from(unique.values());
45
+ }
46
+
47
+ function createEphemeralSigner(): BlossomSigner {
48
+ const secretKey = generateSecretKey();
49
+ return async (template) => {
50
+ const event = finalizeEvent({
51
+ ...template,
52
+ kind: template.kind as 24242,
53
+ created_at: template.created_at,
54
+ content: template.content,
55
+ tags: template.tags,
56
+ }, secretKey);
57
+ return {
58
+ kind: event.kind,
59
+ created_at: event.created_at,
60
+ content: event.content,
61
+ tags: event.tags,
62
+ pubkey: event.pubkey,
63
+ id: event.id,
64
+ sig: event.sig,
65
+ };
66
+ };
67
+ }
68
+
69
+ function releaseReadFetchSlot(): void {
70
+ activeReadFetches = Math.max(0, activeReadFetches - 1);
71
+ pendingReadFetchWaiters.shift()?.();
72
+ }
73
+
74
+ function withReadFetchSlot<T>(loader: () => Promise<T>): Promise<T> {
75
+ return new Promise<T>((resolve, reject) => {
76
+ const start = () => {
77
+ activeReadFetches += 1;
78
+ let pending: Promise<T>;
79
+ try {
80
+ pending = loader();
81
+ } catch (error) {
82
+ releaseReadFetchSlot();
83
+ reject(error);
84
+ return;
85
+ }
86
+
87
+ pending
88
+ .then(resolve, reject)
89
+ .finally(() => {
90
+ releaseReadFetchSlot();
91
+ });
92
+ };
93
+
94
+ if (activeReadFetches < MAX_CONCURRENT_READ_FETCHES) {
95
+ start();
96
+ return;
97
+ }
98
+
99
+ pendingReadFetchWaiters.push(start);
100
+ });
101
+ }
102
+
103
+ export class BlossomTransport {
104
+ private servers: BlossomServerConfig[];
105
+ private readonly signer: BlossomSigner;
106
+ private readonly bandwidthTracker: BlossomBandwidthTracker;
107
+ private readonly inflightFetches = new Map<string, Promise<Uint8Array | null>>();
108
+ private store: BlossomStore;
109
+
110
+ constructor(servers?: BlossomServerConfig[], onBandwidthUpdate?: BlossomBandwidthUpdateHandler) {
111
+ this.servers = normalizeServers(servers);
112
+ this.signer = createEphemeralSigner();
113
+ this.bandwidthTracker = new BlossomBandwidthTracker(onBandwidthUpdate);
114
+ this.store = this.createStore(this.servers);
115
+ }
116
+
117
+ setServers(servers: BlossomServerConfig[]): void {
118
+ this.servers = normalizeServers(servers);
119
+ this.store = this.createStore(this.servers);
120
+ }
121
+
122
+ getServers(): BlossomServerConfig[] {
123
+ return this.servers;
124
+ }
125
+
126
+ getWriteServers(): BlossomServerConfig[] {
127
+ return this.servers.filter(server => !!server.write);
128
+ }
129
+
130
+ getBandwidthStats(): BlossomBandwidthStats {
131
+ return this.bandwidthTracker.getStats();
132
+ }
133
+
134
+ private createStore(servers: BlossomServerConfig[], onUploadProgress?: BlossomUploadCallback): BlossomStore {
135
+ return new BlossomStore({
136
+ servers,
137
+ signer: this.signer,
138
+ onUploadProgress,
139
+ logger: (entry) => {
140
+ this.bandwidthTracker.apply(entry);
141
+ },
142
+ });
143
+ }
144
+
145
+ createUploadStore(onUploadProgress?: BlossomUploadCallback): BlossomStore {
146
+ return this.createStore(this.servers, onUploadProgress);
147
+ }
148
+
149
+ async upload(
150
+ hashHex: string,
151
+ data: Uint8Array,
152
+ _mimeType?: string,
153
+ onUploadProgress?: BlossomUploadCallback
154
+ ): Promise<void> {
155
+ if (!this.servers.some(server => server.write)) return;
156
+ const uploadMimeType = 'application/octet-stream';
157
+ if (onUploadProgress) {
158
+ const store = this.createStore(this.servers, onUploadProgress);
159
+ await store.put(fromHex(hashHex), data, uploadMimeType);
160
+ return;
161
+ }
162
+
163
+ await this.store.put(fromHex(hashHex), data, uploadMimeType);
164
+ }
165
+
166
+ async fetch(hashHex: string): Promise<Uint8Array | null> {
167
+ const inflight = this.inflightFetches.get(hashHex);
168
+ if (inflight) {
169
+ return inflight;
170
+ }
171
+
172
+ const pending = withReadFetchSlot(() => this.store.get(fromHex(hashHex)))
173
+ .finally(() => {
174
+ this.inflightFetches.delete(hashHex);
175
+ });
176
+ this.inflightFetches.set(hashHex, pending);
177
+ return await pending;
178
+ }
179
+ }
@@ -0,0 +1,54 @@
1
+ import type { BlossomServerConfig, ConnectivityState } from '../protocol.js';
2
+
3
+ const PROBE_TIMEOUT_MS = 3500;
4
+ const DUMMY_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
5
+
6
+ function stripTrailingSlash(url: string): string {
7
+ return url.replace(/\/+$/, '');
8
+ }
9
+
10
+ async function probe(url: string): Promise<boolean> {
11
+ const controller = new AbortController();
12
+ const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
13
+ try {
14
+ const res = await fetch(url, {
15
+ method: 'HEAD',
16
+ // Connectivity checks should not fail just because cross-origin response
17
+ // headers are restricted. If the fetch resolves in no-cors mode, the
18
+ // endpoint is reachable.
19
+ mode: 'no-cors',
20
+ signal: controller.signal,
21
+ cache: 'no-store',
22
+ });
23
+ if (res.type === 'opaque') {
24
+ return true;
25
+ }
26
+ return res.status >= 100;
27
+ } catch {
28
+ return false;
29
+ } finally {
30
+ clearTimeout(timeout);
31
+ }
32
+ }
33
+
34
+ export async function probeConnectivity(servers: BlossomServerConfig[]): Promise<ConnectivityState> {
35
+ const readServers = servers.filter(server => server.read !== false);
36
+ const writeServers = servers.filter(server => server.write);
37
+
38
+ const [readResults, writeResults] = await Promise.all([
39
+ Promise.all(readServers.map(server => probe(`${stripTrailingSlash(server.url)}/${DUMMY_HASH}.bin`))),
40
+ Promise.all(writeServers.map(server => probe(`${stripTrailingSlash(server.url)}/upload`))),
41
+ ]);
42
+
43
+ const reachableReadServers = readResults.filter(Boolean).length;
44
+ const reachableWriteServers = writeResults.filter(Boolean).length;
45
+
46
+ return {
47
+ online: typeof navigator === 'undefined' ? true : navigator.onLine,
48
+ reachableReadServers,
49
+ totalReadServers: readServers.length,
50
+ reachableWriteServers,
51
+ totalWriteServers: writeServers.length,
52
+ updatedAt: Date.now(),
53
+ };
54
+ }
@@ -0,0 +1,94 @@
1
+ import { fromHex, sha256, toHex } from '@hashtree/core';
2
+ import { DexieStore } from '@hashtree/dexie';
3
+
4
+ export interface StorageStats {
5
+ items: number;
6
+ bytes: number;
7
+ maxBytes: number;
8
+ }
9
+
10
+ export class IdbBlobStorage {
11
+ private readonly store: DexieStore;
12
+ private maxBytes: number;
13
+ private writesSinceEviction = 0;
14
+ private evictionPromise: Promise<void> | null = null;
15
+
16
+ private static readonly EVICTION_WRITE_INTERVAL = 32;
17
+
18
+ constructor(dbName: string, maxBytes: number) {
19
+ this.store = new DexieStore(dbName);
20
+ this.maxBytes = maxBytes;
21
+ }
22
+
23
+ setMaxBytes(maxBytes: number): void {
24
+ this.maxBytes = maxBytes;
25
+ }
26
+
27
+ getMaxBytes(): number {
28
+ return this.maxBytes;
29
+ }
30
+
31
+ async put(data: Uint8Array): Promise<string> {
32
+ const hashHex = toHex(await sha256(data));
33
+ await this.store.put(fromHex(hashHex), data);
34
+ void this.scheduleEviction();
35
+ return hashHex;
36
+ }
37
+
38
+ async putByHash(hashHex: string, data: Uint8Array): Promise<void> {
39
+ const computed = toHex(await sha256(data));
40
+ if (computed !== hashHex) {
41
+ throw new Error('Hash mismatch while caching fetched blob');
42
+ }
43
+ await this.store.put(fromHex(hashHex), data);
44
+ void this.scheduleEviction();
45
+ }
46
+
47
+ async putByHashTrusted(hashHex: string, data: Uint8Array): Promise<void> {
48
+ await this.store.put(fromHex(hashHex), data);
49
+ void this.scheduleEviction();
50
+ }
51
+
52
+ async get(hashHex: string): Promise<Uint8Array | null> {
53
+ return this.store.get(fromHex(hashHex));
54
+ }
55
+
56
+ async has(hashHex: string): Promise<boolean> {
57
+ return this.store.has(fromHex(hashHex));
58
+ }
59
+
60
+ async delete(hashHex: string): Promise<boolean> {
61
+ return this.store.delete(fromHex(hashHex));
62
+ }
63
+
64
+ async getStats(): Promise<StorageStats> {
65
+ const [items, bytes] = await Promise.all([
66
+ this.store.count(),
67
+ this.store.totalBytes(),
68
+ ]);
69
+ return { items, bytes, maxBytes: this.maxBytes };
70
+ }
71
+
72
+ close(): void {
73
+ this.store.close();
74
+ }
75
+
76
+ private scheduleEviction(): void {
77
+ this.writesSinceEviction += 1;
78
+ if (this.writesSinceEviction < IdbBlobStorage.EVICTION_WRITE_INTERVAL) {
79
+ return;
80
+ }
81
+ this.writesSinceEviction = 0;
82
+
83
+ if (this.evictionPromise) {
84
+ return;
85
+ }
86
+
87
+ this.evictionPromise = this.store
88
+ .evict(this.maxBytes)
89
+ .then(() => {})
90
+ .finally(() => {
91
+ this.evictionPromise = null;
92
+ });
93
+ }
94
+ }