libfw-client 0.1.3 → 0.1.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
@@ -11,6 +11,11 @@ import { LibfwClient } from 'libfw-client';
11
11
  const client = new LibfwClient({
12
12
  baseUrl: '/api', // where libfw-server routes are mounted
13
13
  concurrency: 4, // max parallel file transfers
14
+ uploadWindow: 8, // in-flight chunks per single file upload (raise to
15
+ // reduce upload stutter on high-latency links)
16
+ downloadWindow: 4, // in-flight byte-range GETs per single file download
17
+ // (tus-style parallel download: one file's throughput
18
+ // isn't bounded by a single connection's RTT)
14
19
  compress: true, // zrip streaming compression
15
20
  onEvent: (e) => console.log(e), // { type: 'progress', done, total }
16
21
  });
@@ -38,14 +43,25 @@ client.cancel();
38
43
  - `downloadFolder(token, dirPath?)` / `downloadFile(token, filePath)` — the
39
44
  engine lists (for folders) and downloads each file with `Range`/`If-Range`
40
45
  resume, decompresses the zrip stream, and pushes `Uint8Array` chunks to the
41
- SDK. With the File System Access API the SDK streams them to disk via
42
- `fileHandle.createWritable()`; without it (or with `downloadMode: 'browser'`)
43
- the SDK buffers the chunks and saves the result through a traditional
44
- browser download a single file as-is, a folder packed into a `.zip`.
46
+ SDK. Large files use the **tus-style parallel path**: `downloadWindow`
47
+ concurrent byte-range GETs, reordered in memory so the SDK still receives
48
+ bytes strictly in order (append-mode `createWritable()`, no `.crswap`
49
+ churn), with per-chunk independent retries. With the File System Access API
50
+ the SDK streams them to disk via `fileHandle.createWritable()`; without it
51
+ (or with `downloadMode: 'browser'`) the SDK buffers the chunks and saves the
52
+ result through a traditional browser download — a single file as-is, a
53
+ folder packed into a `.zip`.
45
54
  - `upload(token, files?)` — the engine slices each file into fixed-size
46
55
  chunks, reads them via `readFile`, compresses each chunk into one zstd
47
- frame, and POSTs them with `x-libfw-offset` for server-side resume
48
- validation.
56
+ frame, and POSTs them with an absolute `x-libfw-offset` into a shared
57
+ per-session temp file. Up to `uploadWindow` chunks of one file are kept in
58
+ flight concurrently (independent of the cross-file `concurrency`), so a
59
+ high-latency link stays saturated. Uploads are **tus-style
60
+ verify-then-complete**: the server is the source of truth — the client
61
+ probes the byte ranges the server actually persisted and re-sends only the
62
+ still-missing blocks, filling gaps a lost response may have left (and
63
+ re-probing + refilling if a commit fails) before the final `x-libfw-final`
64
+ request merges the temp into place.
49
65
  - Resume state (`etag`, `offset`, `size`) is persisted per path in
50
66
  IndexedDB and re-validated on every retry.
51
67
  - Pause/resume/cancel drive the WASM state machine
@@ -74,6 +90,12 @@ dist/libfw-client.umd.js UMD bundle (after build:umd)
74
90
  ## API
75
91
 
76
92
  - `new LibfwClient(options?)`
93
+ - `downloadWindow: number` (default `4`) — in-flight byte-range GETs per
94
+ single file download; `1` disables parallelism.
95
+ - `downloadChunkSize: number` (default `262144`, 256 KiB) — byte range size
96
+ for parallel downloads; the engine reorders in-flight chunks in memory
97
+ (worst case ≈ `downloadWindow * downloadChunkSize` bytes) so the SDK
98
+ still receives data in order.
77
99
  - `downloadMode: 'auto' | 'fs' | 'browser'` (default `'auto'`) — `'fs'`
78
100
  streams downloads through the File System Access API; `'browser'` buffers
79
101
  and triggers a traditional browser download (folders become `.zip`);
