@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
@@ -4,16 +4,19 @@ exports.FilesTable = void 0;
4
4
  exports.Files = Files;
5
5
  const user_context_singleton_1 = require("../../context/user-context-singleton");
6
6
  const rpc_types_1 = require("../../rpc-types");
7
- const utils_1 = require("../../utils");
8
7
  const orm_1 = require("../orm");
9
8
  const table_definitions_system_1 = require("../orm/table-definitions.system");
9
+ const chunk_store_1 = require("./chunk-store");
10
10
  const file_types_1 = require("./file.types");
11
11
  const file_read_stream_1 = require("./file-read-stream");
12
+ const file_rows_1 = require("./file-rows");
12
13
  const file_write_stream_1 = require("./file-write-stream");
13
14
  /**
14
15
  * The chunked, content-hashed file store of one data context. Rows describe files; the bytes
15
- * live in chunks on disk, shared by hash, and a device fetches a missing chunk from a connected
16
- * device when it reads. Write through {@link FilesTable.saveFile}, never the table methods.
16
+ * live in chunks in the account's chunk store, shared by hash, and a device fetches a missing
17
+ * chunk from a connected device when it reads. A file's id is derived from its content, and its row never changes.
18
+ * Write through {@link FilesTable.saveFile}, never the table methods. The rules are checked here
19
+ * in every context, and by every device on the rows it receives in a group (`file-rows.ts`).
17
20
  */
