@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.cjs CHANGED
@@ -42,6 +42,8 @@ __export(src_exports, {
42
42
  NotFoundError: () => NotFoundError,
43
43
  ValidationError: () => ValidationError,
44
44
  createArkeClient: () => createArkeClient,
45
+ getAuthorizationHeader: () => getAuthorizationHeader,
46
+ isApiKey: () => isApiKey,
45
47
  parseApiError: () => parseApiError
46
48
  });
47
49
  module.exports = __toCommonJS(src_exports);
@@ -136,6 +138,15 @@ function parseApiError(status, body) {
136
138
  }
137
139
 
138
140
  // src/client/ArkeClient.ts
141
+ function isApiKey(token) {
142
+ return token.startsWith("ak_") || token.startsWith("uk_");
143
+ }
144
+ function getAuthorizationHeader(token) {
145
+ if (isApiKey(token)) {
146
+ return `ApiKey ${token}`;
147
+ }
148
+ return `Bearer ${token}`;
149
+ }
139
150
  var ArkeClient = class {
140
151
  constructor(config = {}) {
141
152
  this.config = {
@@ -150,7 +161,7 @@ var ArkeClient = class {
150
161
  ...this.config.headers
151
162
  };
152
163
  if (this.config.authToken) {
153
- headers["Authorization"] = `Bearer ${this.config.authToken}`;
164
+ headers["Authorization"] = getAuthorizationHeader(this.config.authToken);
154
165
  }
155
166
  if (this.config.network === "test") {
156
167
  headers["X-Arke-Network"] = "test";
@@ -218,6 +229,15 @@ async function computeCid(data) {
218
229
  }
219
230
 
220
231
  // src/operations/upload/engine.ts
232
+ var PHASE_COUNT = 4;
233
+ var PHASE_INDEX = {
234
+ "computing-cids": 0,
235
+ "creating": 1,
236
+ "backlinking": 2,
237
+ "uploading": 3,
238
+ "complete": 4,
239
+ "error": -1
240
+ };
221
241
  async function parallelLimit(items, concurrency, fn) {
222
242
  const results = [];
223
243
  let index = 0;
@@ -232,24 +252,83 @@ async function parallelLimit(items, concurrency, fn) {
232
252
  await Promise.all(workers);
233
253
  return results;
234
254
  }
255
+ var TARGET_BYTES_IN_FLIGHT = 200 * 1024 * 1024;
256
+ var BytePool = class {
257
+ constructor(targetBytes = TARGET_BYTES_IN_FLIGHT) {
258
+ this.targetBytes = targetBytes;
259
+ this.bytesInFlight = 0;
260
+ this.waitQueue = [];
261
+ }
262
+ async run(size, fn) {
263
+ while (this.bytesInFlight > 0 && this.bytesInFlight + size > this.targetBytes) {
264
+ await new Promise((resolve) => this.waitQueue.push(resolve));
265
+ }
266
+ this.bytesInFlight += size;
267
+ try {
268
+ return await fn();
269
+ } finally {
270
+ this.bytesInFlight -= size;
271
+ const next = this.waitQueue.shift();
272
+ if (next) next();
273
+ }
274
+ }
275
+ };
235
276
  function getParentPath(relativePath) {
236
277
  const lastSlash = relativePath.lastIndexOf("/");
237
278
  if (lastSlash === -1) return null;
238
279
  return relativePath.slice(0, lastSlash);
239
280
  }
281
+ function groupFoldersByDepth(folders) {
282
+ const byDepth = /* @__PURE__ */ new Map();
283
+ for (const folder of folders) {
284
+ const depth = folder.relativePath.split("/").length - 1;
285
+ if (!byDepth.has(depth)) byDepth.set(depth, []);
286
+ byDepth.get(depth).push(folder);
287
+ }
288
+ return byDepth;
289
+ }
240
290
  async function uploadTree(client, tree, options) {
241
- const { target, onProgress, concurrency = 5, continueOnError = false, note } = options;
291
+ const { target, onProgress, concurrency = 10, continueOnError = false, note } = options;
242
292
  const errors = [];
243
293
  const createdFolders = [];
244
294
  const createdFiles = [];
295
+ const foldersByPath = /* @__PURE__ */ new Map();
296
+ const totalEntities = tree.files.length + tree.folders.length;
297
+ const totalBytes = tree.files.reduce((sum, f) => sum + f.size, 0);
298
+ let completedEntities = 0;
299
+ let bytesUploaded = 0;
245
300
  const reportProgress = (progress) => {
246
301
  if (onProgress) {
302
+ const phase = progress.phase || "computing-cids";
303
+ const phaseIndex = PHASE_INDEX[phase] ?? -1;
304
+ let phasePercent = 0;
305
+ if (phase === "computing-cids") {
306
+ const done = progress.completedEntities ?? completedEntities;
307
+ phasePercent = tree.files.length > 0 ? Math.round(done / tree.files.length * 100) : 100;
308
+ } else if (phase === "creating") {
309
+ const done = progress.completedEntities ?? completedEntities;
310
+ phasePercent = totalEntities > 0 ? Math.round(done / totalEntities * 100) : 100;
311
+ } else if (phase === "backlinking") {
312
+ const done = progress.completedParents ?? 0;
313
+ const total = progress.totalParents ?? 0;
314
+ phasePercent = total > 0 ? Math.round(done / total * 100) : 100;
315
+ } else if (phase === "uploading") {
316
+ const done = progress.bytesUploaded ?? bytesUploaded;
317
+ phasePercent = totalBytes > 0 ? Math.round(done / totalBytes * 100) : 100;
318
+ } else if (phase === "complete") {
319
+ phasePercent = 100;
320
+ }
247
321
  onProgress({
248
- phase: "scanning",
249
- totalFiles: tree.files.length,
250
- completedFiles: 0,
251
- totalFolders: tree.folders.length,
252
- completedFolders: 0,
322
+ phase,
323
+ phaseIndex,
324
+ phaseCount: PHASE_COUNT,
325
+ phasePercent,
326
+ totalEntities,
327
+ completedEntities,
328
+ totalParents: 0,
329
+ completedParents: 0,
330
+ totalBytes,
331
+ bytesUploaded,
253
332
  ...progress
254
333
  });
255
334
  }
@@ -259,7 +338,6 @@ async function uploadTree(client, tree, options) {
259
338
  let collectionCid;
260
339
  let collectionCreated = false;
261
340
  if (target.createCollection) {
262
- reportProgress({ phase: "scanning", currentFolder: "Creating collection..." });
263
341
  const collectionBody = {
264
342
  label: target.createCollection.label,
265
343
  description: target.createCollection.description,
@@ -288,26 +366,19 @@ async function uploadTree(client, tree, options) {
288
366
  throw new Error("Must provide either collectionId or createCollection in target");
289
367
  }
290
368
  const rootParentId = target.parentId ?? collectionId;
291
- reportProgress({
292
- phase: "computing-cids",
293
- totalFiles: tree.files.length,
294
- completedFiles: 0
295
- });
369
+ reportProgress({ phase: "computing-cids", completedEntities: 0 });
296
370
  const preparedFiles = [];
297
371
  let cidProgress = 0;
298
- await parallelLimit(tree.files, concurrency, async (file) => {
372
+ await parallelLimit(tree.files, Math.max(concurrency, 20), async (file) => {
299
373
  try {
300
374
  const data = await file.getData();
301
375
  const cid = await computeCid(data);
302
- preparedFiles.push({
303
- ...file,
304
- cid
305
- });
376
+ preparedFiles.push({ ...file, cid });
306
377
  cidProgress++;
307
378
  reportProgress({
308
379
  phase: "computing-cids",
309
- completedFiles: cidProgress,
310
- currentFile: file.relativePath
380
+ completedEntities: cidProgress,
381
+ currentItem: file.relativePath
311
382
  });
312
383
  } catch (err) {
313
384
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -318,74 +389,74 @@ async function uploadTree(client, tree, options) {
318
389
  }
319
390
  }
320
391
  });
321
- reportProgress({
322
- phase: "creating-folders",
323
- totalFolders: tree.folders.length,
324
- completedFolders: 0
325
- });
326
- const sortedFolders = [...tree.folders].sort(
327
- (a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length
328
- );
329
- for (let i = 0; i < sortedFolders.length; i++) {
330
- const folder = sortedFolders[i];
331
- try {
332
- const folderBody = {
333
- label: folder.name,
334
- collection: collectionId,
335
- note
336
- };
337
- const { data, error } = await client.api.POST("/folders", {
338
- body: folderBody
339
- });
340
- if (error || !data) {
341
- throw new Error(JSON.stringify(error));
342
- }
343
- createdFolders.push({
344
- name: folder.name,
345
- relativePath: folder.relativePath,
346
- id: data.id,
347
- entityCid: data.cid
348
- });
349
- reportProgress({
350
- phase: "creating-folders",
351
- completedFolders: i + 1,
352
- currentFolder: folder.relativePath
353
- });
354
- } catch (err) {
355
- const errorMsg = err instanceof Error ? err.message : String(err);
356
- if (continueOnError) {
357
- errors.push({ path: folder.relativePath, error: `Folder creation failed: ${errorMsg}` });
358
- } else {
359
- throw new Error(`Failed to create folder ${folder.relativePath}: ${errorMsg}`);
360
- }
361
- }
362
- }
363
- const folderPathToEntity = /* @__PURE__ */ new Map();
364
- for (const folder of createdFolders) {
365
- folderPathToEntity.set(folder.relativePath, folder);
392
+ reportProgress({ phase: "creating", completedEntities: 0 });
393
+ const foldersByDepth = groupFoldersByDepth(tree.folders);
394
+ const sortedDepths = [...foldersByDepth.keys()].sort((a, b) => a - b);
395
+ for (const depth of sortedDepths) {
396
+ const foldersAtDepth = foldersByDepth.get(depth);
397
+ await Promise.all(
398
+ foldersAtDepth.map(async (folder) => {
399
+ try {
400
+ const parentPath = getParentPath(folder.relativePath);
401
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
402
+ const parentType = parentPath ? "folder" : parentId === collectionId ? "collection" : "folder";
403
+ const folderBody = {
404
+ label: folder.name,
405
+ collection: collectionId,
406
+ note,
407
+ relationships: [{ predicate: "in", peer: parentId, peer_type: parentType }]
408
+ };
409
+ const { data, error } = await client.api.POST("/folders", {
410
+ body: folderBody
411
+ });
412
+ if (error || !data) {
413
+ throw new Error(JSON.stringify(error));
414
+ }
415
+ foldersByPath.set(folder.relativePath, { id: data.id, cid: data.cid });
416
+ createdFolders.push({
417
+ name: folder.name,
418
+ relativePath: folder.relativePath,
419
+ id: data.id,
420
+ entityCid: data.cid
421
+ });
422
+ completedEntities++;
423
+ reportProgress({
424
+ phase: "creating",
425
+ completedEntities,
426
+ currentItem: folder.relativePath
427
+ });
428
+ } catch (err) {
429
+ const errorMsg = err instanceof Error ? err.message : String(err);
430
+ if (continueOnError) {
431
+ errors.push({ path: folder.relativePath, error: `Folder creation failed: ${errorMsg}` });
432
+ completedEntities++;
433
+ } else {
434
+ throw new Error(`Failed to create folder ${folder.relativePath}: ${errorMsg}`);
435
+ }
436
+ }
437
+ })
438
+ );
366
439
  }
367
- reportProgress({
368
- phase: "creating-files",
369
- totalFiles: preparedFiles.length,
370
- completedFiles: 0
371
- });
372
- let fileCreateProgress = 0;
373
- await parallelLimit(preparedFiles, concurrency, async (file) => {
440
+ const FILE_CREATION_CONCURRENCY = 50;
441
+ await parallelLimit(preparedFiles, FILE_CREATION_CONCURRENCY, async (file) => {
374
442
  try {
443
+ const parentPath = getParentPath(file.relativePath);
444
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
445
+ const parentType = parentPath ? "folder" : parentId === collectionId ? "collection" : "folder";
375
446
  const fileBody = {
376
447
  key: file.cid,
377
- // Use CID as storage key (best practice)
378
448
  filename: file.name,
379
449
  content_type: file.mimeType,
380
450
  size: file.size,
381
451
  cid: file.cid,
382
- collection: collectionId
452
+ collection: collectionId,
453
+ relationships: [{ predicate: "in", peer: parentId, peer_type: parentType }]
383
454
  };
384
455
  const { data, error } = await client.api.POST("/files", {
385
456
  body: fileBody
386
457
  });
387
458
  if (error || !data) {
388
- throw new Error(JSON.stringify(error));
459
+ throw new Error(`Entity creation failed: ${JSON.stringify(error)}`);
389
460
  }
390
461
  createdFiles.push({
391
462
  ...file,
@@ -394,166 +465,177 @@ async function uploadTree(client, tree, options) {
394
465
  uploadUrl: data.upload_url,
395
466
  uploadExpiresAt: data.upload_expires_at
396
467
  });
397
- fileCreateProgress++;
468
+ completedEntities++;
398
469
  reportProgress({
399
- phase: "creating-files",
400
- completedFiles: fileCreateProgress,
401
- currentFile: file.relativePath
470
+ phase: "creating",
471
+ completedEntities,
472
+ currentItem: file.relativePath
402
473
  });
403
474
  } catch (err) {
404
475
  const errorMsg = err instanceof Error ? err.message : String(err);
405
476
  if (continueOnError) {
406
- errors.push({ path: file.relativePath, error: `File creation failed: ${errorMsg}` });
477
+ errors.push({ path: file.relativePath, error: errorMsg });
478
+ completedEntities++;
407
479
  } else {
408
480
  throw new Error(`Failed to create file ${file.relativePath}: ${errorMsg}`);
409
481
  }
410
482
  }
411
483
  });
412
- const totalBytes = createdFiles.reduce((sum, f) => sum + f.size, 0);
413
- let bytesUploaded = 0;
414
- reportProgress({
415
- phase: "uploading-content",
416
- totalFiles: createdFiles.length,
417
- completedFiles: 0,
418
- totalBytes,
419
- bytesUploaded: 0
420
- });
421
- let uploadProgress = 0;
422
- await parallelLimit(createdFiles, concurrency, async (file) => {
423
- try {
424
- const data = await file.getData();
425
- let body;
426
- if (data instanceof Blob) {
427
- body = data;
428
- } else if (data instanceof Uint8Array) {
429
- const arrayBuffer = new ArrayBuffer(data.byteLength);
430
- new Uint8Array(arrayBuffer).set(data);
431
- body = new Blob([arrayBuffer], { type: file.mimeType });
432
- } else {
433
- body = new Blob([data], { type: file.mimeType });
434
- }
435
- const response = await fetch(file.uploadUrl, {
436
- method: "PUT",
437
- body,
438
- headers: {
439
- "Content-Type": file.mimeType
440
- }
441
- });
442
- if (!response.ok) {
443
- throw new Error(`Upload failed with status ${response.status}`);
444
- }
445
- bytesUploaded += file.size;
446
- uploadProgress++;
447
- reportProgress({
448
- phase: "uploading-content",
449
- completedFiles: uploadProgress,
450
- currentFile: file.relativePath,
451
- bytesUploaded,
452
- totalBytes
453
- });
454
- } catch (err) {
455
- const errorMsg = err instanceof Error ? err.message : String(err);
456
- if (continueOnError) {
457
- errors.push({ path: file.relativePath, error: `Upload failed: ${errorMsg}` });
458
- } else {
459
- throw new Error(`Failed to upload ${file.relativePath}: ${errorMsg}`);
460
- }
461
- }
462
- });
463
- reportProgress({ phase: "linking" });
464
- const filePathToEntity = /* @__PURE__ */ new Map();
465
- for (const file of createdFiles) {
466
- filePathToEntity.set(file.relativePath, file);
467
- }
468
- const parentGroups = /* @__PURE__ */ new Map();
484
+ const childrenByParent = /* @__PURE__ */ new Map();
469
485
  for (const folder of createdFolders) {
470
486
  const parentPath = getParentPath(folder.relativePath);
471
- let parentId;
472
- if (parentPath === null) {
473
- parentId = rootParentId;
474
- } else {
475
- const parentFolder = folderPathToEntity.get(parentPath);
476
- if (!parentFolder) {
477
- errors.push({
478
- path: folder.relativePath,
479
- error: `Parent folder not found: ${parentPath}`
480
- });
481
- continue;
482
- }
483
- parentId = parentFolder.id;
484
- }
485
- if (!parentGroups.has(parentId)) {
486
- parentGroups.set(parentId, { folderId: parentId, children: [] });
487
- }
488
- parentGroups.get(parentId).children.push({ id: folder.id });
487
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
488
+ if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
489
+ childrenByParent.get(parentId).push({ id: folder.id, type: "folder" });
489
490
  }
490
491
  for (const file of createdFiles) {
491
492
  const parentPath = getParentPath(file.relativePath);
492
- let parentId;
493
- if (parentPath === null) {
494
- parentId = rootParentId;
495
- } else {
496
- const parentFolder = folderPathToEntity.get(parentPath);
497
- if (!parentFolder) {
498
- errors.push({
499
- path: file.relativePath,
500
- error: `Parent folder not found: ${parentPath}`
501
- });
502
- continue;
503
- }
504
- parentId = parentFolder.id;
505
- }
506
- if (!parentGroups.has(parentId)) {
507
- parentGroups.set(parentId, { folderId: parentId, children: [] });
508
- }
509
- parentGroups.get(parentId).children.push({ id: file.id });
493
+ const parentId = parentPath ? foldersByPath.get(parentPath).id : rootParentId;
494
+ if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
495
+ childrenByParent.get(parentId).push({ id: file.id, type: "file" });
510
496
  }
511
- for (const [parentId, group] of parentGroups) {
512
- if (group.children.length === 0) continue;
497
+ const totalParents = childrenByParent.size;
498
+ let completedParents = 0;
499
+ reportProgress({ phase: "backlinking", totalParents, completedParents: 0 });
500
+ const parentEntries = [...childrenByParent.entries()];
501
+ await parallelLimit(parentEntries, concurrency, async ([parentId, children]) => {
513
502
  try {
514
- let expectTip;
515
- if (parentId === collectionId) {
516
- const { data, error: error2 } = await client.api.GET("/collections/{id}", {
517
- params: { path: { id: collectionId } }
503
+ const isCollection = parentId === collectionId;
504
+ const relationshipsAdd = children.map((child) => ({
505
+ predicate: "contains",
506
+ peer: child.id,
507
+ peer_type: child.type
508
+ }));
509
+ if (isCollection) {
510
+ const { data: collData, error: getError } = await client.api.GET("/collections/{id}", {
511
+ params: { path: { id: parentId } }
518
512
  });
519
- if (error2 || !data) {
520
- throw new Error(`Failed to fetch collection CID: ${JSON.stringify(error2)}`);
513
+ if (getError || !collData) {
514
+ throw new Error(`Failed to fetch collection: ${JSON.stringify(getError)}`);
515
+ }
516
+ const updateBody = {
517
+ expect_tip: collData.cid,
518
+ relationships_add: relationshipsAdd,
519
+ note: note ? `${note} (backlink)` : "Upload backlink"
520
+ };
521
+ const { error } = await client.api.PUT("/collections/{id}", {
522
+ params: { path: { id: parentId } },
523
+ body: updateBody
524
+ });
525
+ if (error) {
526
+ throw new Error(JSON.stringify(error));
521
527
  }
522
- expectTip = data.cid;
523
528
  } else {
524
- const { data, error: error2 } = await client.api.GET("/folders/{id}", {
529
+ const { data: folderData, error: getError } = await client.api.GET("/folders/{id}", {
525
530
  params: { path: { id: parentId } }
526
531
  });
527
- if (error2 || !data) {
528
- throw new Error(`Failed to fetch folder CID: ${JSON.stringify(error2)}`);
532
+ if (getError || !folderData) {
533
+ throw new Error(`Failed to fetch folder: ${JSON.stringify(getError)}`);
534
+ }
535
+ const updateBody = {
536
+ expect_tip: folderData.cid,
537
+ relationships_add: relationshipsAdd,
538
+ note: note ? `${note} (backlink)` : "Upload backlink"
539
+ };
540
+ const { error } = await client.api.PUT("/folders/{id}", {
541
+ params: { path: { id: parentId } },
542
+ body: updateBody
543
+ });
544
+ if (error) {
545
+ throw new Error(JSON.stringify(error));
529
546
  }
530
- expectTip = data.cid;
531
547
  }
532
- const bulkBody = {
533
- expect_tip: expectTip,
534
- children: group.children,
535
- note
536
- };
537
- const { error } = await client.api.POST("/folders/{id}/children/bulk", {
538
- params: { path: { id: parentId } },
539
- body: bulkBody
548
+ completedParents++;
549
+ reportProgress({
550
+ phase: "backlinking",
551
+ totalParents,
552
+ completedParents,
553
+ currentItem: `parent:${parentId}`
540
554
  });
541
- if (error) {
542
- throw new Error(JSON.stringify(error));
543
- }
544
555
  } catch (err) {
545
556
  const errorMsg = err instanceof Error ? err.message : String(err);
546
557
  if (continueOnError) {
547
- errors.push({
548
- path: `parent:${parentId}`,
549
- error: `Bulk linking failed: ${errorMsg}`
550
- });
558
+ errors.push({ path: `parent:${parentId}`, error: `Backlink failed: ${errorMsg}` });
559
+ completedParents++;
551
560
  } else {
552
- throw new Error(`Failed to link children to ${parentId}: ${errorMsg}`);
561
+ throw new Error(`Failed to backlink parent ${parentId}: ${errorMsg}`);
553
562
  }
554
563
  }
555
- }
556
- reportProgress({ phase: "complete" });
564
+ });
565
+ reportProgress({ phase: "uploading", bytesUploaded: 0 });
566
+ const pool = new BytePool();
567
+ await Promise.all(
568
+ createdFiles.map(async (file) => {
569
+ await pool.run(file.size, async () => {
570
+ try {
571
+ const fileData = await file.getData();
572
+ let body;
573
+ if (fileData instanceof Blob) {
574
+ body = fileData;
575
+ } else if (fileData instanceof Uint8Array) {
576
+ const arrayBuffer = new ArrayBuffer(fileData.byteLength);
577
+ new Uint8Array(arrayBuffer).set(fileData);
578
+ body = new Blob([arrayBuffer], { type: file.mimeType });
579
+ } else {
580
+ body = new Blob([fileData], { type: file.mimeType });
581
+ }
582
+ const uploadResponse = await fetch(file.uploadUrl, {
583
+ method: "PUT",
584
+ body,
585
+ headers: { "Content-Type": file.mimeType }
586
+ });
587
+ if (!uploadResponse.ok) {
588
+ throw new Error(`S3 upload failed with status ${uploadResponse.status}`);
589
+ }
590
+ let confirmTip = file.entityCid;
591
+ let confirmAttempts = 0;
592
+ const MAX_CONFIRM_ATTEMPTS = 3;
593
+ while (confirmAttempts < MAX_CONFIRM_ATTEMPTS) {
594
+ confirmAttempts++;
595
+ const { error: confirmError } = await client.api.POST("/files/{id}/confirm-upload", {
596
+ params: { path: { id: file.id } },
597
+ body: {
598
+ expect_tip: confirmTip,
599
+ note: note ? `${note} (confirmed)` : "Upload confirmed"
600
+ }
601
+ });
602
+ if (!confirmError) {
603
+ break;
604
+ }
605
+ const errorStr = JSON.stringify(confirmError);
606
+ if (errorStr.includes("409") || errorStr.includes("CAS") || errorStr.includes("conflict")) {
607
+ const { data: currentFile, error: fetchError } = await client.api.GET("/files/{id}", {
608
+ params: { path: { id: file.id } }
609
+ });
610
+ if (fetchError || !currentFile) {
611
+ throw new Error(`Failed to fetch file for confirm retry: ${JSON.stringify(fetchError)}`);
612
+ }
613
+ confirmTip = currentFile.cid;
614
+ } else {
615
+ throw new Error(`Confirm upload failed: ${errorStr}`);
616
+ }
617
+ }
618
+ if (confirmAttempts >= MAX_CONFIRM_ATTEMPTS) {
619
+ throw new Error(`Confirm upload failed after ${MAX_CONFIRM_ATTEMPTS} CAS retries`);
620
+ }
621
+ bytesUploaded += file.size;
622
+ reportProgress({
623
+ phase: "uploading",
624
+ bytesUploaded,
625
+ currentItem: file.relativePath
626
+ });
627
+ } catch (err) {
628
+ const errorMsg = err instanceof Error ? err.message : String(err);
629
+ if (continueOnError) {
630
+ errors.push({ path: file.relativePath, error: `Upload failed: ${errorMsg}` });
631
+ } else {
632
+ throw new Error(`Failed to upload ${file.relativePath}: ${errorMsg}`);
633
+ }
634
+ }
635
+ });
636
+ })
637
+ );
638
+ reportProgress({ phase: "complete", totalParents, completedParents, bytesUploaded });
557
639
  const resultFolders = createdFolders.map((f) => ({
558
640
  id: f.id,
559
641
  cid: f.entityCid,
@@ -744,12 +826,12 @@ var FolderOperations = class {
744
826
  concurrency: options.concurrency,
745
827
  onProgress: options.onProgress ? (p) => {
746
828
  options.onProgress({
747
- 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",
748
- totalFiles: p.totalFiles,
749
- completedFiles: p.completedFiles,
750
- totalFolders: p.totalFolders,
751
- completedFolders: p.completedFolders,
752
- currentFile: p.currentFile
829
+ phase: p.phase === "computing-cids" ? "creating-folders" : p.phase === "creating" ? "uploading-files" : p.phase === "backlinking" ? "linking" : p.phase === "complete" ? "complete" : "scanning",
830
+ totalFiles: p.totalEntities,
831
+ completedFiles: p.completedEntities,
832
+ totalFolders: p.totalParents,
833
+ completedFolders: p.completedParents,
834
+ currentFile: p.currentItem
753
835
  });
754
836
  } : void 0
755
837
  });
@@ -833,6 +915,8 @@ var CryptoOperations = class {
833
915
  NotFoundError,
834
916
  ValidationError,
835
917
  createArkeClient,
918
+ getAuthorizationHeader,
919
+ isApiKey,
836
920
  parseApiError
837
921
  });
838
922
  //# sourceMappingURL=index.cjs.map