@cj-tech-master/excelts 7.6.0 → 8.0.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.
Files changed (79) hide show
  1. package/README.md +99 -577
  2. package/README_zh.md +101 -577
  3. package/dist/browser/index.browser.d.ts +3 -0
  4. package/dist/browser/index.browser.js +2 -0
  5. package/dist/browser/index.d.ts +3 -0
  6. package/dist/browser/index.js +2 -0
  7. package/dist/browser/modules/archive/compression/compress.browser.js +4 -4
  8. package/dist/browser/modules/archive/compression/deflate-fallback.d.ts +24 -22
  9. package/dist/browser/modules/archive/compression/deflate-fallback.js +664 -360
  10. package/dist/browser/modules/archive/compression/streaming-compress.browser.d.ts +7 -0
  11. package/dist/browser/modules/archive/compression/streaming-compress.browser.js +15 -3
  12. package/dist/browser/modules/archive/compression/streaming-compress.d.ts +5 -0
  13. package/dist/browser/modules/archive/compression/streaming-compress.js +7 -0
  14. package/dist/browser/modules/archive/zip/stream.js +27 -3
  15. package/dist/browser/modules/excel/workbook.browser.d.ts +72 -0
  16. package/dist/browser/modules/excel/workbook.browser.js +226 -0
  17. package/dist/browser/modules/excel/workbook.d.ts +32 -1
  18. package/dist/browser/modules/excel/workbook.js +47 -2
  19. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +42 -4
  20. package/dist/browser/modules/markdown/constants.d.ts +30 -0
  21. package/dist/browser/modules/markdown/constants.js +30 -0
  22. package/dist/browser/modules/markdown/errors.d.ts +21 -0
  23. package/dist/browser/modules/markdown/errors.js +23 -0
  24. package/dist/browser/modules/markdown/format/index.d.ts +54 -0
  25. package/dist/browser/modules/markdown/format/index.js +307 -0
  26. package/dist/browser/modules/markdown/index.d.ts +15 -0
  27. package/dist/browser/modules/markdown/index.js +22 -0
  28. package/dist/browser/modules/markdown/parse/index.d.ts +70 -0
  29. package/dist/browser/modules/markdown/parse/index.js +428 -0
  30. package/dist/browser/modules/markdown/types.d.ts +130 -0
  31. package/dist/browser/modules/markdown/types.js +6 -0
  32. package/dist/cjs/index.js +5 -1
  33. package/dist/cjs/modules/archive/compression/compress.browser.js +4 -4
  34. package/dist/cjs/modules/archive/compression/deflate-fallback.js +664 -360
  35. package/dist/cjs/modules/archive/compression/streaming-compress.browser.js +15 -2
  36. package/dist/cjs/modules/archive/compression/streaming-compress.js +8 -0
  37. package/dist/cjs/modules/archive/zip/stream.js +26 -2
  38. package/dist/cjs/modules/excel/workbook.browser.js +226 -0
  39. package/dist/cjs/modules/excel/workbook.js +46 -1
  40. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +42 -4
  41. package/dist/cjs/modules/markdown/constants.js +33 -0
  42. package/dist/cjs/modules/markdown/errors.js +28 -0
  43. package/dist/cjs/modules/markdown/format/index.js +310 -0
  44. package/dist/cjs/modules/markdown/index.js +30 -0
  45. package/dist/cjs/modules/markdown/parse/index.js +432 -0
  46. package/dist/cjs/modules/markdown/types.js +7 -0
  47. package/dist/esm/index.browser.js +2 -0
  48. package/dist/esm/index.js +2 -0
  49. package/dist/esm/modules/archive/compression/compress.browser.js +4 -4
  50. package/dist/esm/modules/archive/compression/deflate-fallback.js +664 -360
  51. package/dist/esm/modules/archive/compression/streaming-compress.browser.js +15 -3
  52. package/dist/esm/modules/archive/compression/streaming-compress.js +7 -0
  53. package/dist/esm/modules/archive/zip/stream.js +27 -3
  54. package/dist/esm/modules/excel/workbook.browser.js +226 -0
  55. package/dist/esm/modules/excel/workbook.js +47 -2
  56. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +42 -4
  57. package/dist/esm/modules/markdown/constants.js +30 -0
  58. package/dist/esm/modules/markdown/errors.js +23 -0
  59. package/dist/esm/modules/markdown/format/index.js +307 -0
  60. package/dist/esm/modules/markdown/index.js +22 -0
  61. package/dist/esm/modules/markdown/parse/index.js +428 -0
  62. package/dist/esm/modules/markdown/types.js +6 -0
  63. package/dist/iife/excelts.iife.js +1342 -283
  64. package/dist/iife/excelts.iife.js.map +1 -1
  65. package/dist/iife/excelts.iife.min.js +38 -34
  66. package/dist/types/index.browser.d.ts +3 -0
  67. package/dist/types/index.d.ts +3 -0
  68. package/dist/types/modules/archive/compression/deflate-fallback.d.ts +24 -22
  69. package/dist/types/modules/archive/compression/streaming-compress.browser.d.ts +7 -0
  70. package/dist/types/modules/archive/compression/streaming-compress.d.ts +5 -0
  71. package/dist/types/modules/excel/workbook.browser.d.ts +72 -0
  72. package/dist/types/modules/excel/workbook.d.ts +32 -1
  73. package/dist/types/modules/markdown/constants.d.ts +30 -0
  74. package/dist/types/modules/markdown/errors.d.ts +21 -0
  75. package/dist/types/modules/markdown/format/index.d.ts +54 -0
  76. package/dist/types/modules/markdown/index.d.ts +15 -0
  77. package/dist/types/modules/markdown/parse/index.d.ts +70 -0
  78. package/dist/types/modules/markdown/types.d.ts +130 -0
  79. package/package.json +56 -32
