@peers-app/peers-sdk 0.30.0 → 0.30.1

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.
Files changed (36) hide show
  1. package/dist/contracts/contract-resolvers.js +1 -0
  2. package/dist/contracts/index.d.ts +1 -0
  3. package/dist/contracts/index.js +1 -0
  4. package/dist/contracts/system/file-storage-contract.d.ts +76 -0
  5. package/dist/contracts/system/file-storage-contract.js +75 -0
  6. package/dist/contracts/system/files-contract.d.ts +5 -1
  7. package/dist/contracts/system/files-contract.js +50 -9
  8. package/dist/contracts/system/files-shared.d.ts +68 -3
  9. package/dist/contracts/system/files-shared.js +65 -5
  10. package/dist/data/derived-row-id.d.ts +26 -0
  11. package/dist/data/derived-row-id.js +47 -0
  12. package/dist/data/files/chunk-store.d.ts +112 -0
  13. package/dist/data/files/chunk-store.js +107 -0
  14. package/dist/data/files/file-read-stream.d.ts +32 -3
  15. package/dist/data/files/file-read-stream.js +44 -29
  16. package/dist/data/files/file-rows.d.ts +68 -0
  17. package/dist/data/files/file-rows.js +158 -0
  18. package/dist/data/files/file-write-stream.d.ts +26 -0
  19. package/dist/data/files/file-write-stream.js +66 -42
  20. package/dist/data/files/file.types.d.ts +44 -23
  21. package/dist/data/files/file.types.js +33 -51
  22. package/dist/data/files/files.d.ts +27 -24
  23. package/dist/data/files/files.js +63 -85
  24. package/dist/data/files/index.d.ts +2 -0
  25. package/dist/data/files/index.js +2 -0
  26. package/dist/data/index.d.ts +1 -0
  27. package/dist/data/index.js +1 -0
  28. package/dist/define/system-contracts.d.ts +9 -2
  29. package/dist/define/system-contracts.js +6 -2
  30. package/dist/device-pairing/types.d.ts +2 -2
  31. package/dist/package-installer/package-installer.d.ts +3 -5
  32. package/dist/package-installer/package-installer.js +5 -21
  33. package/dist/package-installer/package-propagation.js +6 -17
  34. package/dist/system-ids.d.ts +2 -0
  35. package/dist/system-ids.js +3 -1
  36. package/package.json +1 -1
@@ -2,7 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FileWriteStream = void 0;
4
4
  const keys_1 = require("../../keys");
5
+ const chunk_store_1 = require("./chunk-store");
5
6
  const file_types_1 = require("./file.types");
