juxscript 1.1.184 → 1.1.185

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.
@@ -98,9 +98,17 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
98
98
  private _updateTable;
99
99
  private _showFilterInput;
100
100
  /**
101
- * ✅ NEW: Show settings modal to reshape CSV data
101
+ * ✅ UPDATED: Show settings modal for CSV OR Excel reshaping
102
102
  */
103
103
  private _showReshapeModal;
104
+ /**
105
+ * ✅ NEW: Excel-specific reshape modal (header row picker)
106
+ */
107
+ private _showExcelReshapeModal;
108
+ /**
109
+ * ✅ CSV-specific reshape modal (existing implementation)
110
+ */
111
+ private _showCSVReshapeModal;
104
112
  update(prop: string, value: any): void;
105
113
  render(targetId?: string | HTMLElement | BaseComponent<any>): this;
106
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,7 @@ export class DataFrameComponent extends BaseComponent {
34
34
  this._sheetChunkSize = 10000; // ✅ Default 10k chunk
35
35
  this._maxFileSize = 50; // ✅ Default 50MB
36
36
  this._showReshapeWarning = true;
37
- this._rawFileData = null; // ✅ Store for re-parsing
37
+ this._rawFileData = null; // ✅ Add isExcel flag
38
38
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
39
39
  this._showStatus = options.showStatus ?? true;
40
40
  this._icon = options.icon ?? '';
@@ -104,6 +104,8 @@ export class DataFrameComponent extends BaseComponent {
104
104
  const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
105
105
  file.name.toLowerCase().endsWith('.xls');
106
106
  if (isExcel) {
107
+ // ✅ Store raw file for reshape
108
+ this._rawFileData = { file, isExcel: true };
107
109
  // ✅ Pass chunking options for large files
108
110
  const sheets = await this._driver.streamFileMultiSheet(file, {
109
111
  maxSheetSize: this._maxSheetSize,
@@ -125,7 +127,7 @@ export class DataFrameComponent extends BaseComponent {
125
127
  else {
126
128
  // ✅ CSV/TSV: Store raw text for reshaping
127
129
  const text = await file.text();
128
- this._rawFileData = { file, text };
130
+ this._rawFileData = { file, text, isExcel: false };
129
131
  const df = this._driver.parseCSV(text, {
130
132
  autoDetectDelimiter: true,
131
133
  hasHeader: true
@@ -393,8 +395,8 @@ export class DataFrameComponent extends BaseComponent {
393
395
  }
394
396
  this._updateTable();
395
397
  this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
396
- // ✅ Add reshape warning button if CSV and enabled
397
- if (this._showReshapeWarning && this._rawFileData?.text) {
398
+ // ✅ FIXED: Add reshape warning button if CSV OR Excel and enabled
399
+ if (this._showReshapeWarning && this._rawFileData) {
398
400
  const statusEl = document.getElementById(`${this._id}-status`);
399
401
  if (statusEl) {
400
402
  const settingsBtn = document.createElement('button');
@@ -482,9 +484,118 @@ export class DataFrameComponent extends BaseComponent {
482
484
  });
483
485
  }
484
486
  /**
485
- * ✅ NEW: Show settings modal to reshape CSV data
487
+ * ✅ UPDATED: Show settings modal for CSV OR Excel reshaping
486
488
  */
487
489
  _showReshapeModal() {
490
+ if (!this._rawFileData)
491
+ return;
492
+ const isExcel = this._rawFileData.isExcel;
493
+ if (isExcel) {
494
+ // ✅ Excel-specific modal: Let user pick header row from sheet
495
+ this._showExcelReshapeModal();
496
+ }
497
+ else {
498
+ // ✅ CSV-specific modal: delimiter + header row
499
+ this._showCSVReshapeModal();
500
+ }
501
+ }
502
+ /**
503
+ * ✅ NEW: Excel-specific reshape modal (header row picker)
504
+ */
505
+ async _showExcelReshapeModal() {
506
+ if (!this._rawFileData?.file)
507
+ return;
508
+ const modal = document.createElement('div');
509
+ modal.className = 'jux-modal-overlay';
510
+ modal.innerHTML = `
511
+ <div class="jux-modal" style="max-width: 800px;">
512
+ <div class="jux-modal-header">
513
+ <div class="jux-modal-header-title">Excel Import Settings</div>
514
+ <button class="jux-modal-close" id="${this._id}-modal-close">×</button>
515
+ </div>
516
+ <div class="jux-modal-content" style="padding: 1.5rem;">
517
+ <div style="margin-bottom: 1rem;">
518
+ <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based)</label>
519
+ <input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
520
+ <div style="font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;">
521
+ ⚠️ Detected: Row 0 has "Exported On:", row 2 has "340B ID". Try setting this to 2.
522
+ </div>
523
+ </div>
524
+
525
+ <div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 300px; overflow-y: auto;">
526
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview (first 10 rows)</div>
527
+ <div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--muted-foreground));"></div>
528
+ </div>
529
+ </div>
530
+ <div class="jux-modal-footer">
531
+ <button id="${this._id}-cancel-reshape" class="jux-button jux-button-ghost">Cancel</button>
532
+ <button id="${this._id}-apply-reshape" class="jux-button">Apply & Re-import</button>
533
+ </div>
534
+ </div>
535
+ `;
536
+ document.body.appendChild(modal);
537
+ const headerRowInput = document.getElementById(`${this._id}-header-row`);
538
+ const previewDiv = document.getElementById(`${this._id}-preview`);
539
+ // ✅ Update preview on header row change
540
+ const updatePreview = async () => {
541
+ const headerRow = parseInt(headerRowInput.value) || 0;
542
+ try {
543
+ // Re-parse Excel with specified header row
544
+ const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
545
+ headerRow, // ✅ Pass header row to parser
546
+ maxSheetSize: 10 // Preview only
547
+ });
548
+ const firstSheet = Object.values(sheets)[0];
549
+ if (!firstSheet) {
550
+ previewDiv.textContent = '⚠️ No data found';
551
+ return;
552
+ }
553
+ const preview = firstSheet.toRows().slice(0, 10).map((row, i) => {
554
+ const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' | ');
555
+ return `${i === 0 ? '📌 ' : ' '}${cols}`;
556
+ }).join('\n');
557
+ previewDiv.textContent = `Columns: ${firstSheet.columns.join(' | ')}\n\n${preview}`;
558
+ }
559
+ catch (err) {
560
+ previewDiv.textContent = `⚠️ Error: ${err.message}`;
561
+ }
562
+ };
563
+ headerRowInput.addEventListener('input', updatePreview);
564
+ updatePreview();
565
+ // ✅ Apply button
566
+ document.getElementById(`${this._id}-apply-reshape`).addEventListener('click', async () => {
567
+ const headerRow = parseInt(headerRowInput.value) || 0;
568
+ this.state.loading = true;
569
+ this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
570
+ try {
571
+ const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
572
+ headerRow,
573
+ maxSheetSize: this._maxSheetSize,
574
+ sheetChunkSize: this._sheetChunkSize
575
+ });
576
+ const sheetNames = Object.keys(sheets);
577
+ await this._driver.store(this._rawFileData.file.name, sheets[sheetNames[0]], { source: this._rawFileData.file.name });
578
+ if (sheetNames.length > 1) {
579
+ this._renderMultiSheet(sheets, this._rawFileData.file.name);
580
+ }
581
+ else {
582
+ this._setDataFrame(sheets[sheetNames[0]], this._rawFileData.file.name);
583
+ }
584
+ modal.remove();
585
+ }
586
+ catch (err) {
587
+ this._updateStatus(`❌ ${err.message}`, 'error');
588
+ }
589
+ });
590
+ // ✅ Cancel/Close buttons
591
+ const closeModal = () => modal.remove();
592
+ document.getElementById(`${this._id}-cancel-reshape`).addEventListener('click', closeModal);
593
+ document.getElementById(`${this._id}-modal-close`).addEventListener('click', closeModal);
594
+ }
595
+ /**
596
+ * ✅ CSV-specific reshape modal (existing implementation)
597
+ */
598
+ _showCSVReshapeModal() {
488
599
  if (!this._rawFileData)
489
600
  return;
490
601
  const modal = document.createElement('div');
@@ -633,6 +744,8 @@ export class DataFrameComponent extends BaseComponent {
633
744
  const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
634
745
  file.name.toLowerCase().endsWith('.xls');
635
746
  if (isExcel) {
747
+ // ✅ Store raw file for reshape
748
+ this._rawFileData = { file, isExcel: true };
636
749
  const sheets = await this._driver.streamFileMultiSheet(file, {
637
750
  maxSheetSize: this._maxSheetSize,
638
751
  sheetChunkSize: this._sheetChunkSize,
@@ -59,7 +59,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
59
59
  private _sheetChunkSize: number = 10000; // ✅ Default 10k chunk
60
60
  private _maxFileSize: number = 50; // ✅ Default 50MB
61
61
  private _showReshapeWarning: boolean = true;
62
- private _rawFileData: { file: File; text?: string } | null = null; // ✅ Store for re-parsing
62
+ private _rawFileData: { file: File; text?: string; isExcel?: boolean } | null = null; // ✅ Add isExcel flag
63
63
 
64
64
  constructor(id: string, options: DataFrameOptions = {}) {
65
65
  super(id, {
@@ -149,6 +149,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
149
149
  file.name.toLowerCase().endsWith('.xls');
150
150
 
151
151
  if (isExcel) {
152
+ // ✅ Store raw file for reshape
153
+ this._rawFileData = { file, isExcel: true };
154
+
152
155
  // ✅ Pass chunking options for large files
153
156
  const sheets = await this._driver.streamFileMultiSheet(file, {
154
157
  maxSheetSize: this._maxSheetSize,
@@ -171,7 +174,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
171
174
  } else {
172
175
  // ✅ CSV/TSV: Store raw text for reshaping
173
176
  const text = await file.text();
174
- this._rawFileData = { file, text };
177
+ this._rawFileData = { file, text, isExcel: false };
175
178
 
176
179
  const df = this._driver.parseCSV(text, {
177
180
  autoDetectDelimiter: true,
@@ -493,8 +496,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
493
496
  'success'
494
497
  );
495
498
 
496
- // ✅ Add reshape warning button if CSV and enabled
497
- if (this._showReshapeWarning && this._rawFileData?.text) {
499
+ // ✅ FIXED: Add reshape warning button if CSV OR Excel and enabled
500
+ if (this._showReshapeWarning && this._rawFileData) {
498
501
  const statusEl = document.getElementById(`${this._id}-status`);
499
502
  if (statusEl) {
500
503
  const settingsBtn = document.createElement('button');
@@ -597,11 +600,134 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
597
600
  }
598
601
 
599
602
  /**
600
- * ✅ NEW: Show settings modal to reshape CSV data
603
+ * ✅ UPDATED: Show settings modal for CSV OR Excel reshaping
601
604
  */
602
605
  private _showReshapeModal(): void {
603
606
  if (!this._rawFileData) return;
604
607
 
608
+ const isExcel = this._rawFileData.isExcel;
609
+
610
+ if (isExcel) {
611
+ // ✅ Excel-specific modal: Let user pick header row from sheet
612
+ this._showExcelReshapeModal();
613
+ } else {
614
+ // ✅ CSV-specific modal: delimiter + header row
615
+ this._showCSVReshapeModal();
616
+ }
617
+ }
618
+
619
+ /**
620
+ * ✅ NEW: Excel-specific reshape modal (header row picker)
621
+ */
622
+ private async _showExcelReshapeModal(): Promise<void> {
623
+ if (!this._rawFileData?.file) return;
624
+
625
+ const modal = document.createElement('div');
626
+ modal.className = 'jux-modal-overlay';
627
+ modal.innerHTML = `
628
+ <div class="jux-modal" style="max-width: 800px;">
629
+ <div class="jux-modal-header">
630
+ <div class="jux-modal-header-title">Excel Import Settings</div>
631
+ <button class="jux-modal-close" id="${this._id}-modal-close">×</button>
632
+ </div>
633
+ <div class="jux-modal-content" style="padding: 1.5rem;">
634
+ <div style="margin-bottom: 1rem;">
635
+ <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based)</label>
636
+ <input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
637
+ <div style="font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;">
638
+ ⚠️ Detected: Row 0 has "Exported On:", row 2 has "340B ID". Try setting this to 2.
639
+ </div>
640
+ </div>
641
+
642
+ <div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 300px; overflow-y: auto;">
643
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview (first 10 rows)</div>
644
+ <div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--muted-foreground));"></div>
645
+ </div>
646
+ </div>
647
+ <div class="jux-modal-footer">
648
+ <button id="${this._id}-cancel-reshape" class="jux-button jux-button-ghost">Cancel</button>
649
+ <button id="${this._id}-apply-reshape" class="jux-button">Apply & Re-import</button>
650
+ </div>
651
+ </div>
652
+ `;
653
+
654
+ document.body.appendChild(modal);
655
+
656
+ const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
657
+ const previewDiv = document.getElementById(`${this._id}-preview`)!;
658
+
659
+ // ✅ Update preview on header row change
660
+ const updatePreview = async () => {
661
+ const headerRow = parseInt(headerRowInput.value) || 0;
662
+
663
+ try {
664
+ // Re-parse Excel with specified header row
665
+ const sheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
666
+ headerRow, // ✅ Pass header row to parser
667
+ maxSheetSize: 10 // Preview only
668
+ });
669
+
670
+ const firstSheet = Object.values(sheets)[0];
671
+ if (!firstSheet) {
672
+ previewDiv.textContent = '⚠️ No data found';
673
+ return;
674
+ }
675
+
676
+ const preview = firstSheet.toRows().slice(0, 10).map((row, i) => {
677
+ const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' | ');
678
+ return `${i === 0 ? '📌 ' : ' '}${cols}`;
679
+ }).join('\n');
680
+
681
+ previewDiv.textContent = `Columns: ${firstSheet.columns.join(' | ')}\n\n${preview}`;
682
+ } catch (err: any) {
683
+ previewDiv.textContent = `⚠️ Error: ${err.message}`;
684
+ }
685
+ };
686
+
687
+ headerRowInput.addEventListener('input', updatePreview);
688
+ updatePreview();
689
+
690
+ // ✅ Apply button
691
+ document.getElementById(`${this._id}-apply-reshape`)!.addEventListener('click', async () => {
692
+ const headerRow = parseInt(headerRowInput.value) || 0;
693
+
694
+ this.state.loading = true;
695
+ this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
696
+
697
+ try {
698
+ const sheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
699
+ headerRow,
700
+ maxSheetSize: this._maxSheetSize,
701
+ sheetChunkSize: this._sheetChunkSize
702
+ });
703
+
704
+ const sheetNames = Object.keys(sheets);
705
+ await this._driver.store(this._rawFileData!.file.name, sheets[sheetNames[0]], { source: this._rawFileData!.file.name });
706
+
707
+ if (sheetNames.length > 1) {
708
+ this._renderMultiSheet(sheets, this._rawFileData!.file.name);
709
+ } else {
710
+ this._setDataFrame(sheets[sheetNames[0]], this._rawFileData!.file.name);
711
+ }
712
+
713
+ modal.remove();
714
+ } catch (err: any) {
715
+ this._updateStatus(`❌ ${err.message}`, 'error');
716
+ }
717
+ });
718
+
719
+ // ✅ Cancel/Close buttons
720
+ const closeModal = () => modal.remove();
721
+ document.getElementById(`${this._id}-cancel-reshape`)!.addEventListener('click', closeModal);
722
+ document.getElementById(`${this._id}-modal-close`)!.addEventListener('click', closeModal);
723
+ }
724
+
725
+ /**
726
+ * ✅ CSV-specific reshape modal (existing implementation)
727
+ */
728
+ private _showCSVReshapeModal(): void {
729
+ if (!this._rawFileData) return;
730
+
605
731
  const modal = document.createElement('div');
606
732
  modal.className = 'jux-modal-overlay';
607
733
  modal.innerHTML = `
@@ -768,6 +894,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
768
894
  file.name.toLowerCase().endsWith('.xls');
769
895
 
770
896
  if (isExcel) {
897
+ // ✅ Store raw file for reshape
898
+ this._rawFileData = { file, isExcel: true };
899
+
771
900
  const sheets = await this._driver.streamFileMultiSheet(file, {
772
901
  maxSheetSize: this._maxSheetSize,
773
902
  sheetChunkSize: this._sheetChunkSize,
@@ -88,8 +88,7 @@ export declare class TabularDriver {
88
88
  */
89
89
  fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
90
90
  /**
91
- * ✅ OPTIMIZED: Stream Excel file with chunked row processing
92
- * Handles large files by processing rows in batches
91
+ * ✅ UPDATED: Stream Excel file with optional headerRow override
93
92
  */
94
93
  streamFileMultiSheet(file: File, options?: ParseOptions): Promise<Record<string, DataFrame>>;
95
94
  private _splitLines;
@@ -1 +1 @@
1
- {"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
@@ -487,11 +487,10 @@ export class TabularDriver {
487
487
  return df;
488
488
  }
489
489
  /**
490
- * ✅ OPTIMIZED: Stream Excel file with chunked row processing
491
- * Handles large files by processing rows in batches
490
+ * ✅ UPDATED: Stream Excel file with optional headerRow override
492
491
  */
493
492
  async streamFileMultiSheet(file, options = {}) {
494
- const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress } = options;
493
+ const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow } = options;
495
494
  let XLSX;
496
495
  try {
497
496
  XLSX = await import('xlsx');
@@ -506,63 +505,29 @@ export class TabularDriver {
506
505
  onProgress(file.size * 0.3, file.size);
507
506
  const workbook = XLSX.read(buffer, {
508
507
  type: 'array',
509
- sheetRows: maxSheetSize, // ✅ Limit rows read per sheet
510
- dense: false // ✅ Use sparse format for memory efficiency
508
+ sheetRows: maxSheetSize,
509
+ dense: false
511
510
  });
512
511
  const sheets = {};
513
512
  const totalSheets = workbook.SheetNames.length;
514
513
  let processedSheets = 0;
515
514
  for (const sheetName of workbook.SheetNames) {
516
515
  const worksheet = workbook.Sheets[sheetName];
517
- // ✅ Get sheet range to determine size
518
516
  const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
519
517
  const totalRows = range.e.r - range.s.r + 1;
520
518
  if (totalRows === 0) {
521
519
  processedSheets++;
522
520
  continue;
523
521
  }
524
- // ✅ Process in chunks if sheet is large
525
- if (totalRows > sheetChunkSize) {
526
- const rows = [];
527
- let headers = [];
528
- for (let startRow = 0; startRow < totalRows; startRow += sheetChunkSize) {
529
- const endRow = Math.min(startRow + sheetChunkSize, totalRows);
530
- // ✅ Read chunk with limited row range
531
- const chunkData = XLSX.utils.sheet_to_json(worksheet, {
532
- range: startRow,
533
- header: startRow === 0 ? undefined : headers,
534
- defval: null,
535
- raw: false, // ✅ Convert dates/numbers to strings to reduce memory
536
- blankrows: false
537
- });
538
- if (startRow === 0 && chunkData.length > 0) {
539
- headers = Object.keys(chunkData[0]);
540
- }
541
- rows.push(...chunkData);
542
- if (onProgress) {
543
- const progress = 0.3 + (0.6 * (processedSheets + (startRow / totalRows)) / totalSheets);
544
- onProgress(file.size * progress, file.size);
545
- }
546
- // ✅ Stop if we hit max rows
547
- if (rows.length >= maxSheetSize) {
548
- console.warn(`⚠️ Sheet "${sheetName}" truncated to ${maxSheetSize} rows`);
549
- break;
550
- }
551
- }
552
- if (rows.length > 0) {
553
- sheets[sheetName] = new DataFrame(rows);
554
- }
555
- }
556
- else {
557
- // ✅ Small sheet: process normally
558
- const jsonData = XLSX.utils.sheet_to_json(worksheet, {
559
- defval: null,
560
- raw: false,
561
- blankrows: false
562
- });
563
- if (jsonData.length > 0) {
564
- sheets[sheetName] = new DataFrame(jsonData);
565
- }
522
+ // ✅ NEW: Support headerRow option
523
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, {
524
+ range: headerRow !== undefined ? headerRow : undefined, // ✅ Start from specified row
525
+ defval: null,
526
+ raw: false,
527
+ blankrows: false
528
+ });
529
+ if (jsonData.length > 0) {
530
+ sheets[sheetName] = new DataFrame(jsonData);
566
531
  }
567
532
  processedSheets++;
568
533
  if (onProgress) {
@@ -607,11 +607,10 @@ export class TabularDriver {
607
607
  }
608
608
 
609
609
  /**
610
- * ✅ OPTIMIZED: Stream Excel file with chunked row processing
611
- * Handles large files by processing rows in batches
610
+ * ✅ UPDATED: Stream Excel file with optional headerRow override
612
611
  */
613
612
  async streamFileMultiSheet(file: File, options: ParseOptions = {}): Promise<Record<string, DataFrame>> {
614
- const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress } = options;
613
+ const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow } = options;
615
614
 
616
615
  let XLSX: any;
617
616
  try {
@@ -628,8 +627,8 @@ export class TabularDriver {
628
627
 
629
628
  const workbook = XLSX.read(buffer, {
630
629
  type: 'array',
631
- sheetRows: maxSheetSize, // ✅ Limit rows read per sheet
632
- dense: false // ✅ Use sparse format for memory efficiency
630
+ sheetRows: maxSheetSize,
631
+ dense: false
633
632
  });
634
633
 
635
634
  const sheets: Record<string, DataFrame> = {};
@@ -639,7 +638,6 @@ export class TabularDriver {
639
638
  for (const sheetName of workbook.SheetNames) {
640
639
  const worksheet = workbook.Sheets[sheetName];
641
640
 
642
- // ✅ Get sheet range to determine size
643
641
  const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
644
642
  const totalRows = range.e.r - range.s.r + 1;
645
643
 
@@ -648,55 +646,16 @@ export class TabularDriver {
648
646
  continue;
649
647
  }
650
648
 
651
- // ✅ Process in chunks if sheet is large
652
- if (totalRows > sheetChunkSize) {
653
- const rows: Record<string, any>[] = [];
654
- let headers: string[] = [];
655
-
656
- for (let startRow = 0; startRow < totalRows; startRow += sheetChunkSize) {
657
- const endRow = Math.min(startRow + sheetChunkSize, totalRows);
658
-
659
- // ✅ Read chunk with limited row range
660
- const chunkData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
661
- range: startRow,
662
- header: startRow === 0 ? undefined : headers,
663
- defval: null,
664
- raw: false, // ✅ Convert dates/numbers to strings to reduce memory
665
- blankrows: false
666
- });
667
-
668
- if (startRow === 0 && chunkData.length > 0) {
669
- headers = Object.keys(chunkData[0]);
670
- }
671
-
672
- rows.push(...chunkData);
673
-
674
- if (onProgress) {
675
- const progress = 0.3 + (0.6 * (processedSheets + (startRow / totalRows)) / totalSheets);
676
- onProgress(file.size * progress, file.size);
677
- }
678
-
679
- // ✅ Stop if we hit max rows
680
- if (rows.length >= maxSheetSize) {
681
- console.warn(`⚠️ Sheet "${sheetName}" truncated to ${maxSheetSize} rows`);
682
- break;
683
- }
684
- }
685
-
686
- if (rows.length > 0) {
687
- sheets[sheetName] = new DataFrame(rows);
688
- }
689
- } else {
690
- // ✅ Small sheet: process normally
691
- const jsonData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
692
- defval: null,
693
- raw: false,
694
- blankrows: false
695
- });
649
+ // ✅ NEW: Support headerRow option
650
+ const jsonData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
651
+ range: headerRow !== undefined ? headerRow : undefined, // Start from specified row
652
+ defval: null,
653
+ raw: false,
654
+ blankrows: false
655
+ });
696
656
 
697
- if (jsonData.length > 0) {
698
- sheets[sheetName] = new DataFrame(jsonData);
699
- }
657
+ if (jsonData.length > 0) {
658
+ sheets[sheetName] = new DataFrame(jsonData);
700
659
  }
701
660
 
702
661
  processedSheets++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.184",
3
+ "version": "1.1.185",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",