libfw-client 0.4.3 → 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 +54 -8
- package/index.d.ts +32 -7
- package/index.js +140 -8
- package/package.json +1 -1
- package/pkg/libfw_client.js +1 -1
- package/pkg/libfw_client_bg.wasm +0 -0
- package/pkg/package.json +2 -2
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
|
-
|
|
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
|
|
|
@@ -106,21 +121,50 @@ dist/libfw-client.umd.js UMD bundle (after build:umd)
|
|
|
106
121
|
both upload chunks and parallel download ranges; the engine reorders
|
|
107
122
|
in-flight chunks in memory (worst case ≈ `downloadWindow * chunkSize`
|
|
108
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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, compressLevel }
|
|
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
|
@@ -127,7 +127,19 @@ export interface LibfwClientOptions {
|
|
|
127
127
|
downloadWindow?: number;
|
|
128
128
|
/** Negotiate zrip compression. Default `true`. */
|
|
129
129
|
compress?: boolean;
|
|
130
|
-
/**
|
|
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. */
|
|
131
143
|
chunkSize?: number;
|
|
132
144
|
/** Retries per chunk/file before failing. Default `3`. */
|
|
133
145
|
maxRetries?: number;
|
|
@@ -160,15 +172,28 @@ export interface LibfwClientOptions {
|
|
|
160
172
|
maxFallbackBytes?: number;
|
|
161
173
|
/**
|
|
162
174
|
* Enable the adaptive tuning engine: the engine probes the server's
|
|
163
|
-
* `/capabilities` limits and TCP-style ramps
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
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`.
|
|
167
186
|
*/
|
|
168
187
|
autoTune?: boolean;
|
|
169
188
|
/**
|
|
170
|
-
* How long
|
|
171
|
-
*
|
|
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.
|
|
172
197
|
*/
|
|
173
198
|
tuneTtlMs?: number;
|
|
174
199
|
/** Optional progress/state listener. Tuning updates arrive as `{ type: 'tuning', phase, params, stats }`. */
|
package/index.js
CHANGED
|
@@ -207,7 +207,20 @@ 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 {boolean} [options.compress=true] negotiate zrip compression
|
|
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).
|
|
211
224
|
* @param {number} [options.chunkSize=2097152] shared chunk size in bytes for
|
|
212
225
|
* both upload chunks and parallel download ranges. The same value is
|
|
213
226
|
* used on both paths, and any value works as long as the server and
|
|
@@ -232,11 +245,23 @@ export class LibfwClient {
|
|
|
232
245
|
* `0` disables the limit.
|
|
233
246
|
* @param {boolean} [options.autoTune=false] enable the adaptive tuning
|
|
234
247
|
* engine: the engine probes the server's `/capabilities` limits and
|
|
235
|
-
* TCP-style ramps
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
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.
|
|
240
265
|
* @param {(event: {type: string, done: number, total: number, path?: string, error?: string}) => void} [options.onEvent]
|
|
241
266
|
* optional progress/state listener. Tuning updates arrive as
|
|
242
267
|
* `{ type: 'tuning', phase, params, stats }` events.
|
|
@@ -268,6 +293,7 @@ export class LibfwClient {
|
|
|
268
293
|
uploadWindow: 8,
|
|
269
294
|
downloadWindow: 4,
|
|
270
295
|
compress: true,
|
|
296
|
+
compressLevel: null,
|
|
271
297
|
chunkSize: 2 * 1024 * 1024,
|
|
272
298
|
maxRetries: 3,
|
|
273
299
|
baseRetryDelayMs: 500,
|
|
@@ -277,7 +303,7 @@ export class LibfwClient {
|
|
|
277
303
|
downloadMode: 'auto',
|
|
278
304
|
maxFallbackBytes: 512 * 1024 * 1024,
|
|
279
305
|
autoTune: false,
|
|
280
|
-
tuneTtlMs:
|
|
306
|
+
tuneTtlMs: 60 * 60 * 1000,
|
|
281
307
|
onEvent: null,
|
|
282
308
|
directoryHandle: null,
|
|
283
309
|
resolveDisplayName: null,
|
|
@@ -333,6 +359,7 @@ export class LibfwClient {
|
|
|
333
359
|
uploadWindow: this._options.uploadWindow,
|
|
334
360
|
downloadWindow: this._options.downloadWindow,
|
|
335
361
|
compress: this._options.compress,
|
|
362
|
+
compressLevel: this._options.compressLevel,
|
|
336
363
|
chunkSize: this._options.chunkSize,
|
|
337
364
|
maxRetries: this._options.maxRetries,
|
|
338
365
|
baseRetryDelayMs: this._options.baseRetryDelayMs,
|
|
@@ -417,7 +444,15 @@ export class LibfwClient {
|
|
|
417
444
|
// poisons a later FS-API resume. Skip persisting download state
|
|
418
445
|
// while a fallback transfer is active.
|
|
419
446
|
if (direction === 'download' && this._fallback) return Promise.resolve();
|
|
420
|
-
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
|
+
});
|
|
421
456
|
},
|
|
422
457
|
getFileList: () => this._getFileList(),
|
|
423
458
|
readFile: (path, offset, length) => this._readFile(path, offset, length),
|
|
@@ -818,6 +853,35 @@ export class LibfwClient {
|
|
|
818
853
|
const writable = await handle.createWritable(
|
|
819
854
|
isResume ? { keepExistingData: true } : undefined
|
|
820
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
|
+
}
|
|
821
885
|
entry = { writable, dir, name, lastOffset: offset };
|
|
822
886
|
this._writables.set(path, entry);
|
|
823
887
|
}
|
|
@@ -825,6 +889,74 @@ export class LibfwClient {
|
|
|
825
889
|
await entry.writable.write(data);
|
|
826
890
|
}
|
|
827
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
|
+
|
|
828
960
|
/**
|
|
829
961
|
* Close the destination writable once a file's transfer completes.
|
|
830
962
|
* @param {string} path virtual path
|
package/package.json
CHANGED
package/pkg/libfw_client.js
CHANGED
|
@@ -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:
|
|
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
|
},
|
package/pkg/libfw_client_bg.wasm
CHANGED
|
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
|
|
5
|
-
"version": "0.4.
|
|
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",
|