libfw-client 0.4.2 → 0.4.4

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
@@ -3,6 +3,11 @@
3
3
  The browser SDK for [libfw](../README.md): a zero-config wrapper around the
4
4
  WASM engine, the File System Access API and IndexedDB.
5
5
 
6
+ > The same protocol engine is also available as a **native Rust client** for
7
+ > non-browser programs — `libfw_client::native::NativeClient` (async
8
+ > `tokio` + `reqwest`), with a runnable CLI in
9
+ > [`examples/rust-client`](../examples/rust-client/README.md).
10
+
6
11
  ## Usage
7
12
 
8
13
  ```js
@@ -16,8 +21,12 @@ const client = new LibfwClient({
16
21
  downloadWindow: 4, // parallel byte-range GETs per single file download
17
22
  // (raise to reduce download stutter on high-latency links)
18
23
  compress: true, // zrip per-block compression
24
+ compressLevel: 'auto',// zrip level policy: 'auto' benchmarks uploads
25
+ // (needs autoTune) / 'fast' / 'balanced' / 'max' / N
19
26
  autoTune: true, // adaptive tuning: probes /capabilities and ramps
20
- // concurrency/windows/chunk sizes from real stats
27
+ tuneTtlMs: 3600000, // how long a settled result stays cached (browser,
28
+ // localStorage); 0 = never cache, re-ramp each time
29
+ // concurrency/windows/chunk size from real stats
21
30
  onEvent: (e) => {
22
31
  if (e.type === 'progress') updateProgressBar(e.done, e.total);
23
32
  else if (e.type === 'tuning') renderTuning(e.phase, e.params, e.stats);
@@ -71,8 +80,14 @@ client.cancel();
71
80
  into place. Only the chunks the server still misses are re-sent
72
81
  (`x-libfw-session-status` probe seeds resume), so interrupted uploads
73
82
  resume BitTorrent-style (only the broken/lost parts are re-transmitted).
83
+ A dropped connection (page refresh, crashed tab) keeps that partial on the
84
+ server, so a reloaded page resumes exactly where it stopped.
74
85
  - Resume state (`etag`, `offset`, `size`) is persisted per path in
75
- IndexedDB and re-validated on every retry.
86
+ IndexedDB and re-validated on every retry. `createWritable()` only
87
+ publishes a file on `close()`, so a download **checkpoints** its prefix to
88
+ disk every time the engine reports a durable offset (~4 MiB) — a hard page
89
+ refresh mid-download therefore resumes from the last checkpoint instead of
90
+ restarting from byte 0.
76
91
  - Pause/resume/cancel drive the WASM state machine
77
92
  (`idle → downloading/uploading → paused → resumed → completed/failed`).
78
93
 
@@ -102,25 +117,54 @@ dist/libfw-client.umd.js UMD bundle (after build:umd)
102
117
  - `downloadWindow: number` (default `4`) — in-flight byte-range window per
103
118
  single file download (how many concurrent `Range` GETs); raise it on
104
119
  high-latency links. `1` disables parallelism (sequential downloads).
105
- - `downloadChunkSize: number` (default `262144`, 256 KiB) — byte range size
106
- for parallel downloads; the engine reorders in-flight chunks in memory
107
- (worst case ≈ `downloadWindow * downloadChunkSize` bytes) so the SDK
108
- still receives data in order.
120
+ - `chunkSize: number` (default `2097152`, 2 MiB) — shared size used for
121
+ both upload chunks and parallel download ranges; the engine reorders
122
+ in-flight chunks in memory (worst case ≈ `downloadWindow * chunkSize`
123
+ bytes) so the SDK still receives data in order.
124
+ - `compress: boolean` (default `true`) — master switch for zrip
125
+ compression; `false` sends every body as identity.
126
+ - `compressLevel: number | 'auto' | 'fast' | 'balanced' | 'max'` (default
127
+ `'balanced'`) — zrip level policy when `compress` is on. `'fast'` is the
128
+ advertised minimum (least CPU, worst ratio), `'balanced'` the advertised
129
+ default, `'max'` the advertised maximum (best ratio); a number is clamped
130
+ into the advertised range. `'auto'` additionally micro-benchmarks the
131
+ advertised range against a real sample of the first uploaded file (while
132
+ `autoTune` is enabled) and picks the best bytes-saved-vs-CPU-time
133
+ trade-off for the measured link speed; downloads request the resolved
134
+ level from the server.
109
135
  - `downloadMode: 'auto' | 'fs' | 'browser'` (default `'auto'`) — `'fs'`
110
136
  streams downloads through the File System Access API; `'browser'` buffers
111
137
  and triggers a traditional browser download (folders become `.zip`);
112
138
  `'auto'` uses `'fs'` when the API exists and falls back to `'browser'`.
139
+ **Download resume requires `'fs'`** (or an injected `directoryHandle`):
140
+ an interrupted fs-mode download is continued from the bytes already on
141
+ disk, while the memory-backed `'browser'` fallback always restarts from
142
+ byte 0 — there is no partial file to continue from.
113
143
  - `maxFallbackBytes: number` (default `536870912`, 512 MiB) — memory cap
114
144
  for the in-memory `'browser'` fallback. File sizes are pre-checked
115
145
  against it before buffering; a download that would exceed it rejects
116
146
  with a `too-large` `LibfwError` instead of risking an OOM. `0` disables.
117
147
  - `autoTune: boolean` (default `false`) — enable the adaptive tuning
118
148
  engine. The engine probes the server's `/capabilities` limits and
119
- TCP-style ramps concurrency / windows / chunk sizes (and the zrip
120
- level) from the advertised minimums using real transfer stats. When
121
- disabled, the configured static values are used as-is.
122
- - `tuneTtlMs: number` (default `3600000`, 1 h) how long a settled
123
- tuning result is reused for the same server origin before re-ramping.
149
+ TCP-style ramps the per-file window and cross-file concurrency from the
150
+ advertised minimums using real transfer stats; the chunk size follows the
151
+ measured throughput (~100 ms of it, clamped into the advertised range) and
152
+ the zrip level is a client policy from `compressLevel`, never ramped. When
153
+ disabled, the configured static values are used as-is. Tuning state is
154
+ **in memory** for the lifetime of the client (a settle is reused by later
155
+ transfers and dropped on failure) **and** is cached in `localStorage` per
156
+ origin + direction, so a page refresh does not re-ramp — see `tuneTtlMs`.
157
+ - `tuneTtlMs: number` (default `3600000`, 1 hour) — how long a cached
158
+ tuning result stays usable. The cache is keyed by origin **and**
159
+ direction (an upload settle says nothing about a download), is tagged with
160
+ the `/capabilities` it was measured against, and expires `tuneTtlMs`
161
+ *after the ramp settled* — not after the last reuse — so a link measured
162
+ long ago is re-measured even if it is used constantly. An entry is also
163
+ discarded early when the capabilities change or a transfer fails. `0`
164
+ disables the cache entirely (every transfer re-ramps). Ignored unless
165
+ `autoTune` is enabled. Storage failures (private mode, quota, disabled
166
+ storage) are swallowed: caching is an optimisation and never fails a
167
+ transfer.
124
168
  - `downloadFolder(token, dirPath?) → Promise<number>`
125
169
  - `downloadFile(token, filePath) → Promise<number>`
126
170
  - `upload(token, files?) → Promise<number>`
@@ -130,7 +174,9 @@ dist/libfw-client.umd.js UMD bundle (after build:umd)
130
174
  - `tuneStatus() → { phase, params, stats, capsHash } | null` — live
131
175
  adaptive-tuning status. `phase` is `uninitialized | ramping | settled |
