@freqhole/midden 0.1.32 → 0.2.2

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/README.md CHANGED
@@ -1,82 +1,105 @@
1
1
  # midden
2
2
 
3
- browser WASM client for freqhole P2P federation.
4
-
5
- ## what it does
6
-
7
- midden provides a `MiddenNode` class that lets browsers connect to freqhole peers over iroh P2P. just need the peer's node_id (public key) to connect - iroh handles discovery via relay.
8
-
9
- ## building
10
-
11
- requires wasm-pack and LLVM toolchain:
12
-
13
- ```bash
14
- # install wasm-pack
15
- cargo install wasm-pack
3
+ the unified wasm iroh node for the freqhole world of apps: a `MiddenNode` class that lets
4
+ browsers talk to freqhole peers over iroh p2p - api requests, verified blob streaming,
5
+ opfs-backed persistent blob storage, chunked import sessions, radio, and raw bidirectional
6
+ streams for app protocols. rust crate built with wasm-bindgen/wasm-pack; npm package
7
+ `@freqhole/midden`.
8
+
9
+ ## architecture
10
+
11
+ ```mermaid
12
+ graph LR
13
+ subgraph browser
14
+ APP[your app / worker]
15
+ MN[MiddenNode wasm]
16
+ OPFS[(opfs blob store)]
17
+ end
18
+ subgraph peers
19
+ P1[always-on peer<br/>grimoire / tumulus / any]
20
+ P2[another browser]
21
+ end
22
+ APP --> MN
23
+ MN --- OPFS
24
+ MN <-- "iroh p2p (relay discovery + direct)" --> P1 & P2
25
+ ```
16
26
 
17
- # on macOS, ensure LLVM is installed
18
- brew install llvm
27
+ the default registered ALPN set is the app-neutral base (`freqhole/1`, automerge sync,
28
+ friendz, admin, events, iroh-blobs). app-specific ALPNs are passed via `extra_alpns` -
29
+ never hardcoded here.
19
30
 
20
- # build (dev mode)
21
- make build
31
+ ## structure
22
32
 
23
- # build (release mode, optimized)
24
- make build-release
33
+ ```
34
+ src/
35
+ lib.rs MiddenNode + options, bi-streams, blob transfer, import sessions
36
+ opfs_store/ persistent browser blob store (storage-generic core, native tests)
37
+ radio.rs radio streaming
38
+ pkg/ committed wasm-pack output - consumers file:-dep straight at it
39
+ Makefile build targets
25
40
  ```
26
41
 
27
- output goes to `pkg/` directory.
42
+ `pkg/` is checked into git: rebuild it whenever `src/` changes or consumers won't see the
43
+ change.
28
44
 
29
- ## usage
45
+ ## getting started
30
46
 
31
47
  ```typescript
