@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.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  // src/parser.ts
2
2
  var MarkdownParser = class {
3
+ // Cache compiled regexes
3
4
  constructor(config) {
4
5
  this.rules = [];
5
6
  this.warnings = [];
7
+ this.compiledPatterns = /* @__PURE__ */ new Map();
6
8
  this.config = {
7
9
  debugMode: false,
8
10
  maxIterations: 1e4,
@@ -12,6 +14,10 @@ var MarkdownParser = class {
12
14
  }
13
15
  addRule(rule) {
14
16
  this.rules.push(rule);
17
+ this.compiledPatterns.set(
18
+ rule,
19
+ new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
20
+ );
15
21
  this.rules.sort((a, b) => {
16
22
  const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
17
23
  const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
@@ -57,12 +63,20 @@ var MarkdownParser = class {
57
63
  iterationCount++;
58
64
  let matched = false;
59
65
  let bestMatch = null;
66
+ let nextBestMatchIndex = null;
60
67
  for (const rule of this.rules) {
61
68
  try {
62
- const pattern = new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""));
69
+ const pattern = this.compiledPatterns.get(rule);
63
70
  const match = remaining.match(pattern);
64
71
  if (match && match.index !== void 0) {
65
- const priority = match.index === 0 ? 1e3 : 1e3 - match.index;
72
+ if (match.index === 0) {
73
+ bestMatch = { rule, match, priority: 1e3 };
74
+ break;
75
+ }
76
+ if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
77
+ nextBestMatchIndex = match.index;
78
+ }
79
+ const priority = 1e3 - match.index;
66
80
  if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
67
81
  bestMatch = { rule, match, priority };
68
82
  }
@@ -109,15 +123,14 @@ var MarkdownParser = class {
109
123
  }
110
124
  }
111
125
  if (!matched) {
112
- const char = remaining[0];
113
- if (char) {
114
- tokens.push({
115
- type: "text",
116
- content: char,
117
- raw: char
118
- });
119
- remaining = remaining.slice(1);
120
- }
126
+ const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
127
+ const textChunk = remaining.slice(0, chunkSize);
128
+ tokens.push({
129
+ type: "text",
130
+ content: textChunk,
131
+ raw: textChunk
132
+ });
133
+ remaining = remaining.slice(chunkSize);
121
134
  }
122
135
  }
123
136
  if (iterationCount >= maxIterations) {
@@ -615,6 +628,171 @@ var MarkdownRenderer = class {
615
628
  }
616
629
  };
617
630
 
631
+ // src/cache.ts
632
+ var LRUCache = class {
633
+ constructor(capacity = 100) {
634
+ this.cache = /* @__PURE__ */ new Map();
635
+ this.hits = 0;
636
+ this.misses = 0;
637
+ this.evictions = 0;
638
+ if (capacity <= 0) {
639
+ throw new Error("Cache capacity must be greater than 0");
640
+ }
641
+ this.capacity = capacity;
642
+ }
643
+ /**
644
+ * Get a value from the cache
645
+ */
646
+ get(key) {
647
+ const entry = this.cache.get(key);
648
+ if (entry) {
649
+ entry.timestamp = Date.now();
650
+ entry.accessCount++;
651
+ this.hits++;
652
+ this.cache.delete(key);
653
+ this.cache.set(key, entry);
654
+ return entry.value;
655
+ }
656
+ this.misses++;
657
+ return void 0;
658
+ }
659
+ /**
660
+ * Set a value in the cache
661
+ */
662
+ set(key, value) {
663
+ if (this.cache.has(key)) {
664
+ this.cache.delete(key);
665
+ } else if (this.cache.size >= this.capacity) {
666
+ this.evictLRU();
667
+ }
668
+ this.cache.set(key, {
669
+ value,
670
+ timestamp: Date.now(),
671
+ accessCount: 0
672
+ });
673
+ }
674
+ /**
675
+ * Check if a key exists in the cache
676
+ */
677
+ has(key) {
678
+ return this.cache.has(key);
679
+ }
680
+ /**
681
+ * Delete a specific key from the cache
682
+ */
683
+ delete(key) {
684
+ return this.cache.delete(key);
685
+ }
686
+ /**
687
+ * Clear all entries from the cache
688
+ */
689
+ clear() {
690
+ this.cache.clear();
691
+ this.hits = 0;
692
+ this.misses = 0;
693
+ this.evictions = 0;
694
+ }
695
+ /**
696
+ * Get the current size of the cache
697
+ */
698
+ get size() {
699
+ return this.cache.size;
700
+ }
701
+ /**
702
+ * Get cache statistics
703
+ */
704
+ getStats() {
705
+ const totalRequests = this.hits + this.misses;
706
+ return {
707
+ size: this.cache.size,
708
+ capacity: this.capacity,
709
+ hits: this.hits,
710
+ misses: this.misses,
711
+ hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
712
+ evictions: this.evictions
713
+ };
714
+ }
715
+ /**
716
+ * Reset cache statistics
717
+ */
718
+ resetStats() {
719
+ this.hits = 0;
720
+ this.misses = 0;
721
+ this.evictions = 0;
722
+ }
723
+ /**
724
+ * Get all keys in the cache
725
+ */
726
+ keys() {
727
+ return Array.from(this.cache.keys());
728
+ }
729
+ /**
730
+ * Get all values in the cache
731
+ */
732
+ values() {
733
+ return Array.from(this.cache.values()).map((entry) => entry.value);
734
+ }
735
+ /**
736
+ * Update cache capacity and evict if necessary
737
+ */
738
+ setCapacity(newCapacity) {
739
+ if (newCapacity <= 0) {
740
+ throw new Error("Cache capacity must be greater than 0");
741
+ }
742
+ this.capacity = newCapacity;
743
+ while (this.cache.size > this.capacity) {
744
+ this.evictLRU();
745
+ }
746
+ }
747
+ /**
748
+ * Evict the least recently used entry
749
+ */
750
+ evictLRU() {
751
+ const firstKey = this.cache.keys().next().value;
752
+ if (firstKey !== void 0) {
753
+ this.cache.delete(firstKey);
754
+ this.evictions++;
755
+ }
756
+ }
757
+ };
758
+ function hashContent(content) {
759
+ if (content.length > 1e4) {
760
+ const start = content.slice(0, 1e3);
761
+ const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
762
+ const end = content.slice(-1e3);
763
+ const sample = content.length + "|" + start + middle + end;
764
+ let hash2 = 2166136261;
765
+ for (let i = 0; i < sample.length; i++) {
766
+ hash2 ^= sample.charCodeAt(i);
767
+ hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
768
+ }
769
+ return (hash2 >>> 0).toString(36);
770
+ }
771
+ let hash = 2166136261;
772
+ for (let i = 0; i < content.length; i++) {
773
+ hash ^= content.charCodeAt(i);
774
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
775
+ }
776
+ return (hash >>> 0).toString(36);
777
+ }
778
+ function memoize(fn, options = {}) {
779
+ const cache = options.cache || new LRUCache(options.maxSize || 100);
780
+ const keyGenerator = options.keyGenerator || ((...args) => JSON.stringify(args));
781
+ const memoized = function(...args) {
782
+ const key = keyGenerator(...args);
783
+ const cached = cache.get(key);
784
+ if (cached !== void 0) {
785
+ return cached;
786
+ }
787
+ const result = fn.apply(this, args);
788
+ cache.set(key, result);
789
+ return result;
790
+ };
791
+ memoized.cache = cache;
792
+ memoized.clearCache = () => cache.clear();
793
+ return memoized;
794
+ }
795
+
618
796
  // src/extensions/core/blockquote.ts
619
797
  var BlockquoteExtension = {
620
798
  name: "blockquote",
@@ -1587,8 +1765,13 @@ function extractVimeoId(url) {
1587
1765
  var ChangerawrMarkdown = class {
1588
1766
  constructor(config) {
1589
1767
  this.extensions = /* @__PURE__ */ new Map();
1768
+ this.parseTime = 0;
1769
+ this.renderTime = 0;
1770
+ this.lastTokenCount = 0;
1590
1771
  this.parser = new MarkdownParser(config?.parser);
1591
1772
  this.renderer = new MarkdownRenderer(config?.renderer);
1773
+ this.parseCache = new LRUCache(100);
1774
+ this.renderCache = new LRUCache(100);
1592
1775
  this.registerCoreExtensions();
1593
1776
  this.registerFeatureExtensions();
1594
1777
  if (config?.extensions) {
@@ -1671,14 +1854,83 @@ var ChangerawrMarkdown = class {
1671
1854
  }
1672
1855
  }
1673
1856
  parse(markdown3) {
1674
- return this.parser.parse(markdown3);
1675
- }
1676
- render(tokens) {
1677
- return this.renderer.render(tokens);
1857
+ const cacheKey = hashContent(markdown3);
1858
+ const cached = this.parseCache.get(cacheKey);
1859
+ if (cached) {
1860
+ this.lastTokenCount = cached.length;
1861
+ return cached;
1862
+ }
1863
+ const startTime = performance.now();
1864
+ const tokens = this.parser.parse(markdown3);
1865
+ this.parseTime = performance.now() - startTime;
1866
+ this.lastTokenCount = tokens.length;
1867
+ this.parseCache.set(cacheKey, tokens);
1868
+ return tokens;
1869
+ }
1870
+ render(tokens, cacheKey) {
1871
+ if (cacheKey) {
1872
+ const cached = this.renderCache.get(cacheKey);
1873
+ if (cached) {
1874
+ return cached;
1875
+ }
1876
+ }
1877
+ const startTime = performance.now();
1878
+ const html = this.renderer.render(tokens);
1879
+ this.renderTime = performance.now() - startTime;
1880
+ if (cacheKey) {
1881
+ this.renderCache.set(cacheKey, html);
1882
+ }
1883
+ return html;
1678
1884
  }
1679
1885
  toHtml(markdown3) {
1886
+ const cacheKey = hashContent(markdown3);
1887
+ const cachedHtml = this.renderCache.get(cacheKey);
1888
+ if (cachedHtml) {
1889
+ return cachedHtml;
1890
+ }
1891
+ const tokens = this.parse(markdown3);
1892
+ return this.render(tokens, cacheKey);
1893
+ }
1894
+ /**
1895
+ * Render markdown with performance metrics
1896
+ */
1897
+ toHtmlWithMetrics(markdown3) {
1898
+ const startTotal = performance.now();
1899
+ const parseCacheKey = hashContent(markdown3);
1900
+ const parseCacheHit = this.parseCache.has(parseCacheKey);
1901
+ const html = this.toHtml(markdown3);
1902
+ const totalTime = performance.now() - startTotal;
1903
+ const metrics = {
1904
+ inputSize: markdown3.length,
1905
+ parseTime: this.parseTime,
1906
+ renderTime: this.renderTime,
1907
+ totalTime,
1908
+ tokenCount: this.lastTokenCount,
1909
+ cacheHit: parseCacheHit
1910
+ };
1911
+ return { html, metrics };
1912
+ }
1913
+ /**
1914
+ * Stream-render large documents in chunks for better performance
1915
+ */
1916
+ async toHtmlStreamed(markdown3, options = {}) {
1917
+ const chunkSize = options.chunkSize || 50;
1680
1918
  const tokens = this.parse(markdown3);
1681
- return this.render(tokens);
1919
+ const totalTokens = tokens.length;
1920
+ const chunks = [];
1921
+ for (let i = 0; i < tokens.length; i += chunkSize) {
1922
+ const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
1923
+ const chunkHtml = this.render(chunkTokens);
1924
+ chunks.push(chunkHtml);
1925
+ if (options.onChunk) {
1926
+ options.onChunk({
1927
+ html: chunkHtml,
1928
+ progress: Math.min(i + chunkSize, tokens.length) / totalTokens
1929
+ });
1930
+ }
1931
+ await new Promise((resolve) => setTimeout(resolve, 0));
1932
+ }
1933
+ return chunks.join("");
1682
1934
  }
1683
1935
  getExtensions() {
1684
1936
  return Array.from(this.extensions.keys());
@@ -1692,25 +1944,50 @@ var ChangerawrMarkdown = class {
1692
1944
  getDebugInfo() {
1693
1945
  return {
1694
1946
  warnings: this.getWarnings(),
1695
- parseTime: 0,
1696
- renderTime: 0,
1697
- tokenCount: 0,
1947
+ parseTime: this.parseTime,
1948
+ renderTime: this.renderTime,
1949
+ tokenCount: this.lastTokenCount,
1698
1950
  iterationCount: 0
1699
1951
  };
1700
1952
  }
1701
1953
  getPerformanceMetrics() {
1702
1954
  return {
1703
- parseTime: 0,
1704
- renderTime: 0,
1705
- totalTime: 0,
1706
- tokenCount: 0
1955
+ parseTime: this.parseTime,
1956
+ renderTime: this.renderTime,
1957
+ totalTime: this.parseTime + this.renderTime,
1958
+ tokenCount: this.lastTokenCount
1707
1959
  };
1708
1960
  }
1961
+ /**
1962
+ * Get cache statistics
1963
+ */
1964
+ getCacheStats() {
1965
+ return {
1966
+ parse: this.parseCache.getStats(),
1967
+ render: this.renderCache.getStats()
1968
+ };
1969
+ }
1970
+ /**
1971
+ * Clear all caches
1972
+ */
1973
+ clearCaches() {
1974
+ this.parseCache.clear();
1975
+ this.renderCache.clear();
1976
+ }
1977
+ /**
1978
+ * Update cache capacity
1979
+ */
1980
+ setCacheSize(size) {
1981
+ this.parseCache.setCapacity(size);
1982
+ this.renderCache.setCapacity(size);
1983
+ }
1709
1984
  rebuildParserAndRenderer() {
1710
1985
  const parserConfig = this.parser.getConfig();
1711
1986
  const rendererConfig = this.renderer.getConfig();
1712
1987
  this.parser = new MarkdownParser(parserConfig);
1713
1988
  this.renderer = new MarkdownRenderer(rendererConfig);
1989
+ this.parseCache.clear();
1990
+ this.renderCache.clear();
1714
1991
  const extensionsToRegister = Array.from(this.extensions.values());
1715
1992
  const featureExtensions = extensionsToRegister.filter(
1716
1993
  (ext) => ["alert", "button", "embed"].includes(ext.name)
@@ -2355,6 +2632,7 @@ export {
2355
2632
  ImageExtension,
2356
2633
  InlineCodeExtension,
2357
2634
  ItalicExtension,
2635
+ LRUCache,
2358
2636
  LineBreakExtension,
2359
2637
  LinkExtension,
2360
2638
  ListExtension,
@@ -2387,10 +2665,12 @@ export {
2387
2665
  generateId,
2388
2666
  getASTStats,
2389
2667
  getTokenStats,
2668
+ hashContent,
2390
2669
  isBrowser,
2391
2670
  isNode,
2392
2671
  isValidUrl,
2393
2672
  markdown2 as markdown,
2673
+ memoize,
2394
2674
  minimalClasses,
2395
2675
  parseASTFromJSON,
2396
2676
  parseCum,