@arke-institute/sdk 2.1.0 → 2.2.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 +385 -134
- package/dist/generated/index.d.ts +385 -134
- 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 +588 -205
- package/openapi/version.json +1 -1
- package/package.json +1 -1
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"] =
|
|
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 =
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
|
|
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
|
-
|
|
418
|
+
completedEntities++;
|
|
350
419
|
reportProgress({
|
|
351
|
-
phase: "creating
|
|
352
|
-
|
|
353
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
424
|
-
if (
|
|
425
|
-
|
|
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
|
-
|
|
445
|
-
if (
|
|
446
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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 (
|
|
472
|
-
throw new Error(`Failed to fetch collection
|
|
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:
|
|
479
|
+
const { data: folderData, error: getError } = await client.api.GET("/folders/{id}", {
|
|
477
480
|
params: { path: { id: parentId } }
|
|
478
481
|
});
|
|
479
|
-
if (
|
|
480
|
-
throw new Error(`Failed to fetch folder
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
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
|
|
511
|
+
throw new Error(`Failed to backlink parent ${parentId}: ${errorMsg}`);
|
|
505
512
|
}
|
|
506
513
|
}
|
|
507
|
-
}
|
|
508
|
-
reportProgress({ phase: "
|
|
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"
|
|
700
|
-
totalFiles: p.
|
|
701
|
-
completedFiles: p.
|
|
702
|
-
totalFolders: p.
|
|
703
|
-
completedFolders: p.
|
|
704
|
-
currentFile: p.
|
|
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
|