32
- import { MiddenNode } from "midden";
33
-
34
- // create node (waits for relay connection)
35
- const node = await MiddenNode.create();
36
- console.log("my node_id:", node.node_id());
37
-
38
- // make API request to peer - accepts plain node_id or full endpoint JSON
39
- const response = await node.proxy_request(
40
- peerNodeId, // e.g. "abc123def456..." or '{"id":"...","addrs":[...]}'
41
- "GET",
42
- "/api/music/songs?limit=10",
43
- null,
48
+ import { MiddenNode, MiddenNodeOptions } from "@freqhole/midden";
49
+
50
+ const options = new MiddenNodeOptions();
51
+ options.set_secret_key(mySecretKeyBytes); // omit to generate fresh
52
+ options.set_opfs_store_dir("myapp-blobs"); // persistent blob store
53
+ options.set_extra_alpns(["myapp/1"]); // app protocol ALPNs
54
+ const node = await MiddenNode.create_with_options(options);
55
+ console.log("node id:", node.node_id());
56
+
57
+ // request/response to a peer (json over iroh; same dispatch shape as http)
58
+ const resp = await node.api_request(peerAddr, "GET", "/api/hello", null);
59
+
60
+ // verified blob download with progress
61
+ const bytes = await node.download_verified_with_ensure_progress(
62
+ peerAddr,
63
+ blake3Hash,
64
+ totalSize,
65
+ (fraction) => {},
66
+ downloadId,
44
67
  );
45
- console.log(response.status, response.body);
46
68
 
47
- // fetch blob from peer
48
- const blob = await node.fetch_blob(peerNodeId, blobId);
49
- console.log(blob.size(), blob.content_type());
50
- // blob.data() returns Uint8Array
69
+ // raw bidirectional stream for your own protocol
70
+ const stream = await node.open_bi(peerAddr, "myapp/1");
71
+ await stream.write_raw_and_finish(encodedRequest);
72
+ const reply = await stream.read_to_end(64 * 1024);
51
73
  ```
52
74
 
53
- ### peer address formats
54
-
55
- midden accepts two formats for `peer_addr`:
75
+ `peer_addr` accepts a bare 64-hex node id (relay discovery) or a full endpoint json
76
+ (`{"id":"...","addrs":[...]}` with relay/ip hints).
56
77
 
57
- 1. **plain node_id** (64 hex chars): uses iroh relay for discovery
78
+ other surface: `import_blob`/`release_blob` (stage bytes for peers), `ImportSession`
79
+ (chunked streaming import with incremental blake3), `Blake3Hasher`, `CancelToken` +
80
+ `download_cancel` (pause/resume), `protect_blob`/`unprotect_blob` (gc pins),
81
+ `has_complete_blob`, opfs selftests, radio.
58
82
 
59
- ```
60
- 13a257b5367d6b5b7ceb67ec6246c3dafbe886af8ed429408cd7619c7a4787b1
61
- ```
83
+ ## developer quick start
62
84
 
63
- 2. **full endpoint JSON**: includes relay URL and/or direct IP hints
64
- ```json
65
- {
66
- "id": "13a257b5...",
67
- "addrs": [{ "Relay": "https://..." }, { "Ip": "192.168.1.100:57383" }]
68
- }
69
- ```
85
+ ```bash
86
+ cargo install wasm-pack
87
+ brew install llvm # macos: wasm toolchain needs it
70
88
 
71
- ## protocol
89
+ make build # dev build -> pkg/
90
+ make build-release # optimized
91
+ cargo test # native tests (opfs_store core runs without a browser)
92
+ ```
72
93
 
73
- uses same protocol as grimoire's federation transport:
94
+ consumers today: tomb/spume + rathole, skein/loam, playlistz - all via `file:` deps at
95
+ `pkg/` (e.g. `file:../../midden/pkg`). vite consumers bundling this inside a worker need
96
+ the resolveId-plugin pattern (see @freqhole/reliquary's README).
74
97
 
75
- - ALPN: `freqhole/1`
76
- - messages: `ProxyRequest`, `ProxyResponse`, `BlobStreamRequest`, `BlobStreamResponse`
77
- - blob streaming: length-prefixed header followed by raw bytes
98
+ ## protocol
78
99
 
79
- see `grimoire/src/federation/transport/protocol.rs` for details.
100
+ same wire protocol as grimoire's federation transport: ALPN `freqhole/1`, messages
101
+ `ApiRequest`/`ApiResponse`, `EnsureBlobRequest`/`EnsureBlobResponse`, blob streaming as a
102
+ length-prefixed header + raw bytes. see `grimoire/src/federation/transport/protocol.rs`.
80
103
 
81
104
  ---
82
105
 
package/midden.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * *This API requires the following crate features to be activated: `ReadableStreamType`*
7
7
  */
8
8
 
9
- type ReadableStreamType = "bytes";
9
+ export type ReadableStreamType = "bytes";
10
10
 
11
11
  /**
12
12
  * a bidirectional QUIC stream for length-delimited message exchange.
@@ -90,11 +90,57 @@ export class BiStream {
90
90
  * race the QUIC flush -- the peer's `read_to_end` then errors with
91
91
  * "connection lost" mid-payload because the in-flight frames are
92
92
  * torn down with the connection. matters most for large payloads
93
- * (e.g. base64-encoded blob bodies in `proxy_response`).
93
+ * (e.g. base64-encoded blob bodies in `api_response`).
94
94
  */
95
95
  write_raw_and_finish(data: Uint8Array): Promise<void>;
96
96
  }
97
97
 
98
+ /**
99
+ * incremental blake3 hasher for streaming uploads — feed fixed-size chunks
100
+ * via update() and read the final hex hash from finalize(). lets JS hash a
101
+ * File while streaming it (file.stream() reader loop) instead of holding
102
+ * the whole payload in memory for a one-shot hash_blake3().
103
+ */
104
+ export class Blake3Hasher {
105
+ free(): void;
106
+ [Symbol.dispose](): void;
107
+ /**
108
+ * finish and return the hash as a 64-char hex string. the hasher can
109
+ * keep absorbing after this (blake3 finalize is non-destructive), but
110
+ * callers should treat the session as done.
111
+ */
112
+ finalize(): string;
113
+ constructor();
114
+ /**
115
+ * absorb the next chunk of data.
116
+ */
117
+ update(chunk: Uint8Array): void;
118
+ }
119
+
120
+ /**
121
+ * cooperative cancellation for in-flight downloads (pause/cancel from JS).
122
+ * the download loops select on `cancelled()` between progress events —
123
+ * cancellation takes effect at the next event boundary, and the partial
124
+ * data stays in the store, so a later download of the same hash resumes
125
+ * from the persisted bitfield (only missing ranges transfer).
126
+ */
127
+ export class CancelToken {
128
+ free(): void;
129
+ [Symbol.dispose](): void;
130
+ /**
131
+ * request cancellation. idempotent.
132
+ */
133
+ cancel(): void;
134
+ /**
135
+ * return a new CancelToken sharing the same cancellation state.
136
+ * needed because passing a wasm class by value consumes the JS handle —
137
+ * callers keep the original and pass a clone into download calls.
138
+ */
139
+ clone_token(): CancelToken;
140
+ is_cancelled(): boolean;
141
+ constructor();
142
+ }
143
+
98
144
  /**
99
145
  * result from fetching the server hello image from a peer
100
146
  */
@@ -106,6 +152,38 @@ export class HelloImageResult {
106
152
  readonly data: Uint8Array;
107
153
  }
108
154
 
155
+ /**
156
+ * chunked import session — the streaming counterpart to import_blob.
157
+ *
158
+ * created via MiddenNode::start_import(). JS feeds fixed-size chunks with
159
+ * push() (backpressured: the promise resolves only once the chunk is
160
+ * queued), then finish() completes the import and returns the blake3 hash.
161
+ * the wasm boundary never sees the whole payload at once; the store's
162
+ * ImportByteStream machinery computes the bao tree incrementally.
163
+ *
164
+ * the finished blob is pinned in the node's active_tags (same as
165
+ * import_blob) until release_blob() is called.
166
+ */
167
+ export class ImportSession {
168
+ private constructor();
169
+ free(): void;
170
+ [Symbol.dispose](): void;
171
+ /**
172
+ * abort the import. any partially-imported data is left to GC.
173
+ */
174
+ abort(): void;
175
+ /**
176
+ * signal end-of-stream, wait for the import to complete, pin the
177
+ * resulting blob, and return its blake3 hash as a hex string.
178
+ */
179
+ finish(): Promise<string>;
180
+ /**
181
+ * queue the next chunk. resolves once the chunk has been accepted by
182
+ * the import stream (bounded channel — this is the backpressure point).
183
+ */
184
+ push(chunk: Uint8Array): Promise<void>;
185
+ }
186
+
109
187
  export class IntoUnderlyingByteSource {
110
188
  private constructor();
111
189
  free(): void;
@@ -138,7 +216,7 @@ export class IntoUnderlyingSource {
138
216
  * browser P2P node for freqhole federation
139
217
  *
140
218
  * supports two protocols:
141
- * - freqhole/1: API proxying and small blob streaming
219
+ * - freqhole/1: API requests and small blob streaming
142
220
  * - iroh-blobs: verified streaming for audio files
143
221
  */
144
222
  export class MiddenNode {
@@ -155,12 +233,34 @@ export class MiddenNode {
155
233
  *
156
234
  * the caller should check `stream.alpn()` to route the connection
157
235
  * to the appropriate handler.
236
+ *
237
+ * a single incoming attempt failing during the TLS handshake (e.g. the
238
+ * peer aborts mid-handshake - normal during connection-path racing, or
239
+ * a peer that redials before noticing an earlier attempt is still
240
+ * live) does not end this call: it's logged and the loop moves on to
241
+ * the next queued incoming connection. propagating that failure to the
242
+ * caller instead would surface as a JS-level error on every accept()
243
+ * call, forcing the caller through a full error-handling/backoff cycle
244
+ * (see `IrohNetworkAdapter`'s accept loop) before the next, perfectly
245
+ * good, already-queued connection is even looked at - under a burst of
246
+ * aborted handshakes this can visibly stall new connections from ever
247
+ * completing.
158
248
  */
159
249
  accept(): Promise<any>;
160
250
  /**
161
251
  * return the number of blobs currently held in the store via active TempTags.
162
252
  */
163
253
  active_blob_count(): number;
254
+ /**
255
+ * send an API request to a peer
256
+ * peer_addr can be plain node_id or full endpoint JSON with relay/IP hints
257
+ */
258
+ api_request(peer_addr: string, method: string, path: string, body?: string | null): Promise<any>;
259
+ /**
260
+ * PROTOTYPE: remove a hash's restriction, returning it to the default
261
+ * (served to anyone) state.
262
+ */
263
+ clear_blob_restriction(blake3_hash: string): void;
164
264
  /**
165
265
  * compute blake3 hash for a blob on demand
166
266
  *
@@ -172,8 +272,10 @@ export class MiddenNode {
172
272
  */
173
273
  compute_blake3(peer_addr: string, blob_id: string): Promise<string | undefined>;
174
274
  /**
175
- * create a new node with random identity
176
- * waits for relay connection before returning.
275
+ * create a new node with random identity, an in-memory blob store, and
276
+ * the default ALPN set. waits for relay connection before returning.
277
+ *
278
+ * deprecated: use `create_with_options` instead.
177
279
  *
178
280
  * `connect_timeout_ms` is an optional per-dial timeout for `open_bi`
179
281
  * (defaults to 10s when omitted/undefined).
@@ -183,6 +285,8 @@ export class MiddenNode {
183
285
  * create a node from existing secret key bytes (for persistence)
184
286
  * key_bytes must be exactly 32 bytes.
185
287
  *
288
+ * deprecated: use `create_with_options` instead.
289
+ *
186
290
  * `connect_timeout_ms` is an optional per-dial timeout for `open_bi`
187
291
  * (defaults to 10s when omitted/undefined).
188
292
  */
@@ -190,13 +294,21 @@ export class MiddenNode {
190
294
  /**
191
295
  * create a node from existing secret key with additional ALPN protocols.
192
296
  *
297
+ * deprecated: use `create_with_options` instead.
298
+ *
193
299
  * `extra_alpns` is a JS array of strings (e.g. ["iroh/automerge-repo/1"]).
194
- * the node always registers "freqhole/1" plus whatever extra ALPNs are given.
300
+ * the node always registers the default ALPN set plus whatever extra ALPNs are given.
195
301
  *
196
302
  * `connect_timeout_ms` is an optional per-dial timeout for `open_bi`
197
303
  * (defaults to 10s when omitted/undefined).
198
304
  */
199
305
  static create_with_alpns(key_bytes: Uint8Array, extra_alpns: Array<any>, connect_timeout_ms?: number | null): Promise<MiddenNode>;
306
+ /**
307
+ * create a node from an options bag. this is the single canonical
308
+ * constructor — `create`/`create_from_key`/`create_with_alpns` below
309
+ * are deprecated wrappers kept for existing callers (spume, playlistz).
310
+ */
311
+ static create_with_options(options: MiddenNodeOptions): Promise<MiddenNode>;
200
312
  /**
201
313
  * download a blob using iroh-blobs verified streaming
202
314
  *
@@ -218,6 +330,13 @@ export class MiddenNode {
218
330
  * returns (blob_data, blake3_hash) for caching the hash for future requests.
219
331
  */
220
332
  download_verified_by_id(peer_addr: string, blob_id: string): Promise<Array<any>>;
333
+ /**
334
+ * full pipeline from blob_id with progress reporting.
335
+ *
336
+ * computes blake3 on demand, then uses verified download with progress.
337
+ * returns [data: Uint8Array, blake3: string].
338
+ */
339
+ download_verified_by_id_progress(peer_addr: string, blob_id: string, total_size: number, on_progress: Function): Promise<Array<any>>;
221
340
  /**
222
341
  * download a verified blob and stream chunks to JS via callback
223
342
  *
@@ -238,13 +357,14 @@ export class MiddenNode {
238
357
  *
239
358
  * returns total bytes streamed.
240
359
  */
241
- download_verified_streaming(peer_addr: string, blake3_hash: string, total_size: number, on_chunk: Function, on_progress: Function): Promise<number>;
360
+ download_verified_streaming(peer_addr: string, blake3_hash: string, total_size: number, on_chunk: Function, on_progress: Function, cancel?: CancelToken | null): Promise<number>;
242
361
  /**
243
362
  * streaming download with auto ensure+retry. first attempts the streaming
244
363
  * download; if the verified download fails (blob not in peer's store), calls
245
- * ensure_blob to load it, then retries.
364
+ * ensure_blob to load it, then retries. a deliberate cancellation is NOT
365
+ * retried — it propagates immediately with the "download cancelled" message.
246
366
  */
247
- download_verified_streaming_with_ensure(peer_addr: string, blake3_hash: string, total_size: number, on_chunk: Function, on_progress: Function): Promise<number>;
367
+ download_verified_streaming_with_ensure(peer_addr: string, blake3_hash: string, total_size: number, on_chunk: Function, on_progress: Function, cancel?: CancelToken | null): Promise<number>;
248
368
  /**
249
369
  * download a blob using iroh-blobs with automatic ensure + retry
250
370
  *
@@ -252,6 +372,35 @@ export class MiddenNode {
252
372
  * calls ensure_blob to load it, then retries.
253
373
  */
254
374
  download_verified_with_ensure(peer_addr: string, blake3_hash: string): Promise<Uint8Array>;
375
+ /**
376
+ * download with ensure + retry and progress reporting.
377
+ *
378
+ * tries download first; if blob not in peer's FsStore, calls ensure_blob
379
+ * then retries. progress callback receives fraction (0.0 to 1.0).
380
+ * `cancel`: optional cooperative cancellation (pause) — a deliberate
381
+ * cancellation is NOT retried, it propagates immediately.
382
+ *
383
+ * NOTE: any failure on the first attempt triggers this same
384
+ * ensure-then-retry fallback, not just the "blob not in FsStore yet"
385
+ * case the fallback was designed for. for a large blob, the first
386
+ * attempt can stream a substantial fraction of the bytes (driving
387
+ * `on_progress` most/all of the way to 1.0) before failing late, so the
388
+ * caller-visible symptom is a full 0->100% progress cycle that silently
389
+ * restarts from 0 for a second full cycle. logging the first attempt's
390
+ * error and explicitly resetting progress to 0 here makes this restart
391
+ * visible/diagnosable instead of looking like a silent glitch.
392
+ */
393
+ download_verified_with_ensure_progress(peer_addr: string, blake3_hash: string, total_size: number, on_progress: Function, cancel?: CancelToken | null): Promise<Uint8Array>;
394
+ /**
395
+ * download a blob with progress reporting via JS callback
396
+ *
397
+ * same as download_verified but calls on_progress(fraction) where
398
+ * fraction is bytes_received / total_size (0.0 to 1.0).
399
+ * total_size should come from the caller's known size field.
400
+ * `cancel`: optional cooperative cancellation (pause) — see
401
+ * download_verified_streaming for the semantics.
402
+ */
403
+ download_verified_with_progress(peer_addr: string, blake3_hash: string, total_size: number, on_progress: Function, cancel?: CancelToken | null): Promise<Uint8Array>;
255
404
  /**
256
405
  * ensure a blob is loaded into the peer's FsStore by blake3 hash
257
406
  *
@@ -268,11 +417,18 @@ export class MiddenNode {
268
417
  */
269
418
  fetch_hello_image(peer_addr: string): Promise<HelloImageResult>;
270
419
  /**
271
- * check whether a blob with the given blake3 hash is currently held in the MemStore
420
+ * check whether a blob with the given blake3 hash is currently held in the store
272
421
  * via an active TempTag. avoids expensive OPFS read + bao recomputation when the
273
422
  * blob is already loaded.
274
423
  */
275
424
  has_active_blob(blake3_hash: string): boolean;
425
+ /**
426
+ * check whether a COMPLETE blob with this hash exists in the blob store
427
+ * itself — with the persistent opfs store this is true across reloads,
428
+ * even when no TempTag pins it. lets serving paths skip re-imports
429
+ * entirely.
430
+ */
431
+ has_complete_blob(blake3_hash: string): Promise<boolean>;
276
432
  /**
277
433
  * import a blob from its pre-computed bao-encoded bytes, skipping the
278
434
  * expensive bao tree computation. `blake3_hash` is the 64-char hex hash,
@@ -288,7 +444,7 @@ export class MiddenNode {
288
444
  * import raw bytes into the iroh-blobs store, returning the blake3 hash.
289
445
  * this makes the blob available for verified download by peers.
290
446
  * the blob stays in the store as long as its TempTag is held in active_tags.
291
- * call release_blob() to allow GC, or it will be evicted when the map exceeds 3 entries.
447
+ * call release_blob() to allow GC.
292
448
  */
293
449
  import_blob(data: Uint8Array): Promise<string>;
294
450
  /**
@@ -317,12 +473,18 @@ export class MiddenNode {
317
473
  * open a bidirectional stream to a peer on a specific ALPN.
318
474
  *
319
475
  * `peer_addr` can be a plain node_id hex string or a full endpoint
320
- * address JSON (same format as proxy_request). `alpn` is the protocol
476
+ * address JSON (same format as api_request). `alpn` is the protocol
321
477
  * to negotiate (e.g. "iroh/automerge-repo/1").
322
478
  *
323
479
  * returns a BiStream for length-delimited message exchange.
324
480
  */
325
481
  open_bi(peer_addr: string, alpn: string): Promise<BiStream>;
482
+ /**
483
+ * pin a hash so gc won't sweep it (e.g. a paused partial download).
484
+ * idempotent. pair with unprotect_blob when the partial is resumed to
485
+ * completion or discarded.
486
+ */
487
+ protect_blob(blake3_hash: string): void;
326
488
  /**
327
489
  * dispatch a typed admin command to a peer over the freqhole-admin/1 ALPN.
328
490
  *
@@ -332,16 +494,23 @@ export class MiddenNode {
332
494
  * schema happens in the spume `AdminClient`.
333
495
  */
334
496
  proxy_admin(peer_addr: string, command: string, args: string): Promise<any>;
335
- /**
336
- * send an API request to a peer
337
- * peer_addr can be plain node_id or full endpoint JSON with relay/IP hints
338
- */
339
- proxy_request(peer_addr: string, method: string, path: string, body?: string | null): Promise<any>;
340
497
  /**
341
498
  * release a blob's TempTag, allowing the store to garbage-collect it.
342
499
  * blake3_hash should be the 64-char hex string returned by import_blob.
343
500
  */
344
501
  release_blob(blake3_hash: string): void;
502
+ /**
503
+ * PROTOTYPE: restrict a blob (by blake3 hex hash) so only the given
504
+ * peer node ids may fetch it over the `iroh-blobs/*` ALPN. a hash with
505
+ * no restriction registered is served to anyone (today's default
506
+ * behavior, unchanged) — calling this is what opts a specific hash
507
+ * into gating.
508
+ *
509
+ * this is a stopgap/demo hook, not the real canvas-ACL integration: it
510
+ * has to be called explicitly, from JS, with an already-resolved list
511
+ * of allowed peer node ids for this one hash.
512
+ */
513
+ restrict_blob_to_peers(blake3_hash: string, peer_node_ids: Array<any>): void;
345
514
  /**
346
515
  * get the secret key bytes for persistence (32 bytes)
347
516
  * store this in IndexedDB to maintain the same identity across sessions
@@ -365,6 +534,12 @@ export class MiddenNode {
365
534
  * they already know the hash of. peer filtering can be added later if needed.
366
535
  */
367
536
  start_blob_server(): void;
537
+ /**
538
+ * begin a chunked import — the streaming counterpart to import_blob for
539
+ * payloads that shouldn't be materialized as one contiguous &[u8] across
540
+ * the wasm boundary. see ImportSession for the push/finish protocol.
541
+ */
542
+ start_import(): ImportSession;
368
543
  /**
369
544
  * connect to a freqhole radio broadcaster.
370
545
  *
@@ -381,6 +556,53 @@ export class MiddenNode {
381
556
  * playback and closes the iroh connection.
382
557
  */
383
558
  tune_radio(peer_addr: string, station_id: string | null | undefined, on_hello: Function, on_meta: Function, on_chunk: Function): Promise<RadioHandle>;
559
+ /**
560
+ * remove a gc pin added by protect_blob (or by a cancelled download).
561
+ */
562
+ unprotect_blob(blake3_hash: string): void;
563
+ }
564
+
565
+ /**
566
+ * options bag for `MiddenNode::create_with_options`, the single canonical
567
+ * constructor. build one, set whichever fields are needed, and pass it in:
568
+ *
569
+ * ```js
570
+ * const opts = new MiddenNodeOptions();
571
+ * opts.opfs_store_dir = "midden-blob-store";
572
+ * opts.connect_timeout_ms = 5000;
573
+ * const node = await MiddenNode.create_with_options(opts);
574
+ * ```
575
+ *
576
+ * `create`/`create_from_key`/`create_with_alpns` remain as deprecated
577
+ * wrappers over this constructor for existing callers (spume, playlistz).
578
+ */
579
+ export class MiddenNodeOptions {
580
+ free(): void;
581
+ [Symbol.dispose](): void;
582
+ constructor();
583
+ /**
584
+ * per-dial timeout (ms) for `open_bi`/`connect` (defaults to 10s).
585
+ */
586
+ get connect_timeout_ms(): number | undefined;
587
+ set connect_timeout_ms(value: number | null | undefined);
588
+ /**
589
+ * additional ALPN protocols to register beyond the default set.
590
+ */
591
+ get extra_alpns(): string[] | undefined;
592
+ set extra_alpns(value: string[] | null | undefined);
593
+ /**
594
+ * when given, blobs persist in an OPFS-backed store under this
595
+ * directory (worker context required); otherwise (or when OPFS is
596
+ * unavailable) an in-memory store is used.
597
+ */
598
+ get opfs_store_dir(): string | undefined;
599
+ set opfs_store_dir(value: string | null | undefined);
600
+ /**
601
+ * the node's secret key (32 raw bytes). omit (or pass null/undefined)
602
+ * to generate a random identity.
603
+ */
604
+ get secret_key(): Uint8Array | undefined;
605
+ set secret_key(value: Uint8Array | null | undefined);
384
606
  }