18
21
  class FilesTable extends orm_1.Table {
19
22
  /** @deprecated Direct inserts forbidden; use saveFile() or saveFileRecord() */
@@ -35,19 +38,31 @@ class FilesTable extends orm_1.Table {
35
38
  /**
36
39
  * Start writing a file in chunks; `finalize()` saves its row.
37
40
  *
38
- * @param metadata - The new file's id, name, size and MIME type.
41
+ * @param metadata - The new file's name, MIME type and `keep`.
39
42
  */
40
43
  async createWriteStream(metadata) {
41
44
  return new file_write_stream_1.FileWriteStream(metadata, this);
42
45
  }
43
46
  /**
44
- * Note: Use `saveFile` to write a file to disk. This method is for managing
45
- * the file metadata in the database which is done automatically by `saveFile`.
46
- * @param fileRecord The file record to save to the database
47
- * @returns The inserted file record
47
+ * Store a file's row as it is, after the checks every device makes: a new row must have the
48
+ * id derived from its content (or be a legacy row), and a stored row never changes. Storing
49
+ * the same file again writes nothing. Writers use {@link saveFile} or a write stream; this is
50
+ * for copying a row from another context with its id.
51
+ *
52
+ * @param fileRecord - The row.
53
+ * @returns The row as stored.
54
+ * @throws When the row is refused, or a different row has its id.
48
55
  */
49
56
  async saveFileRecord(fileRecord) {
50
- return await super.save(fileRecord);
57
+ const existing = await super.get(fileRecord.fileId);
58
+ (0, file_rows_1.checkFileWrite)(existing, fileRecord, fileRecord.fileId);
59
+ if (existing)
60
+ return existing;
61
+ // Another save or sync apply may have inserted the row since the read. In particular, the
62
+ // personal context has no data-source write check, so never let this become an update.
63
+ const saved = await super.save(fileRecord, { insertOnly: true });
64
+ (0, file_rows_1.checkFileWrite)(saved, fileRecord, fileRecord.fileId);
65
+ return saved;
51
66
  }
52
67
  /**
53
68
  * Open a file for chunked reads.
@@ -62,27 +77,19 @@ class FilesTable extends orm_1.Table {
62
77
  if (!fileRecord) {
63
78
  return null;
64
79
  }
65
- return new file_read_stream_1.FileReadStream(fileRecord, this, preloadChunksCount, skipVerification);
80
+ return new file_read_stream_1.FileReadStream(fileRecord, preloadChunksCount, skipVerification);
66
81
  }
67
82
  /**
68
- * Save a whole file: chunk it, store the chunks and write its row.
83
+ * Save a whole file: chunk it, store the chunks and write its row. The same bytes, name, MIME
84
+ * type and `keep` give the same file, whose chunks are stored again.
69
85
  *
70
- * @param metaData - The file's id, name and MIME type. `fileSize` is taken from `data`.
86
+ * @param metaData - The file's name, MIME type and `keep` (true unless given).
71
87
  * @param data - The bytes, or a string saved as UTF-8.
72
- * @returns The saved row.
88
+ * @returns The file's row.
73
89
  */
74
90
  async saveFile(metaData, data) {
75
- // Use FileWriteStream internally to ensure consistent chunking logic
76
- const writeStream = await this.createWriteStream({
77
- ...metaData,
78
- fileSize: data.length, // Set the actual file size
79
- });
80
- // Write the entire data to the stream
81
- if (typeof data === "string") {
82
- data = new Uint8Array(Buffer.from(data, "utf8"));
83
- }
84
- await writeStream.write(data);
85
- // Finalize and return the result
91
+ const writeStream = await this.createWriteStream(metaData);
92
+ await writeStream.write(typeof data === "string" ? new Uint8Array(Buffer.from(data, "utf8")) : data);
86
93
  return await writeStream.finalize();
87
94
  }
88
95
  /**
@@ -93,8 +100,7 @@ class FilesTable extends orm_1.Table {
93
100
  * @returns The bytes, or null when no such file row exists.
94
101
  */
95
102
  async getFileContents(fileId, opts) {
96
- // Use FileReadStream internally to ensure consistent chunk reading logic
97
- const readStream = await this.openReadStream(fileId, undefined, opts?.skipVerification);
103
+ const readStream = await this.openReadStream(fileId, file_types_1.FILE_FETCH_WINDOW, opts?.skipVerification);
98
104
  if (!readStream) {
99
105
  return null;
100
106
  }
@@ -102,65 +108,43 @@ class FilesTable extends orm_1.Table {
102
108
  return await readStream.readAll();
103
109
  }
104
110
  /**
105
- * Delete a file's row, and its index file's. Chunks stay on disk: other files may share them.
111
+ * Make sure this device has every chunk of a file, fetching the missing ones from connected
112
+ * devices {@link FILE_FETCH_WINDOW} at a time, without reading the file into memory.
106
113
  *
107
- * @param fileId - File to delete. A missing file is not an error.
114
+ * @param fileId - File to fetch.
115
+ * @returns Whether every chunk is now on this device; false for a file with no row.
108
116
  */
109
- async deleteFile(fileId) {
110
- // Get file metadata from database
111
- const fileRecord = await super.get(fileId);
112
- if (!fileRecord) {
113
- return; // File doesn't exist
114
- }
115
- // If this file has an index file, recursively delete it first
116
- if (fileRecord.indexFileId) {
117
- await this.deleteFile(fileRecord.indexFileId);
117
+ async fetchFile(fileId) {
118
+ const file = await this.get(fileId);
119
+ if (!file)
120
+ return false;
121
+ const store = await (0, chunk_store_1.getChunkStore)();
122
+ const chunkHashes = file.chunkHashes ?? [];
123
+ let complete = chunkHashes.length > 0 || file.fileSize === 0;
124
+ for (let start = 0; start < chunkHashes.length; start += file_types_1.FILE_FETCH_WINDOW) {
125
+ const window = chunkHashes.slice(start, start + file_types_1.FILE_FETCH_WINDOW);
126
+ const fetched = await Promise.all(window.map(async (hash) => (await store.has(hash)) ||
127
+ // The downloader checks a downloaded chunk's hash and stores it.
128
+ (await (0, chunk_store_1.downloadChunk)(hash).catch(() => null)) !== null));
129
+ if (fetched.includes(false))
130
+ complete = false;
118
131
  }
119
- // Note: We don't delete chunks since they may be shared with other files
120
- // TODO Chunk cleanup needs to be implemented as a separate garbage collection process
121
- // that removes chunks not referenced by any files
122
- // Delete file record from database
123
- await super.delete(fileId);
132
+ return complete;
124
133
  }
125
134
  /**
126
- * Save a large file's chunk hashes as an index file, itself chunked when large.
135
+ * Delete a legacy bundle row, which the runtime owns. Every other file is permanent: it's
136
+ * collected once nothing references it. Chunks stay stored, since other files may share them.
127
137
  *
128
- * @param chunkHashes - The large file's chunk hashes, in order.
129
- * @returns The index file's id.
130
- */
131
- async createIndexFileRecursively(chunkHashes) {
132
- // Create JSON content with chunk hashes
133
- const indexContent = JSON.stringify(chunkHashes);
134
- const indexBuffer = Buffer.from(indexContent, "utf8");
135
- // Generate a new file ID for the index file
136
- const indexFileId = (0, utils_1.newid)();
137
- // Create metadata for the index file
138
- const indexMetadata = {
139
- fileId: indexFileId,
140
- name: `index-${indexFileId}.json`,
141
- fileSize: indexBuffer.length,
142
- mimeType: "application/json",
143
- isIndexFile: true,
144
- };
145
- // Recursively save the index file (this will chunk it if it's too large)
146
- await this.saveFile(indexMetadata, indexBuffer);
147
- return indexFileId;
148
- }
149
- /**
150
- * Read a large file's chunk hashes back from its index file.
151
- *
152
- * @param indexFileId - The index file's id.
153
- * @returns The chunk hashes, in order.
138
+ * @param fileId - File to delete. A missing file is not an error.
139
+ * @throws When the file isn't a legacy bundle row.
154
140
  */
155
- async loadChunkHashesRecursively(indexFileId) {
156
- // Recursively load the index file
157
- const indexBuffer = await this.getFileContents(indexFileId);
158
- if (!indexBuffer) {
159
- throw new Error(`Index file not found: ${indexFileId}`);
141
+ async deleteFile(fileId) {
142
+ const fileRecord = await super.get(fileId);
143
+ if (!fileRecord) {
144
+ return;
160
145
  }
161
- // Parse chunk hashes from JSON
162
- const indexContent = Buffer.from(indexBuffer).toString("utf8");
163
- return JSON.parse(indexContent);
146
+ (0, file_rows_1.checkFileWrite)(fileRecord, undefined, fileId);
147
+ await super.delete(fileId);
164
148
  }
165
149
  }
166
150
  exports.FilesTable = FilesTable;
@@ -184,14 +168,8 @@ rpc_types_1.rpcServerCalls.getFileContentsBase64 = async (fileId) => {
184
168
  return Buffer.from(data).toString("base64");
185
169
  };
186
170
  rpc_types_1.rpcServerCalls.saveFile = async (input) => {
187
- const fileId = input.fileId || (0, utils_1.newid)();
188
171
  const encoding = input.encoding || "utf8";
189
172
  const dataBuffer = encoding === "base64" ? Buffer.from(input.data, "base64") : Buffer.from(input.data, "utf8");
190
- const file = await Files().saveFile({
191
- fileId,
192
- name: input.name,
193
- fileSize: dataBuffer.length,
194
- mimeType: input.mimeType,
195
- }, new Uint8Array(dataBuffer));
196
- return file;
173
+ // Kept: Family Hub 0.3.x doesn't declare its file fields, so nothing would reference these.
174
+ return await Files().saveFile({ name: input.name, mimeType: input.mimeType, keep: true }, new Uint8Array(dataBuffer));
197
175
  };
@@ -1,4 +1,6 @@
1
+ export * from "./chunk-store";
1
2
  export * from "./file.types";
2
3
  export { FileReadStream } from "./file-read-stream";
4
+ export * from "./file-rows";
3
5
  export { FileWriteStream } from "./file-write-stream";
4
6
  export * from "./files";
@@ -15,9 +15,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.FileWriteStream = exports.FileReadStream = void 0;
18
+ __exportStar(require("./chunk-store"), exports);
18
19
  __exportStar(require("./file.types"), exports);
19
20
  var file_read_stream_1 = require("./file-read-stream");
20
21
  Object.defineProperty(exports, "FileReadStream", { enumerable: true, get: function () { return file_read_stream_1.FileReadStream; } });
22
+ __exportStar(require("./file-rows"), exports);
21
23
  var file_write_stream_1 = require("./file-write-stream");
22
24
  Object.defineProperty(exports, "FileWriteStream", { enumerable: true, get: function () { return file_write_stream_1.FileWriteStream; } });
23
25
  __exportStar(require("./files"), exports);
@@ -4,6 +4,7 @@ export * from "./authority-write-checks";
4
4
  export * from "./change-tracking";
5
5
  export * from "./channels";
6
6
  export * from "./create-group";
7
+ export * from "./derived-row-id";
7
8
  export * from "./device-sync-info";
8
9
  export * from "./devices";
9
10
  export * from "./embeddings";
@@ -20,6 +20,7 @@ __exportStar(require("./authority-write-checks"), exports);
20
20
  __exportStar(require("./change-tracking"), exports);
21
21
  __exportStar(require("./channels"), exports);
22
22
  __exportStar(require("./create-group"), exports);
23
+ __exportStar(require("./derived-row-id"), exports);
23
24
  __exportStar(require("./device-sync-info"), exports);
24
25
  __exportStar(require("./devices"), exports);
25
26
  __exportStar(require("./embeddings"), exports);
@@ -140,10 +140,17 @@ export declare const SystemContractCatalog: import("./contract-ref").IContractRe
140
140
  }>;
141
141
  /**
142
142
  * System Files v1: save a file and read it back, in the package's own data context. Files sync
143
- * to the context's other devices. Declare it with `pkg.consumes(SystemFiles)`.
143
+ * to the context's other devices. Declare it with `pkg.consumes(SystemFiles)`; save a file over
144
+ * 8 MiB with {@link saveWholeFile}.
144
145
  */
145
146
  export declare const SystemFiles: import("./contract-ref").IContractRef<{
146
147
  save(input: import("../contracts/system/files-shared").IFilesSaveInput): Promise<import("../contracts/system/files-shared").IFilesSaveResult>;
147
148
  read(input: import("../contracts/system/files-shared").IFilesReadInput): Promise<import("../contracts/system/files-shared").IFilesReadResult>;
149
+ startSave(input: import("../contracts/system/files-shared").IFilesStartSaveInput): Promise<import("../contracts/system/files-shared").IFilesSaveSession>;
150
+ appendSave(input: import("../contracts/system/files-shared").IFilesAppendSaveInput): Promise<import("../contracts/system/files-shared").IFilesAppendSaveResult>;
151
+ finishSave(input: import("../contracts/system/files-shared").IFilesSaveSession): Promise<import("../contracts/system/files-shared").IFilesSaveResult>;
152
+ cancelSave(input: import("../contracts/system/files-shared").IFilesSaveSession): Promise<{
153
+ cancelled: boolean;
154
+ }>;
148
155
  }>;
149
- export { FILES_MAX_CALL_BYTES, type IFilesReadInput, type IFilesReadResult, type IFilesSaveInput, type IFilesSaveResult, readWholeFile, } from "../contracts/system/files-shared";
156
+ export { FILES_MAX_CALL_BYTES, FILES_MAX_FILE_BYTES, FILES_WHOLE_READ_MAX_BYTES, type FileBytesSource, type IFilesAppendSaveInput, type IFilesAppendSaveResult, type IFilesReadInput, type IFilesReadResult, type IFilesSaveInput, type IFilesSaveResult, type IFilesSaveSession, type IFilesStartSaveInput, readWholeFile, saveWholeFile, } from "../contracts/system/files-shared";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readWholeFile = exports.FILES_MAX_CALL_BYTES = exports.SystemFiles = exports.SystemContractCatalog = exports.SystemScheduler = exports.SystemGroupMembers = exports.SystemIdentity = exports.SystemHttp = void 0;
3
+ exports.saveWholeFile = exports.readWholeFile = exports.FILES_WHOLE_READ_MAX_BYTES = exports.FILES_MAX_FILE_BYTES = exports.FILES_MAX_CALL_BYTES = exports.SystemFiles = exports.SystemContractCatalog = exports.SystemScheduler = exports.SystemGroupMembers = exports.SystemIdentity = exports.SystemHttp = void 0;
4
4
  const system_ids_1 = require("../system-ids");
5
5
  const contract_ref_1 = require("./contract-ref");
6
6
  /**
@@ -25,9 +25,13 @@ exports.SystemScheduler = (0, contract_ref_1.contractRef)(system_ids_1.scheduler
25
25
  exports.SystemContractCatalog = (0, contract_ref_1.contractRef)(system_ids_1.contractCatalogContractId, 1);
26
26
  /**
27
27
  * System Files v1: save a file and read it back, in the package's own data context. Files sync
28
- * to the context's other devices. Declare it with `pkg.consumes(SystemFiles)`.
28
+ * to the context's other devices. Declare it with `pkg.consumes(SystemFiles)`; save a file over
29
+ * 8 MiB with {@link saveWholeFile}.
29
30
  */
30
31
  exports.SystemFiles = (0, contract_ref_1.contractRef)(system_ids_1.filesContractId, 1);
31
32
  var files_shared_1 = require("../contracts/system/files-shared");
32
33
  Object.defineProperty(exports, "FILES_MAX_CALL_BYTES", { enumerable: true, get: function () { return files_shared_1.FILES_MAX_CALL_BYTES; } });
34
+ Object.defineProperty(exports, "FILES_MAX_FILE_BYTES", { enumerable: true, get: function () { return files_shared_1.FILES_MAX_FILE_BYTES; } });
35
+ Object.defineProperty(exports, "FILES_WHOLE_READ_MAX_BYTES", { enumerable: true, get: function () { return files_shared_1.FILES_WHOLE_READ_MAX_BYTES; } });
33
36
  Object.defineProperty(exports, "readWholeFile", { enumerable: true, get: function () { return files_shared_1.readWholeFile; } });
37
+ Object.defineProperty(exports, "saveWholeFile", { enumerable: true, get: function () { return files_shared_1.saveWholeFile; } });
@@ -1630,8 +1630,8 @@ export declare const devicePairingStateSchema: z.ZodObject<{
1630
1630
  code?: string | undefined;
1631
1631
  error?: string | undefined;
1632
1632
  role?: "source" | "destination" | undefined;
1633
- expiresAt?: number | undefined;
1634
1633
  sessionId?: string | undefined;
1634
+ expiresAt?: number | undefined;
1635
1635
  remoteDevice?: {
1636
1636
  userName?: string | undefined;
1637
1637
  deviceName?: string | undefined;
@@ -1643,8 +1643,8 @@ export declare const devicePairingStateSchema: z.ZodObject<{
1643
1643
  code?: string | undefined;
1644
1644
  error?: string | undefined;
1645
1645
  role?: "source" | "destination" | undefined;
1646
- expiresAt?: number | undefined;
1647
1646
  sessionId?: string | undefined;
1647
+ expiresAt?: number | undefined;
1648
1648
  remoteDevice?: {
1649
1649
  userName?: string | undefined;
1650
1650
  deviceName?: string | undefined;
@@ -37,11 +37,9 @@ export declare function ensurePackageVersionAppNavs(dataContext: DataContext, de
37
37
  */
38
38
  export declare function installAllPackageBundles(dataContext: DataContext, deps: IPackageInstallerDeps): Promise<void>;
39
39
  /**
40
- * Save a bundle's content as a File record, deduplicating by hash.
41
- *
42
- * If a File with the same hash already exists for this bundle name,
43
- * the existing record is reused — no new File is created. This eliminates
44
- * the "new File record on every dev reload" bug.
40
+ * Save a bundle's content as a file the runtime owns: collected once no package version
41
+ * references it. The same content and name give the same file, so a rebuild with no change
42
+ * adds no row, and every save stores the chunks again (restoring any a device has freed).
45
43
  */
46
44
  export declare function saveBundleFile(dataContext: DataContext, content: string, name: string, _packageId: string): Promise<{
47
45
  fileId: string;
@@ -98,7 +98,7 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
98
98
  });
99
99
  return { packageVersion: existingPv, loaded, unchanged, warning };
100
100
  }
101
- // 7. Save bundle files (deduped by hash)
101
+ // 7. Save bundle files (the same content and name is the same file)
102
102
  const packageFile = await saveBundleFile(dataContext, packageBundle, `${packageId}-package.bundle.js`, packageId);
103
103
  let routesFile;
104
104
  if (routesBundle) {
@@ -278,28 +278,12 @@ async function installAllPackageBundles(dataContext, deps) {
278
278
  }
279
279
  }
280
280
  /**
281
- * Save a bundle's content as a File record, deduplicating by hash.
282
- *
283
- * If a File with the same hash already exists for this bundle name,
284
- * the existing record is reused — no new File is created. This eliminates
285
- * the "new File record on every dev reload" bug.
281
+ * Save a bundle's content as a file the runtime owns: collected once no package version
282
+ * references it. The same content and name give the same file, so a rebuild with no change
283
+ * adds no row, and every save stores the chunks again (restoring any a device has freed).
286
284
  */
287
285
  async function saveBundleFile(dataContext, content, name, _packageId) {
288
- const fileHash = (0, file_types_1.computeFileHash)(content);
289
- const filesTable = (0, files_1.Files)(dataContext);
290
- // Check if a file with this hash already exists for this bundle name
291
- const existing = await filesTable.findOne({ name, fileHash });
292
- if (existing) {
293
- return { fileId: existing.fileId, fileHash: existing.fileHash };
294
- }
295
- const fileId = (0, utils_1.newid)();
296
- const metadata = {
297
- fileId,
298
- name,
299
- fileSize: content.length,
300
- mimeType: "application/javascript",
301
- };
302
- const saved = await filesTable.saveFile(metadata, content);
286
+ const saved = await (0, files_1.Files)(dataContext).saveFile({ name, mimeType: "application/javascript", keep: false }, content);
303
287
  return { fileId: saved.fileId, fileHash: saved.fileHash };
304
288
  }
305
289
  /**
@@ -125,7 +125,7 @@ async function installPvInGroupNoActivate(source, dest, pv) {
125
125
  if (!files)
126
126
  return false;
127
127
  for (const file of files) {
128
- await copyFileTree(source, dest, file);
128
+ await copyBundleRow(dest, file);
129
129
  }
130
130
  const propagatedPv = buildPropagatedPv(pv, files);
131
131
  await (0, package_versions_1.PackageVersions)(dest).signAndSave(propagatedPv, {
@@ -162,9 +162,9 @@ async function installPvInGroup(source, dest, pv, retryCnt = 0) {
162
162
  if (!files) {
163
163
  return await retryOrFail(source, dest, pv, retryCnt, "missing files");
164
164
  }
165
- // Copy file records (including index files) into destination
165
+ // Copy the bundles' rows into the destination
166
166
  for (const file of files) {
167
- await copyFileTree(source, dest, file);
167
+ await copyBundleRow(dest, file);
168
168
  }
169
169
  // Save sanitized PV
170
170
  const propagatedPv = buildPropagatedPv(pv, files);
@@ -231,20 +231,9 @@ async function copyBundleFiles(source, pv) {
231
231
  }
232
232
  return bundles;
233
233
  }
234
- async function copyFileTree(source, dest, bundle, copiedIds = new Set()) {
235
- await saveFileAndIndexTree((0, files_1.Files)(source), (0, files_1.Files)(dest), bundle.file, copiedIds);
236
- }
237
- async function saveFileAndIndexTree(sourceFiles, destFiles, file, copiedIds) {
238
- if (copiedIds.has(file.fileId))
239
- return;
240
- copiedIds.add(file.fileId);
241
- if (file.indexFileId) {
242
- const indexFile = await sourceFiles.get(file.indexFileId);
243
- if (indexFile) {
244
- await saveFileAndIndexTree(sourceFiles, destFiles, indexFile, copiedIds);
245
- }
246
- }
247
- await destFiles.saveFileRecord(file);
234
+ /** Copy a bundle's row into `dest`. It keeps its id: a file's id doesn't depend on its context. */
235
+ async function copyBundleRow(dest, bundle) {
236
+ await (0, files_1.Files)(dest).saveFileRecord(bundle.file);
248
237
  }
249
238
  /** Select the best publisher-signed release under an Admin-approved package's follow policy. */
250
239
  async function activateBestEligibleVersion(dataContext, pkg) {
@@ -217,3 +217,5 @@ export declare const hostShellContractId = "00mveexhw7ezjfghxomlo011g";
217
217
  export declare const groupMembersContractId = "00mvzfzkuf4djwjrsxq9cnaxt";
218
218
  /** Local-only System Files v1: save and read files in the caller's own context; allocated once with newid(). */
219
219
  export declare const filesContractId = "00mw1pvs1eupopu6sttetmki0";
220
+ /** Local-only System File Storage v1: this device's chunk store and its migration; allocated once with newid(). */
221
+ export declare const fileStorageContractId = "00mw2svtlb4yv8opod7vnofgn";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.filesContractId = exports.groupMembersContractId = exports.hostShellContractId = exports.packagesContractId = exports.desktopToolsContractId = exports.hostToolsContractId = exports.toolCatalogContractId = exports.schedulerToolIds = exports.schedulerContractId = exports.contractCatalogToolIds = exports.contractCatalogContractId = exports.httpToolIds = exports.httpContractId = exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.syncCheckContractId = exports.appUpdatesContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCoreRevokedPublishPublicKeys = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
3
+ exports.fileStorageContractId = exports.filesContractId = exports.groupMembersContractId = exports.hostShellContractId = exports.packagesContractId = exports.desktopToolsContractId = exports.hostToolsContractId = exports.toolCatalogContractId = exports.schedulerToolIds = exports.schedulerContractId = exports.contractCatalogToolIds = exports.contractCatalogContractId = exports.httpToolIds = exports.httpContractId = exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.syncCheckContractId = exports.appUpdatesContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCoreRevokedPublishPublicKeys = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
4
4
  /** Stable user ID that owns built-in Peers system records. */
5
5
  exports.peersRootUserId = "000peers0user000000000001";
6
6
  /** Default assistant used when no user-selected assistant is available. */
@@ -224,3 +224,5 @@ exports.hostShellContractId = "00mveexhw7ezjfghxomlo011g";
224
224
  exports.groupMembersContractId = "00mvzfzkuf4djwjrsxq9cnaxt";
225
225
  /** Local-only System Files v1: save and read files in the caller's own context; allocated once with newid(). */
226
226
  exports.filesContractId = "00mw1pvs1eupopu6sttetmki0";
227
+ /** Local-only System File Storage v1: this device's chunk store and its migration; allocated once with newid(). */
228
+ exports.fileStorageContractId = "00mw2svtlb4yv8opod7vnofgn";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"