@arke-institute/sdk 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -88,6 +88,15 @@ function parseApiError(status, body) {
88
88
  }
89
89
 
90
90
  // src/client/ArkeClient.ts
91
+ function isApiKey(token) {
92
+ return token.startsWith("ak_") || token.startsWith("uk_");
93
+ }
94
+ function getAuthorizationHeader(token) {
95
+ if (isApiKey(token)) {
96
+ return `ApiKey ${token}`;
97
+ }
98
+ return `Bearer ${token}`;
99
+ }
91
100
  var ArkeClient = class {
92
101
  constructor(config = {}) {
93
102
  this.config = {
@@ -102,7 +111,7 @@ var ArkeClient = class {
102
111
  ...this.config.headers
103
112
  };
104
113
  if (this.config.authToken) {
105
- headers["Authorization"] = `Bearer ${this.config.authToken}`;
114
+ headers["Authorization"] = getAuthorizationHeader(this.config.authToken);
106
115
  }
107
116
  if (this.config.network === "test") {
108
117
  headers["X-Arke-Network"] = "test";
@@ -170,6 +179,15 @@ async function computeCid(data) {
170
179
  }
171
180
 
172
181
  // src/operations/upload/engine.ts
182
+ var PHASE_COUNT = 4;
183
+ var PHASE_INDEX = {
184
+ "computing-cids": 0,
185
+ "creating": 1,
186
+ "backlinking": 2,
187
+ "uploading": 3,
188
+ "complete": 4,
189
+ "error": -1
190
+ };
173
191
  async function parallelLimit(items, concurrency, fn) {
174
192
  const results = [];
175
193
  let index = 0;
@@ -184,24 +202,83 @@ async function parallelLimit(items, concurrency, fn) {
184
202
  await Promise.all(workers);
185
203
  return results;
186
204
  }
205
+ var TARGET_BYTES_IN_FLIGHT = 200 * 1024 * 1024;
206
+ var BytePool = class {
207
+ constructor(targetBytes = TARGET_BYTES_IN_FLIGHT) {
208
+ this.targetBytes = targetBytes;
209
+ this.bytesInFlight = 0;
210
+ this.waitQueue = [];
211
+ }
212
+ async run(size, fn) {
213
+ while (this.bytesInFlight > 0 && this.bytesInFlight + size > this.targetBytes) {
214
+ await new Promise((resolve) => this.waitQueue.push(resolve));
215
+ }
216
+ this.bytesInFlight += size;
217
+ try {
218
+ return await fn();
219
+ } finally {
220
+ this.bytesInFlight -= size;
221
+ const next = this.waitQueue.shift();
222
+ if (next) next();
223
+ }
224
+ }
225
+ };
187
226
  function getParentPath(relativePath) {
188
227
  const lastSlash = relativePath.lastIndexOf("/");
189
228
  if (lastSlash === -1) return null;
190
229
  return relativePath.slice(0, lastSlash);
191
230
  }
231
+ function groupFoldersByDepth(folders) {
232
+ const byDepth = /* @__PURE__ */ new Map();
233
+ for (const folder of folders) {
234
+ const depth = folder.relativePath.split("/").length - 1;
235
+ if (!byDepth.has(depth)) byDepth.set(depth, []);
236
+ byDepth.get(depth).push(folder);
237
+ }
238
+ return byDepth;
239
+ }
192
240
  async function uploadTree(client, tree, options) {
193
- const { target, onProgress, concurrency = 5, continueOnError = false, note } = options;
241
+ const { target, onProgress, concurrency = 10, continueOnError = false, note } = options;
194
242
  const errors = [];
195
243
  const createdFolders = [];
196
244
  const createdFiles = [];
245
+ const foldersByPath = /* @__PURE__ */ new Map();
246
+ const totalEntities = tree.files.length + tree.folders.length;
247
+ const totalBytes = tree.files.reduce((sum, f) => sum + f.size, 0);
248
+ let completedEntities = 0;
249
+ let bytesUploaded = 0;
197
250
  const reportProgress = (progress) => {
198
251
  if (onProgress) {
252
+ const phase = progress.phase || "computing-cids";
253
+ const phaseIndex = PHASE_INDEX[phase] ?? -1;
254
+ let phasePercent = 0;
255
+ if (phase === "computing-cids") {
256
+ const done = progress.completedEntities ?? completedEntities;
257
+ phasePercent = tree.files.length > 0 ? Math.round(done / tree.files.length * 100) : 100;
258
+ } else if (phase === "creating") {
259
+ const done = progress.completedEntities ?? completedEntities;
260
+ phasePercent = totalEntities > 0 ? Math.round(done / totalEntities * 100) : 100;
261
+ } else if (phase === "backlinking") {
262
+ const done = progress.completedParents ?? 0;
263
+ const total = progress.totalParents ?? 0;
264
+ phasePercent = total > 0 ? Math.round(done / total * 100) : 100;
265
+ } else if (phase === "uploading") {
266
+ const done = progress.bytesUploaded ?? bytesUploaded;
267
+ phasePercent = totalBytes > 0 ? Math.round(done / totalBytes * 100) : 100;
268
+ } else if (phase === "complete") {
269
+ phasePercent = 100;
270
+ }
199
271
  onProgress({
200
- phase: "scanning",
201
- totalFiles: tree.files.length,
202
- completedFiles: 0,
203
- totalFolders: tree.folders.length,
204
- completedFolders: 0,
272
+ phase,
273
+ phaseIndex,
274
+ phaseCount: PHASE_COUNT,
275
+ phasePercent,
276
+ totalEntities,
277
+ completedEntities,
278
+ totalParents: 0,
279
+ completedParents: 0,
280
+ totalBytes,
281
+ bytesUploaded,
205
282
  ...progress
206
283
  });
207
284
  }
@@ -211,7 +288,6 @@ async function uploadTree(client, tree, options) {
211
288
  let collectionCid;
212
289
  let collectionCreated = false;
213
290
  if (target.createCollection) {
214
- reportProgress({ phase: "scanning", currentFolder: "Creating collection..." });
215
291
  const collectionBody = {
216
292
  label: target.createCollection.label,
217
293
  description: target.createCollection.description,
@@ -240,26 +316,19 @@ async function uploadTree(client, tree, options) {
240
316
  throw new Error("Must provide either collectionId or createCollection in target");
241
317
  }
242
318
  const rootParentId = target.parentId ?? collectionId;
243
- reportProgress({
244
- phase: "computing-cids",
245
- totalFiles: tree.files.length,
246
- completedFiles: 0
247
- });
319
+ reportProgress({ phase: "computing-cids", completedEntities: 0 });
248
320
  const preparedFiles = [];
249
321
  let cidProgress = 0;
250
- await parallelLimit(tree.files, concurrency, async (file) => {
322
+ await parallelLimit(tree.files, Math.max(concurrency, 20), async (file) => {
251
323
  try {
252
324
  const data = await file.getData();
253
325
  const cid = await computeCid(data);
254
- preparedFiles.push({
255
- ...file,
256
- cid
257
- });
326
+ preparedFiles.push({ ...file, cid });
258
327
  cidProgress++;
259
328
  reportProgress({
260
329
  phase: "computing-cids",
261
- completedFiles: cidProgress,
262
- currentFile: file.relativePath
330
+ completedEntities: cidProgress,
331
+ currentItem: file.relativePath
263
332
  });
264
333
  } catch (err) {
265
334
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -270,74 +339,74 @@ async function uploadTree(client, tree, options) {
270
339
  }
271
340
  }
272
341
  });
273
- reportProgress({
274
- phase: "creating-folders",
275
- totalFolders: tree.folders.length,
276
- completedFolders: 0
277
- });
278
- const sortedFolders = [...tree.folders].sort(
279
- (a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length
280
- );
281
- for (let i = 0; i < sortedFolders.length; i++) {
282
- const folder = sortedFolders[i];
283
- try {
284
- const folderBody = {
285
- label: folder.name,
286
- collection: collectionId,
287
- note
288
- };
289
- const { data, error } = await client.api.POST("/folders", {
290
- body: folderBody
291
- });
292
- if (error || !data) {
293
- throw new Error(JSON.stringify(error));
294
- }
295
- createdFolders.push({
296
- name: folder.name,
297
- relativePath: folder.relativePath,
298
- id: data.id,
299
- entityCid: data.cid
300
- });
301
- reportProgress({
302
- phase: "creating-folders",
303
- completedFolders: i + 1,
304
- currentFolder: folder.relativePath
305
- });
306
- } catch (err) {
307
- const errorMsg = err instanceof Error ? err.message : String(err);
308
- if (continueOnError) {
309
- errors.push({ path: folder.relativePath, error: `Folder creation failed: ${errorMsg}` });
310
- } else {
311
- throw new Error(`Failed to create folder ${folder.relativePath}: ${errorMsg}`);
312
- }
313
- }
314
- }
315
- const folderPathToEntity = /* @__PURE__ */ new Map();
316
- for (const folder of createdFolders) {
317
- folderPathToEntity.set(folder.relativePath, folder);
342
+ reportProgress({ phase: "creating", completedEntities: 0 });
343
+ const foldersByDepth = groupFoldersByDepth(tree.folders);
344
+ const sortedDepths = [...foldersByDepth.keys()].sort((a, b) => a - b);
345
+ for (const depth of sortedDepths) {
346
+ const foldersAtDepth = foldersByDepth.get(depth);
347
+ await Promise.all(
348
+ foldersAtDepth.map(async (folder) => {
349
+ try {
350
+ const parentPath = getParentPath(folder.relativePath);
351
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
352
+ const parentType = parentPath ? "folder" : parentId === collectionId ? "collection" : "folder";
353
+ const folderBody = {
354
+ label: folder.name,
355
+ collection: collectionId,
356
+ note,
357
+ relationships: [{ predicate: "in", peer: parentId, peer_type: parentType }]
358
+ };
359
+ const { data, error } = await client.api.POST("/folders", {
360
+ body: folderBody
361
+ });
362
+ if (error || !data) {
363
+ throw new Error(JSON.stringify(error));
364
+ }
365
+ foldersByPath.set(folder.relativePath, { id: data.id, cid: data.cid });
366
+ createdFolders.push({
367
+ name: folder.name,
368
+ relativePath: folder.relativePath,
369
+ id: data.id,
370
+ entityCid: data.cid
371
+ });
372
+ completedEntities++;
373
+ reportProgress({
374
+ phase: "creating",
375
+ completedEntities,
376
+ currentItem: folder.relativePath
377
+ });
378
+ } catch (err) {
379
+ const errorMsg = err instanceof Error ? err.message : String(err);
380
+ if (continueOnError) {
381
+ errors.push({ path: folder.relativePath, error: `Folder creation failed: ${errorMsg}` });
382
+ completedEntities++;
383
+ } else {
384
+ throw new Error(`Failed to create folder ${folder.relativePath}: ${errorMsg}`);
385
+ }
386
+ }
387
+ })
388
+ );
318
389
  }
319
- reportProgress({
320
- phase: "creating-files",
321
- totalFiles: preparedFiles.length,
322
- completedFiles: 0
323
- });
324
- let fileCreateProgress = 0;
325
- await parallelLimit(preparedFiles, concurrency, async (file) => {
390
+ const FILE_CREATION_CONCURRENCY = 50;
391
+ await parallelLimit(preparedFiles, FILE_CREATION_CONCURRENCY, async (file) => {
326
392
  try {
393
+ const parentPath = getParentPath(file.relativePath);
394
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
395
+ const parentType = parentPath ? "folder" : parentId === collectionId ? "collection" : "folder";
327
396
  const fileBody = {
328
397
  key: file.cid,
329
- // Use CID as storage key (best practice)
330
398
  filename: file.name,
331
399
  content_type: file.mimeType,
332
400
  size: file.size,
333
401
  cid: file.cid,
334
- collection: collectionId
402
+ collection: collectionId,
403
+ relationships: [{ predicate: "in", peer: parentId, peer_type: parentType }]
335
404
  };
336
405
  const { data, error } = await client.api.POST("/files", {
337
406
  body: fileBody
338
407
  });
339
408
  if (error || !data) {
340
- throw new Error(JSON.stringify(error));
409
+ throw new Error(`Entity creation failed: ${JSON.stringify(error)}`);
341
410
  }
342
411
  createdFiles.push({
343
412
  ...file,
@@ -346,166 +415,177 @@ async function uploadTree(client, tree, options) {
346
415
  uploadUrl: data.upload_url,
347
416
  uploadExpiresAt: data.upload_expires_at
348
417
  });
349
- fileCreateProgress++;
418
+ completedEntities++;
350
419
  reportProgress({
351
- phase: "creating-files",
352
- completedFiles: fileCreateProgress,
353
- currentFile: file.relativePath
420
+ phase: "creating",
421
+ completedEntities,
422
+ currentItem: file.relativePath
354
423
  });
355
424
  } catch (err) {
356
425
  const errorMsg = err instanceof Error ? err.message : String(err);
357
426
  if (continueOnError) {
358
- errors.push({ path: file.relativePath, error: `File creation failed: ${errorMsg}` });
427
+ errors.push({ path: file.relativePath, error: errorMsg });
428
+ completedEntities++;
359
429
  } else {
360
430
  throw new Error(`Failed to create file ${file.relativePath}: ${errorMsg}`);
361
431
  }
362
432
  }
363
433
  });
364
- const totalBytes = createdFiles.reduce((sum, f) => sum + f.size, 0);
365
- let bytesUploaded = 0;
366
- reportProgress({
367
- phase: "uploading-content",
368
- totalFiles: createdFiles.length,
369
- completedFiles: 0,
370
- totalBytes,
371
- bytesUploaded: 0
372
- });
373
- let uploadProgress = 0;
374
- await parallelLimit(createdFiles, concurrency, async (file) => {
375
- try {
376
- const data = await file.getData();
377
- let body;
378
- if (data instanceof Blob) {
379
- body = data;
380
- } else if (data instanceof Uint8Array) {
381
- const arrayBuffer = new ArrayBuffer(data.byteLength);
382
- new Uint8Array(arrayBuffer).set(data);
383
- body = new Blob([arrayBuffer], { type: file.mimeType });
384
- } else {
385
- body = new Blob([data], { type: file.mimeType });
386
- }
387
- const response = await fetch(file.uploadUrl, {
388
- method: "PUT",
389
- body,
390
- headers: {
391
- "Content-Type": file.mimeType
392
- }
393
- });
394
- if (!response.ok) {
395
- throw new Error(`Upload failed with status ${response.status}`);
396
- }
397
- bytesUploaded += file.size;
398
- uploadProgress++;
399
- reportProgress({
400
- phase: "uploading-content",
401
- completedFiles: uploadProgress,
402
- currentFile: file.relativePath,
403
- bytesUploaded,
404
- totalBytes
405
- });
406
- } catch (err) {
407
- const errorMsg = err instanceof Error ? err.message : String(err);
408
- if (continueOnError) {
409
- errors.push({ path: file.relativePath, error: `Upload failed: ${errorMsg}` });
410
- } else {
411
- throw new Error(`Failed to upload ${file.relativePath}: ${errorMsg}`);
412
- }
413
- }
414
- });
415
- reportProgress({ phase: "linking" });
416
- const filePathToEntity = /* @__PURE__ */ new Map();
417
- for (const file of createdFiles) {
418
- filePathToEntity.set(file.relativePath, file);
419
- }
420
- const parentGroups = /* @__PURE__ */ new Map();
434
+ const childrenByParent = /* @__PURE__ */ new Map();
421
435
  for (const folder of createdFolders) {
422
436
  const parentPath = getParentPath(folder.relativePath);
423
- let parentId;
424
- if (parentPath === null) {
425
- parentId = rootParentId;
426
- } else {
427
- const parentFolder = folderPathToEntity.get(parentPath);
428
- if (!parentFolder) {
429
- errors.push({
430
- path: folder.relativePath,
431
- error: `Parent folder not found: ${parentPath}`
432
- });
433
- continue;
434
- }
435
- parentId = parentFolder.id;
436
- }
437
- if (!parentGroups.has(parentId)) {
438
- parentGroups.set(parentId, { folderId: parentId, children: [] });
439
- }
440
- parentGroups.get(parentId).children.push({ id: folder.id });
437
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
438
+ if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
439
+ childrenByParent.get(parentId).push({ id: folder.id, type: "folder" });
441
440
  }
442
441
  for (const file of createdFiles) {
443
442
  const parentPath = getParentPath(file.relativePath);
444
- let parentId;
445
- if (parentPath === null) {
446
- parentId = rootParentId;
447
- } else {
448
- const parentFolder = folderPathToEntity.get(parentPath);
449
- if (!parentFolder) {
450
- errors.push({
451
- path: file.relativePath,
452
- error: `Parent folder not found: ${parentPath}`
453
- });
454
- continue;
455
- }
456
- parentId = parentFolder.id;
457
- }
458
- if (!parentGroups.has(parentId)) {
459
- parentGroups.set(parentId, { folderId: parentId, children: [] });
460
- }
461
- parentGroups.get(parentId).children.push({ id: file.id });
443
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
444
+ if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
445
+ childrenByParent.get(parentId).push({ id: file.id, type: "file" });
462
446
  }
463
- for (const [parentId, group] of parentGroups) {
464
- if (group.children.length === 0) continue;
447
+ const totalParents = childrenByParent.size;
448
+ let completedParents = 0;
449
+ reportProgress({ phase: "backlinking", totalParents, completedParents: 0 });
450
+ const parentEntries = [...childrenByParent.entries()];
451
+ await parallelLimit(parentEntries, concurrency, async ([parentId, children]) => {
465
452
  try {
466
- let expectTip;
467
- if (parentId === collectionId) {
468
- const { data, error: error2 } = await client.api.GET("/collections/{id}", {
469
- params: { path: { id: collectionId } }
453
+ const isCollection = parentId === collectionId;
454
+ const relationshipsAdd = children.map((child) => ({
455
+ predicate: "contains",
456
+ peer: child.id,
457
+ peer_type: child.type
458
+ }));
459
+ if (isCollection) {
460
+ const { data: collData, error: getError } = await client.api.GET("/collections/{id}", {
461
+ params: { path: { id: parentId } }
470
462
  });
471
- if (error2 || !data) {
472
- throw new Error(`Failed to fetch collection CID: ${JSON.stringify(error2)}`);
463
+ if (getError || !collData) {
464
+ throw new Error(`Failed to fetch collection: ${JSON.stringify(getError)}`);
465
+ }
466
+ const updateBody = {
467
+ expect_tip: collData.cid,
468
+ relationships_add: relationshipsAdd,
469
+ note: note ? `${note} (backlink)` : "Upload backlink"
470
+ };
471
+ const { error } = await client.api.PUT("/collections/{id}", {
472
+ params: { path: { id: parentId } },
473
+ body: updateBody
474
+ });
475
+ if (error) {
476
+ throw new Error(JSON.stringify(error));
473
477
  }
474
- expectTip = data.cid;
475
478
  } else {
476
- const { data, error: error2 } = await client.api.GET("/folders/{id}", {
479
+ const { data: folderData, error: getError } = await client.api.GET("/folders/{id}", {
477
480
  params: { path: { id: parentId } }
478
481
  });
479
- if (error2 || !data) {
480
- throw new Error(`Failed to fetch folder CID: ${JSON.stringify(error2)}`);
482
+ if (getError || !folderData) {
483
+ throw new Error(`Failed to fetch folder: ${JSON.stringify(getError)}`);
484
+ }
485
+ const updateBody = {
486
+ expect_tip: folderData.cid,
487
+ relationships_add: relationshipsAdd,
488
+ note: note ? `${note} (backlink)` : "Upload backlink"
489
+ };
490
+ const { error } = await client.api.PUT("/folders/{id}", {
491
+ params: { path: { id: parentId } },
492
+ body: updateBody
493
+ });
494
+ if (error) {
495
+ throw new Error(JSON.stringify(error));
481
496
  }
482
- expectTip = data.cid;
483
497
  }
484
- const bulkBody = {
485
- expect_tip: expectTip,
486
- children: group.children,
487
- note
488
- };
489
- const { error } = await client.api.POST("/folders/{id}/children/bulk", {
490
- params: { path: { id: parentId } },
491
- body: bulkBody
498
+ completedParents++;
499
+ reportProgress({
500
+ phase: "backlinking",
501
+ totalParents,
502
+ completedParents,
503
+ currentItem: `parent:${parentId}`
492
504
  });
493
- if (error) {
494
- throw new Error(JSON.stringify(error));
495
- }
496
505
  } catch (err) {
497
506
  const errorMsg = err instanceof Error ? err.message : String(err);
498
507
  if (continueOnError) {
499
- errors.push({
500
- path: `parent:${parentId}`,
501
- error: `Bulk linking failed: ${errorMsg}`
502
- });
508
+ errors.push({ path: `parent:${parentId}`, error: `Backlink failed: ${errorMsg}` });
509
+ completedParents++;
503
510
  } else {
504
- throw new Error(`Failed to link children to ${parentId}: ${errorMsg}`);
511
+ throw new Error(`Failed to backlink parent ${parentId}: ${errorMsg}`);
505
512
  }
506
513
  }
507
- }
508
- reportProgress({ phase: "complete" });
514
+ });
515
+ reportProgress({ phase: "uploading", bytesUploaded: 0 });
516
+ const pool = new BytePool();
517
+ await Promise.all(
518
+ createdFiles.map(async (file) => {
519
+ await pool.run(file.size, async () => {
520
+ try {
521
+ const fileData = await file.getData();
522
+ let body;
523
+ if (fileData instanceof Blob) {
524
+ body = fileData;
525
+ } else if (fileData instanceof Uint8Array) {
526
+ const arrayBuffer = new ArrayBuffer(fileData.byteLength);
527
+ new Uint8Array(arrayBuffer).set(fileData);
528
+ body = new Blob([arrayBuffer], { type: file.mimeType });
529
+ } else {
530
+ body = new Blob([fileData], { type: file.mimeType });
531
+ }
532
+ const uploadResponse = await fetch(file.uploadUrl, {
533
+ method: "PUT",
534
+ body,
535
+ headers: { "Content-Type": file.mimeType }
536
+ });
537
+ if (!uploadResponse.ok) {
538
+ throw new Error(`S3 upload failed with status ${uploadResponse.status}`);
539
+ }
540
+ let confirmTip = file.entityCid;
541
+ let confirmAttempts = 0;
542
+ const MAX_CONFIRM_ATTEMPTS = 3;
543
+ while (confirmAttempts < MAX_CONFIRM_ATTEMPTS) {
544
+ confirmAttempts++;
545
+ const { error: confirmError } = await client.api.POST("/files/{id}/confirm-upload", {
546
+ params: { path: { id: file.id } },
547
+ body: {
548
+ expect_tip: confirmTip,
549
+ note: note ? `${note} (confirmed)` : "Upload confirmed"
550
+ }
551
+ });
552
+ if (!confirmError) {
553
+ break;
554
+ }
555
+ const errorStr = JSON.stringify(confirmError);
556
+ if (errorStr.includes("409") || errorStr.includes("CAS") || errorStr.includes("conflict")) {
557
+ const { data: currentFile, error: fetchError } = await client.api.GET("/files/{id}", {
558
+ params: { path: { id: file.id } }
559
+ });
560
+ if (fetchError || !currentFile) {
561
+ throw new Error(`Failed to fetch file for confirm retry: ${JSON.stringify(fetchError)}`);
562
+ }
563
+ confirmTip = currentFile.cid;
564
+ } else {
565
+ throw new Error(`Confirm upload failed: ${errorStr}`);
566
+ }
567
+ }
568
+ if (confirmAttempts >= MAX_CONFIRM_ATTEMPTS) {
569
+ throw new Error(`Confirm upload failed after ${MAX_CONFIRM_ATTEMPTS} CAS retries`);
570
+ }
571
+ bytesUploaded += file.size;
572
+ reportProgress({
573
+ phase: "uploading",
574
+ bytesUploaded,
575
+ currentItem: file.relativePath
576
+ });
577
+ } catch (err) {
578
+ const errorMsg = err instanceof Error ? err.message : String(err);
579
+ if (continueOnError) {
580
+ errors.push({ path: file.relativePath, error: `Upload failed: ${errorMsg}` });
581
+ } else {
582
+ throw new Error(`Failed to upload ${file.relativePath}: ${errorMsg}`);
583
+ }
584
+ }
585
+ });
586
+ })
587
+ );
588
+ reportProgress({ phase: "complete", totalParents, completedParents, bytesUploaded });
509
589
  const resultFolders = createdFolders.map((f) => ({
510
590
  id: f.id,
511
591
  cid: f.entityCid,
@@ -696,12 +776,12 @@ var FolderOperations = class {
696
776
  concurrency: options.concurrency,
697
777
  onProgress: options.onProgress ? (p) => {
698
778
  options.onProgress({
699
- phase: p.phase === "computing-cids" || p.phase === "creating-folders" ? "creating-folders" : p.phase === "creating-files" || p.phase === "uploading-content" ? "uploading-files" : p.phase === "linking" ? "linking" : p.phase === "complete" ? "complete" : "scanning",
700
- totalFiles: p.totalFiles,
701
- completedFiles: p.completedFiles,
702
- totalFolders: p.totalFolders,
703
- completedFolders: p.completedFolders,
704
- currentFile: p.currentFile
779
+ phase: p.phase === "computing-cids" ? "creating-folders" : p.phase === "creating" ? "uploading-files" : p.phase === "backlinking" ? "linking" : p.phase === "complete" ? "complete" : "scanning",
780
+ totalFiles: p.totalEntities,
781
+ completedFiles: p.completedEntities,
782
+ totalFolders: p.totalParents,
783
+ completedFolders: p.completedParents,
784
+ currentFile: p.currentItem
705
785
  });
706
786
  } : void 0
707
787
  });
@@ -784,6 +864,8 @@ export {
784
864
  NotFoundError,
785
865
  ValidationError,
786
866
  createArkeClient,
867
+ getAuthorizationHeader,
868
+ isApiKey,
787
869
  parseApiError
788
870
  };
789
871
  //# sourceMappingURL=index.js.map