7
+ const file_rows_1 = require("./file-rows");
8
+ /**
9
+ * `text` as every host's storage keeps it. The id must be derived from the text as stored, or no
10
+ * other device could rederive it. A lone surrogate (possible in a Windows file name) becomes
11
+ * U+FFFD. The PWA's sql.js also cuts text at a NUL and drops a leading U+FEFF when reading, so
12
+ * neither is kept anywhere.
13
+ */
14
+ function asStored(text) {
15
+ if (typeof text !== "string")
16
+ return text;
17
+ // ignoreBOM leaves U+FEFF for the replace, which drops every leading one (sql.js drops one).
18
+ const wellFormed = new TextDecoder("utf-8", { ignoreBOM: true }).decode(new TextEncoder().encode(text));
19
+ return wellFormed.split("\0", 1)[0].replace(/^\uFEFF+/, "");
20
+ }
21
+ /**
22
+ * Writes one file in pieces: each full chunk is stored as it arrives, and `finalize` saves the
23
+ * row under the id derived from the content. Open one with {@link FilesTable.createWriteStream}.
24
+ */
6
25
  class FileWriteStream {
7
26
  metadata;
8
27
  fileTable;
@@ -11,10 +30,20 @@ class FileWriteStream {
11
30
  bytesWritten = 0;
12
31
  finalized = false;
13
32
  aborted = false;
33
+ /**
34
+ * @param metadata - The file's name, MIME type and `keep`.
35
+ * @param fileTable - The Files table the row is saved to.
36
+ */
14
37
  constructor(metadata, fileTable) {
15
38
  this.metadata = metadata;
16
39
  this.fileTable = fileTable;
17
40
  }
41
+ /**
42
+ * Append bytes. Full chunks are stored as they complete.
43
+ *
44
+ * @param chunk - The next bytes, of any length.
45
+ * @throws When the file would exceed {@link FILE_MAX_SIZE}, or the stream has ended.
46
+ */
18
47
  async write(chunk) {
19
48
  if (this.finalized) {
20
49
  throw new Error("Cannot write to finalized stream");
@@ -22,6 +51,9 @@ class FileWriteStream {
22
51
  if (this.aborted) {
23
52
  throw new Error("Cannot write to aborted stream");
24
53
  }
54
+ if (this.bytesWritten + chunk.length > file_types_1.FILE_MAX_SIZE) {
55
+ throw new Error(`A file can be at most ${file_types_1.FILE_MAX_SIZE / (1024 * 1024)} MiB`);
56
+ }
25
57
  // Add chunk to buffer
26
58
  this.buffer = new Uint8Array(Buffer.concat([this.buffer, chunk]));
27
59
  this.bytesWritten += chunk.length;
@@ -29,18 +61,25 @@ class FileWriteStream {
29
61
  await this.processCompleteChunks();
30
62
  }
31
63
  async processCompleteChunks() {
32
- const ops = await (0, file_types_1.getFileOps)();
33
64
  while (this.buffer.length >= file_types_1.FILE_CHUNK_SIZE) {
34
- // Extract one chunk
35
65
  const chunkData = this.buffer.subarray(0, file_types_1.FILE_CHUNK_SIZE);
36
66
  this.buffer = this.buffer.subarray(file_types_1.FILE_CHUNK_SIZE);
37
- // Hash and store the chunk
38
- const chunkHash = (0, keys_1.hashBytes)(chunkData);
39
- this.chunkHashes.push(chunkHash);
40
- const chunkPath = `${file_types_1.CHUNKS_DIR}/${chunkHash}`;
41
- await ops.writeFile(chunkPath, chunkData);
67
+ await this.storeChunk(chunkData);
42
68
  }
43
69
  }
70
+ async storeChunk(chunkData) {
71
+ const chunkHash = (0, keys_1.hashBytes)(chunkData);
72
+ this.chunkHashes.push(chunkHash);
73
+ // Stored even when present: a save restores bytes a device has freed, and refreshes the
74
+ // chunk's age so a sweep can't free it under this save.
75
+ await (await (0, chunk_store_1.getChunkStore)()).put(chunkHash, chunkData);
76
+ }
77
+ /**
78
+ * Store the last chunk and save the file's row. Saving content that already has a row (the
79
+ * same bytes, name, MIME type and `keep`) writes no row and returns the stored one.
80
+ *
81
+ * @returns The file's row.
82
+ */
44
83
  async finalize() {
45
84
  if (this.finalized) {
46
85
  throw new Error("Stream already finalized");
@@ -48,45 +87,28 @@ class FileWriteStream {
48
87
  if (this.aborted) {
49
88
  throw new Error("Cannot finalize aborted stream");
50
89
  }
51
- // First, process any complete chunks in the buffer
52
90
  await this.processCompleteChunks();
53
- // Then process any remaining partial data as final chunk
54
91
  if (this.buffer.length > 0) {
55
- const ops = await (0, file_types_1.getFileOps)();
56
- const finalChunkHash = (0, keys_1.hashBytes)(this.buffer);
57
- this.chunkHashes.push(finalChunkHash);
58
- const chunkPath = `${file_types_1.CHUNKS_DIR}/${finalChunkHash}`;
59
- await ops.writeFile(chunkPath, this.buffer);
92
+ await this.storeChunk(this.buffer);
60
93
  this.buffer = new Uint8Array(0);
61
94
  }
62
- // Create the file record using the same logic as FileTable.saveFile
63
- const chunkHashesString = JSON.stringify(this.chunkHashes);
64
- const fileHash = (0, keys_1.hashBytes)(Buffer.from(chunkHashesString, "utf8"));
65
- let fileRecord;
66
- if (this.chunkHashes.length > file_types_1.CHUNK_INDEX_THRESHOLD) {
67
- // Large file: use recursive index file
68
- const indexFileId = await this.fileTable.createIndexFileRecursively(this.chunkHashes);
69
- fileRecord = {
70
- ...this.metadata,
71
- fileSize: this.bytesWritten,
72
- fileHash,
73
- indexFileId,
74
- };
75
- }
76
- else {
77
- // Small file: store chunk hashes directly
78
- fileRecord = {
79
- ...this.metadata,
80
- fileSize: this.bytesWritten,
81
- fileHash,
82
- chunkHashes: this.chunkHashes,
83
- };
84
- }
85
- // Save to database
86
- await this.fileTable.saveFileRecord(fileRecord);
95
+ const description = {
96
+ name: asStored(this.metadata.name),
97
+ fileSize: this.bytesWritten,
98
+ fileHash: (0, file_types_1.fileHashOf)(this.chunkHashes),
99
+ mimeType: asStored(this.metadata.mimeType ?? ""),
100
+ keep: this.metadata.keep ?? true,
101
+ };
102
+ const fileRecord = {
103
+ fileId: (0, file_rows_1.fileIdFor)(description),
104
+ ...description,
105
+ chunkHashes: this.chunkHashes,
106
+ };
107
+ const saved = await this.fileTable.saveFileRecord(fileRecord);
87
108
  this.finalized = true;
88
- return fileRecord;
109
+ return saved;
89
110
  }
111
+ /** Stop without saving a row. Stored chunks stay until collection frees them. */
90
112
  async abort() {
91
113
  if (this.finalized) {
92
114
  throw new Error("Cannot abort finalized stream");
@@ -94,18 +116,20 @@ class FileWriteStream {
94
116
  this.aborted = true;
95
117
  this.buffer = Buffer.alloc(0);
96
118
  this.chunkHashes = [];
97
- // Note: We don't delete chunks since they may be shared with other files
98
- // Chunk cleanup should be handled by a separate garbage collection process
99
119
  }
120
+ /** Bytes written so far. */
100
121
  getBytesWritten() {
101
122
  return this.bytesWritten;
102
123
  }
124
+ /** Chunks stored so far. */
103
125
  getChunkCount() {
104
126
  return this.chunkHashes.length;
105
127
  }
128
+ /** Whether `finalize` has saved the row. */
106
129
  isFinalized() {
107
130
  return this.finalized;
108
131
  }
132
+ /** Whether `abort` was called. */
109
133
  isAborted() {
110
134
  return this.aborted;
111
135
  }
@@ -1,5 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { type ITableMetaData } from "../orm/types";
3
+ /**
4
+ * A `Files` row: one file's description and its chunk list. Rows saved since ids became derived
5
+ * from content have a `000r` id and never change; older rows ("legacy") have random ids.
6
+ */
3
7
  export declare const fileSchema: z.ZodObject<{
4
8
  fileId: z.ZodEffects<z.ZodString, string, string>;
5
9
  name: z.ZodString;
@@ -7,47 +11,64 @@ export declare const fileSchema: z.ZodObject<{
7
11
  fileHash: z.ZodString;
8
12
  mimeType: z.ZodOptional<z.ZodString>;
9
13
  chunkHashes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
- isIndexFile: z.ZodOptional<z.ZodBoolean>;
11
- indexFileId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
14
+ keep: z.ZodOptional<z.ZodBoolean>;
12
15
  }, "strip", z.ZodTypeAny, {
13
16
  name: string;
14
17
  fileId: string;
15
18
  fileSize: number;
16
19
  fileHash: string;
20
+ keep?: boolean | undefined;
17
21
  mimeType?: string | undefined;
18
22
  chunkHashes?: string[] | undefined;
19
- isIndexFile?: boolean | undefined;
20
- indexFileId?: string | undefined;
21
23
  }, {
22
24
  name: string;
23
25
  fileId: string;
24
26
  fileSize: number;
25
27
  fileHash: string;
28
+ keep?: boolean | undefined;
26
29
  mimeType?: string | undefined;
27
30
  chunkHashes?: string[] | undefined;
28
- isIndexFile?: boolean | undefined;
29
- indexFileId?: string | undefined;
30
31
  }>;
32
+ /** A `Files` row. */
31
33
  export type IFile = z.infer<typeof fileSchema>;
32
- export type IFileInput = Pick<IFile, "fileId" | "name" | "fileSize" | "mimeType" | "isIndexFile">;
34
+ /**
35
+ * What a writer chooses about a new file. The id is derived from the content and these, so
36
+ * saving the same bytes with the same name, MIME type and `keep` returns the same file.
37
+ */
38
+ export interface IFileInput {
39
+ /**
40
+ * Stored as every host can keep it: a lone surrogate becomes U+FFFD, the text ends at a NUL,
41
+ * and a leading U+FEFF is dropped. The saved row has the name as stored.
42
+ */
43
+ name: string;
44
+ /** Stored like `name`; an omitted MIME type is stored empty. */
45
+ mimeType?: string;
46
+ /**
47
+ * Whether the file is never collected. Defaults to true, so a writer that doesn't declare its
48
+ * references keeps its files. System Files saves and the package installer pass false.
49
+ */
50
+ keep?: boolean;
51
+ }
52
+ /** The `Files` system table: every context has one. */
33
53
  export declare const filesMetaData: ITableMetaData;
54
+ /** Size of each chunk a file is split into; the last chunk may be shorter. */
34
55
  export declare const FILE_CHUNK_SIZE: number;
35
- export declare const CHUNKS_DIR = "file_chunks";
36
- export declare let CHUNK_INDEX_THRESHOLD: number;
37
- export declare function setChunkIndexThreshold(threshold: number): void;
56
+ /** Most chunks one file may have: a row's chunk list holds every chunk of any file. */
57
+ export declare const FILE_MAX_CHUNKS = 1024;
58
+ /** Largest file Peers stores (1 GiB). It can rise in a later release, never fall. */
59
+ export declare const FILE_MAX_SIZE: number;
60
+ /** Most chunks a read fetches from other devices at once, so a large file isn't one stream. */
61
+ export declare const FILE_FETCH_WINDOW = 16;
62
+ /**
63
+ * A file's `fileHash`: the SHA-256 of its chunk hashes as JSON, in order.
64
+ *
65
+ * @param chunkHashes - The file's chunk hashes.
66
+ */
67
+ export declare function fileHashOf(chunkHashes: readonly string[]): string;
38
68
  /**
39
- * Compute the file-system hash for content without saving it.
40
- * Mirrors the chunking + hashing logic of FileWriteStream so the result
41
- * matches `IFile.fileHash` for the same bytes.
69
+ * Compute the file-system hash for content without saving it. Mirrors the chunking of
70
+ * `FileWriteStream`, so the result matches `IFile.fileHash` for the same bytes.
71
+ *
72
+ * @param content - The bytes, or a string hashed as UTF-8.
42
73
  */
43
74
  export declare function computeFileHash(content: string | Uint8Array): string;
44
- export interface FileOps {
45
- downloadFileChunk(chunkHash: string): Promise<Uint8Array | null>;
46
- fileExists(path: string): Promise<boolean>;
47
- readFile(path: string): Promise<Uint8Array>;
48
- writeFile(path: string, data: Uint8Array): Promise<void>;
49
- deletePath(path: string): Promise<void>;
50
- }
51
- export declare function setFileOps(ops: FileOps): void;
52
- export declare function getFileOps(): Promise<FileOps>;
53
- export declare function resetFileOps(): void;
@@ -1,80 +1,62 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CHUNK_INDEX_THRESHOLD = exports.CHUNKS_DIR = exports.FILE_CHUNK_SIZE = exports.filesMetaData = exports.fileSchema = void 0;
4
- exports.setChunkIndexThreshold = setChunkIndexThreshold;
3
+ exports.FILE_FETCH_WINDOW = exports.FILE_MAX_SIZE = exports.FILE_MAX_CHUNKS = exports.FILE_CHUNK_SIZE = exports.filesMetaData = exports.fileSchema = void 0;
4
+ exports.fileHashOf = fileHashOf;
5
5
  exports.computeFileHash = computeFileHash;
6
- exports.setFileOps = setFileOps;
7
- exports.getFileOps = getFileOps;
8
- exports.resetFileOps = resetFileOps;
9
6
  const zod_1 = require("zod");
10
7
  const keys_1 = require("../../keys");
11
8
  const zod_types_1 = require("../../types/zod-types");
12
9
  const types_1 = require("../orm/types");
10
+ /**
11
+ * A `Files` row: one file's description and its chunk list. Rows saved since ids became derived
12
+ * from content have a `000r` id and never change; older rows ("legacy") have random ids.
13
+ */
13
14
  exports.fileSchema = zod_1.z.object({
14
15
  fileId: zod_types_1.zodPeerId,
15
16
  name: zod_1.z.string().describe("The name of the file"),
16
17
  fileSize: zod_1.z.number().describe("The size of the file in bytes"),
17
18
  fileHash: zod_1.z.string().describe("Hash of the chunk hashes array for integrity verification"),
18
- mimeType: zod_1.z.string().optional().describe("The MIME type of the file"),
19
- chunkHashes: zod_1.z
20
- .array(zod_1.z.string())
21
- .optional()
22
- .describe("SHA-256 hashes of each chunk in order (for small files)"),
23
- isIndexFile: zod_1.z
19
+ mimeType: zod_1.z.string().optional().describe("The MIME type of the file; empty when unknown"),
20
+ chunkHashes: zod_1.z.array(zod_1.z.string()).optional().describe("SHA-256 hashes of each chunk in order"),
21
+ keep: zod_1.z
24
22
  .boolean()
25
23
  .optional()
26
- .describe("True if this is an index file for another large file"),
27
- indexFileId: zod_types_1.zodPeerId.optional().describe("FileId of index file (for large files)"),
24
+ .describe("True for a file that is never collected, even with nothing referencing it"),
28
25
  });
26
+ /** The `Files` system table: every context has one. */
29
27
  exports.filesMetaData = {
30
28
  name: "Files",
31
29
  description: "Files stored in the chunked file system for peer sharing",
32
30
  primaryKeyName: "fileId",
33
31
  fields: (0, types_1.schemaToFields)(exports.fileSchema),
34
32
  };
35
- // File storage configuration
36
- exports.FILE_CHUNK_SIZE = 1024 * 1024; // 1MB chunks
37
- exports.CHUNKS_DIR = "file_chunks";
38
- exports.CHUNK_INDEX_THRESHOLD = 1000; // Use chunk index file for files with >1000 chunks (~1GB)
39
- // For testing - allow modifying the threshold
40
- function setChunkIndexThreshold(threshold) {
41
- exports.CHUNK_INDEX_THRESHOLD = threshold;
33
+ /** Size of each chunk a file is split into; the last chunk may be shorter. */
34
+ exports.FILE_CHUNK_SIZE = 1024 * 1024;
35
+ /** Most chunks one file may have: a row's chunk list holds every chunk of any file. */
36
+ exports.FILE_MAX_CHUNKS = 1024;
37
+ /** Largest file Peers stores (1 GiB). It can rise in a later release, never fall. */
38
+ exports.FILE_MAX_SIZE = exports.FILE_MAX_CHUNKS * exports.FILE_CHUNK_SIZE;
39
+ /** Most chunks a read fetches from other devices at once, so a large file isn't one stream. */
40
+ exports.FILE_FETCH_WINDOW = 16;
41
+ /**
42
+ * A file's `fileHash`: the SHA-256 of its chunk hashes as JSON, in order.
43
+ *
44
+ * @param chunkHashes - The file's chunk hashes.
45
+ */
46
+ function fileHashOf(chunkHashes) {
47
+ return (0, keys_1.hashBytes)(new TextEncoder().encode(JSON.stringify(chunkHashes)));
42
48
  }
43
49
  /**
44
- * Compute the file-system hash for content without saving it.
45
- * Mirrors the chunking + hashing logic of FileWriteStream so the result
46
- * matches `IFile.fileHash` for the same bytes.
50
+ * Compute the file-system hash for content without saving it. Mirrors the chunking of
51
+ * `FileWriteStream`, so the result matches `IFile.fileHash` for the same bytes.
52
+ *
53
+ * @param content - The bytes, or a string hashed as UTF-8.
47
54
  */
48
55
  function computeFileHash(content) {
49
56
  const data = typeof content === "string" ? new Uint8Array(Buffer.from(content, "utf8")) : content;
50
57
  const chunkHashes = [];
51
- let offset = 0;
52
- while (offset < data.length) {
53
- const end = Math.min(offset + exports.FILE_CHUNK_SIZE, data.length);
54
- chunkHashes.push((0, keys_1.hashBytes)(data.subarray(offset, end)));
55
- offset = end;
58
+ for (let offset = 0; offset < data.length; offset += exports.FILE_CHUNK_SIZE) {
59
+ chunkHashes.push((0, keys_1.hashBytes)(data.subarray(offset, offset + exports.FILE_CHUNK_SIZE)));
56
60
  }
57
- return (0, keys_1.hashBytes)(new Uint8Array(Buffer.from(JSON.stringify(chunkHashes), "utf8")));
58
- }
59
- let fileOps = null;
60
- let fileOpsReady = null;
61
- function setFileOps(ops) {
62
- fileOps = ops;
63
- if (fileOpsReady) {
64
- fileOpsReady(ops);
65
- fileOpsReady = null; // Reset the promise if it was waiting
66
- }
67
- }
68
- function getFileOps() {
69
- if (fileOps) {
70
- return Promise.resolve(fileOps);
71
- }
72
- return new Promise((resolve) => {
73
- fileOpsReady = resolve;
74
- });
75
- }
76
- // For testing - reset the global state
77
- function resetFileOps() {
78
- fileOps = null;
79
- fileOpsReady = null;
61
+ return fileHashOf(chunkHashes);
80
62
  }
@@ -5,8 +5,10 @@ import { FileReadStream } from "./file-read-stream";
5
5
  import { FileWriteStream } from "./file-write-stream";
6
6
  /**
7
7
  * The chunked, content-hashed file store of one data context. Rows describe files; the bytes
8
- * live in chunks on disk, shared by hash, and a device fetches a missing chunk from a connected
9
- * device when it reads. Write through {@link FilesTable.saveFile}, never the table methods.
8
+ * live in chunks in the account's chunk store, shared by hash, and a device fetches a missing
9
+ * chunk from a connected device when it reads. A file's id is derived from its content, and its row never changes.
10
+ * Write through {@link FilesTable.saveFile}, never the table methods. The rules are checked here
11
+ * in every context, and by every device on the rows it receives in a group (`file-rows.ts`).
10
12
  */
11
13
  export declare class FilesTable extends Table<IFile> {
12
14
  /** @deprecated Direct inserts forbidden; use saveFile() or saveFileRecord() */
@@ -20,14 +22,18 @@ export declare class FilesTable extends Table<IFile> {
20
22
  /**
21
23
  * Start writing a file in chunks; `finalize()` saves its row.
22
24
  *
23
- * @param metadata - The new file's id, name, size and MIME type.
25
+ * @param metadata - The new file's name, MIME type and `keep`.
24
26
  */
25
27
  createWriteStream(metadata: IFileInput): Promise<FileWriteStream>;
26
28
  /**
27
- * Note: Use `saveFile` to write a file to disk. This method is for managing
28
- * the file metadata in the database which is done automatically by `saveFile`.
29
- * @param fileRecord The file record to save to the database
30
- * @returns The inserted file record
29
+ * Store a file's row as it is, after the checks every device makes: a new row must have the
30
+ * id derived from its content (or be a legacy row), and a stored row never changes. Storing
31
+ * the same file again writes nothing. Writers use {@link saveFile} or a write stream; this is
32
+ * for copying a row from another context with its id.
33
+ *
34
+ * @param fileRecord - The row.
35
+ * @returns The row as stored.
36
+ * @throws When the row is refused, or a different row has its id.
31
37
  */
32
38
  saveFileRecord(fileRecord: IFile): Promise<IFile>;
33
39
  /**
@@ -40,11 +46,12 @@ export declare class FilesTable extends Table<IFile> {
40
46
  */
41
47
  openReadStream(fileId: string, preloadChunksCount?: number, skipVerification?: boolean): Promise<FileReadStream | null>;
42
48
  /**
43
- * Save a whole file: chunk it, store the chunks and write its row.
49
+ * Save a whole file: chunk it, store the chunks and write its row. The same bytes, name, MIME
50
+ * type and `keep` give the same file, whose chunks are stored again.
44
51
  *
45
- * @param metaData - The file's id, name and MIME type. `fileSize` is taken from `data`.
52
+ * @param metaData - The file's name, MIME type and `keep` (true unless given).
46
53
  * @param data - The bytes, or a string saved as UTF-8.
47
- * @returns The saved row.
54
+ * @returns The file's row.
48
55
  */
49
56
  saveFile(metaData: IFileInput, data: Uint8Array | string): Promise<IFile>;
50
57
  /**
@@ -58,25 +65,21 @@ export declare class FilesTable extends Table<IFile> {
58
65
  skipVerification?: boolean;
59
66
  }): Promise<Uint8Array | null>;
60
67
  /**
61
- * Delete a file's row, and its index file's. Chunks stay on disk: other files may share them.
62
- *
63
- * @param fileId - File to delete. A missing file is not an error.
64
- */
65
- deleteFile(fileId: string): Promise<void>;
66
- /**
67
- * Save a large file's chunk hashes as an index file, itself chunked when large.
68
+ * Make sure this device has every chunk of a file, fetching the missing ones from connected
69
+ * devices {@link FILE_FETCH_WINDOW} at a time, without reading the file into memory.
68
70
  *
69
- * @param chunkHashes - The large file's chunk hashes, in order.
70
- * @returns The index file's id.
71
+ * @param fileId - File to fetch.
72
+ * @returns Whether every chunk is now on this device; false for a file with no row.
71
73
  */
72
- createIndexFileRecursively(chunkHashes: string[]): Promise<string>;
74
+ fetchFile(fileId: string): Promise<boolean>;
73
75
  /**
74
- * Read a large file's chunk hashes back from its index file.
76
+ * Delete a legacy bundle row, which the runtime owns. Every other file is permanent: it's
77
+ * collected once nothing references it. Chunks stay stored, since other files may share them.
75
78
  *
76
- * @param indexFileId - The index file's id.
77
- * @returns The chunk hashes, in order.
79
+ * @param fileId - File to delete. A missing file is not an error.
80
+ * @throws When the file isn't a legacy bundle row.
78
81
  */
79
- loadChunkHashesRecursively(indexFileId: string): Promise<string[]>;
82
+ deleteFile(fileId: string): Promise<void>;
80
83
  }
81
84
  /**
82
85
  * The Files table of a data context.