@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/{crypto-CQnwqWQn.d.ts → crypto-7c990p-j.d.ts} +40 -16
- package/dist/{crypto-iYgzUi77.d.cts → crypto-El5Z3bNI.d.cts} +40 -16
- package/dist/generated/index.d.cts +389 -140
- package/dist/generated/index.d.ts +389 -140
- package/dist/index.cjs +294 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +292 -210
- package/dist/index.js.map +1 -1
- package/dist/operations/index.cjs +280 -209
- package/dist/operations/index.cjs.map +1 -1
- package/dist/operations/index.d.cts +25 -9
- package/dist/operations/index.d.ts +25 -9
- package/dist/operations/index.js +280 -209
- package/dist/operations/index.js.map +1 -1
- package/openapi/spec.json +598 -227
- package/openapi/version.json +1 -1
- package/package.json +1 -1
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"] =
|
|
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 =
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
468
|
+
completedEntities++;
|
|
398
469
|
reportProgress({
|
|
399
|
-
phase: "creating
|
|
400
|
-
|
|
401
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
472
|
-
if (
|
|
473
|
-
|
|
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
|
-
|
|
493
|
-
if (
|
|
494
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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 (
|
|
520
|
-
throw new Error(`Failed to fetch collection
|
|
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:
|
|
529
|
+
const { data: folderData, error: getError } = await client.api.GET("/folders/{id}", {
|
|
525
530
|
params: { path: { id: parentId } }
|
|
526
531
|
});
|
|
527
|
-
if (
|
|
528
|
-
throw new Error(`Failed to fetch folder
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
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
|
|
561
|
+
throw new Error(`Failed to backlink parent ${parentId}: ${errorMsg}`);
|
|
553
562
|
}
|
|
554
563
|
}
|
|
555
|
-
}
|
|
556
|
-
reportProgress({ phase: "
|
|
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"
|
|
748
|
-
totalFiles: p.
|
|
749
|
-
completedFiles: p.
|
|
750
|
-
totalFolders: p.
|
|
751
|
-
completedFolders: p.
|
|
752
|
-
currentFile: p.
|
|
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
|