@filefreak.io/sdk 0.0.0-stage → 0.2.0
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 +189 -2
- package/dist/client.d.ts +78 -0
- package/dist/client.js +168 -0
- package/dist/generated.d.ts +1060 -0
- package/dist/generated.js +329 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -0
- package/dist/links.d.ts +72 -0
- package/dist/links.js +355 -0
- package/dist/md5.d.ts +18 -0
- package/dist/md5.js +130 -0
- package/dist/node.d.ts +36 -0
- package/dist/node.js +124 -0
- package/dist/ranges.d.ts +34 -0
- package/dist/ranges.js +303 -0
- package/dist/reader.d.ts +76 -0
- package/dist/reader.js +510 -0
- package/dist/shards.d.ts +59 -0
- package/dist/shards.js +140 -0
- package/dist/transport.d.ts +61 -0
- package/dist/transport.js +173 -0
- package/dist/uploads.d.ts +53 -0
- package/dist/uploads.js +259 -0
- package/package.json +27 -4
package/dist/ranges.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** At most this many ranges at once. Six filled the line measured; more only adds requests. */
|
|
2
|
+
export declare const RANGES_AT_ONCE = 6;
|
|
3
|
+
/** No range is cut smaller than this: each is a request, and a request costs a round trip before its first byte. */
|
|
4
|
+
export declare const LEAST_RANGE: number;
|
|
5
|
+
/** Where a download's bytes go. */
|
|
6
|
+
export interface Sink {
|
|
7
|
+
/** Write `data` at `position` in the file. Called for several positions at once. */
|
|
8
|
+
write(position: number, data: Uint8Array<ArrayBuffer>): Promise<void>;
|
|
9
|
+
/** Called once every byte has been written. */
|
|
10
|
+
close?(): Promise<void>;
|
|
11
|
+
/** Called instead of close when the download fails or is stopped: a file with holes in it is worse than none. */
|
|
12
|
+
abort?(reason: unknown): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface RangesOptions {
|
|
15
|
+
url: string;
|
|
16
|
+
sizeBytes: number;
|
|
17
|
+
sink: Sink;
|
|
18
|
+
signal?: AbortSignal | undefined;
|
|
19
|
+
/** How many bytes have been written, after each write. */
|
|
20
|
+
progress?: ((writtenBytes: number) => void) | undefined;
|
|
21
|
+
/** At most this many ranges at once. */
|
|
22
|
+
ranges?: number | undefined;
|
|
23
|
+
fetch?: typeof fetch;
|
|
24
|
+
sleep?: (ms: number) => Promise<void>;
|
|
25
|
+
quietMs?: number;
|
|
26
|
+
/** LEAST_RANGE, unless a test makes it smaller rather than make a file of gigabytes. */
|
|
27
|
+
leastRange?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fetch `url` as ranges and write each where it belongs. Resolves when every
|
|
31
|
+
* byte has been written; rejects, with every request stopped, when one range
|
|
32
|
+
* cannot be finished.
|
|
33
|
+
*/
|
|
34
|
+
export declare function fetchRanges(options: RangesOptions): Promise<void>;
|
package/dist/ranges.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// Fetching one file from storage as several ranges at once, each written
|
|
2
|
+
// where it belongs.
|
|
3
|
+
//
|
|
4
|
+
// The website's engine for saving a large file (web/lib/rangedSave.ts),
|
|
5
|
+
// without the page: the same measurements, the same rules. One request from
|
|
6
|
+
// storage was measured at 360 to 420 Mbps on a line that carries about
|
|
7
|
+
// 1 Gbps, and six ranges at once at 560 to 660 (plan 04, B1.5), so a file
|
|
8
|
+
// large enough is fetched as up to six ranges, and a small one as one.
|
|
9
|
+
//
|
|
10
|
+
// One address, asked for once, because asking for another counts the file as
|
|
11
|
+
// downloaded again. An address works for fifteen minutes and a request that
|
|
12
|
+
// has started is not cut off when it passes, so each range is one long
|
|
13
|
+
// request, all of them made at the start. When one finishes early it takes
|
|
14
|
+
// the second half of the largest one left, as a new request. If that request
|
|
15
|
+
// is not answered (the address has passed, say), the half goes back to the
|
|
16
|
+
// range it was taken from, whose own request asked for it in the first place:
|
|
17
|
+
// that range waits at the point it was cut until the new request has been
|
|
18
|
+
// answered, so that it is still there to give back to. A range whose
|
|
19
|
+
// connection breaks, or goes quiet, is asked for again from where it stopped.
|
|
20
|
+
// After the address has passed that is refused, and the download stops with
|
|
21
|
+
// an error saying so.
|
|
22
|
+
import { FileFreakError } from "./transport.js";
|
|
23
|
+
const MIB = 1024 * 1024;
|
|
24
|
+
/** At most this many ranges at once. Six filled the line measured; more only adds requests. */
|
|
25
|
+
export const RANGES_AT_ONCE = 6;
|
|
26
|
+
/** No range is cut smaller than this: each is a request, and a request costs a round trip before its first byte. */
|
|
27
|
+
export const LEAST_RANGE = 16 * MIB;
|
|
28
|
+
/** Written to the sink this much at a time. Fewer writes, for this much memory for each range. */
|
|
29
|
+
const WRITE_BYTES = 4 * MIB;
|
|
30
|
+
/** A connection that breaks is asked for again this many times in a row, from where it stopped. */
|
|
31
|
+
const TRIES = 5;
|
|
32
|
+
/** A request with nothing arriving for this long is given up and asked for again. */
|
|
33
|
+
const QUIET_MS = 30_000;
|
|
34
|
+
/** A connection that broke or went quiet. The only failure worth asking again after. */
|
|
35
|
+
class Broke extends Error {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fetch `url` as ranges and write each where it belongs. Resolves when every
|
|
39
|
+
* byte has been written; rejects, with every request stopped, when one range
|
|
40
|
+
* cannot be finished.
|
|
41
|
+
*/
|
|
42
|
+
export async function fetchRanges(options) {
|
|
43
|
+
// The first range that fails stops the others: the file cannot be finished without it.
|
|
44
|
+
const stop = new AbortController();
|
|
45
|
+
const outer = options.signal;
|
|
46
|
+
const passOn = () => stop.abort(outer?.reason);
|
|
47
|
+
if (outer?.aborted)
|
|
48
|
+
passOn();
|
|
49
|
+
outer?.addEventListener("abort", passOn);
|
|
50
|
+
let written = 0;
|
|
51
|
+
const run = {
|
|
52
|
+
url: options.url,
|
|
53
|
+
sizeBytes: options.sizeBytes,
|
|
54
|
+
sink: options.sink,
|
|
55
|
+
signal: stop.signal,
|
|
56
|
+
fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
|
|
57
|
+
sleep: options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
58
|
+
quietMs: options.quietMs ?? QUIET_MS,
|
|
59
|
+
wrote: (bytes) => {
|
|
60
|
+
written += bytes;
|
|
61
|
+
options.progress?.(written);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const least = options.leastRange ?? LEAST_RANGE;
|
|
65
|
+
const most = Math.max(1, Math.min(RANGES_AT_ONCE, options.ranges ?? RANGES_AT_ONCE));
|
|
66
|
+
const count = Math.max(1, Math.min(most, Math.floor(options.sizeBytes / least)));
|
|
67
|
+
const ranges = [];
|
|
68
|
+
for (let i = 0; i < count; i++) {
|
|
69
|
+
ranges.push({ at: Math.floor((i * options.sizeBytes) / count), end: Math.floor(((i + 1) * options.sizeBytes) / count), giving: null });
|
|
70
|
+
}
|
|
71
|
+
// One cut at a time, so that a range has at most one half out that could come back.
|
|
72
|
+
let cut = null;
|
|
73
|
+
let cutting = most > 1;
|
|
74
|
+
const worker = async (first) => {
|
|
75
|
+
await read(first, run);
|
|
76
|
+
for (;;) {
|
|
77
|
+
while (cut)
|
|
78
|
+
await cut;
|
|
79
|
+
if (!cutting || stop.signal.aborted)
|
|
80
|
+
return;
|
|
81
|
+
const from = ranges.filter((r) => r.end - r.at >= 2 * least).sort((a, b) => b.end - b.at - (a.end - a.at))[0];
|
|
82
|
+
if (!from)
|
|
83
|
+
return;
|
|
84
|
+
const before = from.end;
|
|
85
|
+
const half = { at: from.at + Math.floor((from.end - from.at) / 2), end: before, giving: null };
|
|
86
|
+
let settle = () => { };
|
|
87
|
+
cut = from.giving = new Promise((resolve) => (settle = resolve));
|
|
88
|
+
const settled = () => {
|
|
89
|
+
from.giving = null;
|
|
90
|
+
cut = null;
|
|
91
|
+
settle();
|
|
92
|
+
};
|
|
93
|
+
from.end = half.at;
|
|
94
|
+
ranges.push(half);
|
|
95
|
+
try {
|
|
96
|
+
await read(half, run, settled);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
if (!from.giving)
|
|
100
|
+
throw e;
|
|
101
|
+
// Never answered, so nothing of it was written: the range it came
|
|
102
|
+
// from takes it back and carries on to where it was going. No more
|
|
103
|
+
// cuts, since whatever stopped this one would stop the next.
|
|
104
|
+
from.end = before;
|
|
105
|
+
ranges.splice(ranges.indexOf(half), 1);
|
|
106
|
+
cutting = false;
|
|
107
|
+
settled();
|
|
108
|
+
if (stop.signal.aborted)
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
try {
|
|
114
|
+
await Promise.all(ranges.slice().map((r) => worker(r).catch((e) => {
|
|
115
|
+
stop.abort(e);
|
|
116
|
+
throw e;
|
|
117
|
+
})));
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
outer?.removeEventListener("abort", passOn);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Read one range to its end, asking again from where it stopped when its
|
|
125
|
+
* connection breaks. With `answered`, the range is a half just taken from
|
|
126
|
+
* another: until storage has answered for it, any failure is thrown at once
|
|
127
|
+
* (so that the half can go back) rather than asked again, and `answered` is
|
|
128
|
+
* called when it has.
|
|
129
|
+
*/
|
|
130
|
+
async function read(range, run, answered) {
|
|
131
|
+
let tries = 0;
|
|
132
|
+
for (;;) {
|
|
133
|
+
// What is asked for is what the range holds now, which a cut that has
|
|
134
|
+
// not been answered could still change.
|
|
135
|
+
while (range.giving)
|
|
136
|
+
await range.giving;
|
|
137
|
+
if (range.at >= range.end)
|
|
138
|
+
return;
|
|
139
|
+
const startedAt = range.at;
|
|
140
|
+
const asking = new Asking(run);
|
|
141
|
+
try {
|
|
142
|
+
const res = await asking.fetch(range);
|
|
143
|
+
answered?.();
|
|
144
|
+
answered = undefined;
|
|
145
|
+
await drain(range, res, run, asking);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
if (run.signal.aborted || answered || !(e instanceof Broke))
|
|
149
|
+
throw e;
|
|
150
|
+
tries = await again(range.at > startedAt ? 0 : tries, run, e);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
asking.done();
|
|
155
|
+
}
|
|
156
|
+
if (range.at >= range.end)
|
|
157
|
+
return;
|
|
158
|
+
tries = await again(range.at > startedAt ? 0 : tries, run, new Broke("the connection closed before the end of the range"));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* One request, given up when nothing arrives on it for a while. Stopped with
|
|
163
|
+
* everything else when the download is.
|
|
164
|
+
*/
|
|
165
|
+
class Asking {
|
|
166
|
+
run;
|
|
167
|
+
controller = new AbortController();
|
|
168
|
+
timer;
|
|
169
|
+
stop = () => this.controller.abort(this.run.signal.reason);
|
|
170
|
+
constructor(run) {
|
|
171
|
+
this.run = run;
|
|
172
|
+
run.signal.addEventListener("abort", this.stop);
|
|
173
|
+
}
|
|
174
|
+
/** Something arrived, or is about to be waited for. */
|
|
175
|
+
listen() {
|
|
176
|
+
clearTimeout(this.timer);
|
|
177
|
+
this.timer = setTimeout(() => this.controller.abort(new Broke("nothing arrived for a while")), this.run.quietMs);
|
|
178
|
+
}
|
|
179
|
+
/** Nothing is expected for now: this side is not reading. */
|
|
180
|
+
deaf() {
|
|
181
|
+
clearTimeout(this.timer);
|
|
182
|
+
}
|
|
183
|
+
done() {
|
|
184
|
+
this.deaf();
|
|
185
|
+
this.run.signal.removeEventListener("abort", this.stop);
|
|
186
|
+
}
|
|
187
|
+
/** Ask for the rest of `range`, and check that storage answered with it. */
|
|
188
|
+
async fetch(range) {
|
|
189
|
+
this.listen();
|
|
190
|
+
let res;
|
|
191
|
+
try {
|
|
192
|
+
res = await this.run.fetch(this.run.url, { headers: { Range: `bytes=${range.at}-${range.end - 1}` }, signal: this.controller.signal });
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
throw this.run.signal.aborted ? e : new Broke("the request failed", { cause: e });
|
|
196
|
+
}
|
|
197
|
+
const refuse = async (error) => {
|
|
198
|
+
await res.body?.cancel().catch(() => undefined);
|
|
199
|
+
throw error;
|
|
200
|
+
};
|
|
201
|
+
if (res.status === 403) {
|
|
202
|
+
return refuse(new FileFreakError(403, "address_expired", "The download stopped: a connection broke after the download's address had run out, so the rest could not be asked for. Download it again."));
|
|
203
|
+
}
|
|
204
|
+
if (res.status === 408 || res.status === 429 || res.status >= 500)
|
|
205
|
+
return refuse(new Broke(`storage answered ${res.status}`));
|
|
206
|
+
if (res.status !== 206)
|
|
207
|
+
return refuse(new FileFreakError(res.status, "storage_refused", `Storage answered ${res.status} when asked for part of the file.`));
|
|
208
|
+
// Content-Range is one of the headers storage lets a page read. Without
|
|
209
|
+
// it, a part written at the wrong place would go unnoticed.
|
|
210
|
+
const sent = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(res.headers.get("Content-Range") ?? "");
|
|
211
|
+
if (!sent || !res.body)
|
|
212
|
+
return refuse(new FileFreakError(res.status, "storage_refused", "Storage did not say which part of the file it sent."));
|
|
213
|
+
if (Number(sent[3]) !== this.run.sizeBytes) {
|
|
214
|
+
return refuse(new FileFreakError(res.status, "file_changed", "The file in storage is not the size the API gave. It may have been replaced since; download it again."));
|
|
215
|
+
}
|
|
216
|
+
if (Number(sent[1]) !== range.at)
|
|
217
|
+
return refuse(new FileFreakError(res.status, "storage_refused", "Storage sent another part of the file than the one asked for."));
|
|
218
|
+
return res;
|
|
219
|
+
}
|
|
220
|
+
/** The next piece of the body, or null at its end. */
|
|
221
|
+
async next(reader) {
|
|
222
|
+
this.listen();
|
|
223
|
+
try {
|
|
224
|
+
const { done, value } = await reader.read();
|
|
225
|
+
return done ? null : value;
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
throw this.run.signal.aborted ? e : new Broke("the connection broke", { cause: e });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** Write what arrives on `res` into the range, until the range ends or the body does. */
|
|
233
|
+
async function drain(range, res, run, asking) {
|
|
234
|
+
const reader = res.body.getReader();
|
|
235
|
+
const held = [];
|
|
236
|
+
let heldBytes = 0;
|
|
237
|
+
const flush = async () => {
|
|
238
|
+
if (heldBytes === 0)
|
|
239
|
+
return;
|
|
240
|
+
const data = join(held, heldBytes);
|
|
241
|
+
const position = range.at - heldBytes;
|
|
242
|
+
held.length = 0;
|
|
243
|
+
heldBytes = 0;
|
|
244
|
+
asking.deaf();
|
|
245
|
+
await run.sink.write(position, data);
|
|
246
|
+
run.wrote(data.byteLength);
|
|
247
|
+
};
|
|
248
|
+
try {
|
|
249
|
+
for (;;) {
|
|
250
|
+
let chunk = await asking.next(reader);
|
|
251
|
+
if (!chunk)
|
|
252
|
+
break;
|
|
253
|
+
while (chunk.byteLength > 0) {
|
|
254
|
+
if (range.at >= range.end) {
|
|
255
|
+
await flush();
|
|
256
|
+
if (range.giving) {
|
|
257
|
+
// Cut here. This request goes on covering the rest, so wait,
|
|
258
|
+
// reading nothing, to learn whether the rest comes back.
|
|
259
|
+
asking.deaf();
|
|
260
|
+
await range.giving;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
await reader.cancel().catch(() => undefined);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const take = chunk.subarray(0, Math.min(chunk.byteLength, range.end - range.at));
|
|
267
|
+
held.push(take);
|
|
268
|
+
heldBytes += take.byteLength;
|
|
269
|
+
range.at += take.byteLength;
|
|
270
|
+
chunk = chunk.subarray(take.byteLength);
|
|
271
|
+
if (heldBytes >= WRITE_BYTES)
|
|
272
|
+
await flush();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
// What arrived before a break is right, so it is written, and the range
|
|
278
|
+
// is asked for again from after it.
|
|
279
|
+
if (!run.signal.aborted)
|
|
280
|
+
await flush();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** Wait before asking again, a little longer each time; give up after TRIES in a row with nothing arriving. */
|
|
284
|
+
async function again(tries, run, why) {
|
|
285
|
+
if (tries + 1 >= TRIES) {
|
|
286
|
+
throw Object.assign(new FileFreakError(0, "storage_unreachable", "The download stopped: storage could not be reached after several tries. Download it again."), { cause: why });
|
|
287
|
+
}
|
|
288
|
+
await run.sleep(Math.min(8000, 500 * 2 ** tries));
|
|
289
|
+
if (run.signal.aborted)
|
|
290
|
+
throw run.signal.reason;
|
|
291
|
+
return tries + 1;
|
|
292
|
+
}
|
|
293
|
+
function join(parts, bytes) {
|
|
294
|
+
if (parts.length === 1)
|
|
295
|
+
return parts[0];
|
|
296
|
+
const out = new Uint8Array(bytes);
|
|
297
|
+
let at = 0;
|
|
298
|
+
for (const p of parts) {
|
|
299
|
+
out.set(p, at);
|
|
300
|
+
at += p.byteLength;
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Sink } from "./ranges.js";
|
|
2
|
+
/** The most bytes of fragments being fetched, or fetched and not yet written, at once in this program: five of the largest. */
|
|
3
|
+
export declare const MOST_IN_FLIGHT_BYTES: number;
|
|
4
|
+
/** Fragments fetched at once from one storage host. */
|
|
5
|
+
export declare const PER_HOST = 6;
|
|
6
|
+
/** Said when a fragment arrives and fails its check. */
|
|
7
|
+
export declare const NOT_WHAT_WAS_STORED = "Part of this file is not what was stored. Nothing was saved.";
|
|
8
|
+
/** Said when a fragment cannot be had from storage. */
|
|
9
|
+
export declare const NOT_FETCHED = "Part of this file could not be fetched from storage. Nothing was saved; try again later.";
|
|
10
|
+
/** A page of a protected file's map, as the reader uses it: files.shards's answer, or a link's. */
|
|
11
|
+
export interface MapPage {
|
|
12
|
+
contentId: string;
|
|
13
|
+
sizeBytes: number;
|
|
14
|
+
fragmentBytes: number;
|
|
15
|
+
count: number;
|
|
16
|
+
/** The file's key, in base64. */
|
|
17
|
+
key: string;
|
|
18
|
+
fragments: ReadonlyArray<{
|
|
19
|
+
place: number;
|
|
20
|
+
url: string;
|
|
21
|
+
}>;
|
|
22
|
+
/** When this page's addresses stop working. */
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
}
|
|
25
|
+
/** Asks for the page of a map that begins at a place. A function, so that a map is read the same way whoever gives it. */
|
|
26
|
+
export type Pages = (from: number, signal: AbortSignal) => Promise<MapPage>;
|
|
27
|
+
/**
|
|
28
|
+
* A number of units, taken and given back, first come first served: a
|
|
29
|
+
* budget of bytes, or turns at a host. A taker who asks for more than there
|
|
30
|
+
* ever is waits for all of it, and goes alone.
|
|
31
|
+
*/
|
|
32
|
+
export declare class Gate {
|
|
33
|
+
private readonly most;
|
|
34
|
+
private free;
|
|
35
|
+
private readonly queue;
|
|
36
|
+
constructor(most: number);
|
|
37
|
+
/** Wait for `units`. Answers the way to give them back, which may be called more than once. */
|
|
38
|
+
take(units: number, signal: AbortSignal): Promise<() => void>;
|
|
39
|
+
private giver;
|
|
40
|
+
private next;
|
|
41
|
+
}
|
|
42
|
+
export interface ReadOptions {
|
|
43
|
+
pages: Pages;
|
|
44
|
+
sink: Sink;
|
|
45
|
+
/**
|
|
46
|
+
* The file's size as it was listed. A map of another size is of a file
|
|
47
|
+
* replaced since, and is not read: a sink made for the size listed, as
|
|
48
|
+
* bytes() makes one, could not hold it.
|
|
49
|
+
*/
|
|
50
|
+
sizeBytes?: number;
|
|
51
|
+
signal?: AbortSignal | undefined;
|
|
52
|
+
/** How many bytes of the file have been written, after each fragment. */
|
|
53
|
+
progress?: ((writtenBytes: number) => void) | undefined;
|
|
54
|
+
fetch?: typeof fetch;
|
|
55
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
56
|
+
now?: () => number;
|
|
57
|
+
/** Bytes of fragments in flight: the program's, unless a test gives a read its own. */
|
|
58
|
+
budget?: Gate;
|
|
59
|
+
/** Fragments fetched at once from one storage host. */
|
|
60
|
+
perHost?: number;
|
|
61
|
+
quietMs?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read the protected file whose map `pages` gives into `sink`. Resolves once
|
|
65
|
+
* every fragment has been written; the caller closes the sink, or aborts it
|
|
66
|
+
* when this rejects, as with fetchRanges. Rejects only once every fragment
|
|
67
|
+
* still in flight has stopped, so nothing is written to the sink after.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readProtected(options: ReadOptions): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Fetch a file the way it is kept: through its map when it is protected,
|
|
72
|
+
* from its address when not. One converted since it was listed is refused
|
|
73
|
+
* the way the listing chose (409 not_protected, or shard_protected) before
|
|
74
|
+
* anything is written, and is fetched the other way, once.
|
|
75
|
+
*/
|
|
76
|
+
export declare function asItIsKept(isProtected: boolean, plain: () => Promise<void>, mapped: () => Promise<void>): Promise<void>;
|