@arke-institute/sdk 2.0.0 → 2.1.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.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/operations/index.ts
@@ -22,10 +32,668 @@ var operations_exports = {};
22
32
  __export(operations_exports, {
23
33
  BatchOperations: () => BatchOperations,
24
34
  CryptoOperations: () => CryptoOperations,
25
- FolderOperations: () => FolderOperations
35
+ FolderOperations: () => FolderOperations,
36
+ buildUploadTree: () => buildUploadTree,
37
+ computeCid: () => computeCid,
38
+ getMimeType: () => getMimeType,
39
+ scanDirectory: () => scanDirectory,
40
+ scanFileList: () => scanFileList,
41
+ scanFileSystemEntries: () => scanFileSystemEntries,
42
+ uploadTree: () => uploadTree,
43
+ verifyCid: () => verifyCid
26
44
  });
27
45
  module.exports = __toCommonJS(operations_exports);
28
46
 
47
+ // src/operations/upload/cid.ts
48
+ var import_cid = require("multiformats/cid");
49
+ var import_sha2 = require("multiformats/hashes/sha2");
50
+ var raw = __toESM(require("multiformats/codecs/raw"), 1);
51
+ async function computeCid(data) {
52
+ let bytes;
53
+ if (data instanceof Blob) {
54
+ const buffer = await data.arrayBuffer();
55
+ bytes = new Uint8Array(buffer);
56
+ } else if (data instanceof ArrayBuffer) {
57
+ bytes = new Uint8Array(data);
58
+ } else {
59
+ bytes = data;
60
+ }
61
+ const hash = await import_sha2.sha256.digest(bytes);
62
+ const cid = import_cid.CID.create(1, raw.code, hash);
63
+ return cid.toString();
64
+ }
65
+ async function verifyCid(data, expectedCid) {
66
+ const computed = await computeCid(data);
67
+ return computed === expectedCid;
68
+ }
69
+
70
+ // src/operations/upload/engine.ts
71
+ async function parallelLimit(items, concurrency, fn) {
72
+ const results = [];
73
+ let index = 0;
74
+ async function worker() {
75
+ while (index < items.length) {
76
+ const currentIndex = index++;
77
+ const item = items[currentIndex];
78
+ results[currentIndex] = await fn(item, currentIndex);
79
+ }
80
+ }
81
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
82
+ await Promise.all(workers);
83
+ return results;
84
+ }
85
+ function getParentPath(relativePath) {
86
+ const lastSlash = relativePath.lastIndexOf("/");
87
+ if (lastSlash === -1) return null;
88
+ return relativePath.slice(0, lastSlash);
89
+ }
90
+ async function uploadTree(client, tree, options) {
91
+ const { target, onProgress, concurrency = 5, continueOnError = false, note } = options;
92
+ const errors = [];
93
+ const createdFolders = [];
94
+ const createdFiles = [];
95
+ const reportProgress = (progress) => {
96
+ if (onProgress) {
97
+ onProgress({
98
+ phase: "scanning",
99
+ totalFiles: tree.files.length,
100
+ completedFiles: 0,
101
+ totalFolders: tree.folders.length,
102
+ completedFolders: 0,
103
+ ...progress
104
+ });
105
+ }
106
+ };
107
+ try {
108
+ let collectionId;
109
+ let collectionCid;
110
+ let collectionCreated = false;
111
+ if (target.createCollection) {
112
+ reportProgress({ phase: "scanning", currentFolder: "Creating collection..." });
113
+ const collectionBody = {
114
+ label: target.createCollection.label,
115
+ description: target.createCollection.description,
116
+ roles: target.createCollection.roles,
117
+ note
118
+ };
119
+ const { data, error } = await client.api.POST("/collections", {
120
+ body: collectionBody
121
+ });
122
+ if (error || !data) {
123
+ throw new Error(`Failed to create collection: ${JSON.stringify(error)}`);
124
+ }
125
+ collectionId = data.id;
126
+ collectionCid = data.cid;
127
+ collectionCreated = true;
128
+ } else if (target.collectionId) {
129
+ collectionId = target.collectionId;
130
+ const { data, error } = await client.api.GET("/collections/{id}", {
131
+ params: { path: { id: collectionId } }
132
+ });
133
+ if (error || !data) {
134
+ throw new Error(`Failed to fetch collection: ${JSON.stringify(error)}`);
135
+ }
136
+ collectionCid = data.cid;
137
+ } else {
138
+ throw new Error("Must provide either collectionId or createCollection in target");
139
+ }
140
+ const rootParentId = target.parentId ?? collectionId;
141
+ reportProgress({
142
+ phase: "computing-cids",
143
+ totalFiles: tree.files.length,
144
+ completedFiles: 0
145
+ });
146
+ const preparedFiles = [];
147
+ let cidProgress = 0;
148
+ await parallelLimit(tree.files, concurrency, async (file) => {
149
+ try {
150
+ const data = await file.getData();
151
+ const cid = await computeCid(data);
152
+ preparedFiles.push({
153
+ ...file,
154
+ cid
155
+ });
156
+ cidProgress++;
157
+ reportProgress({
158
+ phase: "computing-cids",
159
+ completedFiles: cidProgress,
160
+ currentFile: file.relativePath
161
+ });
162
+ } catch (err) {
163
+ const errorMsg = err instanceof Error ? err.message : String(err);
164
+ if (continueOnError) {
165
+ errors.push({ path: file.relativePath, error: `CID computation failed: ${errorMsg}` });
166
+ } else {
167
+ throw new Error(`Failed to compute CID for ${file.relativePath}: ${errorMsg}`);
168
+ }
169
+ }
170
+ });
171
+ reportProgress({
172
+ phase: "creating-folders",
173
+ totalFolders: tree.folders.length,
174
+ completedFolders: 0
175
+ });
176
+ const sortedFolders = [...tree.folders].sort(
177
+ (a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length
178
+ );
179
+ for (let i = 0; i < sortedFolders.length; i++) {
180
+ const folder = sortedFolders[i];
181
+ try {
182
+ const folderBody = {
183
+ label: folder.name,
184
+ collection: collectionId,
185
+ note
186
+ };
187
+ const { data, error } = await client.api.POST("/folders", {
188
+ body: folderBody
189
+ });
190
+ if (error || !data) {
191
+ throw new Error(JSON.stringify(error));
192
+ }
193
+ createdFolders.push({
194
+ name: folder.name,
195
+ relativePath: folder.relativePath,
196
+ id: data.id,
197
+ entityCid: data.cid
198
+ });
199
+ reportProgress({
200
+ phase: "creating-folders",
201
+ completedFolders: i + 1,
202
+ currentFolder: folder.relativePath
203
+ });
204
+ } catch (err) {
205
+ const errorMsg = err instanceof Error ? err.message : String(err);
206
+ if (continueOnError) {
207
+ errors.push({ path: folder.relativePath, error: `Folder creation failed: ${errorMsg}` });
208
+ } else {
209
+ throw new Error(`Failed to create folder ${folder.relativePath}: ${errorMsg}`);
210
+ }
211
+ }
212
+ }
213
+ const folderPathToEntity = /* @__PURE__ */ new Map();
214
+ for (const folder of createdFolders) {
215
+ folderPathToEntity.set(folder.relativePath, folder);
216
+ }
217
+ reportProgress({
218
+ phase: "creating-files",
219
+ totalFiles: preparedFiles.length,
220
+ completedFiles: 0
221
+ });
222
+ let fileCreateProgress = 0;
223
+ await parallelLimit(preparedFiles, concurrency, async (file) => {
224
+ try {
225
+ const fileBody = {
226
+ key: file.cid,
227
+ // Use CID as storage key (best practice)
228
+ filename: file.name,
229
+ content_type: file.mimeType,
230
+ size: file.size,
231
+ cid: file.cid,
232
+ collection: collectionId
233
+ };
234
+ const { data, error } = await client.api.POST("/files", {
235
+ body: fileBody
236
+ });
237
+ if (error || !data) {
238
+ throw new Error(JSON.stringify(error));
239
+ }
240
+ createdFiles.push({
241
+ ...file,
242
+ id: data.id,
243
+ entityCid: data.cid,
244
+ uploadUrl: data.upload_url,
245
+ uploadExpiresAt: data.upload_expires_at
246
+ });
247
+ fileCreateProgress++;
248
+ reportProgress({
249
+ phase: "creating-files",
250
+ completedFiles: fileCreateProgress,
251
+ currentFile: file.relativePath
252
+ });
253
+ } catch (err) {
254
+ const errorMsg = err instanceof Error ? err.message : String(err);
255
+ if (continueOnError) {
256
+ errors.push({ path: file.relativePath, error: `File creation failed: ${errorMsg}` });
257
+ } else {
258
+ throw new Error(`Failed to create file ${file.relativePath}: ${errorMsg}`);
259
+ }
260
+ }
261
+ });
262
+ const totalBytes = createdFiles.reduce((sum, f) => sum + f.size, 0);
263
+ let bytesUploaded = 0;
264
+ reportProgress({
265
+ phase: "uploading-content",
266
+ totalFiles: createdFiles.length,
267
+ completedFiles: 0,
268
+ totalBytes,
269
+ bytesUploaded: 0
270
+ });
271
+ let uploadProgress = 0;
272
+ await parallelLimit(createdFiles, concurrency, async (file) => {
273
+ try {
274
+ const data = await file.getData();
275
+ let body;
276
+ if (data instanceof Blob) {
277
+ body = data;
278
+ } else if (data instanceof Uint8Array) {
279
+ const arrayBuffer = new ArrayBuffer(data.byteLength);
280
+ new Uint8Array(arrayBuffer).set(data);
281
+ body = new Blob([arrayBuffer], { type: file.mimeType });
282
+ } else {
283
+ body = new Blob([data], { type: file.mimeType });
284
+ }
285
+ const response = await fetch(file.uploadUrl, {
286
+ method: "PUT",
287
+ body,
288
+ headers: {
289
+ "Content-Type": file.mimeType
290
+ }
291
+ });
292
+ if (!response.ok) {
293
+ throw new Error(`Upload failed with status ${response.status}`);
294
+ }
295
+ bytesUploaded += file.size;
296
+ uploadProgress++;
297
+ reportProgress({
298
+ phase: "uploading-content",
299
+ completedFiles: uploadProgress,
300
+ currentFile: file.relativePath,
301
+ bytesUploaded,
302
+ totalBytes
303
+ });
304
+ } catch (err) {
305
+ const errorMsg = err instanceof Error ? err.message : String(err);
306
+ if (continueOnError) {
307
+ errors.push({ path: file.relativePath, error: `Upload failed: ${errorMsg}` });
308
+ } else {
309
+ throw new Error(`Failed to upload ${file.relativePath}: ${errorMsg}`);
310
+ }
311
+ }
312
+ });
313
+ reportProgress({ phase: "linking" });
314
+ const filePathToEntity = /* @__PURE__ */ new Map();
315
+ for (const file of createdFiles) {
316
+ filePathToEntity.set(file.relativePath, file);
317
+ }
318
+ const parentGroups = /* @__PURE__ */ new Map();
319
+ for (const folder of createdFolders) {
320
+ const parentPath = getParentPath(folder.relativePath);
321
+ let parentId;
322
+ if (parentPath === null) {
323
+ parentId = rootParentId;
324
+ } else {
325
+ const parentFolder = folderPathToEntity.get(parentPath);
326
+ if (!parentFolder) {
327
+ errors.push({
328
+ path: folder.relativePath,
329
+ error: `Parent folder not found: ${parentPath}`
330
+ });
331
+ continue;
332
+ }
333
+ parentId = parentFolder.id;
334
+ }
335
+ if (!parentGroups.has(parentId)) {
336
+ parentGroups.set(parentId, { folderId: parentId, children: [] });
337
+ }
338
+ parentGroups.get(parentId).children.push({ id: folder.id });
339
+ }
340
+ for (const file of createdFiles) {
341
+ const parentPath = getParentPath(file.relativePath);
342
+ let parentId;
343
+ if (parentPath === null) {
344
+ parentId = rootParentId;
345
+ } else {
346
+ const parentFolder = folderPathToEntity.get(parentPath);
347
+ if (!parentFolder) {
348
+ errors.push({
349
+ path: file.relativePath,
350
+ error: `Parent folder not found: ${parentPath}`
351
+ });
352
+ continue;
353
+ }
354
+ parentId = parentFolder.id;
355
+ }
356
+ if (!parentGroups.has(parentId)) {
357
+ parentGroups.set(parentId, { folderId: parentId, children: [] });
358
+ }
359
+ parentGroups.get(parentId).children.push({ id: file.id });
360
+ }
361
+ for (const [parentId, group] of parentGroups) {
362
+ if (group.children.length === 0) continue;
363
+ try {
364
+ let expectTip;
365
+ if (parentId === collectionId) {
366
+ const { data, error: error2 } = await client.api.GET("/collections/{id}", {
367
+ params: { path: { id: collectionId } }
368
+ });
369
+ if (error2 || !data) {
370
+ throw new Error(`Failed to fetch collection CID: ${JSON.stringify(error2)}`);
371
+ }
372
+ expectTip = data.cid;
373
+ } else {
374
+ const { data, error: error2 } = await client.api.GET("/folders/{id}", {
375
+ params: { path: { id: parentId } }
376
+ });
377
+ if (error2 || !data) {
378
+ throw new Error(`Failed to fetch folder CID: ${JSON.stringify(error2)}`);
379
+ }
380
+ expectTip = data.cid;
381
+ }
382
+ const bulkBody = {
383
+ expect_tip: expectTip,
384
+ children: group.children,
385
+ note
386
+ };
387
+ const { error } = await client.api.POST("/folders/{id}/children/bulk", {
388
+ params: { path: { id: parentId } },
389
+ body: bulkBody
390
+ });
391
+ if (error) {
392
+ throw new Error(JSON.stringify(error));
393
+ }
394
+ } catch (err) {
395
+ const errorMsg = err instanceof Error ? err.message : String(err);
396
+ if (continueOnError) {
397
+ errors.push({
398
+ path: `parent:${parentId}`,
399
+ error: `Bulk linking failed: ${errorMsg}`
400
+ });
401
+ } else {
402
+ throw new Error(`Failed to link children to ${parentId}: ${errorMsg}`);
403
+ }
404
+ }
405
+ }
406
+ reportProgress({ phase: "complete" });
407
+ const resultFolders = createdFolders.map((f) => ({
408
+ id: f.id,
409
+ cid: f.entityCid,
410
+ type: "folder",
411
+ relativePath: f.relativePath
412
+ }));
413
+ const resultFiles = createdFiles.map((f) => ({
414
+ id: f.id,
415
+ cid: f.entityCid,
416
+ type: "file",
417
+ relativePath: f.relativePath
418
+ }));
419
+ return {
420
+ success: errors.length === 0,
421
+ collection: {
422
+ id: collectionId,
423
+ cid: collectionCid,
424
+ created: collectionCreated
425
+ },
426
+ folders: resultFolders,
427
+ files: resultFiles,
428
+ errors
429
+ };
430
+ } catch (err) {
431
+ const errorMsg = err instanceof Error ? err.message : String(err);
432
+ reportProgress({
433
+ phase: "error",
434
+ error: errorMsg
435
+ });
436
+ return {
437
+ success: false,
438
+ collection: {
439
+ id: target.collectionId ?? "",
440
+ cid: "",
441
+ created: false
442
+ },
443
+ folders: createdFolders.map((f) => ({
444
+ id: f.id,
445
+ cid: f.entityCid,
446
+ type: "folder",
447
+ relativePath: f.relativePath
448
+ })),
449
+ files: createdFiles.map((f) => ({
450
+ id: f.id,
451
+ cid: f.entityCid,
452
+ type: "file",
453
+ relativePath: f.relativePath
454
+ })),
455
+ errors: [...errors, { path: "", error: errorMsg }]
456
+ };
457
+ }
458
+ }
459
+
460
+ // src/operations/upload/scanners.ts
461
+ function getMimeType(filename) {
462
+ const ext = filename.toLowerCase().split(".").pop() || "";
463
+ const mimeTypes = {
464
+ // Images
465
+ jpg: "image/jpeg",
466
+ jpeg: "image/jpeg",
467
+ png: "image/png",
468
+ gif: "image/gif",
469
+ webp: "image/webp",
470
+ svg: "image/svg+xml",
471
+ ico: "image/x-icon",
472
+ bmp: "image/bmp",
473
+ tiff: "image/tiff",
474
+ tif: "image/tiff",
475
+ // Documents
476
+ pdf: "application/pdf",
477
+ doc: "application/msword",
478
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
479
+ xls: "application/vnd.ms-excel",
480
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
481
+ ppt: "application/vnd.ms-powerpoint",
482
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
483
+ odt: "application/vnd.oasis.opendocument.text",
484
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
485
+ odp: "application/vnd.oasis.opendocument.presentation",
486
+ // Text
487
+ txt: "text/plain",
488
+ md: "text/markdown",
489
+ csv: "text/csv",
490
+ html: "text/html",
491
+ htm: "text/html",
492
+ css: "text/css",
493
+ xml: "text/xml",
494
+ rtf: "application/rtf",
495
+ // Code
496
+ js: "text/javascript",
497
+ mjs: "text/javascript",
498
+ ts: "text/typescript",
499
+ jsx: "text/javascript",
500
+ tsx: "text/typescript",
501
+ json: "application/json",
502
+ yaml: "text/yaml",
503
+ yml: "text/yaml",
504
+ // Archives
505
+ zip: "application/zip",
506
+ tar: "application/x-tar",
507
+ gz: "application/gzip",
508
+ rar: "application/vnd.rar",
509
+ "7z": "application/x-7z-compressed",
510
+ // Audio
511
+ mp3: "audio/mpeg",
512
+ wav: "audio/wav",
513
+ ogg: "audio/ogg",
514
+ m4a: "audio/mp4",
515
+ flac: "audio/flac",
516
+ // Video
517
+ mp4: "video/mp4",
518
+ webm: "video/webm",
519
+ avi: "video/x-msvideo",
520
+ mov: "video/quicktime",
521
+ mkv: "video/x-matroska",
522
+ // Fonts
523
+ woff: "font/woff",
524
+ woff2: "font/woff2",
525
+ ttf: "font/ttf",
526
+ otf: "font/otf",
527
+ // Other
528
+ wasm: "application/wasm"
529
+ };
530
+ return mimeTypes[ext] || "application/octet-stream";
531
+ }
532
+ async function scanDirectory(directoryPath, options = {}) {
533
+ const fs = await import("fs/promises");
534
+ const path = await import("path");
535
+ const { ignore = ["node_modules", ".git", ".DS_Store"], includeHidden = false } = options;
536
+ const files = [];
537
+ const folders = [];
538
+ const rootName = path.basename(directoryPath);
539
+ async function scanDir(dirPath, relativePath) {
540
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
541
+ for (const entry of entries) {
542
+ const name = entry.name;
543
+ if (!includeHidden && name.startsWith(".")) {
544
+ continue;
545
+ }
546
+ if (ignore.some((pattern) => name === pattern || name.match(pattern))) {
547
+ continue;
548
+ }
549
+ const fullPath = path.join(dirPath, name);
550
+ const entryRelativePath = relativePath ? `${relativePath}/${name}` : name;
551
+ if (entry.isDirectory()) {
552
+ folders.push({
553
+ name,
554
+ relativePath: entryRelativePath
555
+ });
556
+ await scanDir(fullPath, entryRelativePath);
557
+ } else if (entry.isFile()) {
558
+ const stat = await fs.stat(fullPath);
559
+ files.push({
560
+ name,
561
+ relativePath: entryRelativePath,
562
+ size: stat.size,
563
+ mimeType: getMimeType(name),
564
+ getData: async () => {
565
+ const buffer = await fs.readFile(fullPath);
566
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
567
+ }
568
+ });
569
+ }
570
+ }
571
+ }
572
+ await scanDir(directoryPath, "");
573
+ folders.sort((a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length);
574
+ return { files, folders };
575
+ }
576
+ async function scanFileSystemEntries(entries, options = {}) {
577
+ const { ignore = ["node_modules", ".git", ".DS_Store"] } = options;
578
+ const files = [];
579
+ const folders = [];
580
+ async function processEntry(entry, parentPath) {
581
+ const name = entry.name;
582
+ if (ignore.some((pattern) => name === pattern)) {
583
+ return;
584
+ }
585
+ const relativePath = parentPath ? `${parentPath}/${name}` : name;
586
+ if (entry.isFile) {
587
+ const fileEntry = entry;
588
+ const file = await new Promise((resolve, reject) => {
589
+ fileEntry.file(resolve, reject);
590
+ });
591
+ files.push({
592
+ name,
593
+ relativePath,
594
+ size: file.size,
595
+ mimeType: file.type || getMimeType(name),
596
+ getData: async () => file.arrayBuffer()
597
+ });
598
+ } else if (entry.isDirectory) {
599
+ const dirEntry = entry;
600
+ folders.push({
601
+ name,
602
+ relativePath
603
+ });
604
+ const reader = dirEntry.createReader();
605
+ const childEntries = await new Promise((resolve, reject) => {
606
+ const allEntries = [];
607
+ function readEntries() {
608
+ reader.readEntries((entries2) => {
609
+ if (entries2.length === 0) {
610
+ resolve(allEntries);
611
+ } else {
612
+ allEntries.push(...entries2);
613
+ readEntries();
614
+ }
615
+ }, reject);
616
+ }
617
+ readEntries();
618
+ });
619
+ for (const childEntry of childEntries) {
620
+ await processEntry(childEntry, relativePath);
621
+ }
622
+ }
623
+ }
624
+ for (const entry of entries) {
625
+ if (entry) {
626
+ await processEntry(entry, "");
627
+ }
628
+ }
629
+ folders.sort((a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length);
630
+ return { files, folders };
631
+ }
632
+ async function scanFileList(fileList, options = {}) {
633
+ const { ignore = ["node_modules", ".git", ".DS_Store"] } = options;
634
+ const files = [];
635
+ const folderPaths = /* @__PURE__ */ new Set();
636
+ for (let i = 0; i < fileList.length; i++) {
637
+ const file = fileList[i];
638
+ if (!file) continue;
639
+ const relativePath = file.webkitRelativePath || file.name;
640
+ const name = file.name;
641
+ const pathSegments = relativePath.split("/");
642
+ if (pathSegments.some((segment) => ignore.includes(segment))) {
643
+ continue;
644
+ }
645
+ const pathParts = relativePath.split("/");
646
+ for (let j = 1; j < pathParts.length; j++) {
647
+ const folderPath = pathParts.slice(0, j).join("/");
648
+ folderPaths.add(folderPath);
649
+ }
650
+ const fileRef = file;
651
+ files.push({
652
+ name,
653
+ relativePath,
654
+ size: fileRef.size,
655
+ mimeType: fileRef.type || getMimeType(name),
656
+ getData: async () => fileRef.arrayBuffer()
657
+ });
658
+ }
659
+ const folders = Array.from(folderPaths).map((path) => ({
660
+ name: path.split("/").pop(),
661
+ relativePath: path
662
+ })).sort((a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length);
663
+ return { files, folders };
664
+ }
665
+ function buildUploadTree(items) {
666
+ const files = [];
667
+ const folderPaths = /* @__PURE__ */ new Set();
668
+ for (const item of items) {
669
+ const pathParts = item.path.split("/");
670
+ const name = pathParts.pop();
671
+ for (let i = 1; i <= pathParts.length; i++) {
672
+ folderPaths.add(pathParts.slice(0, i).join("/"));
673
+ }
674
+ let size;
675
+ if (item.data instanceof Blob) {
676
+ size = item.data.size;
677
+ } else if (item.data instanceof ArrayBuffer) {
678
+ size = item.data.byteLength;
679
+ } else {
680
+ size = item.data.length;
681
+ }
682
+ files.push({
683
+ name,
684
+ relativePath: item.path,
685
+ size,
686
+ mimeType: item.mimeType || getMimeType(name),
687
+ getData: async () => item.data
688
+ });
689
+ }
690
+ const folders = Array.from(folderPaths).map((path) => ({
691
+ name: path.split("/").pop(),
692
+ relativePath: path
693
+ })).sort((a, b) => a.relativePath.split("/").length - b.relativePath.split("/").length);
694
+ return { files, folders };
695
+ }
696
+
29
697
  // src/operations/folders.ts
30
698
  var FolderOperations = class {
31
699
  constructor(client) {
@@ -34,15 +702,32 @@ var FolderOperations = class {
34
702
  /**
35
703
  * Upload a local directory to Arke
36
704
  *
37
- * TODO: Implement this method
38
- * Steps:
39
- * 1. Scan directory structure
40
- * 2. Create folder hierarchy (depth-first)
41
- * 3. Upload files in parallel (with concurrency limit)
42
- * 4. Create bidirectional relationships (folder contains file)
705
+ * @deprecated Use uploadTree and scanDirectory instead
43
706
  */
44
- async uploadDirectory(_localPath, _options) {
45
- throw new Error("FolderOperations.uploadDirectory is not yet implemented");
707
+ async uploadDirectory(localPath, options) {
708
+ const tree = await scanDirectory(localPath);
709
+ const result = await uploadTree(this.client, tree, {
710
+ target: {
711
+ collectionId: options.collectionId,
712
+ parentId: options.parentFolderId
713
+ },
714
+ concurrency: options.concurrency,
715
+ onProgress: options.onProgress ? (p) => {
716
+ options.onProgress({
717
+ 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",
718
+ totalFiles: p.totalFiles,
719
+ completedFiles: p.completedFiles,
720
+ totalFolders: p.totalFolders,
721
+ completedFolders: p.completedFolders,
722
+ currentFile: p.currentFile
723
+ });
724
+ } : void 0
725
+ });
726
+ return {
727
+ rootFolder: result.folders[0] || null,
728
+ folders: result.folders,
729
+ files: result.files
730
+ };
46
731
  }
47
732
  };
48
733
 
@@ -108,6 +793,14 @@ var CryptoOperations = class {
108
793
  0 && (module.exports = {
109
794
  BatchOperations,
110
795
  CryptoOperations,
111
- FolderOperations
796
+ FolderOperations,
797
+ buildUploadTree,
798
+ computeCid,
799
+ getMimeType,
800
+ scanDirectory,
801
+ scanFileList,
802
+ scanFileSystemEntries,
803
+ uploadTree,
804
+ verifyCid
112
805
  });
113
806
  //# sourceMappingURL=index.cjs.map