@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/upload.js +117 -62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karpeleslab/klbfw",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "Frontend Framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
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} path - API endpoint path (e.g., 'Misc/Debug:testUpload')
277
- * @param {Buffer|ArrayBuffer|Object} file - File to upload. Can be:
278
- * - A Buffer or ArrayBuffer with file content
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
- * @param {Object} [options] - Upload options
281
- * @param {string} [options.filename] - Filename (defaults to 'file.bin')
282
- * @param {string} [options.type] - MIME type (defaults to 'application/octet-stream')
283
- * @param {Object} [options.params] - Additional parameters to send with the upload
284
- * @param {Object} [options.context] - Request context
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 progress tracking
299
- * const result = await uploadFile('Misc/Debug:testUpload', largeBuffer, {
300
- * filename: 'large-file.bin',
301
- * onProgress: (progress) => console.log(`${Math.round(progress * 100)}%`)
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(path, file, options = {}) {
305
- // Normalize file to a file-like object
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
- if (file instanceof ArrayBuffer ||
308
- (file.buffer instanceof ArrayBuffer) ||
309
- (typeof Buffer !== 'undefined' && file instanceof Buffer)) {
310
- // Raw buffer - wrap in file-like object
311
- const size = file.byteLength || file.length;
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: options.filename || 'file.bin',
314
- size: size,
315
- type: options.type || 'application/octet-stream',
330
+ name: params.filename || 'file.txt',
331
+ size: uint8Array.length,
332
+ type: params.type || 'text/plain',
316
333
  lastModified: Date.now(),
317
- content: file
334
+ content: uint8Array.buffer
318
335
  };
319
- } else if (file.content !== undefined) {
320
- // Already a file-like object
336
+ }
337
+ // Handle ArrayBuffer
338
+ else if (buffer instanceof ArrayBuffer) {
321
339
  fileObj = {
322
- name: file.name || options.filename || 'file.bin',
323
- size: file.size || file.content.byteLength || file.content.length,
324
- type: file.type || options.type || 'application/octet-stream',
325
- lastModified: file.lastModified || Date.now(),
326
- content: file.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
- } else {
329
- throw new Error('Invalid file: must be a Buffer, ArrayBuffer, or file-like object with content');
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
- const context = options.context || fwWrapper.getContext();
333
- const params = { ...(options.params || {}) };
334
-
335
- // Set file metadata
336
- params.filename = fileObj.name;
337
- params.size = fileObj.size;
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(path, 'POST', params, context);
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, options.onProgress);
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, options.onProgress);
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, onProgress) {
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, onProgress) {
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;