@karpeleslab/klbfw 0.2.16 → 0.2.17
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/package.json +1 -1
- package/upload.js +117 -62
package/package.json
CHANGED
package/upload.js
CHANGED
|
@@ -273,83 +273,140 @@ const utils = {
|
|
|
273
273
|
* that resolves when the upload is complete. It doesn't use global state or the
|
|
274
274
|
* upload.run() process.
|
|
275
275
|
*
|
|
276
|
-
* @param {string}
|
|
277
|
-
* @param {Buffer|ArrayBuffer|Object}
|
|
278
|
-
* - A Buffer
|
|
276
|
+
* @param {string} api - API endpoint path (e.g., 'Misc/Debug:testUpload')
|
|
277
|
+
* @param {Buffer|ArrayBuffer|Uint8Array|File|Object} buffer - File to upload. Can be:
|
|
278
|
+
* - A Node.js Buffer
|
|
279
|
+
* - An ArrayBuffer
|
|
280
|
+
* - A Uint8Array or other TypedArray
|
|
281
|
+
* - A browser File object
|
|
279
282
|
* - A file-like object with { name, size, type, content, lastModified }
|
|
280
|
-
*
|
|
281
|
-
* @param {string} [
|
|
282
|
-
* @param {
|
|
283
|
-
*
|
|
284
|
-
* @param {Object} [
|
|
285
|
-
* @param {Function} [options.onProgress] - Progress callback(progress) where progress is 0-1
|
|
283
|
+
* - A string (will be converted to UTF-8 bytes)
|
|
284
|
+
* @param {string} [method='POST'] - HTTP method for the initial API call
|
|
285
|
+
* @param {Object} [params={}] - Additional parameters to send with the upload.
|
|
286
|
+
* Can include `filename` and `type` to override defaults.
|
|
287
|
+
* @param {Object} [context=null] - Request context (uses default context if not provided)
|
|
286
288
|
* @returns {Promise<Object>} - Resolves with the upload result data
|
|
287
289
|
*
|
|
288
290
|
* @example
|
|
289
|
-
* // Upload a buffer
|
|
291
|
+
* // Upload a buffer with filename
|
|
290
292
|
* const buffer = Buffer.from('Hello, World!');
|
|
291
|
-
* const result = await uploadFile('Misc/Debug:testUpload', buffer, {
|
|
293
|
+
* const result = await uploadFile('Misc/Debug:testUpload', buffer, 'POST', {
|
|
292
294
|
* filename: 'hello.txt',
|
|
293
295
|
* type: 'text/plain'
|
|
294
296
|
* });
|
|
295
|
-
* console.log(result); // { Blob__: '...', SHA256: '...', ... }
|
|
296
297
|
*
|
|
297
298
|
* @example
|
|
298
|
-
* // Upload with
|
|
299
|
-
* const result = await uploadFile('Misc/Debug:testUpload',
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
299
|
+
* // Upload with defaults
|
|
300
|
+
* const result = await uploadFile('Misc/Debug:testUpload', buffer);
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* // Upload a File object (browser)
|
|
304
|
+
* const result = await uploadFile('Misc/Debug:testUpload', fileInput.files[0]);
|
|
303
305
|
*/
|
|
304
|
-
async function uploadFile(
|
|
305
|
-
//
|
|
306
|
+
async function uploadFile(api, buffer, method, params, context) {
|
|
307
|
+
// Handle default values
|
|
308
|
+
method = method || 'POST';
|
|
309
|
+
params = params || {};
|
|
310
|
+
|
|
311
|
+
// Get context from framework if not provided, and add available values
|
|
312
|
+
if (!context) {
|
|
313
|
+
context = fwWrapper.getContext();
|
|
314
|
+
} else {
|
|
315
|
+
// Merge with default context values if available
|
|
316
|
+
const defaultContext = fwWrapper.getContext();
|
|
317
|
+
if (defaultContext) {
|
|
318
|
+
context = { ...defaultContext, ...context };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Normalize buffer to a file-like object
|
|
306
323
|
let fileObj;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const
|
|
324
|
+
|
|
325
|
+
// Handle string input
|
|
326
|
+
if (typeof buffer === 'string') {
|
|
327
|
+
const encoder = new TextEncoder();
|
|
328
|
+
const uint8Array = encoder.encode(buffer);
|
|
312
329
|
fileObj = {
|
|
313
|
-
name:
|
|
314
|
-
size:
|
|
315
|
-
type:
|
|
330
|
+
name: params.filename || 'file.txt',
|
|
331
|
+
size: uint8Array.length,
|
|
332
|
+
type: params.type || 'text/plain',
|
|
316
333
|
lastModified: Date.now(),
|
|
317
|
-
content:
|
|
334
|
+
content: uint8Array.buffer
|
|
318
335
|
};
|
|
319
|
-
}
|
|
320
|
-
|
|
336
|
+
}
|
|
337
|
+
// Handle ArrayBuffer
|
|
338
|
+
else if (buffer instanceof ArrayBuffer) {
|
|
321
339
|
fileObj = {
|
|
322
|
-
name:
|
|
323
|
-
size:
|
|
324
|
-
type:
|
|
325
|
-
lastModified:
|
|
326
|
-
content:
|
|
340
|
+
name: params.filename || 'file.bin',
|
|
341
|
+
size: buffer.byteLength,
|
|
342
|
+
type: params.type || 'application/octet-stream',
|
|
343
|
+
lastModified: Date.now(),
|
|
344
|
+
content: buffer
|
|
327
345
|
};
|
|
328
|
-
}
|
|
329
|
-
|
|
346
|
+
}
|
|
347
|
+
// Handle TypedArray (Uint8Array, etc.)
|
|
348
|
+
else if (buffer && buffer.buffer instanceof ArrayBuffer) {
|
|
349
|
+
fileObj = {
|
|
350
|
+
name: params.filename || 'file.bin',
|
|
351
|
+
size: buffer.byteLength,
|
|
352
|
+
type: params.type || 'application/octet-stream',
|
|
353
|
+
lastModified: Date.now(),
|
|
354
|
+
content: buffer
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
// Handle Node.js Buffer
|
|
358
|
+
else if (typeof Buffer !== 'undefined' && buffer instanceof Buffer) {
|
|
359
|
+
fileObj = {
|
|
360
|
+
name: params.filename || 'file.bin',
|
|
361
|
+
size: buffer.length,
|
|
362
|
+
type: params.type || 'application/octet-stream',
|
|
363
|
+
lastModified: Date.now(),
|
|
364
|
+
content: buffer
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
// Handle browser File object
|
|
368
|
+
else if (env.isBrowser && typeof File !== 'undefined' && buffer instanceof File) {
|
|
369
|
+
fileObj = {
|
|
370
|
+
name: buffer.name || params.filename || 'file.bin',
|
|
371
|
+
size: buffer.size,
|
|
372
|
+
type: buffer.type || params.type || 'application/octet-stream',
|
|
373
|
+
lastModified: buffer.lastModified || Date.now(),
|
|
374
|
+
browserFile: buffer // Keep reference to original File for reading
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
// Handle file-like object with content property
|
|
378
|
+
else if (buffer && buffer.content !== undefined) {
|
|
379
|
+
fileObj = {
|
|
380
|
+
name: buffer.name || params.filename || 'file.bin',
|
|
381
|
+
size: buffer.size || buffer.content.byteLength || buffer.content.length,
|
|
382
|
+
type: buffer.type || params.type || 'application/octet-stream',
|
|
383
|
+
lastModified: buffer.lastModified || Date.now(),
|
|
384
|
+
content: buffer.content
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
throw new Error('Invalid file: must be a Buffer, ArrayBuffer, Uint8Array, File, string, or file-like object with content');
|
|
330
389
|
}
|
|
331
390
|
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
params.lastModified = fileObj.lastModified / 1000;
|
|
339
|
-
params.type = fileObj.type;
|
|
391
|
+
// Merge params with file metadata (file metadata takes precedence for these fields)
|
|
392
|
+
const uploadParams = { ...params };
|
|
393
|
+
uploadParams.filename = fileObj.name;
|
|
394
|
+
uploadParams.size = fileObj.size;
|
|
395
|
+
uploadParams.lastModified = fileObj.lastModified / 1000;
|
|
396
|
+
uploadParams.type = fileObj.type;
|
|
340
397
|
|
|
341
398
|
// Initialize upload with the server
|
|
342
|
-
const response = await rest.rest(
|
|
399
|
+
const response = await rest.rest(api, method, uploadParams, context);
|
|
343
400
|
const data = response.data;
|
|
344
401
|
|
|
345
402
|
// Method 1: AWS signed multipart upload
|
|
346
403
|
if (data.Cloud_Aws_Bucket_Upload__) {
|
|
347
|
-
return doAwsUpload(fileObj, data, context
|
|
404
|
+
return doAwsUpload(fileObj, data, context);
|
|
348
405
|
}
|
|
349
406
|
|
|
350
407
|
// Method 2: Direct PUT upload
|
|
351
408
|
if (data.PUT) {
|
|
352
|
-
return doPutUpload(fileObj, data, context
|
|
409
|
+
return doPutUpload(fileObj, data, context);
|
|
353
410
|
}
|
|
354
411
|
|
|
355
412
|
throw new Error('Invalid upload response format: no upload method available');
|
|
@@ -359,12 +416,11 @@ async function uploadFile(path, file, options = {}) {
|
|
|
359
416
|
* Perform a direct PUT upload (simple upload method)
|
|
360
417
|
* @private
|
|
361
418
|
*/
|
|
362
|
-
async function doPutUpload(file, uploadInfo, context
|
|
419
|
+
async function doPutUpload(file, uploadInfo, context) {
|
|
363
420
|
const blockSize = uploadInfo.Blocksize || file.size;
|
|
364
421
|
const blocks = Math.ceil(file.size / blockSize);
|
|
365
422
|
|
|
366
423
|
// Upload blocks with concurrency limit
|
|
367
|
-
let completedBlocks = 0;
|
|
368
424
|
const maxConcurrent = 3;
|
|
369
425
|
|
|
370
426
|
// Process blocks in batches
|
|
@@ -375,11 +431,6 @@ async function doPutUpload(file, uploadInfo, context, onProgress) {
|
|
|
375
431
|
}
|
|
376
432
|
|
|
377
433
|
await Promise.all(batch);
|
|
378
|
-
completedBlocks += batch.length;
|
|
379
|
-
|
|
380
|
-
if (onProgress) {
|
|
381
|
-
onProgress(completedBlocks / blocks);
|
|
382
|
-
}
|
|
383
434
|
}
|
|
384
435
|
|
|
385
436
|
// All blocks done, call completion
|
|
@@ -424,7 +475,7 @@ async function uploadPutBlock(file, uploadInfo, blockNum, blockSize) {
|
|
|
424
475
|
* Perform an AWS multipart upload
|
|
425
476
|
* @private
|
|
426
477
|
*/
|
|
427
|
-
async function doAwsUpload(file, uploadInfo, context
|
|
478
|
+
async function doAwsUpload(file, uploadInfo, context) {
|
|
428
479
|
// Calculate optimal block size (min 5MB for AWS, target ~10k parts)
|
|
429
480
|
let blockSize = Math.ceil(file.size / 10000);
|
|
430
481
|
if (blockSize < 5242880) blockSize = 5242880;
|
|
@@ -446,7 +497,6 @@ async function doAwsUpload(file, uploadInfo, context, onProgress) {
|
|
|
446
497
|
|
|
447
498
|
// Upload all parts with concurrency limit
|
|
448
499
|
const etags = {};
|
|
449
|
-
let completedBlocks = 0;
|
|
450
500
|
const maxConcurrent = 3;
|
|
451
501
|
|
|
452
502
|
for (let i = 0; i < blocks; i += maxConcurrent) {
|
|
@@ -459,11 +509,6 @@ async function doAwsUpload(file, uploadInfo, context, onProgress) {
|
|
|
459
509
|
}
|
|
460
510
|
|
|
461
511
|
await Promise.all(batch);
|
|
462
|
-
completedBlocks += batch.length;
|
|
463
|
-
|
|
464
|
-
if (onProgress) {
|
|
465
|
-
onProgress(completedBlocks / blocks);
|
|
466
|
-
}
|
|
467
512
|
}
|
|
468
513
|
|
|
469
514
|
// Complete multipart upload
|
|
@@ -522,6 +567,16 @@ async function uploadAwsBlock(file, uploadInfo, uploadId, blockNum, blockSize, c
|
|
|
522
567
|
*/
|
|
523
568
|
function readFileSlice(file, start, end) {
|
|
524
569
|
return new Promise((resolve, reject) => {
|
|
570
|
+
// Handle browser File objects
|
|
571
|
+
if (file.browserFile) {
|
|
572
|
+
const slice = file.browserFile.slice(start, end);
|
|
573
|
+
const reader = new FileReader();
|
|
574
|
+
reader.addEventListener('loadend', () => resolve(reader.result));
|
|
575
|
+
reader.addEventListener('error', (e) => reject(e));
|
|
576
|
+
reader.readAsArrayBuffer(slice);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
525
580
|
if (!file.content) {
|
|
526
581
|
reject(new Error('Cannot read file content - no content property'));
|
|
527
582
|
return;
|