385
607
 
386
608
  /**
@@ -404,4 +626,19 @@ export class RadioHandle {
404
626
  */
405
627
  export function hash_blake3(data: Uint8Array): string;
406
628
 
629
+ /**
630
+ * opfs store selftest — runs the full import/export round trip against
631
+ * real OPFS through the real iroh-blobs api. worker context required
632
+ * (sync access handles). wasm-only debug helper, used for manual
633
+ * debugging from the blob worker, not from automated tests.
634
+ */
635
+ export function opfs_store_selftest(): Promise<string>;
636
+
637
+ /**
638
+ * persistence selftest: blobs + tags survive a store shutdown/reopen over
639
+ * the same OPFS directory. worker context required. wasm-only debug
640
+ * helper, used for manual debugging from the blob worker.
641
+ */
642
+ export function opfs_store_selftest_persistence(): Promise<string>;
643
+
407
644
  export function start(): void;
package/midden.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /* @ts-self-types="./midden.d.ts" */
2
-
3
2
  import * as wasm from "./midden_bg.wasm";
4
3
  import { __wbg_set_wasm } from "./midden_bg.js";
4
+
5
5
  __wbg_set_wasm(wasm);
6
6
  wasm.__wbindgen_start();
7
7
  export {
8
- BiStream, HelloImageResult, IntoUnderlyingByteSource, IntoUnderlyingSink, IntoUnderlyingSource, MiddenNode, RadioHandle, hash_blake3, start
8
+ BiStream, Blake3Hasher, CancelToken, HelloImageResult, ImportSession, IntoUnderlyingByteSource, IntoUnderlyingSink, IntoUnderlyingSource, MiddenNode, MiddenNodeOptions, RadioHandle, hash_blake3, opfs_store_selftest, opfs_store_selftest_persistence, start
9
9
  } from "./midden_bg.js";