package/index.d.ts CHANGED
@@ -52,8 +52,30 @@ export interface LibfwEvent {
52
52
  export interface LibfwClientOptions {
53
53
  /** Base URL the libfw server routes are mounted under. Default `''`. */
54
54
  baseUrl?: string;
55
- /** Max concurrent file transfers. Default `4`. */
55
+ /** Max concurrently-transferring files. Default `4`. */
56
56
  concurrency?: number;
57
+ /**
58
+ * In-flight chunk window for a single file's upload, independent of
59
+ * `concurrency`. A higher value keeps high-latency links saturated and
60
+ * reduces upload stutter; keep it within your server's connection limit
61
+ * (~6 for HTTP/1.1). Default `8`.
62
+ */
63
+ uploadWindow?: number;
64
+ /**
65
+ * In-flight byte-range window for a single file's download. Large files
66
+ * are fetched as `downloadWindow` concurrent `Range` GETs (tus-style
67
+ * parallel transfer), so a single file's throughput is bounded by
68
+ * bandwidth instead of one connection's `chunkSize / RTT` on high-latency
69
+ * links. `1` disables parallelism. Default `4`.
70
+ */
71
+ downloadWindow?: number;
72
+ /**
73
+ * Byte range size for parallel downloads. Smaller than the upload chunk
74
+ * on purpose: the engine reorders in-flight chunks in memory (worst case
75
+ * ≈ `downloadWindow * downloadChunkSize` bytes) so the SDK still receives
76
+ * data strictly in order. Default `262144` (256 KiB).
77
+ */
78
+ downloadChunkSize?: number;
57
79
  /** Negotiate zrip compression. Default `true`. */
58
80
  compress?: boolean;
59
81
  /** Upload chunk size in bytes. Default 2 MiB. */
package/index.js CHANGED
@@ -188,7 +188,22 @@ export class LibfwClient {
188
188
  /**
189
189
  * @param {object} [options]
190
190
  * @param {string} [options.baseUrl=''] base URL the server routes are mounted under
191
- * @param {number} [options.concurrency=4] max concurrent file transfers
191
+ * @param {number} [options.concurrency=4] max concurrently-transferring files
192
+ * @param {number} [options.uploadWindow=8] in-flight chunk window for a
193
+ * single file's upload; independent of `concurrency`, keeps a
194
+ * high-latency link saturated (raise it to reduce upload stutter;
195
+ * keep within your server's connection limit, ~6 for HTTP/1.1)
196
+ * @param {number} [options.downloadWindow=4] in-flight byte-range window
197
+ * for a single file's download. Large files are fetched as
198
+ * `downloadWindow` concurrent `Range` GETs (tus-style parallel
199
+ * transfer), so a single file's throughput is bounded by bandwidth
200
+ * instead of one connection's `chunkSize / RTT` on high-latency
201
+ * links. `1` disables parallelism (sequential downloads).
202
+ * @param {number} [options.downloadChunkSize=262144] byte range size for
203
+ * parallel downloads (256 KiB default). Smaller than the upload
204
+ * chunk on purpose: the engine reorders in-flight chunks in memory
205
+ * (worst case ≈ `downloadWindow * downloadChunkSize` bytes) so the
206
+ * SDK still receives data strictly in order.
192
207
  * @param {boolean} [options.compress=true] negotiate zrip compression
193
208
  * @param {number} [options.chunkSize=2097152] upload chunk size in bytes
194
209
  * @param {number} [options.maxRetries=3] retries per chunk/file before failing
@@ -216,6 +231,9 @@ export class LibfwClient {
216
231
  this._options = {
217
232
  baseUrl: '',
218
233
  concurrency: 4,
234
+ uploadWindow: 8,
235
+ downloadWindow: 4,
236
+ downloadChunkSize: 256 * 1024,
219
237
  compress: true,
220
238
  chunkSize: 2 * 1024 * 1024,
221
239
  maxRetries: 3,
@@ -272,6 +290,9 @@ export class LibfwClient {
272
290
  await this._initPromise;
273
291
  const engine = new WasmEngine({
274
292
  concurrency: this._options.concurrency,
293
+ uploadWindow: this._options.uploadWindow,
294
+ downloadWindow: this._options.downloadWindow,
295
+ downloadChunkSize: this._options.downloadChunkSize,
275
296
  compress: this._options.compress,
276
297
  chunkSize: this._options.chunkSize,
277
298
  maxRetries: this._options.maxRetries,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libfw-client",
3
- "version": "0.1.3",
3
+ "version": "0.1.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",
@@ -33,8 +33,9 @@ export class LibfwClient {
33
33
  has_callbacks(): boolean;
34
34
  /**
35
35
  * Create an engine. `options` may include:
36
- * `{ concurrency, compress, chunkSize, maxRetries, baseRetryDelayMs,
37
- * maxRetryDelayMs, timeoutMs }`.
36
+ * `{ concurrency, uploadWindow, downloadWindow, downloadChunkSize,
37
+ * compress, chunkSize, maxRetries, baseRetryDelayMs, maxRetryDelayMs,
38
+ * timeoutMs }`.
38
39
  */
39
40
  constructor(opts: any);
40
41
  /**
@@ -76,8 +76,9 @@ export class LibfwClient {
76
76
  }
77
77
  /**
78
78
  * Create an engine. `options` may include:
79
- * `{ concurrency, compress, chunkSize, maxRetries, baseRetryDelayMs,
80
- * maxRetryDelayMs, timeoutMs }`.
79
+ * `{ concurrency, uploadWindow, downloadWindow, downloadChunkSize,
80
+ * compress, chunkSize, maxRetries, baseRetryDelayMs, maxRetryDelayMs,
81
+ * timeoutMs }`.
81
82
  * @param {any} opts
82
83
  */
83
84
  constructor(opts) {
@@ -462,7 +463,7 @@ function __wbg_get_imports() {
462
463
  return ret;
463
464
  },
464
465
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
465
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 135, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
466
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 145, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
466
467
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_);
467
468
  return ret;
468
469
  },
Binary file
package/pkg/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "libfw-client",
3
3
  "type": "module",
4
4
  "description": "WASM engine + JS SDK for libfw browser clients",
5
- "version": "0.1.3",
5
+ "version": "0.1.4",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",