nodio-cli 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodio-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Nodio distributed storage network",
5
5
  "main": "src/server/index.js",
6
6
  "type": "commonjs",
@@ -120,6 +120,7 @@ async function uploadFile(options) {
120
120
  const serverUrl = options.server;
121
121
  const shardSizeMb = Number(options.shardSizeMb || 1);
122
122
  const replicas = Number(options.replicas || 5);
123
+ const directTimeoutMs = Number(options.directTimeoutMs || 4000);
123
124
 
124
125
  if (!Number.isFinite(shardSizeMb) || shardSizeMb <= 0) {
125
126
  throw new Error('shard-size-mb must be greater than 0');
@@ -127,6 +128,9 @@ async function uploadFile(options) {
127
128
  if (!Number.isInteger(replicas) || replicas < 5) {
128
129
  throw new Error('replicas must be an integer >= 5');
129
130
  }
131
+ if (!Number.isFinite(directTimeoutMs) || directTimeoutMs <= 0) {
132
+ throw new Error('direct-timeout-ms must be greater than 0');
133
+ }
130
134
 
131
135
  const shardSizeBytes = Math.floor(shardSizeMb * 1024 * 1024);
132
136
  const plainBuffer = await fs.readFile(filePath);
@@ -173,21 +177,29 @@ async function uploadFile(options) {
173
177
  throw new Error(`placement failed for ${shardId}: expected ${replicas} replicas`);
174
178
  }
175
179
 
176
- const successfulReplicas = [];
177
- const failedReplicas = [];
178
-
179
- for (const replica of plannedReplicas) {
180
- try {
180
+ const directWriteResults = await Promise.allSettled(
181
+ plannedReplicas.map(async (replica) => {
181
182
  const putUrl = `${normalizeUrl(replica.url)}/shards/${shardId}`;
182
183
  await axios.put(putUrl, encrypted.cipherText, {
183
184
  headers: { 'Content-Type': 'application/octet-stream' },
184
- timeout: 30000
185
+ timeout: directTimeoutMs
185
186
  });
186
- successfulReplicas.push(replica);
187
- } catch (error) {
188
- failedReplicas.push({ nodeId: replica.nodeId, url: replica.url, error: error.message });
189
- }
190
- }
187
+ return replica;
188
+ })
189
+ );
190
+
191
+ const successfulReplicas = directWriteResults
192
+ .filter((result) => result.status === 'fulfilled')
193
+ .map((result) => result.value);
194
+
195
+ const failedReplicas = directWriteResults
196
+ .map((result, index) => ({ result, replica: plannedReplicas[index] }))
197
+ .filter((entry) => entry.result.status === 'rejected')
198
+ .map((entry) => ({
199
+ nodeId: entry.replica.nodeId,
200
+ url: entry.replica.url,
201
+ error: entry.result.reason?.message || 'direct write failed'
202
+ }));
191
203
 
192
204
  if (failedReplicas.length > 0) {
193
205
  try {
@@ -260,6 +272,11 @@ async function downloadFile(options) {
260
272
  const serverUrl = options.server;
261
273
  const output = options.output ? path.resolve(options.output) : path.resolve(`./${fileId}.downloaded`);
262
274
  const keyBuffer = parseAesKey(options.keyBase64);
275
+ const directTimeoutMs = Number(options.directTimeoutMs || 4000);
276
+
277
+ if (!Number.isFinite(directTimeoutMs) || directTimeoutMs <= 0) {
278
+ throw new Error('direct-timeout-ms must be greater than 0');
279
+ }
263
280
 
264
281
  const api = createApiClient(serverUrl);
265
282
  const manifestResponse = await api.get(`/files/${fileId}/manifest`);
@@ -283,22 +300,24 @@ async function downloadFile(options) {
283
300
  }
284
301
 
285
302
  let encryptedBuffer = null;
286
- for (const replica of shard.replicas || []) {
287
- try {
303
+ const directFetchResults = await Promise.allSettled(
304
+ (shard.replicas || []).map(async (replica) => {
288
305
  const getUrl = `${normalizeUrl(replica.url)}/shards/${shard.shardId}`;
289
306
  const response = await axios.get(getUrl, {
290
307
  responseType: 'arraybuffer',
291
- timeout: 30000
308
+ timeout: directTimeoutMs
292
309
  });
293
310
  const candidate = Buffer.from(response.data);
294
311
  if (sha256Hex(candidate) !== shard.checksum) {
295
- continue;
312
+ throw new Error('checksum mismatch');
296
313
  }
297
- encryptedBuffer = candidate;
298
- break;
299
- } catch {
300
- continue;
301
- }
314
+ return candidate;
315
+ })
316
+ );
317
+
318
+ const successfulDirectFetch = directFetchResults.find((result) => result.status === 'fulfilled');
319
+ if (successfulDirectFetch) {
320
+ encryptedBuffer = successfulDirectFetch.value;
302
321
  }
303
322
 
304
323
  if (!encryptedBuffer && Array.isArray(shard.replicas) && shard.replicas.length > 0) {
package/src/user/index.js CHANGED
@@ -14,6 +14,7 @@ program
14
14
  .option('--file-id <id>', 'custom file ID (optional)')
15
15
  .option('--shard-size-mb <mb>', 'plaintext shard size in MB', '1')
16
16
  .option('--replicas <count>', 'replicas per shard (minimum 5)', '5')
17
+ .option('--direct-timeout-ms <ms>', 'timeout for each direct donor attempt before relay fallback', '4000')
17
18
  .option('--key-base64 <key>', '32-byte AES key in base64 (optional)')
18
19
  .action(async (options) => {
19
20
  await uploadFile(options);
@@ -26,6 +27,7 @@ program
26
27
  .requiredOption('--key-base64 <key>', '32-byte AES key in base64 from upload output')
27
28
  .option('--server <url>', 'central server URL', 'https://api.nodio.me')
28
29
  .option('--output <path>', 'output file path')
30
+ .option('--direct-timeout-ms <ms>', 'timeout for each direct donor attempt before relay fallback', '4000')
29
31
  .action(async (options) => {
30
32
  await downloadFile(options);
31
33
  });