@@ -19,6 +19,7 @@ exports.createGzipStream = createGzipStream;
19
19
  exports.createGunzipStream = createGunzipStream;
20
20
  exports.createZlibStream = createZlibStream;
21
21
  exports.createUnzlibStream = createUnzlibStream;
22
+ exports.hasNativeAsyncDeflate = hasNativeAsyncDeflate;
22
23
  const event_emitter_1 = require("../../../utils/event-emitter.js");
23
24
  const deflate_fallback_1 = require("./deflate-fallback.js");
24
25
  Object.defineProperty(exports, "SyncDeflater", { enumerable: true, get: function () { return deflate_fallback_1.SyncDeflater; } });
@@ -264,10 +265,13 @@ function createStreamCodec(type, options) {
264
265
  if (options.useWorker && (0, index_browser_1.hasWorkerSupport)()) {
265
266
  return createWorkerStreamCodec(type, options.workerPool, level, options.allowTransfer);
266
267
  }
267
- if ((0, compress_base_1.hasDeflateRawWebStreams)()) {
268
+ // Use native CompressionStream/DecompressionStream when the required
269
+ // direction is available. Compression only needs CompressionStream;
270
+ // decompression only needs DecompressionStream.
271
+ if (type === "deflate" ? (0, compress_base_1.hasDeflateRawCompressionStream)() : (0, compress_base_1.hasDeflateRawWebStreams)()) {
268
272
  return createNativeWebStreamCodec("deflate-raw", type === "deflate");
269
273
  }
270
- return new BufferedCodec(type === "deflate" ? deflate_fallback_1.deflateRawCompressed : deflate_fallback_1.inflateRaw);
274
+ return new BufferedCodec(type === "deflate" ? data => (0, deflate_fallback_1.deflateRawCompressed)(data, level) : deflate_fallback_1.inflateRaw);
271
275
  }
272
276
  // =============================================================================
273
277
  // Public API
@@ -319,3 +323,12 @@ function createZlibStream(options = {}) {
319
323
  function createUnzlibStream(options = {}) {
320
324
  return createWrappedStream(ZLIB_CONFIG, false, options);
321
325
  }
326
+ /**
327
+ * Returns true when the browser supports native `CompressionStream("deflate-raw")`,
328
+ * signalling that `push()` should prefer the async path over `SyncDeflater`.
329
+ *
330
+ * Only checks for compression support — decompression is not needed for writing.
331
+ */
332
+ function hasNativeAsyncDeflate() {
333
+ return (0, compress_base_1.hasDeflateRawCompressionStream)();
334
+ }
@@ -14,6 +14,7 @@ exports.createGzipStream = createGzipStream;
14
14
  exports.createGunzipStream = createGunzipStream;
15
15
  exports.createZlibStream = createZlibStream;
16
16
  exports.createUnzlibStream = createUnzlibStream;
17
+ exports.hasNativeAsyncDeflate = hasNativeAsyncDeflate;
17
18
  const zlib_1 = require("zlib");
18
19
  const _stream_1 = require("../../stream/index.js");
19
20
  const defaults_1 = require("../shared/defaults.js");
@@ -177,3 +178,10 @@ class SyncDeflater {
177
178
  }
178
179
  }
179
180
  exports.SyncDeflater = SyncDeflater;
181
+ /**
182
+ * On Node.js, `zlib.deflateRawSync` is native and fast — no need to detour
183
+ * through the async streaming path. Always returns false.
184
+ */
185
+ function hasNativeAsyncDeflate() {
186
+ return false;
187
+ }
@@ -574,6 +574,12 @@ class ZipDeflateFile {
574
574
  this._tapCallback(promise, callback);
575
575
  return promise;
576
576
  }
577
+ // If a previous async operation already failed, don't do more work.
578
+ if (this._completeError) {
579
+ const promise = Promise.reject(this._completeError);
580
+ this._tapCallback(promise, callback);
581
+ return promise;
582
+ }
577
583
  if (this._deflateWanted === null) {
578
584
  this._accumulateSampleLen(data);
579
585
  if (!this._shouldDecide(final)) {
@@ -616,7 +622,16 @@ class ZipDeflateFile {
616
622
  * memory growth when callers push data in a tight synchronous loop.
617
623
  */
618
624
  push(data, final = false, callback) {
619
- if (!this._deflate && this._encryptionMethod === "none") {
625
+ // Use the synchronous path only when:
626
+ // 1. No async deflate stream is already active
627
+ // 2. No encryption (which requires async crypto)
628
+ // 3. No native async deflate available (browser CompressionStream)
629
+ //
630
+ // When a native async CompressionStream("deflate-raw") is available
631
+ // (modern browsers), prefer the async path — it produces better
632
+ // compression (Dynamic Huffman via the browser's native engine) and
633
+ // doesn't block the main thread.
634
+ if (!this._deflate && this._encryptionMethod === "none" && !(0, streaming_compress_1.hasNativeAsyncDeflate)()) {
620
635
  try {
621
636
  this._pushSyncPath(data, final);
622
637
  callback?.();
@@ -627,7 +642,10 @@ class ZipDeflateFile {
627
642
  }
628
643
  return Promise.resolve();
629
644
  }
630
- const promise = (this._pushChain = this._pushChain.then(() => this._pushUnchained(data, final, callback)));
645
+ // Chain the async push so calls are serialized. Use a recovery wrapper
646
+ // so that a single failed push does not break the chain for subsequent
647
+ // pushes — errors are surfaced via onerror/rejectComplete instead.
648
+ const promise = (this._pushChain = this._pushChain.then(() => this._pushUnchained(data, final, callback), () => this._pushUnchained(data, final, callback)));
631
649
  // Prevent unhandled rejection when callers intentionally ignore the Promise.
632
650
  promise.catch(() => { });
633
651
  return promise;
@@ -698,6 +716,12 @@ class ZipDeflateFile {
698
716
  this._syncDeflater = null;
699
717
  }
700
718
  this._emitDataDescriptor();
719
+ // _emitDataDescriptor may call _rejectComplete instead of throwing
720
+ // (e.g. ZIP64 required but zip64=false). In the sync path, surface
721
+ // this as a throw so push() can reject properly.
722
+ if (this._completeError) {
723
+ throw this._completeError;
724
+ }
701
725
  }
702
726
  /**
703
727
  * Emit local file header with Data Descriptor flag
@@ -22,6 +22,8 @@ const parse_1 = require("../csv/parse/index.js");
22
22
  const format_1 = require("../csv/format/index.js");
23
23
  const stream_1 = require("../csv/stream/index.js");
24
24
  const number_1 = require("../csv/utils/number.js");
25
+ const index_1 = require("../markdown/parse/index.js");
26
+ const index_2 = require("../markdown/format/index.js");
25
27
  const errors_1 = require("./errors.js");
26
28
  const _stream_1 = require("../stream/index.js");
27
29
  const utils_base_1 = require("../stream/utils.base.js");
@@ -116,6 +118,57 @@ function createDefaultWriteMapper(dateFormat, dateUTC) {
116
118
  };
117
119
  }
118
120
  // =============================================================================
121
+ // Markdown Value Mapper (Internal)
122
+ // =============================================================================
123
+ /**
124
+ * Create a stringify function for Markdown output.
125
+ * Handles hyperlinks, formulas, rich text, dates, errors, and objects.
126
+ */
127
+ function createMarkdownStringify(dateFormat, dateUTC) {
128
+ const formatter = dateFormat
129
+ ? datetime_1.DateFormatter.create(dateFormat, { utc: dateUTC })
130
+ : datetime_1.DateFormatter.iso(dateUTC);
131
+ return function stringify(value) {
132
+ if (value === null || value === undefined) {
133
+ return "";
134
+ }
135
+ if (typeof value === "string") {
136
+ return value;
137
+ }
138
+ if (typeof value === "number" || typeof value === "bigint") {
139
+ return String(value);
140
+ }
141
+ if (typeof value === "boolean") {
142
+ return value ? "true" : "false";
143
+ }
144
+ if (value instanceof Date) {
145
+ return formatter.format(value);
146
+ }
147
+ if (typeof value === "object") {
148
+ const v = value;
149
+ if (v.text || v.hyperlink) {
150
+ return v.hyperlink || v.text || "";
151
+ }
152
+ if (v.formula || v.result) {
153
+ return v.result != null ? String(v.result) : "";
154
+ }
155
+ if (v.richText && Array.isArray(v.richText)) {
156
+ return v.richText.map((r) => r.text).join("");
157
+ }
158
+ if (v.error) {
159
+ return v.error;
160
+ }
161
+ try {
162
+ return JSON.stringify(value);
163
+ }
164
+ catch {
165
+ return "[object Object]";
166
+ }
167
+ }
168
+ return String(value);
169
+ };
170
+ }
171
+ // =============================================================================
119
172
  // CSV Input Type Detection (Internal)
120
173
  // =============================================================================
121
174
  function isUrl(input) {
@@ -563,6 +616,179 @@ class Workbook {
563
616
  formatter.end();
564
617
  await pipelinePromise;
565
618
  }
619
+ /**
620
+ * Populate a worksheet from a parsed Markdown table result.
621
+ * Shared by readMarkdown and readMarkdownAll.
622
+ */
623
+ _populateMarkdownWorksheet(worksheet, result, map) {
624
+ worksheet.addRow(result.headers);
625
+ worksheet._markdownAlignments = result.alignments;
626
+ for (const row of result.rows) {
627
+ if (map) {
628
+ worksheet.addRow(row.map((v, i) => map(v, i)));
629
+ }
630
+ else {
631
+ worksheet.addRow(row);
632
+ }
633
+ }
634
+ }
635
+ /**
636
+ * Read a Markdown table and add as worksheet.
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * // From a Markdown string
641
+ * workbook.readMarkdown("| Name | Age |\n| --- | --- |\n| Alice | 30 |");
642
+ *
643
+ * // With options
644
+ * workbook.readMarkdown(markdownString, { sheetName: "Data", map: (v, col) => Number(v) || v });
645
+ * ```
646
+ */
647
+ readMarkdown(input, options) {
648
+ const parseResult = (0, index_1.parseMarkdown)(input, {
649
+ trim: options?.trim,
650
+ unescape: options?.unescape,
651
+ skipEmptyRows: options?.skipEmptyRows,
652
+ maxRows: options?.maxRows,
653
+ convertBr: options?.convertBr
654
+ });
655
+ const worksheet = this.addWorksheet(options?.sheetName);
656
+ this._populateMarkdownWorksheet(worksheet, parseResult, options?.map);
657
+ return worksheet;
658
+ }
659
+ /**
660
+ * Read all Markdown tables from a document, each becoming a separate worksheet.
661
+ *
662
+ * @param input - Markdown string containing one or more tables
663
+ * @param options - Parse options (sheetName is used as prefix: "sheetName", "sheetName_2", ...)
664
+ * @returns Array of created worksheets (empty if no tables found)
665
+ *
666
+ * @example
667
+ * ```ts
668
+ * // Parse a document with multiple tables
669
+ * const sheets = workbook.readMarkdownAll(markdownDoc);
670
+ * console.log(`Created ${sheets.length} worksheets`);
671
+ *
672
+ * // With a naming prefix
673
+ * const sheets = workbook.readMarkdownAll(markdownDoc, { sheetName: "Table" });
674
+ * // Creates "Table", "Table_2", "Table_3", ...
675
+ * ```
676
+ */
677
+ readMarkdownAll(input, options) {
678
+ const parseResults = (0, index_1.parseMarkdownAll)(input, {
679
+ trim: options?.trim,
680
+ unescape: options?.unescape,
681
+ skipEmptyRows: options?.skipEmptyRows,
682
+ maxRows: options?.maxRows,
683
+ convertBr: options?.convertBr
684
+ });
685
+ const baseName = options?.sheetName;
686
+ const map = options?.map;
687
+ const worksheets = [];
688
+ for (let t = 0; t < parseResults.length; t++) {
689
+ const name = baseName ? (t === 0 ? baseName : `${baseName}_${t + 1}`) : undefined;
690
+ const worksheet = this.addWorksheet(name);
691
+ this._populateMarkdownWorksheet(worksheet, parseResults[t], map);
692
+ worksheets.push(worksheet);
693
+ }
694
+ return worksheets;
695
+ }
696
+ /**
697
+ * Write worksheet as a Markdown table string.
698
+ *
699
+ * @example
700
+ * ```ts
701
+ * // Write first worksheet
702
+ * const markdownText = workbook.writeMarkdown();
703
+ *
704
+ * // Write specific worksheet with options
705
+ * const markdownText = workbook.writeMarkdown({ sheetName: "Data", padding: true });
706
+ * ```
707
+ */
708
+ writeMarkdown(options) {
709
+ const worksheet = this.getWorksheet(options?.sheetName || options?.sheetId);
710
+ if (!worksheet) {
711
+ return "";
712
+ }
713
+ const dateFormat = options?.dateFormat;
714
+ const dateUTC = options?.dateUTC;
715
+ const includeEmptyRows = options?.includeEmptyRows !== false;
716
+ // Build stringify function
717
+ const stringify = options?.stringify ?? createMarkdownStringify(dateFormat, dateUTC);
718
+ // Collect all rows from worksheet
719
+ const allRows = [];
720
+ let lastRow = 1;
721
+ worksheet.eachRow((row, rowNumber) => {
722
+ if (includeEmptyRows) {
723
+ while (lastRow++ < rowNumber - 1) {
724
+ allRows.push([]);
725
+ }
726
+ }
727
+ // row.values is a 1-indexed sparse array — use Array.from to fill holes
728
+ // with undefined, then slice(1) to remove the leading 1-indexed slot
729
+ const values = Array.from(row.values).slice(1);
730
+ allRows.push(values);
731
+ lastRow = rowNumber;
732
+ });
733
+ if (allRows.length === 0) {
734
+ return "";
735
+ }
736
+ // First row is the header
737
+ const headerRow = allRows[0];
738
+ const headers = headerRow.map(v => stringify(v));
739
+ const dataRows = allRows.slice(1);
740
+ // Check for stored alignments from a previous readMarkdown
741
+ const storedAlignments = worksheet
742
+ ._markdownAlignments;
743
+ // Build column configs
744
+ const columns = options?.columns;
745
+ let resolvedColumns;
746
+ if (!columns && storedAlignments) {
747
+ // Use stored alignments from parsed Markdown
748
+ resolvedColumns = headers.map((h, i) => ({
749
+ header: h,
750
+ alignment: i < storedAlignments.length ? storedAlignments[i] : undefined
751
+ }));
752
+ }
753
+ return (0, index_2.formatMarkdown)(headers, dataRows, {
754
+ columns: resolvedColumns ?? columns,
755
+ alignment: options?.alignment,
756
+ padding: options?.padding,
757
+ trailingNewline: options?.trailingNewline,
758
+ escapeContent: options?.escapeContent,
759
+ stringify
760
+ });
761
+ }
762
+ /**
763
+ * Write worksheet to Markdown buffer (Uint8Array).
764
+ *
765
+ * @example
766
+ * ```ts
767
+ * const buffer = workbook.writeMarkdownBuffer();
768
+ * ```
769
+ */
770
+ writeMarkdownBuffer(options) {
771
+ const markdownString = this.writeMarkdown(options);
772
+ return new TextEncoder().encode(markdownString);
773
+ }
774
+ /**
775
+ * Read Markdown from file (Node.js only - throws in browser)
776
+ */
777
+ async readMarkdownFile(_filename, _options) {
778
+ throw new errors_1.ExcelNotSupportedError("readMarkdownFile()", "not available in browser. Use readMarkdown(string) instead.");
779
+ }
780
+ /**
781
+ * Read all Markdown tables from file (Node.js only - throws in browser)
782
+ */
783
+ async readMarkdownAllFile(_filename, _options) {
784
+ throw new errors_1.ExcelNotSupportedError("readMarkdownAllFile()", "not available in browser. Use readMarkdownAll(string) instead.");
785
+ }
786
+ /**
787
+ * Write Markdown to file (Node.js only - throws in browser)
788
+ */
789
+ async writeMarkdownFile(_filename, _options) {
790
+ throw new errors_1.ExcelNotSupportedError("writeMarkdownFile()", "not available in browser. Use writeMarkdown() and trigger a download instead.");
791
+ }
566
792
  // ===========================================================================
567
793
  // Static Factory Methods for Streaming
568
794
  // ===========================================================================
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Workbook - Node.js Version
4
4
  *
5
- * Extends browser Workbook with Node.js file system support for CSV operations.
5
+ * Extends browser Workbook with Node.js file system support for CSV and Markdown operations.
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.Workbook = void 0;
@@ -60,5 +60,50 @@ class Workbook extends workbook_browser_1.Workbook {
60
60
  }
61
61
  return this._writeCsvStream(writeStream, options);
62
62
  }
63
+ /**
64
+ * Read Markdown table from file (Node.js only)
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * await workbook.readMarkdownFile("table.md");
69
+ * await workbook.readMarkdownFile("table.md", { sheetName: "Data" });
70
+ * ```
71
+ */
72
+ async readMarkdownFile(filename, options) {
73
+ if (!(await (0, fs_1.fileExists)(filename))) {
74
+ throw new errors_1.ExcelFileError(filename, "read", "file not found");
75
+ }
76
+ const content = await (0, fs_1.readFileText)(filename);
77
+ return this.readMarkdown(content, options);
78
+ }
79
+ /**
80
+ * Read all Markdown tables from file, each as a separate worksheet (Node.js only)
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * await workbook.readMarkdownAllFile("doc.md");
85
+ * await workbook.readMarkdownAllFile("doc.md", { sheetName: "Table" });
86
+ * ```
87
+ */
88
+ async readMarkdownAllFile(filename, options) {
89
+ if (!(await (0, fs_1.fileExists)(filename))) {
90
+ throw new errors_1.ExcelFileError(filename, "read", "file not found");
91
+ }
92
+ const content = await (0, fs_1.readFileText)(filename);
93
+ return this.readMarkdownAll(content, options);
94
+ }
95
+ /**
96
+ * Write Markdown table to file (Node.js only)
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * await workbook.writeMarkdownFile("output.md");
101
+ * await workbook.writeMarkdownFile("output.md", { sheetName: "Data", padding: true });
102
+ * ```
103
+ */
104
+ async writeMarkdownFile(filename, options) {
105
+ const markdownString = this.writeMarkdown(options);
106
+ await (0, fs_1.writeFileText)(filename, markdownString);
107
+ }
63
108
  }
64
109
  exports.Workbook = Workbook;
@@ -49,6 +49,13 @@ class StreamingZipWriterAdapter {
49
49
  // Backpressure tracking
50
50
  this._needsDrain = false;
51
51
  this._drainResolvers = [];
52
+ // Count of in-flight async write() calls whose backpressure result is unknown.
53
+ // waitForDrain() must wait for this to reach 0 before checking _needsDrain.
54
+ this._pendingWrites = 0;
55
+ this._pendingWriteResolvers = [];
56
+ // Buffer errors that occur before _finalize registers its error listener,
57
+ // so async compression errors during writeToZip() are never silently lost.
58
+ this._earlyError = null;
52
59
  this.level = options?.level ?? 6;
53
60
  this.modTime = options?.modTime;
54
61
  this.timestamps = options?.timestamps;
@@ -72,6 +79,15 @@ class StreamingZipWriterAdapter {
72
79
  });
73
80
  }
74
81
  _emit(event, ...args) {
82
+ // Buffer error events that fire before any listener is registered,
83
+ // so _finalize() can surface them even if it registers late.
84
+ if (event === "error") {
85
+ const callbacks = this.events.get(event);
86
+ if (!callbacks || callbacks.size === 0) {
87
+ this._earlyError = args[0] instanceof Error ? args[0] : new Error(String(args[0]));
88
+ return;
89
+ }
90
+ }
75
91
  const callbacks = this.events.get(event);
76
92
  if (!callbacks) {
77
93
  return;
@@ -86,12 +102,21 @@ class StreamingZipWriterAdapter {
86
102
  */
87
103
  _checkBackpressure(ok) {
88
104
  if (ok instanceof Promise) {
105
+ this._pendingWrites++;
89
106
  ok.then(result => {
90
107
  if (result === false) {
91
108
  this._needsDrain = true;
92
109
  }
93
110
  }, () => { } // write errors surface via the stream's error event
94
- );
111
+ ).finally(() => {
112
+ this._pendingWrites--;
113
+ if (this._pendingWrites === 0) {
114
+ const resolvers = this._pendingWriteResolvers.splice(0);
115
+ for (const resolve of resolvers) {
116
+ resolve();
117
+ }
118
+ }
119
+ });
95
120
  return;
96
121
  }
97
122
  if (ok === false) {
@@ -102,6 +127,12 @@ class StreamingZipWriterAdapter {
102
127
  const callbacks = this.events.get(event) || new Set();
103
128
  callbacks.add(callback);
104
129
  this.events.set(event, callbacks);
130
+ // If an error was buffered before any listener was registered, deliver it now.
131
+ if (event === "error" && this._earlyError) {
132
+ const err = this._earlyError;
133
+ this._earlyError = null;
134
+ callback(err);
135
+ }
105
136
  return this;
106
137
  }
107
138
  once(event, callback) {
@@ -137,11 +168,18 @@ class StreamingZipWriterAdapter {
137
168
  }
138
169
  /**
139
170
  * Wait for the downstream writable to drain if it signaled backpressure.
140
- * Resolves immediately if no backpressure is active.
171
+ * If any write() calls are still in-flight (returned a Promise that hasn't
172
+ * settled), waits for all of them first so the backpressure signal isn't missed.
141
173
  */
142
- waitForDrain() {
174
+ async waitForDrain() {
175
+ // Wait for all in-flight async writes to settle so _needsDrain is up to date.
176
+ if (this._pendingWrites > 0) {
177
+ await new Promise(resolve => {
178
+ this._pendingWriteResolvers.push(resolve);
179
+ });
180
+ }
143
181
  if (!this._needsDrain || !this.pipedStream) {
144
- return Promise.resolve();
182
+ return;
145
183
  }
146
184
  return new Promise(resolve => {
147
185
  this._drainResolvers.push(resolve);
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * Markdown Module Constants
4
+ *
5
+ * Shared constants used across the Markdown module.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.NEWLINE_IN_CELL = exports.BR_TAG_REGEX = exports.UNESCAPE_REGEX = exports.ESCAPE_AND_NEWLINE = exports.LINEBREAK_REGEX = void 0;
9
+ /**
10
+ * Pre-compiled regex for line splitting (matches CR, LF, or CRLF)
11
+ */
12
+ exports.LINEBREAK_REGEX = /\r\n|\r|\n/;
13
+ /**
14
+ * Characters that need escaping in Markdown table cells.
15
+ * Also matches CRLF/CR/LF so that escaping + newline conversion
16
+ * can be done in a single `replace()` call.
17
+ * Note: `\r\n` must come before `\r` to match CRLF as a single unit.
18
+ */
19
+ exports.ESCAPE_AND_NEWLINE = /\r\n|[|\\\r\n]/g;
20
+ /**
21
+ * Regex to unescape Markdown table cell content (`\|` → `|`, `\\` → `\`)
22
+ */
23
+ exports.UNESCAPE_REGEX = /\\([|\\])/g;
24
+ /**
25
+ * Regex to match `<br>`, `<br/>`, or `<br />` tags (case-insensitive).
26
+ * Used to convert multiline cell representations back to newlines during parsing.
27
+ */
28
+ exports.BR_TAG_REGEX = /<br\s*\/?>/gi;
29
+ /**
30
+ * Regex to match literal newlines (CR, LF, or CRLF) in cell content.
31
+ * Used when escaping is disabled but newline conversion is still needed.
32
+ */
33
+ exports.NEWLINE_IN_CELL = /\r\n|\r|\n/g;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * Markdown module error types.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MarkdownParseError = exports.MarkdownError = void 0;
7
+ const errors_1 = require("../../utils/errors.js");
8
+ /**
9
+ * Base class for all Markdown-related errors.
10
+ */
11
+ class MarkdownError extends errors_1.BaseError {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.name = "MarkdownError";
15
+ }
16
+ }
17
+ exports.MarkdownError = MarkdownError;
18
+ /**
19
+ * Error thrown when Markdown parsing fails.
20
+ */
21
+ class MarkdownParseError extends MarkdownError {
22
+ constructor(message, line, options) {
23
+ super(`Line ${line}: ${message}`, options);
24
+ this.name = "MarkdownParseError";
25
+ this.line = line;
26
+ }
27
+ }
28
+ exports.MarkdownParseError = MarkdownParseError;