@filefreak.io/sdk 0.1.0 → 0.3.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/dist/generated.js CHANGED
@@ -17,9 +17,11 @@ export const OPERATIONS = {
17
17
  "uploads.begin": { method: "POST", path: "/v1/uploads", idempotent: true, repeatable: false },
18
18
  "uploads.get": { method: "GET", path: "/v1/uploads/:id", idempotent: false, repeatable: false },
19
19
  "uploads.parts": { method: "GET", path: "/v1/uploads/:id/parts", idempotent: false, repeatable: false },
20
+ "uploads.fragments": { method: "POST", path: "/v1/uploads/:id/fragments", idempotent: false, repeatable: true },
20
21
  "uploads.complete": { method: "POST", path: "/v1/uploads/:id/complete", idempotent: false, repeatable: true },
21
22
  "uploads.cancel": { method: "DELETE", path: "/v1/uploads/:id", idempotent: false, repeatable: false },
22
23
  "files.downloadUrl": { method: "GET", path: "/v1/files/:id/download", idempotent: false, repeatable: false },
24
+ "files.shards": { method: "GET", path: "/v1/files/:id/shards", idempotent: false, repeatable: false },
23
25
  "files.image": { method: "GET", path: "/v1/files/:id/image", idempotent: false, repeatable: false },
24
26
  "usage.get": { method: "GET", path: "/v1/usage", idempotent: false, repeatable: false },
25
27
  "me.get": { method: "GET", path: "/v1/me", idempotent: false, repeatable: false },
@@ -70,6 +72,8 @@ export function bind(call) {
70
72
  *
71
73
  * Any of the three, in one request. They are applied in that order, each as its own change: if the second is refused the first has still happened, and the answer says which was refused. Moving a folder moves everything in it and is one change however much that is. Making a folder public makes everything in it public, except what has a setting of its own.
72
74
  *
75
+ * protection is a fourth, applied last: on puts the file or folder under Shard Protection, off takes it out, and inherit has it follow the folder it is in. A folder's setting reaches everything in it that has none of its own. Turning it on is refused while the account's plan does not include it (402 shard_protection_needs_pro); turning it off never is. Nothing is converted in this request: the service converts each file within a few minutes, and its protected says when. A key confined to a folder decides about what is in it, and not about the folder.
76
+ *
73
77
  * PATCH /v1/files/:id. Needs the scope files:write.
74
78
  */
75
79
  update: (id, body, options) => call("files.update", { params: { id }, body }, options),
@@ -92,11 +96,19 @@ export function bind(call) {
92
96
  /**
93
97
  * An address to download a file from.
94
98
  *
95
- * A short-lived address on storage itself. Anybody holding it can fetch the file until it expires, with no key, so it can be handed to a browser. Each one issued counts the file's size toward the month's downloads. For an address that does not expire, make the file public and use its publicUrl. A file under Shard Protection is answered with 409 shard_protected: the developer API cannot read protected files yet.
99
+ * A short-lived address on storage itself. Anybody holding it can fetch the file until it expires, with no key, so it can be handed to a browser. Each one issued counts the file's size toward the month's downloads. For an address that does not expire, make the file public and use its publicUrl. A file under Shard Protection has no address: it is answered with 409 shard_protected, and read through its map with files.shards.
96
100
  *
97
101
  * GET /v1/files/:id/download. Needs the scope files:read.
98
102
  */
99
103
  downloadUrl: (id, query, options) => call("files.downloadUrl", { params: { id }, query }, options),
104
+ /**
105
+ * A protected file's map.
106
+ *
107
+ * A file under Shard Protection is kept as fragments, each sealed with the file's own key and stored across several storage accounts. Its map says where each fragment is, up to 1,000 at a time, with the key. Fetch each fragment from its address (no key: the address is the permission), check and open it as the SDK's openFragment does, and write it at its place. The first page counts the file's size toward the month's downloads; later pages count nothing. Addresses work for four hours: before then, ask for the page again from the first fragment not yet fetched. The key is in this answer and nowhere else. This API never keeps it, and neither should a program, beyond the read.
108
+ *
109
+ * GET /v1/files/:id/shards. Needs the scope files:read.
110
+ */
111
+ shards: (id, query, options) => call("files.shards", { params: { id }, query }, options),
100
112
  /**
101
113
  * An image at a smaller size.
102
114
  *
@@ -148,13 +160,15 @@ export function bind(call) {
148
160
  *
149
161
  * The room is set aside now, so an upload that would not fit is refused before a byte is sent. The parts can be sent by anything that can make a PUT request, including a browser that has never seen the API key: hand it the addresses.
150
162
  *
163
+ * Into a place under Shard Protection, an upload begun with protectable true is sealed as it is sent. A place is protected by the setting nearest it (files.update's protection), or with none by the drive's default: an app's, which its owner turns on from the app's page, or for a device the account's. The answer's shards then says how the file is cut, and uploads.get gives its key. Each part is a fragment: the file's bytes from its number times fragmentBytes, sealed with that key as the SDK's sealFragment does, under a nonce of its own, and sent to the part's address. uploads.fragments confirms them, and completing makes the file, protected from its first byte, with a null clientSha256. A sealed upload sets blocks aside: none is kept, and none reused. Begun without protectable, a file lands as an ordinary file and the service protects it within a few minutes; from then on it is read through its map (files.shards).
164
+ *
151
165
  * POST /v1/uploads. Needs the scope files:write. Honours an Idempotency-Key.
152
166
  */
153
167
  begin: (body, options) => call("uploads.begin", { body }, options),
154
168
  /**
155
169
  * An upload, and which parts have arrived.
156
170
  *
157
- * For carrying on after an interruption: storage itself says which parts it has, so nothing the client remembered is needed.
171
+ * For carrying on after an interruption: storage itself says which parts it has, so nothing the client remembered is needed. For an upload sealed as it is sent, uploadedParts are the fragments confirmed and recorded, and shards gives the key, which this API keeps nowhere else. Confirm any fragment that may have been sent before sending it again. This answer is never kept, by this API or anything between it and the caller.
158
172
  *
159
173
  * GET /v1/uploads/:id. Needs the scope files:write.
160
174
  */
@@ -162,11 +176,19 @@ export function bind(call) {
162
176
  /**
163
177
  * Addresses for more parts.
164
178
  *
165
- * Addresses are given out a window at a time and each lasts half an hour. Ask again for any that have run out.
179
+ * Addresses are given out a window at a time and each lasts half an hour. Ask again for any that have run out. For an upload sealed as it is sent, each number is a fragment's place, and its address takes the sealed fragment, with its Content-MD5. Confirm fragments with uploads.fragments once they are sent.
166
180
  *
167
181
  * GET /v1/uploads/:id/parts. Needs the scope files:write.
168
182
  */
169
183
  parts: (id, query, options) => call("uploads.parts", { params: { id }, query }, options),
184
+ /**
185
+ * Confirm sealed fragments.
186
+ *
187
+ * Confirms fragments of a sealed upload that have been sent. Each is copied inside storage to its own name, measured, and recorded once it is the size its place holds. recorded: done, never send it again. pending: storage is still copying it, or another confirmation of it is under way, so ask again shortly. resend: not there, or not its size and removed, so seal it again, under a new nonce, send it and confirm it. Safe to send again.
188
+ *
189
+ * POST /v1/uploads/:id/fragments. Needs the scope files:write. Safe to send again.
190
+ */
191
+ fragments: (id, body, options) => call("uploads.fragments", { params: { id }, body }, options),
170
192
  /**
171
193
  * Finish an upload.
172
194
  *
@@ -188,7 +210,7 @@ export function bind(call) {
188
210
  /**
189
211
  * What the app is using.
190
212
  *
191
- * What this app holds, against the ceiling set for it if there is one; and what the account it belongs to has used this calendar month, across its personal files and all its apps, against what its plan includes. An app's files count toward the account's one storage allowance. For a device signed in with ff login, which uses the personal drive, app is null.
213
+ * What this app holds, against the ceiling set for it if there is one; and what the account it belongs to has used this calendar month, across its personal files and all its apps, against what its plan includes. An app's files count toward the account's one storage allowance. For a device signed in with filefreak login, which uses the personal drive, app is null.
192
214
  *
193
215
  * GET /v1/usage. Needs the scope usage:read.
194
216
  */
@@ -198,7 +220,7 @@ export function bind(call) {
198
220
  /**
199
221
  * Who this is.
200
222
  *
201
- * The account a key, a token or a device acts for, and what it may do there. A key's program and a page's token are told the account's username and plan, never its email address: only a device signed in with ff login, which is the person, is told that.
223
+ * The account a key, a token or a device acts for, and what it may do there. A key's program and a page's token are told the account's username and plan, never its email address: only a device signed in with filefreak login, which is the person, is told that.
202
224
  *
203
225
  * GET /v1/me. Any key may call it.
204
226
  */
@@ -208,25 +230,25 @@ export function bind(call) {
208
230
  /**
209
231
  * Make a share link.
210
232
  *
211
- * A link that opens one file or folder for whoever holds it, separate from making it public: a private file can have one. It can have a password, an end date and a number of downloads after which it stops. Share links are a person's, so only a device signed in with ff login can make them here, as on the website; an app's key cannot.
233
+ * A link that opens one file or folder for whoever holds it, separate from making it public: a private file can have one. It can have a password, an end date and a number of downloads after which it stops. Share links are a person's, so only a device signed in with filefreak login can make them here, as on the website; an app's key cannot. A link to a file under Shard Protection is a shard link: whoever opens it is given the file's key with its map, so their browser can put the file back together, show it and save it. The storage it is kept in still gives nobody anything on its own.
212
234
  *
213
- * POST /v1/shares. Only a device signed in with ff login may call it. Honours an Idempotency-Key.
235
+ * POST /v1/shares. Only a device signed in with filefreak login may call it. Honours an Idempotency-Key.
214
236
  */
215
237
  create: (body, options) => call("shares.create", { body }, options),
216
238
  /**
217
239
  * List share links.
218
240
  *
219
- * The links on one file or folder, or every link the account has, newest first. Removed links are not listed. Only a device signed in with ff login.
241
+ * The links on one file or folder, or every link the account has, newest first. Removed links are not listed. Only a device signed in with filefreak login.
220
242
  *
221
- * GET /v1/shares. Only a device signed in with ff login may call it.
243
+ * GET /v1/shares. Only a device signed in with filefreak login may call it.
222
244
  */
223
245
  list: (query, options) => call("shares.list", { query }, options),
224
246
  /**
225
247
  * Remove a share link.
226
248
  *
227
- * It stops working with the next request, and cannot be brought back. Only a device signed in with ff login.
249
+ * It stops working with the next request, and cannot be brought back. Only a device signed in with filefreak login.
228
250
  *
229
- * DELETE /v1/shares/:id. Only a device signed in with ff login may call it.
251
+ * DELETE /v1/shares/:id. Only a device signed in with filefreak login may call it.
230
252
  */
231
253
  revoke: (id, options) => call("shares.revoke", { params: { id } }, options),
232
254
  },
@@ -234,9 +256,9 @@ export function bind(call) {
234
256
  /**
235
257
  * List sites.
236
258
  *
237
- * The account's static sites, by name, with the deploy each serves. A site is a person's, so only a device signed in with ff login reaches sites, as on the website; an app's key cannot. Sites are part of Pro.
259
+ * The account's static sites, by name, with the deploy each serves. A site is a person's, so only a device signed in with filefreak login reaches sites, as on the website; an app's key cannot. Sites are part of Pro.
238
260
  *
239
- * GET /v1/sites. Only a device signed in with ff login may call it.
261
+ * GET /v1/sites. Only a device signed in with filefreak login may call it.
240
262
  */
241
263
  list: (options) => call("sites.list", {}, options),
242
264
  /**
@@ -244,7 +266,7 @@ export function bind(call) {
244
266
  *
245
267
  * A site with no deploy yet. Its name is its address: 3 to 40 lower-case letters, digits and single hyphens, and not a name FileFreak keeps, another account's username, or one that looks like another company's (invalid_site_name, site_name_taken). Refused with sites_need_pro on a plan without sites.
246
268
  *
247
- * POST /v1/sites. Only a device signed in with ff login may call it. Honours an Idempotency-Key.
269
+ * POST /v1/sites. Only a device signed in with filefreak login may call it. Honours an Idempotency-Key.
248
270
  */
249
271
  create: (body, options) => call("sites.create", { body }, options),
250
272
  /**
@@ -252,7 +274,7 @@ export function bind(call) {
252
274
  *
253
275
  * A site, and its deploys newest first. A site keeps its 10 newest deploys that have ended besides the live one.
254
276
  *
255
- * GET /v1/sites/:id. Only a device signed in with ff login may call it.
277
+ * GET /v1/sites/:id. Only a device signed in with filefreak login may call it.
256
278
  */
257
279
  get: (id, options) => call("sites.get", { params: { id } }, options),
258
280
  /**
@@ -260,7 +282,7 @@ export function bind(call) {
260
282
  *
261
283
  * Begins a deploy with up to 1000 of its files: each one's path, size and SHA-256. Declare the rest with sites.declareFiles, more set to false on the last list. The answer says which files to send: PUT each one's bytes to its address, then call sites.finishDeploy. Bytes the site has stored already are not asked for. A deploy may have 20000 files, 100 MB each and 1 GB together, and what it adds counts as storage from the moment it is declared (quota_exceeded). An unfinished deploy nothing has asked about for an hour is given up on.
262
284
  *
263
- * POST /v1/sites/:id/deploys. Only a device signed in with ff login may call it. Honours an Idempotency-Key.
285
+ * POST /v1/sites/:id/deploys. Only a device signed in with filefreak login may call it. Honours an Idempotency-Key.
264
286
  */
265
287
  startDeploy: (id, body, options) => call("sites.startDeploy", { params: { id }, body }, options),
266
288
  /**
@@ -268,7 +290,7 @@ export function bind(call) {
268
290
  *
269
291
  * The next 1000 files of a deploy at most, as sites.startDeploy takes them. A path declared twice is refused (path_declared_twice).
270
292
  *
271
- * POST /v1/sites/deploys/:id/files. Only a device signed in with ff login may call it. Honours an Idempotency-Key.
293
+ * POST /v1/sites/deploys/:id/files. Only a device signed in with filefreak login may call it. Honours an Idempotency-Key.
272
294
  */
273
295
  declareFiles: (id, body, options) => call("sites.declareFiles", { params: { id }, body }, options),
274
296
  /**
@@ -276,7 +298,7 @@ export function bind(call) {
276
298
  *
277
299
  * New addresses for files of an unfinished deploy whose bytes have not arrived, when the first ones ran out or a finish answered deploy_incomplete. Files whose bytes have arrived are left out.
278
300
  *
279
- * POST /v1/sites/deploys/:id/send. Only a device signed in with ff login may call it. Safe to send again.
301
+ * POST /v1/sites/deploys/:id/send. Only a device signed in with filefreak login may call it. Safe to send again.
280
302
  */
281
303
  sendAgain: (id, body, options) => call("sites.sendAgain", { params: { id }, body }, options),
282
304
  /**
@@ -284,7 +306,7 @@ export function bind(call) {
284
306
  *
285
307
  * Storage puts each file in place and is asked its size, and the deploy is ready, and live if live is true. Safe to call again: storage may take longer than one request waits, and the answer is then 202 with how long to wait before asking again. A file whose bytes have not arrived, or arrived at another size, is answered with deploy_incomplete and the SHA-256s to send again.
286
308
  *
287
- * POST /v1/sites/deploys/:id/finish. Only a device signed in with ff login may call it. Safe to send again.
309
+ * POST /v1/sites/deploys/:id/finish. Only a device signed in with filefreak login may call it. Safe to send again.
288
310
  */
289
311
  finishDeploy: (id, body, options) => call("sites.finishDeploy", { params: { id }, body }, options),
290
312
  /**
@@ -292,7 +314,7 @@ export function bind(call) {
292
314
  *
293
315
  * Cancels a deploy that is uploading, and what it set aside is given back at once. Deletes one that has ended, with whatever bytes no other deploy of the site uses. Never the live one (deploy_live).
294
316
  *
295
- * DELETE /v1/sites/deploys/:id. Only a device signed in with ff login may call it.
317
+ * DELETE /v1/sites/deploys/:id. Only a device signed in with filefreak login may call it.
296
318
  */
297
319
  removeDeploy: (id, options) => call("sites.removeDeploy", { params: { id } }, options),
298
320
  /**
@@ -300,7 +322,7 @@ export function bind(call) {
300
322
  *
301
323
  * Makes a ready deploy the one visitors are served, which is also how a site is rolled back; null takes the site offline. Visitors see the change within a minute.
302
324
  *
303
- * POST /v1/sites/:id/live. Only a device signed in with ff login may call it. Safe to send again.
325
+ * POST /v1/sites/:id/live. Only a device signed in with filefreak login may call it. Safe to send again.
304
326
  */
305
327
  setLive: (id, body, options) => call("sites.setLive", { params: { id }, body }, options),
306
328
  },
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { FileFreak, Walk, type DownloadOptions, type FollowOptions } from "./client.js";
2
2
  export * from "./generated.js";
3
+ export { get, type GetOptions, type Got, type Into, type LinkedFile } from "./links.js";
3
4
  export { type Sink } from "./ranges.js";
4
5
  export { openFragment, sealFragment, SHARD_OVERHEAD, SHARD_VERSION, ShardError, shardKey, ShardLayout, type FragmentAt, type ShardErrorCode, type ShardKey, } from "./shards.js";
5
6
  export { FileFreakError, type CallOptions, type ClientOptions, type TokenSource } from "./transport.js";
package/dist/index.js CHANGED
@@ -5,8 +5,13 @@
5
5
  //
6
6
  // In a web page, give it a token the page's own server made with its key
7
7
  // (ff.tokens.create), never the key. Files on disk are in "@filefreak.io/sdk/node".
8
+ //
9
+ // Saving what a share link or a public page holds needs no key at all:
10
+ //
11
+ // await get("https://filefreak.link/share/...", toFolder("downloads"));
8
12
  export { FileFreak, Walk } from "./client.js";
9
13
  export * from "./generated.js";
14
+ export { get } from "./links.js";
10
15
  export { openFragment, sealFragment, SHARD_OVERHEAD, SHARD_VERSION, ShardError, shardKey, ShardLayout, } from "./shards.js";
11
16
  export { FileFreakError } from "./transport.js";
12
17
  export { UploadStopped } from "./uploads.js";
@@ -0,0 +1,72 @@
1
+ import { type Sink } from "./ranges.js";
2
+ /** A file a link holds, as get() hands it to the function that makes its sink. */
3
+ export interface LinkedFile {
4
+ id: string;
5
+ /**
6
+ * Its name, as whoever shared it wrote it. A link can name any site, and
7
+ * that site can say anything, so never use it as a path unchecked:
8
+ * safeName and toFolder, in "@filefreak.io/sdk/node", check it.
9
+ */
10
+ name: string;
11
+ /** The folders it is in, from the shared folder, by its name, down. Empty for a link to one file. As unchecked as `name`. */
12
+ folders: string[];
13
+ sizeBytes: number;
14
+ contentType: string | null;
15
+ /** Under Shard Protection: put back together from its map, each part checked as it arrives. */
16
+ protected: boolean;
17
+ }
18
+ /** Where get() puts what a link holds: one Sink, for a link to one file, or a function that makes one for each file. */
19
+ export type Into = Sink | ((file: LinkedFile) => Sink | Promise<Sink>);
20
+ export interface GetOptions {
21
+ /** The link's password, for a link that has one. */
22
+ password?: string;
23
+ /** Stops the saving, and aborts the sink of the file being saved. */
24
+ signal?: AbortSignal;
25
+ /** Called as bytes are written, with how many have been and how many there are in all. */
26
+ onProgress?: (writtenBytes: number, totalBytes: number) => void;
27
+ /** Another fetch, for a runtime without one or for tests. */
28
+ fetch?: typeof fetch;
29
+ }
30
+ export interface Got {
31
+ /** What the link is to. */
32
+ kind: "file" | "folder";
33
+ /** Its name, as its sharer wrote it. */
34
+ name: string;
35
+ /** Every file saved, in the order they were saved. */
36
+ files: LinkedFile[];
37
+ /** Their bytes, together. */
38
+ sizeBytes: number;
39
+ }
40
+ /** What a link is to, and the site to ask about it. */
41
+ export type Link = {
42
+ origin: string;
43
+ } & ({
44
+ kind: "share";
45
+ token: string;
46
+ } | {
47
+ kind: "public";
48
+ id: string;
49
+ username: string | null;
50
+ });
51
+ export declare const NOT_A_LINK = "That is not a FileFreak share link or public file address.";
52
+ /**
53
+ * The link a web address is: `/share/<token>`, `/public/<username>/<id>`,
54
+ * or a public file's direct address, `/api/public/files/<id>/raw`. On https,
55
+ * or on http for the local stack only.
56
+ */
57
+ export declare function linkOf(url: string | URL): Link;
58
+ /**
59
+ * Save what a share link or a public page holds, with no key: one file into
60
+ * `into`, a Sink, or a folder and everything in it, each file into the Sink
61
+ * that `into`, a function, makes for it. Resolves once every file has been
62
+ * saved and its sink closed.
63
+ *
64
+ * Takes `https://<host>/share/<token>`, `https://<host>/public/<username>/<id>`
65
+ * and a public file's direct address. A protected file is put back together
66
+ * from its map and checked as it arrives. A failure is a FileFreakError, with
67
+ * the site's code and sentence or one of get's own: not_a_link, not_a_file
68
+ * (a folder's link given one Sink), password_required, not_enough_downloads,
69
+ * too_deep, fragment_damaged or fragment_unavailable. The file being saved
70
+ * when it stops has its sink aborted; files saved before it stay saved.
71
+ */
72
+ export declare function get(url: string | URL, into: Into, options?: GetOptions): Promise<Got>;