@dignetwork/dig-sdk 0.0.1-alpha.70 → 0.0.1-alpha.72

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.
@@ -112,7 +112,7 @@ class DigPeer {
112
112
  await PropagationServer_1.PropagationServer.downloadStore(this.storeId, rootHash, this.IpAddress);
113
113
  }
114
114
  async downloadData(dataPath) {
115
- await this.propagationServer.downloadFile(dataPath);
115
+ await this.propagationServer.downloadFile(dataPath, dataPath);
116
116
  }
117
117
  }
118
118
  exports.DigPeer = DigPeer;
@@ -10,7 +10,6 @@ export declare class PropagationServer {
10
10
  username: string | undefined;
11
11
  password: string | undefined;
12
12
  private static readonly port;
13
- private static multiBar;
14
13
  constructor(ipAddress: string, storeId: string);
15
14
  /**
16
15
  * Initialize the Wallet instance.
@@ -22,9 +21,6 @@ export declare class PropagationServer {
22
21
  createHttpsAgent(): https.Agent;
23
22
  /**
24
23
  * Check if the store and optional root hash exist by making a HEAD request.
25
- *
26
- * @param {string} [rootHash] - Optional root hash to check for existence.
27
- * @returns {Promise<{ storeExists: boolean, rootHashExists: boolean }>} - An object indicating if the store and root hash exist.
28
24
  */
29
25
  checkStoreExists(rootHash?: string): Promise<{
30
26
  storeExists: boolean;
@@ -32,14 +28,10 @@ export declare class PropagationServer {
32
28
  }>;
33
29
  /**
34
30
  * Start an upload session by sending a POST request with the rootHash.dat file.
35
- *
36
- * @param {string} rootHash - The root hash used to name the .dat file.
37
- * @param {string} datFilePath - The full path to the rootHash.dat file.
38
31
  */
39
32
  startUploadSession(rootHash: string): Promise<void>;
40
33
  /**
41
34
  * Request a nonce for a file by sending a HEAD request to the server.
42
- * Checks if the file already exists based on the 'x-file-exists' header.
43
35
  */
44
36
  getFileNonce(filename: string): Promise<{
45
37
  nonce: string;
@@ -47,36 +39,24 @@ export declare class PropagationServer {
47
39
  }>;
48
40
  /**
49
41
  * Upload a file to the server by sending a PUT request.
50
- * Logs progress using cli-progress for each file.
42
+ * Logs progress using a local cli-progress bar.
51
43
  */
52
- uploadFile(label: string, dataPath: string): Promise<any>;
44
+ uploadFile(label: string, dataPath: string): Promise<void>;
53
45
  /**
54
46
  * Commit the upload session by sending a POST request to the server.
55
- * This finalizes the upload and moves files from the temporary session directory to the permanent location.
56
47
  */
57
48
  commitUploadSession(): Promise<any>;
58
49
  /**
59
50
  * Static function to handle the entire upload process for multiple files based on rootHash.
60
- * @param {string} storeId - The ID of the DataStore.
61
- * @param {string} rootHash - The root hash used to derive the file set.
62
- * @param {string} publicKey - The public key of the user.
63
- * @param {string} ipAddress - The IP address of the server.
64
51
  */
65
52
  static uploadStore(storeId: string, rootHash: string, ipAddress: string): Promise<void>;
66
53
  /**
67
54
  * Fetch a file from the server by sending a GET request and return its content in memory.
68
- * Logs progress using cli-progress.
69
- * @param {string} dataPath - The data path of the file to download.
70
- * @returns {Promise<Buffer>} - The file content in memory as a Buffer.
55
+ * Logs progress using a local cli-progress bar.
71
56
  */
72
57
  fetchFile(dataPath: string): Promise<Buffer>;
73
58
  /**
74
59
  * Get details of a file, including whether it exists and its size.
75
- * Makes a HEAD request to the server and checks the response headers.
76
- *
77
- * @param {string} dataPath - The path of the file within the DataStore.
78
- * @param {string} rootHash - The root hash associated with the DataStore.
79
- * @returns {Promise<{ exists: boolean; size: number }>} - An object containing file existence and size information.
80
60
  */
81
61
  getFileDetails(dataPath: string, rootHash: string): Promise<{
82
62
  exists: boolean;
@@ -84,15 +64,11 @@ export declare class PropagationServer {
84
64
  }>;
85
65
  /**
86
66
  * Download a file from the server by sending a GET request.
87
- * Logs progress using cli-progress.
88
- * @param {string} dataPath - The data path of the file to download.
67
+ * Logs progress using a local cli-progress bar.
89
68
  */
90
- downloadFile(dataPath: string): Promise<void>;
69
+ downloadFile(label: string, dataPath: string): Promise<void>;
91
70
  /**
92
71
  * Static function to handle downloading multiple files from a DataStore based on file paths.
93
- * @param {string} storeId - The ID of the DataStore.
94
- * @param {string[]} dataPaths - The list of data paths to download.
95
- * @param {string} ipAddress - The IP address of the server.
96
72
  */
97
73
  static downloadStore(storeId: string, rootHash: string, ipAddress: string): Promise<void>;
98
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PropagationServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/PropagationServer.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAC;AAiB1B,qBAAa,iBAAiB;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;IAIpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAqCrB;gBAEU,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAY9C;;OAEG;IACG,gBAAgB;IAKtB;;OAEG;IACH,gBAAgB;IAQhB;;;;;OAKG;IACG,gBAAgB,CACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IAwC7D;;;;;OAKG;IACG,kBAAkB,CAAC,QAAQ,EAAE,MAAM;IAqDzC;;;OAGG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IAyBlD;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IA+DhD;;;OAGG;IACG,mBAAmB;IAgCzB;;;;;;OAMG;WACU,WAAW,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;IAiDnB;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4ClD;;;;;;;OAOG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IA2B7C;;;;OAIG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM;IAgEnC;;;;;OAKG;WACU,aAAa,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;CA+BpB"}
1
+ {"version":3,"file":"PropagationServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/PropagationServer.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,OAAO,CAAC;AAoB1B,qBAAa,iBAAiB;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;gBAExB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAY9C;;OAEG;IACG,gBAAgB;IAOtB;;OAEG;IACH,gBAAgB;IAQhB;;OAEG;IACG,gBAAgB,CACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IAwC7D;;OAEG;IACG,kBAAkB,CAAC,QAAQ,EAAE,MAAM;IAiDzC;;OAEG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IAyBlD;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAyFhD;;OAEG;IACG,mBAAmB;IAgCzB;;OAEG;WACU,WAAW,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;IA4DnB;;;OAGG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwElD;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IA0B7C;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IA6ElD;;OAEG;WACU,aAAa,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;CAuDpB"}
@@ -1,9 +1,33 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
29
  exports.PropagationServer = void 0;
30
+ // Import necessary modules and dependencies
7
31
  const axios_1 = __importDefault(require("axios"));
8
32
  const fs_1 = __importDefault(require("fs"));
9
33
  const path_1 = __importDefault(require("path"));
@@ -13,24 +37,26 @@ const ssl_1 = require("../utils/ssl");
13
37
  const credentialsUtils_1 = require("../utils/credentialsUtils");
14
38
  const https_1 = __importDefault(require("https"));
15
39
  const cli_progress_1 = __importDefault(require("cli-progress"));
16
- const colorette_1 = require("colorette"); // For colored output
40
+ const colorette_1 = require("colorette");
17
41
  const config_1 = require("../utils/config");
18
42
  const hashUtils_1 = require("../utils/hashUtils");
19
43
  const nanospinner_1 = require("nanospinner");
44
+ const progress_stream_1 = __importDefault(require("progress-stream"));
20
45
  // Helper function to trim long filenames with ellipsis and ensure consistent padding
21
46
  function formatFilename(filename, maxLength = 30) {
47
+ if (!filename) {
48
+ return "Unknown File".padEnd(maxLength, " ");
49
+ }
22
50
  if (filename.length > maxLength) {
23
- // Trim the filename and add ellipsis
24
51
  return `...${filename.slice(-(maxLength - 3))}`.padEnd(maxLength, " ");
25
52
  }
26
- // For shorter filenames, just pad to the maxLength
27
53
  return filename.padEnd(maxLength, " ");
28
54
  }
29
55
  class PropagationServer {
30
56
  constructor(ipAddress, storeId) {
31
57
  this.storeId = storeId;
32
- this.sessionId = ""; // Session ID will be set after starting the upload session
33
- this.publicKey = ""; // Public key will be set after initializing the wallet
58
+ this.sessionId = "";
59
+ this.publicKey = "";
34
60
  this.ipAddress = ipAddress;
35
61
  // Get or create SSL certificates
36
62
  const { certPath, keyPath } = (0, ssl_1.getOrCreateSSLCerts)();
@@ -42,7 +68,7 @@ class PropagationServer {
42
68
  */
43
69
  async initializeWallet() {
44
70
  this.wallet = await blockchain_1.Wallet.load("default");
45
- this.publicKey = await this.wallet.getPublicSyntheticKey().toString("hex");
71
+ this.publicKey = (await this.wallet.getPublicSyntheticKey()).toString("hex");
46
72
  }
47
73
  /**
48
74
  * Create an Axios HTTPS Agent with self-signed certificate allowance.
@@ -51,14 +77,11 @@ class PropagationServer {
51
77
  return new https_1.default.Agent({
52
78
  cert: fs_1.default.readFileSync(this.certPath),
53
79
  key: fs_1.default.readFileSync(this.keyPath),
54
- rejectUnauthorized: false, // Allow self-signed certificates
80
+ rejectUnauthorized: false,
55
81
  });
56
82
  }
57
83
  /**
58
84
  * Check if the store and optional root hash exist by making a HEAD request.
59
- *
60
- * @param {string} [rootHash] - Optional root hash to check for existence.
61
- * @returns {Promise<{ storeExists: boolean, rootHashExists: boolean }>} - An object indicating if the store and root hash exist.
62
85
  */
63
86
  async checkStoreExists(rootHash) {
64
87
  const spinner = (0, nanospinner_1.createSpinner)(`Checking if store ${this.storeId} exists...`).start();
@@ -94,9 +117,6 @@ class PropagationServer {
94
117
  }
95
118
  /**
96
119
  * Start an upload session by sending a POST request with the rootHash.dat file.
97
- *
98
- * @param {string} rootHash - The root hash used to name the .dat file.
99
- * @param {string} datFilePath - The full path to the rootHash.dat file.
100
120
  */
101
121
  async startUploadSession(rootHash) {
102
122
  const spinner = (0, nanospinner_1.createSpinner)(`Starting upload session for store ${this.storeId}...`).start();
@@ -138,7 +158,6 @@ class PropagationServer {
138
158
  }
139
159
  /**
140
160
  * Request a nonce for a file by sending a HEAD request to the server.
141
- * Checks if the file already exists based on the 'x-file-exists' header.
142
161
  */
143
162
  async getFileNonce(filename) {
144
163
  try {
@@ -160,7 +179,7 @@ class PropagationServer {
160
179
  }
161
180
  /**
162
181
  * Upload a file to the server by sending a PUT request.
163
- * Logs progress using cli-progress for each file.
182
+ * Logs progress using a local cli-progress bar.
164
183
  */
165
184
  async uploadFile(label, dataPath) {
166
185
  const filePath = path_1.default.join(config_1.STORE_PATH, this.storeId, dataPath);
@@ -172,48 +191,70 @@ class PropagationServer {
172
191
  const wallet = await blockchain_1.Wallet.load("default");
173
192
  const keyOwnershipSig = await wallet.createKeyOwnershipSignature(nonce);
174
193
  const publicKey = await wallet.getPublicSyntheticKey();
175
- const formData = new form_data_1.default();
176
- formData.append("file", fs_1.default.createReadStream(filePath));
194
+ // Get the file size
177
195
  const fileSize = fs_1.default.statSync(filePath).size;
178
- // Create a new progress bar for each file
179
- const progressBar = PropagationServer.multiBar.create(fileSize, 0, {
180
- filename: (0, colorette_1.yellow)(path_1.default.basename(label)),
181
- percentage: 0,
182
- });
183
- let uploadedBytes = 0;
184
- const config = {
185
- headers: {
186
- "Content-Type": "multipart/form-data",
196
+ let progressBar;
197
+ try {
198
+ // Create a new progress bar for the file
199
+ progressBar = new cli_progress_1.default.SingleBar({
200
+ format: `${(0, colorette_1.blue)('[{bar}]')} | ${(0, colorette_1.yellow)('{filename}')} | {percentage}% | {value}/{total} bytes`,
201
+ hideCursor: true,
202
+ barsize: 30,
203
+ align: "left",
204
+ autopadding: true,
205
+ noTTYOutput: false,
206
+ stopOnComplete: true,
207
+ clearOnComplete: false,
208
+ }, cli_progress_1.default.Presets.legacy);
209
+ progressBar.start(fileSize, 0, {
210
+ filename: formatFilename(path_1.default.basename(label)),
211
+ });
212
+ // Create a progress stream
213
+ const progressStream = (0, progress_stream_1.default)({
214
+ length: fileSize,
215
+ time: 500, // Adjust as needed
216
+ });
217
+ progressStream.on("progress", (progress) => {
218
+ progressBar.update(progress.transferred);
219
+ });
220
+ // Create a read stream and pipe it through the progress stream
221
+ const fileReadStream = fs_1.default
222
+ .createReadStream(filePath)
223
+ .pipe(progressStream);
224
+ // Use form-data to construct the request body
225
+ const formData = new form_data_1.default();
226
+ formData.append("file", fileReadStream);
227
+ const headers = {
228
+ ...formData.getHeaders(),
187
229
  "x-nonce": nonce,
188
230
  "x-public-key": publicKey.toString("hex"),
189
231
  "x-key-ownership-sig": keyOwnershipSig,
190
- ...formData.getHeaders(),
191
- },
192
- httpsAgent: this.createHttpsAgent(),
193
- // Tracking upload progress and updating the progress bar
194
- onUploadProgress: (progressEvent) => {
195
- const uploadedBytes = progressEvent.loaded;
196
- const percentage = Math.round((100 * progressEvent.loaded) / progressEvent.total);
197
- progressBar.update(uploadedBytes, { percentage });
198
- },
199
- };
200
- try {
232
+ };
233
+ const config = {
234
+ headers: headers,
235
+ httpsAgent: this.createHttpsAgent(),
236
+ maxContentLength: Infinity,
237
+ maxBodyLength: Infinity,
238
+ };
201
239
  const url = `https://${this.ipAddress}:${PropagationServer.port}/upload/${this.storeId}/${this.sessionId}/${dataPath}`;
202
240
  const response = await axios_1.default.put(url, formData, config);
203
- // Complete the progress bar
204
- progressBar.update(fileSize, { percentage: 100 });
205
- progressBar.stop();
206
- return response.data;
241
+ // Wait for the progress stream to finish
242
+ await new Promise((resolve, reject) => {
243
+ progressStream.on("end", resolve);
244
+ progressStream.on("error", reject);
245
+ });
207
246
  }
208
247
  catch (error) {
209
- console.error((0, colorette_1.red)(`✖ Error uploading file ${label}:`), error.message);
210
- progressBar.stop(); // Stop the progress bar in case of error
211
248
  throw error;
212
249
  }
250
+ finally {
251
+ if (progressBar) {
252
+ progressBar.stop();
253
+ }
254
+ }
213
255
  }
214
256
  /**
215
257
  * Commit the upload session by sending a POST request to the server.
216
- * This finalizes the upload and moves files from the temporary session directory to the permanent location.
217
258
  */
218
259
  async commitUploadSession() {
219
260
  const spinner = (0, nanospinner_1.createSpinner)(`Committing upload session for store ${this.storeId}...`).start();
@@ -242,10 +283,6 @@ class PropagationServer {
242
283
  }
243
284
  /**
244
285
  * Static function to handle the entire upload process for multiple files based on rootHash.
245
- * @param {string} storeId - The ID of the DataStore.
246
- * @param {string} rootHash - The root hash used to derive the file set.
247
- * @param {string} publicKey - The public key of the user.
248
- * @param {string} ipAddress - The IP address of the server.
249
286
  */
250
287
  static async uploadStore(storeId, rootHash, ipAddress) {
251
288
  const propagationServer = new PropagationServer(ipAddress, storeId);
@@ -268,69 +305,92 @@ class PropagationServer {
268
305
  await propagationServer.startUploadSession(rootHash);
269
306
  const dataStore = blockchain_1.DataStore.from(storeId);
270
307
  const files = await dataStore.getFileSetForRootHash(rootHash);
271
- // Upload each file
272
- for (const file of files) {
273
- await propagationServer.uploadFile(file.name, file.path);
274
- }
308
+ // Prepare upload tasks
309
+ const uploadTasks = files.map((file) => ({
310
+ label: file.name,
311
+ dataPath: file.path,
312
+ }));
313
+ // Limit the number of concurrent uploads
314
+ const concurrencyLimit = 3; // Adjust this number as needed
315
+ // Import asyncPool from your utilities
316
+ const { asyncPool } = await Promise.resolve().then(() => __importStar(require("../utils/asyncPool")));
317
+ await asyncPool(concurrencyLimit, uploadTasks, async (task) => {
318
+ await propagationServer.uploadFile(task.label, task.dataPath);
319
+ });
275
320
  // Commit the session after all files have been uploaded
276
321
  await propagationServer.commitUploadSession();
277
322
  console.log((0, colorette_1.green)(`✔ All files have been uploaded to DataStore ${storeId}.`));
278
323
  }
279
324
  /**
280
325
  * Fetch a file from the server by sending a GET request and return its content in memory.
281
- * Logs progress using cli-progress.
282
- * @param {string} dataPath - The data path of the file to download.
283
- * @returns {Promise<Buffer>} - The file content in memory as a Buffer.
326
+ * Logs progress using a local cli-progress bar.
284
327
  */
285
328
  async fetchFile(dataPath) {
329
+ const url = `https://${this.ipAddress}:${PropagationServer.port}/fetch/${this.storeId}/${dataPath}`;
286
330
  const config = {
287
- responseType: "arraybuffer", // To store the file content in memory
331
+ responseType: "stream",
288
332
  httpsAgent: this.createHttpsAgent(),
289
333
  };
290
- const url = `https://${this.ipAddress}:${PropagationServer.port}/fetch/${this.storeId}/${dataPath}`;
334
+ let progressBar;
291
335
  try {
292
336
  const response = await axios_1.default.get(url, config);
293
- const totalLength = parseInt(response.headers["content-length"], 10);
294
- console.log((0, colorette_1.cyan)(`Starting fetch for ${dataPath}...`));
295
- // Create a progress bar for the download
296
- const progressBar = PropagationServer.multiBar.create(totalLength, 0, {
297
- dataPath: (0, colorette_1.yellow)(dataPath),
298
- percentage: 0,
337
+ const totalLengthHeader = response.headers["content-length"];
338
+ const totalLength = totalLengthHeader
339
+ ? parseInt(totalLengthHeader, 10)
340
+ : null;
341
+ if (!totalLength) {
342
+ throw new Error("Content-Length header is missing");
343
+ }
344
+ // Create a new progress bar for the file
345
+ progressBar = new cli_progress_1.default.SingleBar({
346
+ format: `${(0, colorette_1.blue)('[{bar}]')} | ${(0, colorette_1.yellow)('{filename}')} | {percentage}% | {value}/{total} bytes`,
347
+ hideCursor: true,
348
+ barsize: 30,
349
+ align: "left",
350
+ autopadding: true,
351
+ noTTYOutput: false,
352
+ stopOnComplete: true,
353
+ clearOnComplete: false,
354
+ }, cli_progress_1.default.Presets.legacy);
355
+ progressBar.start(totalLength, 0, {
356
+ filename: formatFilename(dataPath),
299
357
  });
300
- let downloadedBytes = 0;
301
- // Track progress of downloading the file
302
- response.data.on("data", (chunk) => {
303
- downloadedBytes += chunk.length;
304
- progressBar.update(downloadedBytes, {
305
- percentage: Math.round((downloadedBytes / totalLength) * 100),
306
- });
358
+ let dataBuffers = [];
359
+ const progressStream = (0, progress_stream_1.default)({
360
+ length: totalLength,
361
+ time: 500, // Adjust as needed
362
+ });
363
+ progressStream.on("progress", (progress) => {
364
+ progressBar.update(progress.transferred);
307
365
  });
308
- // Complete the progress bar once done
309
- progressBar.update(totalLength, { percentage: 100 });
310
- progressBar.stop();
311
- console.log((0, colorette_1.green)(`✔ File ${dataPath} fetched successfully.`));
312
- // Return the file contents as a Buffer
313
- return Buffer.from(response.data);
366
+ response.data.pipe(progressStream);
367
+ progressStream.on("data", (chunk) => {
368
+ dataBuffers.push(chunk);
369
+ });
370
+ // Wait for the progress stream to finish
371
+ await new Promise((resolve, reject) => {
372
+ progressStream.on("end", resolve);
373
+ progressStream.on("error", reject);
374
+ });
375
+ return Buffer.concat(dataBuffers);
314
376
  }
315
377
  catch (error) {
316
- console.error((0, colorette_1.red)(`✖ Error fetching file ${dataPath}:`), error);
317
378
  throw error;
318
379
  }
380
+ finally {
381
+ if (progressBar) {
382
+ progressBar.stop();
383
+ }
384
+ }
319
385
  }
320
386
  /**
321
387
  * Get details of a file, including whether it exists and its size.
322
- * Makes a HEAD request to the server and checks the response headers.
323
- *
324
- * @param {string} dataPath - The path of the file within the DataStore.
325
- * @param {string} rootHash - The root hash associated with the DataStore.
326
- * @returns {Promise<{ exists: boolean; size: number }>} - An object containing file existence and size information.
327
388
  */
328
389
  async getFileDetails(dataPath, rootHash) {
329
390
  try {
330
391
  const config = {
331
392
  httpsAgent: this.createHttpsAgent(),
332
393
  };
333
- // Construct the URL for the HEAD request to check file details
334
394
  const url = `https://${this.ipAddress}:${PropagationServer.port}/store/${this.storeId}/${rootHash}/${dataPath}`;
335
395
  const response = await axios_1.default.head(url, config);
336
396
  // Check the headers for file existence and size
@@ -338,7 +398,7 @@ class PropagationServer {
338
398
  const fileSize = parseInt(response.headers["x-file-size"], 10);
339
399
  return {
340
400
  exists: fileExists,
341
- size: fileExists ? fileSize : 0, // Return 0 size if file doesn't exist
401
+ size: fileExists ? fileSize : 0,
342
402
  };
343
403
  }
344
404
  catch (error) {
@@ -348,63 +408,73 @@ class PropagationServer {
348
408
  }
349
409
  /**
350
410
  * Download a file from the server by sending a GET request.
351
- * Logs progress using cli-progress.
352
- * @param {string} dataPath - The data path of the file to download.
411
+ * Logs progress using a local cli-progress bar.
353
412
  */
354
- async downloadFile(dataPath) {
355
- const config = {
356
- responseType: "stream", // Make sure the response is streamed
357
- httpsAgent: this.createHttpsAgent(),
358
- };
413
+ async downloadFile(label, dataPath) {
359
414
  const url = `https://${this.ipAddress}:${PropagationServer.port}/fetch/${this.storeId}/${dataPath}`;
360
- const downloadPath = path_1.default.join(config_1.STORE_PATH, this.storeId, dataPath); // Save to the correct store directory
415
+ const downloadPath = path_1.default.join(config_1.STORE_PATH, this.storeId, dataPath);
361
416
  // Ensure that the directory for the file exists
362
417
  fs_1.default.mkdirSync(path_1.default.dirname(downloadPath), { recursive: true });
363
- const fileWriteStream = fs_1.default.createWriteStream(downloadPath);
418
+ const config = {
419
+ responseType: "stream",
420
+ httpsAgent: this.createHttpsAgent(),
421
+ };
422
+ let progressBar;
364
423
  try {
365
424
  const response = await axios_1.default.get(url, config);
366
- const totalLength = parseInt(response.headers["content-length"], 10);
367
- console.log((0, colorette_1.cyan)(`Starting download for ${dataPath}...`));
368
- // Create a progress bar for the download
369
- const progressBar = PropagationServer.multiBar.create(totalLength, 0, {
370
- dataPath: (0, colorette_1.yellow)(dataPath),
371
- percentage: 0,
425
+ const totalLengthHeader = response.headers["content-length"];
426
+ const totalLength = totalLengthHeader
427
+ ? parseInt(totalLengthHeader, 10)
428
+ : null;
429
+ if (!totalLength) {
430
+ throw new Error("Content-Length header is missing");
431
+ }
432
+ // Create a new progress bar for the file
433
+ progressBar = new cli_progress_1.default.SingleBar({
434
+ format: `${(0, colorette_1.blue)('[{bar}]')} | ${(0, colorette_1.yellow)('{filename}')} | {percentage}% | {value}/{total} bytes`,
435
+ hideCursor: true,
436
+ barsize: 30,
437
+ align: "left",
438
+ autopadding: true,
439
+ noTTYOutput: false,
440
+ stopOnComplete: true,
441
+ clearOnComplete: false,
442
+ }, cli_progress_1.default.Presets.legacy);
443
+ progressBar.start(totalLength, 0, {
444
+ filename: formatFilename(label),
372
445
  });
373
- let downloadedBytes = 0;
374
- // Pipe the response data to the file stream and track progress
375
- response.data.on("data", (chunk) => {
376
- downloadedBytes += chunk.length;
377
- progressBar.update(downloadedBytes, {
378
- percentage: Math.round((downloadedBytes / totalLength) * 100),
379
- });
446
+ const fileWriteStream = fs_1.default.createWriteStream(downloadPath);
447
+ const progressStream = (0, progress_stream_1.default)({
448
+ length: totalLength,
449
+ time: 500, // Adjust as needed
380
450
  });
381
- // Pipe the data into the file and finalize
382
- response.data.pipe(fileWriteStream);
383
- return new Promise((resolve, reject) => {
384
- fileWriteStream.on("finish", () => {
385
- progressBar.update(totalLength, { percentage: 100 });
386
- progressBar.stop();
387
- console.log((0, colorette_1.green)(`
388
- File ${dataPath} downloaded successfully to ${downloadPath}.`));
389
- resolve();
390
- });
391
- fileWriteStream.on("error", (error) => {
392
- progressBar.stop();
393
- console.error((0, colorette_1.red)(`✖ Error downloading file ${dataPath}:`), error);
394
- reject(error);
395
- });
451
+ progressStream.on("progress", (progress) => {
452
+ progressBar.update(progress.transferred);
396
453
  });
454
+ response.data.pipe(progressStream).pipe(fileWriteStream);
455
+ // Wait for both the file write stream and the progress stream to finish
456
+ await Promise.all([
457
+ new Promise((resolve, reject) => {
458
+ fileWriteStream.on("finish", resolve);
459
+ fileWriteStream.on("error", reject);
460
+ }),
461
+ new Promise((resolve, reject) => {
462
+ progressStream.on("end", resolve);
463
+ progressStream.on("error", reject);
464
+ }),
465
+ ]);
397
466
  }
398
467
  catch (error) {
399
- console.error((0, colorette_1.red)(`✖ Error downloading file ${dataPath}:`), error);
400
468
  throw error;
401
469
  }
470
+ finally {
471
+ if (progressBar) {
472
+ progressBar.stop();
473
+ }
474
+ }
402
475
  }
403
476
  /**
404
477
  * Static function to handle downloading multiple files from a DataStore based on file paths.
405
- * @param {string} storeId - The ID of the DataStore.
406
- * @param {string[]} dataPaths - The list of data paths to download.
407
- * @param {string} ipAddress - The IP address of the server.
408
478
  */
409
479
  static async downloadStore(storeId, rootHash, ipAddress) {
410
480
  const propagationServer = new PropagationServer(ipAddress, storeId);
@@ -415,13 +485,25 @@ class PropagationServer {
415
485
  if (!storeExists || !rootHashExists) {
416
486
  throw new Error(`Store ${storeId} does not exist.`);
417
487
  }
418
- await propagationServer.downloadFile("height.json");
488
+ // Fetch the rootHash.dat file
419
489
  const datFileContent = await propagationServer.fetchFile(`${rootHash}.dat`);
420
490
  const root = JSON.parse(datFileContent.toString());
491
+ // Prepare download tasks
492
+ const downloadTasks = [];
421
493
  for (const [fileKey, fileData] of Object.entries(root.files)) {
422
- const dataPath = (0, hashUtils_1.getFilePathFromSha256)(root.files[fileKey].sha256, "data");
423
- await propagationServer.downloadFile(dataPath);
494
+ const dataPath = (0, hashUtils_1.getFilePathFromSha256)(fileData.sha256, "data");
495
+ const label = Buffer.from(fileKey, "hex").toString("utf-8");
496
+ downloadTasks.push({ label, dataPath });
424
497
  }
498
+ // Limit the number of concurrent downloads
499
+ const concurrencyLimit = 5; // Adjust this number as needed
500
+ // Import asyncPool from your utilities
501
+ const { asyncPool } = await Promise.resolve().then(() => __importStar(require("../utils/asyncPool")));
502
+ await asyncPool(concurrencyLimit, downloadTasks, async (task) => {
503
+ await propagationServer.downloadFile(task.label, task.dataPath);
504
+ });
505
+ // Save the rootHash.dat file
506
+ fs_1.default.writeFileSync(path_1.default.join(config_1.STORE_PATH, storeId, `${rootHash}.dat`), datFileContent);
425
507
  const dataStore = blockchain_1.DataStore.from(storeId);
426
508
  await dataStore.generateManifestFile();
427
509
  console.log((0, colorette_1.green)(`✔ All files have been downloaded to ${storeId}.`));
@@ -429,29 +511,3 @@ class PropagationServer {
429
511
  }
430
512
  exports.PropagationServer = PropagationServer;
431
513
  PropagationServer.port = 4159; // Static port used for all requests
432
- // Adjust cliProgress settings to align output properly and handle potential undefined values
433
- // MultiBar for handling multiple progress bars with green progress and margin between bars
434
- PropagationServer.multiBar = new cli_progress_1.default.MultiBar({
435
- clearOnComplete: false,
436
- hideCursor: true,
437
- format: (options, params, payload) => {
438
- // Bar length and padding settings
439
- const barCompleteChar = (0, colorette_1.green)(options.barCompleteChar || "="); // Green bar for completed portion
440
- const barIncompleteChar = options.barIncompleteChar || "-"; // Default incomplete character
441
- const barSize = options.barsize || 40; // Default size of the bar
442
- // Calculate the bar progress
443
- const progressBar = `${barCompleteChar.repeat(Math.round(params.progress * barSize))}${barIncompleteChar.repeat(barSize - Math.round(params.progress * barSize))}`;
444
- // Calculate the percentage manually
445
- const percentage = Math.round((params.value / params.total) * 100);
446
- // Format the filename to a fixed length
447
- const formattedFilename = formatFilename(payload.filename, 30); // Trim to max 30 chars
448
- // Padding the filename, percentage, and size
449
- const percentageStr = `${percentage}%`.padEnd(4); // Ensure percentage is always 7 characters wide
450
- const size = `${params.value}/${params.total} bytes`.padEnd(20); // Ensure size is always 20 characters wide
451
- // Return the complete formatted progress bar with padding
452
- return `${progressBar} | ${formattedFilename.padEnd(35)} | ${percentageStr} | ${size}`;
453
- },
454
- stopOnComplete: true,
455
- barsize: 40,
456
- align: "left",
457
- }, cli_progress_1.default.Presets.shades_classic);
@@ -1 +1 @@
1
- {"version":3,"file":"DataStore.d.ts","sourceRoot":"","sources":["../../src/blockchain/DataStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,SAAS,IAAI,eAAe,EAE5B,iBAAiB,EAIlB,MAAM,8BAA8B,CAAC;AAWtC,OAAO,EAAE,eAAe,EAAW,MAAM,UAAU,CAAC;AAGpD,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAYjD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAoB;gBAEpB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB;IAiB/D,IAAW,OAAO,IAAI,MAAM,CAE3B;IAED,IAAW,IAAI,IAAI,iBAAiB,CAEnC;IAEM,QAAQ,IAAI,MAAM;IAIlB,QAAQ,IAAI,MAAM;IAIlB,SAAS,IAAI,MAAM;WAMN,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;WAOtD,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;WAK1C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS;WAiBtD,MAAM,CACxB,MAAM,GAAE,qBAA0B,GACjC,OAAO,CAAC,SAAS,CAAC;mBAyCA,IAAI;IA4FzB;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAK5C;;;OAGG;WACiB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIxD;;;;OAIG;mBACkB,mBAAmB;WAqB1B,YAAY,IAAI,SAAS,EAAE;IAW5B,aAAa,IAAI,OAAO,CAAC;QACpC,WAAW,EAAE,eAAe,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAyGW,iBAAiB,IAAI,OAAO,CAAC;QACxC,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;YA0BY,iBAAiB;IAYlB,cAAc,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAwC5C,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAexD,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAKzC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAM5B,uBAAuB,CAClC,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,OAAO,CAAC;IAkBN,cAAc,CACzB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,eAAe,CAAC;IA+C3B;;;;;OAKG;IACU,qBAAqB,CAChC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAsD7C"}
1
+ {"version":3,"file":"DataStore.d.ts","sourceRoot":"","sources":["../../src/blockchain/DataStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,SAAS,IAAI,eAAe,EAE5B,iBAAiB,EAIlB,MAAM,8BAA8B,CAAC;AAWtC,OAAO,EAAE,eAAe,EAAW,MAAM,UAAU,CAAC;AAGpD,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAYjD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAoB;gBAEpB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB;IAiB/D,IAAW,OAAO,IAAI,MAAM,CAE3B;IAED,IAAW,IAAI,IAAI,iBAAiB,CAEnC;IAEM,QAAQ,IAAI,MAAM;IAIlB,QAAQ,IAAI,MAAM;IAIlB,SAAS,IAAI,MAAM;WAMN,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;WAOtD,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;WAK1C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS;WAiBtD,MAAM,CACxB,MAAM,GAAE,qBAA0B,GACjC,OAAO,CAAC,SAAS,CAAC;mBAyCA,IAAI;IA4FzB;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAK5C;;;OAGG;WACiB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIxD;;;;OAIG;mBACkB,mBAAmB;WAqB1B,YAAY,IAAI,SAAS,EAAE;IAW5B,aAAa,IAAI,OAAO,CAAC;QACpC,WAAW,EAAE,eAAe,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAyGW,iBAAiB,IAAI,OAAO,CAAC;QACxC,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;YA0BY,iBAAiB;IAYlB,cAAc,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAwC5C,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAexD,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAKzC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAM5B,uBAAuB,CAClC,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,OAAO,CAAC;IAkBN,cAAc,CACzB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,eAAe,CAAC;IA+C3B;;;;;OAKG;IACU,qBAAqB,CAChC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CA2C7C"}
@@ -374,13 +374,8 @@ class DataStore {
374
374
  async getFileSetForRootHash(rootHash) {
375
375
  const datFilePath = path_1.default.join(config_1.STORE_PATH, this.storeId, `${rootHash}.dat`);
376
376
  const datFileContent = JSON.parse(fs_1.default.readFileSync(datFilePath, "utf-8"));
377
- const heightDatFilePath = path_1.default.join(config_1.STORE_PATH, this.storeId, "height.json");
378
377
  // Use a Set to ensure uniqueness
379
378
  const filesInvolved = new Set();
380
- filesInvolved.add({
381
- name: "height.json",
382
- path: "height.json",
383
- });
384
379
  // Iterate over each file and perform an integrity check
385
380
  for (const [fileKey, fileData] of Object.entries(datFileContent.files)) {
386
381
  const filePath = (0, hashUtils_1.getFilePathFromSha256)(datFileContent.files[fileKey].sha256, "data");
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Processes items in sequential batches with a concurrency limit.
3
+ * @param {number} limit - The maximum number of concurrent executions per batch.
4
+ * @param {Array<T>} items - The array of items to process.
5
+ * @param {(item: T) => Promise<R>} iteratorFn - The async function to apply to each item.
6
+ * @returns {Promise<Array<R>>} - A promise that resolves when all items have been processed.
7
+ */
8
+ export declare function asyncPool<T, R>(limit: number, items: T[], iteratorFn: (item: T) => Promise<R>): Promise<R[]>;
9
+ //# sourceMappingURL=asyncPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asyncPool.d.ts","sourceRoot":"","sources":["../../src/utils/asyncPool.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,CAAC,EAChC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,CAAC,EAAE,EACV,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,EAAE,CAAC,CAgBd"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asyncPool = asyncPool;
4
+ /**
5
+ * Processes items in sequential batches with a concurrency limit.
6
+ * @param {number} limit - The maximum number of concurrent executions per batch.
7
+ * @param {Array<T>} items - The array of items to process.
8
+ * @param {(item: T) => Promise<R>} iteratorFn - The async function to apply to each item.
9
+ * @returns {Promise<Array<R>>} - A promise that resolves when all items have been processed.
10
+ */
11
+ async function asyncPool(limit, items, iteratorFn) {
12
+ const ret = [];
13
+ for (let i = 0; i < items.length; i += limit) {
14
+ const batchItems = items.slice(i, i + limit);
15
+ const batchPromises = batchItems.map((item) => iteratorFn(item));
16
+ // Wait for the current batch to complete before starting the next one
17
+ const batchResults = await Promise.all(batchPromises);
18
+ ret.push(...batchResults);
19
+ // Optional: add a cooldown between batches
20
+ // await new Promise((resolve) => setTimeout(resolve, 500));
21
+ }
22
+ return ret;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dignetwork/dig-sdk",
3
- "version": "0.0.1-alpha.70",
3
+ "version": "0.0.1-alpha.72",
4
4
  "description": "",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -46,7 +46,7 @@
46
46
  "nanospinner": "^1.1.0",
47
47
  "nconf": "^0.12.1",
48
48
  "node-cache": "^5.1.2",
49
- "p-limit": "^6.1.0",
49
+ "progress-stream": "^2.0.0",
50
50
  "superagent": "^10.0.0",
51
51
  "unzipper": "^0.12.3"
52
52
  },
@@ -61,6 +61,7 @@
61
61
  "@types/mocha": "^10.0.7",
62
62
  "@types/nconf": "^0.10.7",
63
63
  "@types/node": "^22.1.0",
64
+ "@types/progress-stream": "^2.0.5",
64
65
  "@types/superagent": "^8.1.8",
65
66
  "@types/unzipper": "^0.10.10",
66
67
  "chai": "^5.1.1",