@filefreak.io/sdk 0.4.0 → 0.6.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 +30 -3
- package/dist/client.js +2 -1
- package/dist/cut.d.ts +96 -0
- package/dist/cut.js +220 -0
- package/dist/generated.d.ts +75 -9
- package/dist/generated.js +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/links.js +26 -3
- package/dist/reader.d.ts +11 -0
- package/dist/reader.js +69 -27
- package/dist/shards.d.ts +31 -0
- package/dist/shards.js +64 -0
- package/dist/uploads.d.ts +6 -3
- package/dist/uploads.js +250 -72
- package/package.json +1 -1
package/dist/links.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// API call is counted, since no key is used.
|
|
18
18
|
import { fetchRanges } from "./ranges.js";
|
|
19
19
|
import { asItIsKept, readProtected } from "./reader.js";
|
|
20
|
+
import { READS_FORMATS, SHARD_VERSION } from "./shards.js";
|
|
20
21
|
import { backoff, FileFreakError, sleep } from "./transport.js";
|
|
21
22
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22
23
|
/** As the API's route takes a token (api/src/routes/public.ts). */
|
|
@@ -106,8 +107,30 @@ class Reader {
|
|
|
106
107
|
this.fail();
|
|
107
108
|
if (!isCount(v.sizeBytes) || !isCount(v.fragmentBytes) || !isCount(v.count) || !Array.isArray(v.fragments))
|
|
108
109
|
this.fail();
|
|
110
|
+
// A site from before version 2 of the format says no version, and every file it has is in version 1.
|
|
111
|
+
const format = v.format === undefined ? SHARD_VERSION : v.format;
|
|
112
|
+
if (!isCount(format))
|
|
113
|
+
this.fail();
|
|
114
|
+
const outline = v.outline === undefined || v.outline === null ? null : this.outline(v.outline);
|
|
109
115
|
const fragments = v.fragments.map((f) => (isObject(f) && isCount(f.place) && isWebAddress(f.url) ? { place: f.place, url: f.url } : this.fail()));
|
|
110
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
contentId: v.contentId,
|
|
118
|
+
format,
|
|
119
|
+
sizeBytes: v.sizeBytes,
|
|
120
|
+
fragmentBytes: v.fragmentBytes,
|
|
121
|
+
count: v.count,
|
|
122
|
+
key: v.key,
|
|
123
|
+
outline,
|
|
124
|
+
fragments,
|
|
125
|
+
expiresAt: v.expiresAt,
|
|
126
|
+
grant: v.grant,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
outline(v) {
|
|
130
|
+
if (!isObject(v) || typeof v.mac !== "string" || !Array.isArray(v.blocks))
|
|
131
|
+
this.fail();
|
|
132
|
+
const blocks = v.blocks.map((b) => (isObject(b) && typeof b.id === "string" && isCount(b.length) ? { id: b.id, length: b.length } : this.fail()));
|
|
133
|
+
return { mac: v.mac, blocks };
|
|
111
134
|
}
|
|
112
135
|
}
|
|
113
136
|
/** The link's own site, asked with nothing that says who is asking. */
|
|
@@ -216,7 +239,7 @@ async function openShare(site, token, password) {
|
|
|
216
239
|
downloadsLeft: view.downloadsLeft,
|
|
217
240
|
list: async (folder, offset) => site.read.listing(await site.ask(`${base}/items?parent=${folder}&offset=${offset}&limit=${PAGE}`, { share })),
|
|
218
241
|
address: async (id) => site.read.address(await site.ask(`${base}/files/${id}/url`, { share })),
|
|
219
|
-
page: async (id, from, read, signal) => site.read.map(await site.ask(`${base}/files/${id}/shards?from=${from}`, { share, read }, undefined, signal)),
|
|
242
|
+
page: async (id, from, read, signal) => site.read.map(await site.ask(`${base}/files/${id}/shards?from=${from}&formats=${READS_FORMATS}`, { share, read }, undefined, signal)),
|
|
220
243
|
};
|
|
221
244
|
}
|
|
222
245
|
async function openPublic(site, id, username) {
|
|
@@ -232,7 +255,7 @@ async function openPublic(site, id, username) {
|
|
|
232
255
|
downloadsLeft: null,
|
|
233
256
|
list: async (folder, offset) => site.read.listing(await site.ask(`/api/public/files/${folder}/items?offset=${offset}&limit=${PAGE}`)),
|
|
234
257
|
address: async (file) => site.read.address(await site.ask(`/api/public/files/${file}/url`)),
|
|
235
|
-
page: async (file, from, read, signal) => site.read.map(await site.ask(`/api/public/files/${file}/shards?from=${from}`, { read }, undefined, signal)),
|
|
258
|
+
page: async (file, from, read, signal) => site.read.map(await site.ask(`/api/public/files/${file}/shards?from=${from}&formats=${READS_FORMATS}`, { read }, undefined, signal)),
|
|
236
259
|
};
|
|
237
260
|
}
|
|
238
261
|
/** Every file in a shared folder, with the folders it is in, read folder by folder. */
|
package/dist/reader.d.ts
CHANGED
|
@@ -10,11 +10,22 @@ export declare const NOT_FETCHED = "Part of this file could not be fetched from
|
|
|
10
10
|
/** A page of a protected file's map, as the reader uses it: files.shards's answer, or a link's. */
|
|
11
11
|
export interface MapPage {
|
|
12
12
|
contentId: string;
|
|
13
|
+
/** The version of the shard format the file is in, 1 or 2. */
|
|
14
|
+
format: number;
|
|
13
15
|
sizeBytes: number;
|
|
16
|
+
/** In version 2, the largest block. */
|
|
14
17
|
fragmentBytes: number;
|
|
15
18
|
count: number;
|
|
16
19
|
/** The file's key, in base64. */
|
|
17
20
|
key: string;
|
|
21
|
+
/** In version 2, on the first page only: the outline's MAC, and each block's id and length, in base64. */
|
|
22
|
+
outline: {
|
|
23
|
+
mac: string;
|
|
24
|
+
blocks: ReadonlyArray<{
|
|
25
|
+
id: string;
|
|
26
|
+
length: number;
|
|
27
|
+
}>;
|
|
28
|
+
} | null;
|
|
18
29
|
fragments: ReadonlyArray<{
|
|
19
30
|
place: number;
|
|
20
31
|
url: string;
|
package/dist/reader.js
CHANGED
|
@@ -28,7 +28,13 @@
|
|
|
28
28
|
//
|
|
29
29
|
// The key is a WebCrypto key that cannot be exported, held inside one read
|
|
30
30
|
// and let go of with it. No error here carries it.
|
|
31
|
-
|
|
31
|
+
//
|
|
32
|
+
// A file in version 2 of the format is read the same way, a block at each
|
|
33
|
+
// place, once its outline is checked: the map's first page carries it, and
|
|
34
|
+
// nothing is fetched until its MAC holds under the file's key, since a map
|
|
35
|
+
// whose blocks were reordered or swapped would otherwise be written out in
|
|
36
|
+
// the wrong order with every block passing its own check.
|
|
37
|
+
import { BlockLayout, checkOutline, fileKeys, openBlock, openFragment, ShardError, shardKey, ShardLayout, SHARD_OVERHEAD, SHARD_VERSION, SHARD_VERSION_2, } from "./shards.js";
|
|
32
38
|
import { backoff, FileFreakError, sleep } from "./transport.js";
|
|
33
39
|
const MIB = 1024 * 1024;
|
|
34
40
|
/** The most bytes of fragments being fetched, or fetched and not yet written, at once in this program: five of the largest. */
|
|
@@ -131,11 +137,17 @@ function unreadable(what) {
|
|
|
131
137
|
const damaged = (cause) => stopped("fragment_damaged", NOT_WHAT_WAS_STORED, cause);
|
|
132
138
|
const notFetched = (cause) => stopped("fragment_unavailable", NOT_FETCHED, cause);
|
|
133
139
|
/**
|
|
134
|
-
* A page whose count, or one of whose places, the file's
|
|
135
|
-
*
|
|
140
|
+
* A page whose count, or one of whose places, the file's layout does not
|
|
141
|
+
* allow: a map that does not add up is not read from. In version 2 its
|
|
142
|
+
* count and fragment size are the outline's, its block count and largest.
|
|
136
143
|
*/
|
|
137
144
|
function agrees(page, layout) {
|
|
138
|
-
if (
|
|
145
|
+
if (layout instanceof BlockLayout) {
|
|
146
|
+
if (page.count !== layout.count || page.fragmentBytes !== layout.fragmentSize) {
|
|
147
|
+
throw stopped("layout", `This file's map does not add up: it says ${page.count} blocks of at most ${page.fragmentBytes} bytes, and its outline names ${layout.count} of at most ${layout.fragmentSize}. Nothing was saved.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (page.count !== layout.count) {
|
|
139
151
|
throw stopped("layout", `This file's map does not add up: it says ${page.count} fragments, where ${layout.size} bytes in fragments of ${layout.fragmentSize} make ${layout.count}. Nothing was saved.`);
|
|
140
152
|
}
|
|
141
153
|
const wrong = page.fragments.find((f) => !Number.isInteger(f.place) || f.place < 0 || f.place >= layout.count);
|
|
@@ -143,17 +155,50 @@ function agrees(page, layout) {
|
|
|
143
155
|
throw stopped("layout", `This file's map names fragment ${wrong.place}, and the file has ${layout.count}, numbered from 0. Nothing was saved.`);
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
/** Base64 from the map, as bytes, or the map is unreadable. */
|
|
159
|
+
function bytesOf(text, what) {
|
|
148
160
|
try {
|
|
149
|
-
|
|
161
|
+
return Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
|
|
150
162
|
}
|
|
151
163
|
catch {
|
|
152
|
-
throw unreadable(
|
|
164
|
+
throw unreadable(what);
|
|
153
165
|
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The file's pieces as its first page describes them. In version 2 the
|
|
169
|
+
* outline is checked under the key here, before a block is fetched, and one
|
|
170
|
+
* that fails is the same failure as a fragment that fails its check.
|
|
171
|
+
*/
|
|
172
|
+
async function piecesOf(first) {
|
|
173
|
+
const raw = bytesOf(first.key, "key");
|
|
154
174
|
if (raw.length !== 32)
|
|
155
175
|
throw unreadable("key");
|
|
156
|
-
|
|
176
|
+
try {
|
|
177
|
+
if (first.format === SHARD_VERSION) {
|
|
178
|
+
const layout = new ShardLayout(first.sizeBytes, first.fragmentBytes);
|
|
179
|
+
const key = await shardKey(raw);
|
|
180
|
+
return { layout, open: (place, blob) => openFragment(key, { contentId: first.contentId, layout, place }, blob) };
|
|
181
|
+
}
|
|
182
|
+
if (first.format === SHARD_VERSION_2) {
|
|
183
|
+
if (!first.outline)
|
|
184
|
+
throw unreadable("outline");
|
|
185
|
+
const outline = {
|
|
186
|
+
contentId: first.contentId,
|
|
187
|
+
size: first.sizeBytes,
|
|
188
|
+
blocks: first.outline.blocks.map((b) => ({ id: bytesOf(b.id, "outline"), length: b.length })),
|
|
189
|
+
};
|
|
190
|
+
const layout = new BlockLayout(outline);
|
|
191
|
+
const keys = await fileKeys(raw);
|
|
192
|
+
await checkOutline(keys, outline, bytesOf(first.outline.mac, "outline"));
|
|
193
|
+
return { layout, open: (place, blob) => openBlock(keys, outline.blocks[place], blob) };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
if (e instanceof ShardError)
|
|
198
|
+
throw e.code === "layout" ? stopped("layout", `${e.message} Nothing was saved.`) : damaged(e);
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
throw stopped("format_unsupported", `This file is in version ${first.format} of the shard format, which this version of the SDK does not read. Update it to read the file.`);
|
|
157
202
|
}
|
|
158
203
|
/**
|
|
159
204
|
* Read the protected file whose map `pages` gives into `sink`. Resolves once
|
|
@@ -172,19 +217,11 @@ export async function readProtected(options) {
|
|
|
172
217
|
const first = await options.pages(0, stop.signal);
|
|
173
218
|
if (options.sizeBytes !== undefined && first.sizeBytes !== options.sizeBytes)
|
|
174
219
|
throw stopped("content_changed", CHANGED);
|
|
175
|
-
let layout;
|
|
176
|
-
try {
|
|
177
|
-
layout = new ShardLayout(first.sizeBytes, first.fragmentBytes);
|
|
178
|
-
}
|
|
179
|
-
catch (e) {
|
|
180
|
-
if (e instanceof ShardError)
|
|
181
|
-
throw stopped("layout", `${e.message} Nothing was saved.`);
|
|
182
|
-
throw e;
|
|
183
|
-
}
|
|
184
|
-
agrees(first, layout);
|
|
185
220
|
if (!UUID.test(first.contentId))
|
|
186
221
|
throw unreadable("content id");
|
|
187
|
-
const
|
|
222
|
+
const pieces = await piecesOf(first);
|
|
223
|
+
agrees(first, pieces.layout);
|
|
224
|
+
const reading = new Reading(options, stop, pieces, first.contentId, first.format);
|
|
188
225
|
reading.hold(first);
|
|
189
226
|
await reading.fill();
|
|
190
227
|
}
|
|
@@ -216,9 +253,9 @@ export async function asItIsKept(isProtected, plain, mapped) {
|
|
|
216
253
|
class Reading {
|
|
217
254
|
options;
|
|
218
255
|
stop;
|
|
219
|
-
|
|
256
|
+
pieces;
|
|
220
257
|
contentId;
|
|
221
|
-
|
|
258
|
+
format;
|
|
222
259
|
addresses = new Map();
|
|
223
260
|
/** Held while a page is asked for, so that fragments waiting on the same page ask once. */
|
|
224
261
|
asking = new Gate(1);
|
|
@@ -230,13 +267,15 @@ class Reading {
|
|
|
230
267
|
wait;
|
|
231
268
|
now;
|
|
232
269
|
quietMs;
|
|
270
|
+
layout;
|
|
233
271
|
written = 0;
|
|
234
|
-
constructor(options, stop,
|
|
272
|
+
constructor(options, stop, pieces, contentId, format) {
|
|
235
273
|
this.options = options;
|
|
236
274
|
this.stop = stop;
|
|
237
|
-
this.
|
|
275
|
+
this.pieces = pieces;
|
|
238
276
|
this.contentId = contentId;
|
|
239
|
-
this.
|
|
277
|
+
this.format = format;
|
|
278
|
+
this.layout = pieces.layout;
|
|
240
279
|
this.budget = options.budget ?? shared();
|
|
241
280
|
this.fetcher = options.fetch ?? fetch.bind(globalThis);
|
|
242
281
|
this.wait = options.sleep ?? sleep;
|
|
@@ -248,7 +287,10 @@ class Reading {
|
|
|
248
287
|
}
|
|
249
288
|
/** Keep a page's addresses, once it is known to be a page of this file's map. */
|
|
250
289
|
hold(page) {
|
|
251
|
-
if (page.contentId !== this.contentId ||
|
|
290
|
+
if (page.contentId !== this.contentId ||
|
|
291
|
+
page.format !== this.format ||
|
|
292
|
+
page.sizeBytes !== this.layout.size ||
|
|
293
|
+
page.fragmentBytes !== this.layout.fragmentSize) {
|
|
252
294
|
throw stopped("content_changed", CHANGED);
|
|
253
295
|
}
|
|
254
296
|
agrees(page, this.layout);
|
|
@@ -424,7 +466,7 @@ class Reading {
|
|
|
424
466
|
}
|
|
425
467
|
let plain;
|
|
426
468
|
try {
|
|
427
|
-
plain = await
|
|
469
|
+
plain = await this.pieces.open(place, blob);
|
|
428
470
|
}
|
|
429
471
|
catch (e) {
|
|
430
472
|
throw damaged(e);
|
package/dist/shards.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare class ShardLayout {
|
|
|
25
25
|
};
|
|
26
26
|
/** How many bytes of the file fragment `place` holds. */
|
|
27
27
|
length(place: number): number;
|
|
28
|
+
/** The place of the fragment that holds byte `offset` of the file. */
|
|
29
|
+
placeAt(offset: number): number;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* A content's key as WebCrypto holds it. Named through crypto.subtle and not
|
|
@@ -59,6 +61,8 @@ export declare function sealWithNonce(key: ShardKey, at: FragmentAt, plaintext:
|
|
|
59
61
|
*/
|
|
60
62
|
export declare function openFragment(key: ShardKey, at: FragmentAt, blob: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
61
63
|
export declare const SHARD_VERSION_2 = 2;
|
|
64
|
+
/** The versions of the format this reads, as a reader asks for a map in them (formats). */
|
|
65
|
+
export declare const READS_FORMATS = "1,2";
|
|
62
66
|
/** The most bytes of a file one block holds, as the API takes blocks. */
|
|
63
67
|
export declare const MOST_BLOCK_BYTES: number;
|
|
64
68
|
/** A block id: the HMAC-SHA256 of its bytes under the file's block key. */
|
|
@@ -108,6 +112,33 @@ export declare function sealBlockWithNonce(keys: FileKeys, id: Uint8Array, plain
|
|
|
108
112
|
* returned.
|
|
109
113
|
*/
|
|
110
114
|
export declare function openBlock(keys: FileKeys, block: OutlineBlock, blob: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
115
|
+
/**
|
|
116
|
+
* Where each block of a file in version 2 is, from its outline: what
|
|
117
|
+
* ShardLayout is for version 1, to a reader that writes each block at its
|
|
118
|
+
* place in the file, or reads a range from the blocks that hold it. It
|
|
119
|
+
* refuses an outline that describes no file, before anything is fetched by
|
|
120
|
+
* it; that the outline is the file's is checkOutline's to say.
|
|
121
|
+
*/
|
|
122
|
+
export declare class BlockLayout {
|
|
123
|
+
readonly size: number;
|
|
124
|
+
readonly count: number;
|
|
125
|
+
/** The largest block, which is what a map of a file in version 2 gives as its fragment size. */
|
|
126
|
+
readonly fragmentSize: number;
|
|
127
|
+
/** Where each block begins, then the file's size: count + 1 offsets, rising. */
|
|
128
|
+
private readonly starts;
|
|
129
|
+
constructor(outline: Outline);
|
|
130
|
+
/** Where in the file block `place` begins, and where it ends (exclusive). */
|
|
131
|
+
range(place: number): {
|
|
132
|
+
start: number;
|
|
133
|
+
end: number;
|
|
134
|
+
};
|
|
135
|
+
/** How many bytes of the file block `place` holds. */
|
|
136
|
+
length(place: number): number;
|
|
137
|
+
/** The place of the block that holds byte `offset` of the file: the last that begins at or before it. */
|
|
138
|
+
placeAt(offset: number): number;
|
|
139
|
+
}
|
|
140
|
+
/** Where each piece of a file is, in either version: the reader's view of it, whichever the map says. */
|
|
141
|
+
export type Layout = ShardLayout | BlockLayout;
|
|
111
142
|
/** The outline's MAC, which the writer sends with the upload's completion and every reader checks before it releases a byte. */
|
|
112
143
|
export declare function outlineMac(keys: FileKeys, outline: Outline): Promise<Uint8Array<ArrayBuffer>>;
|
|
113
144
|
/**
|
package/dist/shards.js
CHANGED
|
@@ -60,6 +60,13 @@ export class ShardLayout {
|
|
|
60
60
|
const { start, end } = this.range(place);
|
|
61
61
|
return end - start;
|
|
62
62
|
}
|
|
63
|
+
/** The place of the fragment that holds byte `offset` of the file. */
|
|
64
|
+
placeAt(offset) {
|
|
65
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset >= this.size) {
|
|
66
|
+
throw new RangeError(`There is no byte ${offset}: this file has ${this.size}, numbered from 0.`);
|
|
67
|
+
}
|
|
68
|
+
return Math.floor(offset / this.fragmentSize);
|
|
69
|
+
}
|
|
63
70
|
}
|
|
64
71
|
/** A content's key, 32 bytes, made usable for its fragments and nothing else. */
|
|
65
72
|
export function shardKey(raw) {
|
|
@@ -136,6 +143,8 @@ export async function openFragment(key, at, blob) {
|
|
|
136
143
|
// Version 2: blocks cut by content, under a file's key that versions share
|
|
137
144
|
// ---------------------------------------------------------------------------
|
|
138
145
|
export const SHARD_VERSION_2 = 2;
|
|
146
|
+
/** The versions of the format this reads, as a reader asks for a map in them (formats). */
|
|
147
|
+
export const READS_FORMATS = `${SHARD_VERSION},${SHARD_VERSION_2}`;
|
|
139
148
|
/** The most bytes of a file one block holds, as the API takes blocks. */
|
|
140
149
|
export const MOST_BLOCK_BYTES = 64 * 1024 * 1024;
|
|
141
150
|
/** A block id: the HMAC-SHA256 of its bytes under the file's block key. */
|
|
@@ -249,6 +258,61 @@ function checkShape(outline) {
|
|
|
249
258
|
if (total !== size)
|
|
250
259
|
throw new ShardError("layout", `The blocks hold ${total} bytes, and the file is ${size}.`);
|
|
251
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Where each block of a file in version 2 is, from its outline: what
|
|
263
|
+
* ShardLayout is for version 1, to a reader that writes each block at its
|
|
264
|
+
* place in the file, or reads a range from the blocks that hold it. It
|
|
265
|
+
* refuses an outline that describes no file, before anything is fetched by
|
|
266
|
+
* it; that the outline is the file's is checkOutline's to say.
|
|
267
|
+
*/
|
|
268
|
+
export class BlockLayout {
|
|
269
|
+
size;
|
|
270
|
+
count;
|
|
271
|
+
/** The largest block, which is what a map of a file in version 2 gives as its fragment size. */
|
|
272
|
+
fragmentSize;
|
|
273
|
+
/** Where each block begins, then the file's size: count + 1 offsets, rising. */
|
|
274
|
+
starts;
|
|
275
|
+
constructor(outline) {
|
|
276
|
+
checkShape(outline);
|
|
277
|
+
this.size = outline.size;
|
|
278
|
+
this.count = outline.blocks.length;
|
|
279
|
+
this.starts = [0];
|
|
280
|
+
let largest = 0;
|
|
281
|
+
for (const b of outline.blocks) {
|
|
282
|
+
this.starts.push(this.starts[this.starts.length - 1] + b.length);
|
|
283
|
+
largest = Math.max(largest, b.length);
|
|
284
|
+
}
|
|
285
|
+
this.fragmentSize = largest;
|
|
286
|
+
}
|
|
287
|
+
/** Where in the file block `place` begins, and where it ends (exclusive). */
|
|
288
|
+
range(place) {
|
|
289
|
+
if (!Number.isInteger(place) || place < 0 || place >= this.count) {
|
|
290
|
+
throw new RangeError(`There is no block ${place}: this file has ${this.count}, numbered from 0.`);
|
|
291
|
+
}
|
|
292
|
+
return { start: this.starts[place], end: this.starts[place + 1] };
|
|
293
|
+
}
|
|
294
|
+
/** How many bytes of the file block `place` holds. */
|
|
295
|
+
length(place) {
|
|
296
|
+
const { start, end } = this.range(place);
|
|
297
|
+
return end - start;
|
|
298
|
+
}
|
|
299
|
+
/** The place of the block that holds byte `offset` of the file: the last that begins at or before it. */
|
|
300
|
+
placeAt(offset) {
|
|
301
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset >= this.size) {
|
|
302
|
+
throw new RangeError(`There is no byte ${offset}: this file has ${this.size}, numbered from 0.`);
|
|
303
|
+
}
|
|
304
|
+
let low = 0;
|
|
305
|
+
let high = this.count - 1;
|
|
306
|
+
while (low < high) {
|
|
307
|
+
const middle = Math.ceil((low + high) / 2);
|
|
308
|
+
if (this.starts[middle] <= offset)
|
|
309
|
+
low = middle;
|
|
310
|
+
else
|
|
311
|
+
high = middle - 1;
|
|
312
|
+
}
|
|
313
|
+
return low;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
252
316
|
/** The bytes the outline's MAC is over: the label, the content id, the size, the count, and each block's id and length in order. */
|
|
253
317
|
function outlineBytes(outline) {
|
|
254
318
|
const head = OUTLINE_LABEL.length + 16 + 8 + 4;
|
package/dist/uploads.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Rule } from "./cut.js";
|
|
2
|
+
import type { FilesFrameAnswer, FilesGetAnswer, Item, Upload, UploadsBeginAnswer, UploadsBeginBody, UploadsBlocksAnswer, UploadsBlocksBody, UploadsCancelAnswer, UploadsCompleteAnswer, UploadsCompleteBody, UploadsFragmentsAnswer, UploadsFragmentsBody, UploadsGetAnswer, UploadsPartsAnswer, UploadsPartsQuery } from "./generated.js";
|
|
2
3
|
import { type CallOptions } from "./transport.js";
|
|
3
4
|
/** What can be uploaded. A File's name and type are used when none are given. */
|
|
4
5
|
export type Source = Blob | ArrayBuffer | Uint8Array;
|
|
@@ -18,7 +19,7 @@ export interface UploadOptions {
|
|
|
18
19
|
contentType?: string;
|
|
19
20
|
/** Smaller copies to make of an image as soon as it is stored. */
|
|
20
21
|
imageSizes?: number[];
|
|
21
|
-
/** Bytes in each part, from 8 MiB to 64 MiB. Left out, 32 MiB, or less for a small file. */
|
|
22
|
+
/** Bytes in each part, from 8 MiB to 64 MiB, for a file not cut by its content: one under 16 MiB, or one of more blocks than the API takes. Left out, 32 MiB, or less for a small file. */
|
|
22
23
|
partBytes?: number;
|
|
23
24
|
/** Parts sent at once. */
|
|
24
25
|
concurrency?: number;
|
|
@@ -52,11 +53,13 @@ export interface UploadCalls {
|
|
|
52
53
|
begin(body: UploadsBeginBody, options?: CallOptions): Promise<UploadsBeginAnswer>;
|
|
53
54
|
get(id: string, options?: CallOptions): Promise<UploadsGetAnswer>;
|
|
54
55
|
parts(id: string, query?: UploadsPartsQuery, options?: CallOptions): Promise<UploadsPartsAnswer>;
|
|
56
|
+
blocks(id: string, body: UploadsBlocksBody, options?: CallOptions): Promise<UploadsBlocksAnswer>;
|
|
55
57
|
fragments(id: string, body: UploadsFragmentsBody, options?: CallOptions): Promise<UploadsFragmentsAnswer>;
|
|
56
58
|
complete(id: string, body?: UploadsCompleteBody, options?: CallOptions): Promise<UploadsCompleteAnswer>;
|
|
59
|
+
cancel(id: string, options?: CallOptions): Promise<UploadsCancelAnswer>;
|
|
57
60
|
file(id: string, options?: CallOptions): Promise<FilesGetAnswer>;
|
|
58
61
|
frame(id: string, options?: CallOptions): Promise<FilesFrameAnswer>;
|
|
59
62
|
}
|
|
60
63
|
/** The part size to ask for: 32 MiB, halved while that would leave fewer parts than are sent at once. */
|
|
61
64
|
export declare function partSizeFor(fileBytes: number, atOnce: number): number;
|
|
62
|
-
export declare function upload(calls: UploadCalls, fetcher: typeof fetch, source: Source, options?: UploadOptions): Promise<Item>;
|
|
65
|
+
export declare function upload(calls: UploadCalls, fetcher: typeof fetch, source: Source, options?: UploadOptions, rule?: Rule): Promise<Item>;
|