132
176
  degraded`; `params` is `{ concurrency, uploadWindow, downloadWindow,
133
- chunkSize, downloadChunkSize, compressLevel }`; `stats` is
177
+ chunkSize, compressLevel }` (the zrip level is the resolved *policy*, i.e.
178
+ what downloads request — uploads may use an `'auto'`-benchmarked level for
179
+ the session); `stats` is
134
180
  `{ rttMs, mbps }` (EWMA request RTT, last-window throughput). `null`
135
181
  until the WASM engine is initialised.
136
182
  - Events: with `autoTune` enabled, `onEvent` additionally receives
package/index.d.ts CHANGED
@@ -55,10 +55,8 @@ export interface TuningParams {
55
55
  uploadWindow: number;
56
56
  /** In-flight byte-range GETs per single-file download. */
57
57
  downloadWindow: number;
58
- /** Upload chunk size in bytes. */
58
+ /** Shared chunk size in bytes for uploads and parallel downloads. */
59
59
  chunkSize: number;
60
- /** Download byte-range size in bytes. */
61
- downloadChunkSize: number;
62
60
  /** zrip compression level (negative = faster, positive = smaller). */
63
61
  compressLevel: number;
64
62
  }
@@ -127,16 +125,21 @@ export interface LibfwClientOptions {
127
125
  * parallelism (sequential downloads). Default `4`.
128
126
  */
129
127
  downloadWindow?: number;
130
- /**
131
- * Byte range size for parallel downloads. The engine reorders in-flight
132
- * chunks in memory (worst case ≈ `downloadWindow * downloadChunkSize`
133
- * bytes) so the SDK still receives data strictly in order. Default
134
- * `262144` (256 KiB).
135
- */
136
- downloadChunkSize?: number;
137
128
  /** Negotiate zrip compression. Default `true`. */
138
129
  compress?: boolean;
139
- /** Upload chunk size in bytes. Default 2 MiB. */
130
+ /**
131
+ * zrip level policy when `compress` is on. `'fast'` = advertised minimum
132
+ * (least CPU), `'balanced'` (default) = advertised default, `'max'` =
133
+ * advertised maximum (best ratio); a number is that level clamped into the
134
+ * advertised range.
135
+ *
136
+ * `'auto'` additionally micro-benchmarks the advertised range against a
137
+ * real sample of the first uploaded file while `autoTune` is enabled,
138
+ * choosing the level with the best bytes-saved-vs-CPU-time trade-off.
139
+ * Downloads request the resolved level from the server.
140
+ */
141
+ compressLevel?: number | 'auto' | 'fast' | 'balanced' | 'max';
142
+ /** Shared chunk size in bytes for uploads and parallel downloads. Default 2 MiB. */
140
143
  chunkSize?: number;
141
144
  /** Retries per chunk/file before failing. Default `3`. */
142
145
  maxRetries?: number;
@@ -169,15 +172,28 @@ export interface LibfwClientOptions {
169
172
  maxFallbackBytes?: number;
170
173
  /**
171
174
  * Enable the adaptive tuning engine: the engine probes the server's
172
- * `/capabilities` limits and TCP-style ramps concurrency / windows /
173
- * chunk sizes (and the zrip level) from the advertised minimums using
174
- * real transfer stats. When disabled the configured static values are
175
- * used as-is. Default `false`.
175
+ * `/capabilities` limits and TCP-style ramps the per-file window and
176
+ * cross-file concurrency from the advertised minimums using real transfer
177
+ * stats. The chunk size follows the measured throughput (~100 ms of it,
178
+ * clamped into the advertised range) and the zrip level is a client policy
179
+ * from `compressLevel`, never ramped. When disabled the configured static
180
+ * values are used as-is. Default `false`.
181
+ *
182
+ * Tuning state lives in memory for this client instance (a settle is reused
183
+ * by later transfers and dropped on failure) **and** is cached in
184
+ * `localStorage` per origin + direction, so a page reload does not re-ramp.
185
+ * See `tuneTtlMs` for the lifetime of that cache. Default `false`.
176
186
  */
177
187
  autoTune?: boolean;
178
188
  /**
179
- * How long (ms) a settled tuning result is reused for the same server
180
- * origin before re-ramping. Default `3600000` (1 hour).
189
+ * How long a cached tuning result stays usable, in milliseconds. Default
190
+ * `3600000` (1 hour); `0` disables the cache so every transfer re-ramps.
191
+ *
192
+ * The cache is browser-only (the Rust native client keeps its settle in
193
+ * memory) and is keyed by origin + direction. The TTL counts from the moment
194
+ * the ramp settled, and an entry is discarded early when the server's
195
+ * `/capabilities` change or a transfer fails. Ignored unless `autoTune` is
196
+ * enabled.
181
197
  */
182
198
  tuneTtlMs?: number;
183
199
  /** Optional progress/state listener. Tuning updates arrive as `{ type: 'tuning', phase, params, stats }`. */
@@ -291,7 +307,7 @@ export declare class LibfwClient {
291
307
  * @returns `{ phase, params, stats, capsHash }` — `phase` is
292
308
  * `uninitialized | ramping | settled | degraded`; `params` holds the
293
309
  * tuned `concurrency` / `uploadWindow` / `downloadWindow` / `chunkSize`
294
- * / `downloadChunkSize` / `compressLevel`; `stats` is
310
+ * / `compressLevel`; `stats` is
295
311
  * `{ rttMs, mbps }` (EWMA request RTT, last-window throughput).
296
312
  * `null` until the WASM engine is initialised (or when `autoTune` is
297
313
  * disabled, `phase` stays `uninitialized`).
package/index.js CHANGED
@@ -207,16 +207,24 @@ export class LibfwClient {
207
207
  * transfer), so a single file's throughput is bounded by bandwidth
208
208
  * instead of one connection's `chunkSize / RTT` on high-latency
209
209
  * links. `1` disables parallelism (sequential downloads).
210
- * @param {number} [options.downloadChunkSize=262144] byte range size for
211
- * parallel downloads (256 KiB default). Smaller than the upload
212
- * chunk on purpose: the engine reorders in-flight chunks in memory
213
- * (worst case `downloadWindow * downloadChunkSize` bytes) so the
214
- * SDK still receives data strictly in order.
215
- * @param {boolean} [options.compress=true] negotiate zrip compression
216
- * @param {number} [options.chunkSize=2097152] upload chunk size in bytes
217
- * (each chunk is split into many small ~64 KiB compressed frames, so
218
- * any value works; larger = fewer, bigger POST requests — bounded
219
- * only by the server's upload limit and client memory)
210
+ * @param {boolean} [options.compress=true] negotiate zrip compression.
211
+ * Master switch: `false` sends every body as identity, regardless of
212
+ * `compressLevel`.
213
+ * @param {number|'auto'|'fast'|'balanced'|'max'} [options.compressLevel='balanced']
214
+ * zrip level policy when `compress` is on. `'fast'` = advertised
215
+ * minimum (least CPU, worst ratio), `'balanced'`/`'auto'`-without-a-
216
+ * sample = advertised default, `'max'` = advertised maximum (best
217
+ * ratio), a number = that level clamped into the advertised range.
218
+ * `'auto'` additionally micro-benchmarks the advertised range against
219
+ * a real sample of the first uploaded file (only while `autoTune` is
220
+ * enabled) and picks the level with the best
221
+ * bytes-saved-vs-CPU-time trade-off for the measured link speed.
222
+ * Downloads request the resolved level from the server (no sample is
223
+ * available before the transfer starts).
224
+ * @param {number} [options.chunkSize=2097152] shared chunk size in bytes for
225
+ * both upload chunks and parallel download ranges. The same value is
226
+ * used on both paths, and any value works as long as the server and
227
+ * memory budget permit it (larger = fewer, bigger requests).
220
228
  * @param {number} [options.maxRetries=3] retries per chunk/file before failing
221
229
  * @param {number} [options.baseRetryDelayMs=500] initial backoff (ms)
222
230
  * @param {number} [options.maxRetryDelayMs=30000] backoff ceiling (ms)
@@ -237,11 +245,23 @@ export class LibfwClient {
237
245
  * `0` disables the limit.
238
246
  * @param {boolean} [options.autoTune=false] enable the adaptive tuning
239
247
  * engine: the engine probes the server's `/capabilities` limits and
240
- * TCP-style ramps concurrency / windows / chunk sizes (and the zrip
241
- * level) from the advertised minimums using real transfer stats.
242
- * When disabled the configured static values are used as-is.
243
- * @param {number} [options.tuneTtlMs=3600000] how long a settled tuning
244
- * result is reused for the same server origin before re-ramping
248
+ * TCP-style ramps the per-file window and cross-file concurrency from
249
+ * the advertised minimums using real transfer stats; the chunk size
250
+ * follows the measured throughput (~100 ms of it, clamped into the
251
+ * advertised range) and the zrip level is a client policy from
252
+ * `compressLevel`, never ramped. When disabled the configured static
253
+ * values are used as-is. A settled result is kept in memory for this
254
+ * client instance (reused by later transfers, dropped on failure)
255
+ * **and** cached in `localStorage` per origin + direction, so a page
256
+ * reload does not re-ramp; see `tuneTtlMs` for how long that cache
257
+ * lives.
258
+ * @param {number} [options.tuneTtlMs=3600000] how long a cached tuning
259
+ * result (browser only — the Rust native client keeps it in memory)
260
+ * stays usable, in milliseconds. The TTL counts from the moment the
261
+ * ramp settled, is scoped to one origin + direction, and is
262
+ * invalidated early if the server's `/capabilities` change or a
263
+ * transfer fails. `0` disables the cache entirely, so every transfer
264
+ * (and every page load) re-ramps. Ignored unless `autoTune` is on.
245
265
  * @param {(event: {type: string, done: number, total: number, path?: string, error?: string}) => void} [options.onEvent]
246
266
  * optional progress/state listener. Tuning updates arrive as
247
267
  * `{ type: 'tuning', phase, params, stats }` events.
@@ -272,8 +292,8 @@ export class LibfwClient {
272
292
  concurrency: 4,
273
293
  uploadWindow: 8,
274
294
  downloadWindow: 4,
275
- downloadChunkSize: 256 * 1024,
276
295
  compress: true,
296
+ compressLevel: null,
277
297
  chunkSize: 2 * 1024 * 1024,
278
298
  maxRetries: 3,
279
299
  baseRetryDelayMs: 500,
@@ -283,7 +303,7 @@ export class LibfwClient {
283
303
  downloadMode: 'auto',
284
304
  maxFallbackBytes: 512 * 1024 * 1024,
285
305
  autoTune: false,
286
- tuneTtlMs: 3600000,
306
+ tuneTtlMs: 60 * 60 * 1000,
287
307
  onEvent: null,
288
308
  directoryHandle: null,
289
309
  resolveDisplayName: null,
@@ -338,8 +358,8 @@ export class LibfwClient {
338
358
  concurrency: this._options.concurrency,
339
359
  uploadWindow: this._options.uploadWindow,
340
360
  downloadWindow: this._options.downloadWindow,
341
- downloadChunkSize: this._options.downloadChunkSize,
342
361
  compress: this._options.compress,
362
+ compressLevel: this._options.compressLevel,
343
363
  chunkSize: this._options.chunkSize,
344
364
  maxRetries: this._options.maxRetries,
345
365
  baseRetryDelayMs: this._options.baseRetryDelayMs,
@@ -424,7 +444,15 @@ export class LibfwClient {
424
444
  // poisons a later FS-API resume. Skip persisting download state
425
445
  // while a fallback transfer is active.
426
446
  if (direction === 'download' && this._fallback) return Promise.resolve();
427
- return Idb.saveState(`${direction}:${path}`, state);
447
+ return Idb.saveState(`${direction}:${path}`, state).then(() => {
448
+ // The engine reports a durable offset every few MiB: use it to
449
+ // COMMIT the prefix to disk, so an interrupted download (a tab
450
+ // refresh or a crash — no `finally` block runs) still has a partial
451
+ // to resume from instead of starting over.
452
+ if (direction === 'download') {
453
+ return this._checkpointDownload(path, Number(state?.offset) || 0);
454
+ }
455
+ });
428
456
  },
429
457
  getFileList: () => this._getFileList(),
430
458
  readFile: (path, offset, length) => this._readFile(path, offset, length),
@@ -825,6 +853,35 @@ export class LibfwClient {
825
853
  const writable = await handle.createWritable(
826
854
  isResume ? { keepExistingData: true } : undefined
827
855
  );
856
+ if (isResume) {
857
+ // `createWritable({ keepExistingData: true })` KEEPS the existing
858
+ // bytes but still positions the stream at 0 — it is not an append. A
859
+ // resumed tail written without seeking would overwrite the prefix and
860
+ // then close, leaving a file that is only the tail (measured: a
861
+ // 20 MiB download resumed at 6.8 MiB produced a 14.1 MiB, corrupt
862
+ // file). Seek past what is already on disk so the tail really
863
+ // appends; `_loadResumeState` clamped `offset` to the on-disk length.
864
+ const existing = (await handle.getFile()).size;
865
+ if (existing < offset) {
866
+ // The prefix we are asked to resume past is not on disk: appending
867
+ // would leave a hole. Fail loudly instead of committing a corrupt
868
+ // file (the caller can retry, which restarts cleanly).
869
+ await this._discardWritable(path, writable);
870
+ throw new LibfwError(
871
+ `cannot resume \`${path}\`: only ${existing} of ${offset} bytes are on disk`,
872
+ 'storage'
873
+ );
874
+ }
875
+ try {
876
+ await writable.seek(offset);
877
+ } catch (err) {
878
+ await this._discardWritable(path, writable);
879
+ throw new LibfwError(
880
+ `cannot resume \`${path}\`: the destination cannot be seeked (${err?.message ?? err})`,
881
+ 'storage'
882
+ );
883
+ }
884
+ }
828
885
  entry = { writable, dir, name, lastOffset: offset };
829
886
  this._writables.set(path, entry);
830
887
  }
@@ -832,6 +889,74 @@ export class LibfwClient {
832
889
  await entry.writable.write(data);
833
890
  }
834
891
 
892
+ /**
893
+ * Commit the bytes received so far for a download to disk.
894
+ *
895
+ * `createWritable()` only publishes the file on `close()` — everything else
896
+ * lives in a Chromium swap file (`.name.crswap`) that is DISCARDED when the
897
+ * page dies. A hard refresh mid-download would therefore find a 0-byte (or
898
+ * stale) target and restart from byte 0. The engine reports a durable
899
+ * offset every few MiB (`RESUME_SAVE_EVERY`), so this closes the writable
900
+ * (publishing the prefix) and immediately reopens it with
901
+ * `keepExistingData` + `seek`, leaving a resumable partial on disk at the
902
+ * cost of one close/reopen per checkpoint.
903
+ *
904
+ * Called from the `saveState` callback, which the engine awaits between
905
+ * chunks — no write for this path is in flight here.
906
+ * @param {string} path
907
+ * @param {number} offset durable offset reported by the engine
908
+ * @returns {Promise<void>}
909
+ * @private
910
+ */
911
+ async _checkpointDownload(path, offset) {
912
+ const entry = this._writables.get(path);
913
+ const handle = this._fileHandles.get(path);
914
+ if (!entry || !handle || offset <= 0) return;
915
+ let onDisk = 0;
916
+ try {
917
+ onDisk = (await handle.getFile()).size;
918
+ } catch {
919
+ return; // handle gone: nothing safe to commit
920
+ }
921
+ // Nothing new to publish (an earlier checkpoint already covers it), or
922
+ // the engine is behind the bytes we committed: leave the stream alone.
923
+ if (onDisk >= offset) return;
924
+ try {
925
+ await entry.writable.close();
926
+ } catch {
927
+ return; // keep the (still open) stream; the next chunk continues on it
928
+ }
929
+ try {
930
+ const writable = await handle.createWritable({ keepExistingData: true });
931
+ // The committed file now holds everything written so far; `close()`
932
+ // published exactly `lastOffset` bytes, so appending resumes there.
933
+ await writable.seek((await handle.getFile()).size);
934
+ entry.writable = writable;
935
+ } catch {
936
+ // Reopening failed: forget the stream so the next chunk re-opens it
937
+ // through the normal resume path (which re-validates the prefix).
938
+ this._writables.delete(path);
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Drop an open writable (and its uncommitted swap file) without failing the
944
+ * caller. Used when a resume cannot be honoured so no partial/corrupt file
945
+ * is committed.
946
+ * @param {string} path
947
+ * @param {FileSystemWritableFileStream} writable
948
+ * @returns {Promise<void>}
949
+ * @private
950
+ */
951
+ async _discardWritable(path, writable) {
952
+ this._writables.delete(path);
953
+ try {
954
+ await writable.abort();
955
+ } catch {
956
+ /* best-effort discard */
957
+ }
958
+ }
959
+
835
960
  /**
836
961
  * Close the destination writable once a file's transfer completes.
837
962
  * @param {string} path virtual path
@@ -1246,8 +1371,7 @@ export class LibfwClient {
1246
1371
  *
1247
1372
  * - `phase`: `uninitialized | ramping | settled | degraded`
1248
1373
  * - `params`: `{ concurrency, uploadWindow, downloadWindow, chunkSize,
1249
- * downloadChunkSize, compressLevel }` — the parameters the engine is
1250
- * currently tuned to
1374
+ * compressLevel }` — the parameters the engine is currently tuned to
1251
1375
  * - `stats`: `{ rttMs, mbps }` — EWMA request RTT and last-window
1252
1376
  * throughput of the most recent transfer
1253
1377
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libfw-client",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "High-performance streaming file & folder transfer SDK for the browser (libfw WASM engine + File System Access API + IndexedDB resume).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,8 +29,8 @@ export class LibfwClient {
29
29
  has_callbacks(): boolean;
30
30
  /**
31
31
  * Create an engine. `options` may include:
32
- * `{ concurrency, uploadWindow, downloadWindow, downloadChunkSize,
33
- * compress, compressLevel, chunkSize, maxRetries, baseRetryDelayMs,
32
+ * `{ concurrency, uploadWindow, downloadWindow, compress,
33
+ * compressLevel, chunkSize, maxRetries, baseRetryDelayMs,
34
34
  * maxRetryDelayMs, timeoutMs, autoTune, tuneTtlMs }`.
35
35
  */
36
36
  constructor(opts: any);
@@ -72,8 +72,8 @@ export class LibfwClient {
72
72
  }
73
73
  /**
74
74
  * Create an engine. `options` may include:
75
- * `{ concurrency, uploadWindow, downloadWindow, downloadChunkSize,
76
- * compress, compressLevel, chunkSize, maxRetries, baseRetryDelayMs,
75
+ * `{ concurrency, uploadWindow, downloadWindow, compress,
76
+ * compressLevel, chunkSize, maxRetries, baseRetryDelayMs,
77
77
  * maxRetryDelayMs, timeoutMs, autoTune, tuneTtlMs }`.
78
78
  * @param {any} opts
79
79
  */
@@ -592,7 +592,7 @@ function __wbg_get_imports() {
592
592
  return ret;
593
593
  }, arguments); },
594
594
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
595
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 209, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
595
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 206, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
596
596
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue__core_ed718c3d60ebd546___result__Result_____wasm_bindgen_b2c787b844a3ac79___JsError___true_);
597
597
  return ret;
598
598
  },
Binary file
package/pkg/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "libfw-client",
3
3
  "type": "module",
4
- "description": "WASM engine + JS SDK for libfw browser clients",
5
- "version": "0.4.2",
4
+ "description": "libfw client: native Rust client + WASM engine + JS SDK",
5
+ "version": "0.4.4",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",