@changerawr/markdown 1.1.5 → 1.1.6

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/index.js CHANGED
@@ -43,6 +43,7 @@ __export(index_exports, {
43
43
  ImageExtension: () => ImageExtension,
44
44
  InlineCodeExtension: () => InlineCodeExtension,
45
45
  ItalicExtension: () => ItalicExtension,
46
+ LRUCache: () => LRUCache,
46
47
  LineBreakExtension: () => LineBreakExtension,
47
48
  LinkExtension: () => LinkExtension,
48
49
  ListExtension: () => ListExtension,
@@ -75,10 +76,12 @@ __export(index_exports, {
75
76
  generateId: () => generateId,
76
77
  getASTStats: () => getASTStats,
77
78
  getTokenStats: () => getTokenStats,
79
+ hashContent: () => hashContent,
78
80
  isBrowser: () => isBrowser,
79
81
  isNode: () => isNode,
80
82
  isValidUrl: () => isValidUrl,
81
83
  markdown: () => markdown2,
84
+ memoize: () => memoize,
82
85
  minimalClasses: () => minimalClasses,
83
86
  parseASTFromJSON: () => parseASTFromJSON,
84
87
  parseCum: () => parseCum,
@@ -108,9 +111,11 @@ module.exports = __toCommonJS(index_exports);
108
111
 
109
112
  // src/parser.ts
110
113
  var MarkdownParser = class {
114
+ // Cache compiled regexes
111
115
  constructor(config) {
112
116
  this.rules = [];
113
117
  this.warnings = [];
118
+ this.compiledPatterns = /* @__PURE__ */ new Map();
114
119
  this.config = {
115
120
  debugMode: false,
116
121
  maxIterations: 1e4,
@@ -120,6 +125,10 @@ var MarkdownParser = class {
120
125
  }
121
126
  addRule(rule) {
122
127
  this.rules.push(rule);
128
+ this.compiledPatterns.set(
129
+ rule,
130
+ new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
131
+ );
123
132
  this.rules.sort((a, b) => {
124
133
  const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
125
134
  const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
@@ -165,12 +174,20 @@ var MarkdownParser = class {
165
174
  iterationCount++;
166
175
  let matched = false;
167
176
  let bestMatch = null;
177
+ let nextBestMatchIndex = null;
168
178
  for (const rule of this.rules) {
169
179
  try {
170
- const pattern = new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""));
180
+ const pattern = this.compiledPatterns.get(rule);
171
181
  const match = remaining.match(pattern);
172
182
  if (match && match.index !== void 0) {
173
- const priority = match.index === 0 ? 1e3 : 1e3 - match.index;
183
+ if (match.index === 0) {
184
+ bestMatch = { rule, match, priority: 1e3 };
185
+ break;
186
+ }
187
+ if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
188
+ nextBestMatchIndex = match.index;
189
+ }
190
+ const priority = 1e3 - match.index;
174
191
  if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
175
192
  bestMatch = { rule, match, priority };
176
193
  }
@@ -217,15 +234,14 @@ var MarkdownParser = class {
217
234
  }
218
235
  }
219
236
  if (!matched) {
220
- const char = remaining[0];
221
- if (char) {
222
- tokens.push({
223
- type: "text",
224
- content: char,
225
- raw: char
226
- });
227
- remaining = remaining.slice(1);
228
- }
237
+ const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
238
+ const textChunk = remaining.slice(0, chunkSize);
239
+ tokens.push({
240
+ type: "text",
241
+ content: textChunk,
242
+ raw: textChunk
243
+ });
244
+ remaining = remaining.slice(chunkSize);
229
245
  }
230
246
  }
231
247
  if (iterationCount >= maxIterations) {
@@ -723,6 +739,171 @@ var MarkdownRenderer = class {
723
739
  }
724
740
  };
725
741
 
742
+ // src/cache.ts
743
+ var LRUCache = class {
744
+ constructor(capacity = 100) {
745
+ this.cache = /* @__PURE__ */ new Map();
746
+ this.hits = 0;
747
+ this.misses = 0;
748
+ this.evictions = 0;
749
+ if (capacity <= 0) {
750
+ throw new Error("Cache capacity must be greater than 0");
751
+ }
752
+ this.capacity = capacity;
753
+ }
754
+ /**
755
+ * Get a value from the cache
756
+ */
757
+ get(key) {
758
+ const entry = this.cache.get(key);
759
+ if (entry) {
760
+ entry.timestamp = Date.now();
761
+ entry.accessCount++;
762
+ this.hits++;
763
+ this.cache.delete(key);
764
+ this.cache.set(key, entry);
765
+ return entry.value;
766
+ }
767
+ this.misses++;
768
+ return void 0;
769
+ }
770
+ /**
771
+ * Set a value in the cache
772
+ */
773
+ set(key, value) {
774
+ if (this.cache.has(key)) {
775
+ this.cache.delete(key);
776
+ } else if (this.cache.size >= this.capacity) {
777
+ this.evictLRU();
778
+ }
779
+ this.cache.set(key, {
780
+ value,
781
+ timestamp: Date.now(),
782
+ accessCount: 0
783
+ });
784
+ }
785
+ /**
786
+ * Check if a key exists in the cache
787
+ */
788
+ has(key) {
789
+ return this.cache.has(key);
790
+ }
791
+ /**
792
+ * Delete a specific key from the cache
793
+ */
794
+ delete(key) {
795
+ return this.cache.delete(key);
796
+ }
797
+ /**
798
+ * Clear all entries from the cache
799
+ */
800
+ clear() {
801
+ this.cache.clear();
802
+ this.hits = 0;
803
+ this.misses = 0;
804
+ this.evictions = 0;
805
+ }
806
+ /**
807
+ * Get the current size of the cache
808
+ */
809
+ get size() {
810
+ return this.cache.size;
811
+ }
812
+ /**
813
+ * Get cache statistics
814
+ */
815
+ getStats() {
816
+ const totalRequests = this.hits + this.misses;
817
+ return {
818
+ size: this.cache.size,
819
+ capacity: this.capacity,
820
+ hits: this.hits,
821
+ misses: this.misses,
822
+ hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
823
+ evictions: this.evictions
824
+ };
825
+ }
826
+ /**
827
+ * Reset cache statistics
828
+ */
829
+ resetStats() {
830
+ this.hits = 0;
831
+ this.misses = 0;
832
+ this.evictions = 0;
833
+ }
834
+ /**
835
+ * Get all keys in the cache
836
+ */
837
+ keys() {
838
+ return Array.from(this.cache.keys());
839
+ }
840
+ /**
841
+ * Get all values in the cache
842
+ */
843
+ values() {
844
+ return Array.from(this.cache.values()).map((entry) => entry.value);
845
+ }
846
+ /**
847
+ * Update cache capacity and evict if necessary
848
+ */
849
+ setCapacity(newCapacity) {
850
+ if (newCapacity <= 0) {
851
+ throw new Error("Cache capacity must be greater than 0");
852
+ }
853
+ this.capacity = newCapacity;
854
+ while (this.cache.size > this.capacity) {
855
+ this.evictLRU();
856
+ }
857
+ }
858
+ /**
859
+ * Evict the least recently used entry
860
+ */
861
+ evictLRU() {
862
+ const firstKey = this.cache.keys().next().value;
863
+ if (firstKey !== void 0) {
864
+ this.cache.delete(firstKey);
865
+ this.evictions++;
866
+ }
867
+ }
868
+ };
869
+ function hashContent(content) {
870
+ if (content.length > 1e4) {
871
+ const start = content.slice(0, 1e3);
872
+ const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
873
+ const end = content.slice(-1e3);
874
+ const sample = content.length + "|" + start + middle + end;
875
+ let hash2 = 2166136261;
876
+ for (let i = 0; i < sample.length; i++) {
877
+ hash2 ^= sample.charCodeAt(i);
878
+ hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
879
+ }
880
+ return (hash2 >>> 0).toString(36);
881
+ }
882
+ let hash = 2166136261;
883
+ for (let i = 0; i < content.length; i++) {
884
+ hash ^= content.charCodeAt(i);
885
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
886
+ }
887
+ return (hash >>> 0).toString(36);
888
+ }
889
+ function memoize(fn, options = {}) {
890
+ const cache = options.cache || new LRUCache(options.maxSize || 100);
891
+ const keyGenerator = options.keyGenerator || ((...args) => JSON.stringify(args));
892
+ const memoized = function(...args) {
893
+ const key = keyGenerator(...args);
894
+ const cached = cache.get(key);
895
+ if (cached !== void 0) {
896
+ return cached;
897
+ }
898
+ const result = fn.apply(this, args);
899
+ cache.set(key, result);
900
+ return result;
901
+ };
902
+ memoized.cache = cache;
903
+ memoized.clearCache = () => cache.clear();
904
+ return memoized;
905
+ }
906
+
726
907
  // src/extensions/core/blockquote.ts
727
908
  var BlockquoteExtension = {
728
909
  name: "blockquote",
@@ -1695,8 +1876,13 @@ function extractVimeoId(url) {
1695
1876
  var ChangerawrMarkdown = class {
1696
1877
  constructor(config) {
1697
1878
  this.extensions = /* @__PURE__ */ new Map();
1879
+ this.parseTime = 0;
1880
+ this.renderTime = 0;
1881
+ this.lastTokenCount = 0;
1698
1882
  this.parser = new MarkdownParser(config?.parser);
1699
1883
  this.renderer = new MarkdownRenderer(config?.renderer);
1884
+ this.parseCache = new LRUCache(100);
1885
+ this.renderCache = new LRUCache(100);
1700
1886
  this.registerCoreExtensions();
1701
1887
  this.registerFeatureExtensions();
1702
1888
  if (config?.extensions) {
@@ -1779,14 +1965,83 @@ var ChangerawrMarkdown = class {
1779
1965
  }
1780
1966
  }
1781
1967
  parse(markdown3) {
1782
- return this.parser.parse(markdown3);
1783
- }
1784
- render(tokens) {
1785
- return this.renderer.render(tokens);
1968
+ const cacheKey = hashContent(markdown3);
1969
+ const cached = this.parseCache.get(cacheKey);
1970
+ if (cached) {
1971
+ this.lastTokenCount = cached.length;
1972
+ return cached;
1973
+ }
1974
+ const startTime = performance.now();
1975
+ const tokens = this.parser.parse(markdown3);
1976
+ this.parseTime = performance.now() - startTime;
1977
+ this.lastTokenCount = tokens.length;
1978
+ this.parseCache.set(cacheKey, tokens);
1979
+ return tokens;
1980
+ }
1981
+ render(tokens, cacheKey) {
1982
+ if (cacheKey) {
1983
+ const cached = this.renderCache.get(cacheKey);
1984
+ if (cached) {
1985
+ return cached;
1986
+ }
1987
+ }
1988
+ const startTime = performance.now();
1989
+ const html = this.renderer.render(tokens);
1990
+ this.renderTime = performance.now() - startTime;
1991
+ if (cacheKey) {
1992
+ this.renderCache.set(cacheKey, html);
1993
+ }
1994
+ return html;
1786
1995
  }
1787
1996
  toHtml(markdown3) {
1997
+ const cacheKey = hashContent(markdown3);
1998
+ const cachedHtml = this.renderCache.get(cacheKey);
1999
+ if (cachedHtml) {
2000
+ return cachedHtml;
2001
+ }
2002
+ const tokens = this.parse(markdown3);
2003
+ return this.render(tokens, cacheKey);
2004
+ }
2005
+ /**
2006
+ * Render markdown with performance metrics
2007
+ */
2008
+ toHtmlWithMetrics(markdown3) {
2009
+ const startTotal = performance.now();
2010
+ const parseCacheKey = hashContent(markdown3);
2011
+ const parseCacheHit = this.parseCache.has(parseCacheKey);
2012
+ const html = this.toHtml(markdown3);
2013
+ const totalTime = performance.now() - startTotal;
2014
+ const metrics = {
2015
+ inputSize: markdown3.length,
2016
+ parseTime: this.parseTime,
2017
+ renderTime: this.renderTime,
2018
+ totalTime,
2019
+ tokenCount: this.lastTokenCount,
2020
+ cacheHit: parseCacheHit
2021
+ };
2022
+ return { html, metrics };
2023
+ }
2024
+ /**
2025
+ * Stream-render large documents in chunks for better performance
2026
+ */
2027
+ async toHtmlStreamed(markdown3, options = {}) {
2028
+ const chunkSize = options.chunkSize || 50;
1788
2029
  const tokens = this.parse(markdown3);
1789
- return this.render(tokens);
2030
+ const totalTokens = tokens.length;
2031
+ const chunks = [];
2032
+ for (let i = 0; i < tokens.length; i += chunkSize) {
2033
+ const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
2034
+ const chunkHtml = this.render(chunkTokens);
2035
+ chunks.push(chunkHtml);
2036
+ if (options.onChunk) {
2037
+ options.onChunk({
2038
+ html: chunkHtml,
2039
+ progress: Math.min(i + chunkSize, tokens.length) / totalTokens
2040
+ });
2041
+ }
2042
+ await new Promise((resolve) => setTimeout(resolve, 0));
2043
+ }
2044
+ return chunks.join("");
1790
2045
  }
1791
2046
  getExtensions() {
1792
2047
  return Array.from(this.extensions.keys());
@@ -1800,25 +2055,50 @@ var ChangerawrMarkdown = class {
1800
2055
  getDebugInfo() {
1801
2056
  return {
1802
2057
  warnings: this.getWarnings(),
1803
- parseTime: 0,
1804
- renderTime: 0,
1805
- tokenCount: 0,
2058
+ parseTime: this.parseTime,
2059
+ renderTime: this.renderTime,
2060
+ tokenCount: this.lastTokenCount,
1806
2061
  iterationCount: 0
1807
2062
  };
1808
2063
  }
1809
2064
  getPerformanceMetrics() {
1810
2065
  return {
1811
- parseTime: 0,
1812
- renderTime: 0,
1813
- totalTime: 0,
1814
- tokenCount: 0
2066
+ parseTime: this.parseTime,
2067
+ renderTime: this.renderTime,
2068
+ totalTime: this.parseTime + this.renderTime,
2069
+ tokenCount: this.lastTokenCount
1815
2070
  };
1816
2071
  }
2072
+ /**
2073
+ * Get cache statistics
2074
+ */
2075
+ getCacheStats() {
2076
+ return {
2077
+ parse: this.parseCache.getStats(),
2078
+ render: this.renderCache.getStats()
2079
+ };
2080
+ }
2081
+ /**
2082
+ * Clear all caches
2083
+ */
2084
+ clearCaches() {
2085
+ this.parseCache.clear();
2086
+ this.renderCache.clear();
2087
+ }
2088
+ /**
2089
+ * Update cache capacity
2090
+ */
2091
+ setCacheSize(size) {
2092
+ this.parseCache.setCapacity(size);
2093
+ this.renderCache.setCapacity(size);
2094
+ }
1817
2095
  rebuildParserAndRenderer() {
1818
2096
  const parserConfig = this.parser.getConfig();
1819
2097
  const rendererConfig = this.renderer.getConfig();
1820
2098
  this.parser = new MarkdownParser(parserConfig);
1821
2099
  this.renderer = new MarkdownRenderer(rendererConfig);
2100
+ this.parseCache.clear();
2101
+ this.renderCache.clear();
1822
2102
  const extensionsToRegister = Array.from(this.extensions.values());
1823
2103
  const featureExtensions = extensionsToRegister.filter(
1824
2104
  (ext) => ["alert", "button", "embed"].includes(ext.name)
@@ -2464,6 +2744,7 @@ function createEngineWithPreset(presetName, additionalConfig) {
2464
2744
  ImageExtension,
2465
2745
  InlineCodeExtension,
2466
2746
  ItalicExtension,
2747
+ LRUCache,
2467
2748
  LineBreakExtension,
2468
2749
  LinkExtension,
2469
2750
  ListExtension,
@@ -2495,10 +2776,12 @@ function createEngineWithPreset(presetName, additionalConfig) {
2495
2776
  generateId,
2496
2777
  getASTStats,
2497
2778
  getTokenStats,
2779
+ hashContent,
2498
2780
  isBrowser,
2499
2781
  isNode,
2500
2782
  isValidUrl,
2501
2783
  markdown,
2784
+ memoize,
2502
2785
  minimalClasses,
2503
2786
  parseASTFromJSON,
2504
2787
  parseCum,