@changerawr/markdown 1.1.5 → 1.1.7

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
- var MarkdownParser = class {
2
+ var MarkdownParser = class _MarkdownParser {
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) {
@@ -199,7 +212,18 @@ var MarkdownParser = class {
199
212
  recursivelyParseBlockContent(token) {
200
213
  const blockTypes = ["alert", "blockquote", "list-item", "task-item"];
201
214
  if (blockTypes.includes(token.type) && token.content && token.content.trim()) {
202
- const children = this.parse(token.content);
215
+ let children;
216
+ if ((token.type === "list-item" || token.type === "task-item") && this.rules.some((r) => r.name === "list-item")) {
217
+ const parserWithoutListRule = new _MarkdownParser(this.config);
218
+ this.rules.forEach((rule) => {
219
+ if (rule.name !== "list-item" && rule.name !== "task-item") {
220
+ parserWithoutListRule.addRule(rule);
221
+ }
222
+ });
223
+ children = parserWithoutListRule.parse(token.content);
224
+ } else {
225
+ children = this.parse(token.content);
226
+ }
203
227
  return {
204
228
  ...token,
205
229
  children
@@ -615,6 +639,171 @@ var MarkdownRenderer = class {
615
639
  }
616
640
  };
617
641
 
642
+ // src/cache.ts
643
+ var LRUCache = class {
644
+ constructor(capacity = 100) {
645
+ this.cache = /* @__PURE__ */ new Map();
646
+ this.hits = 0;
647
+ this.misses = 0;
648
+ this.evictions = 0;
649
+ if (capacity <= 0) {
650
+ throw new Error("Cache capacity must be greater than 0");
651
+ }
652
+ this.capacity = capacity;
653
+ }
654
+ /**
655
+ * Get a value from the cache
656
+ */
657
+ get(key) {
658
+ const entry = this.cache.get(key);
659
+ if (entry) {
660
+ entry.timestamp = Date.now();
661
+ entry.accessCount++;
662
+ this.hits++;
663
+ this.cache.delete(key);
664
+ this.cache.set(key, entry);
665
+ return entry.value;
666
+ }
667
+ this.misses++;
668
+ return void 0;
669
+ }
670
+ /**
671
+ * Set a value in the cache
672
+ */
673
+ set(key, value) {
674
+ if (this.cache.has(key)) {
675
+ this.cache.delete(key);
676
+ } else if (this.cache.size >= this.capacity) {
677
+ this.evictLRU();
678
+ }
679
+ this.cache.set(key, {
680
+ value,
681
+ timestamp: Date.now(),
682
+ accessCount: 0
683
+ });
684
+ }
685
+ /**
686
+ * Check if a key exists in the cache
687
+ */
688
+ has(key) {
689
+ return this.cache.has(key);
690
+ }
691
+ /**
692
+ * Delete a specific key from the cache
693
+ */
694
+ delete(key) {
695
+ return this.cache.delete(key);
696
+ }
697
+ /**
698
+ * Clear all entries from the cache
699
+ */
700
+ clear() {
701
+ this.cache.clear();
702
+ this.hits = 0;
703
+ this.misses = 0;
704
+ this.evictions = 0;
705
+ }
706
+ /**
707
+ * Get the current size of the cache
708
+ */
709
+ get size() {
710
+ return this.cache.size;
711
+ }
712
+ /**
713
+ * Get cache statistics
714
+ */
715
+ getStats() {
716
+ const totalRequests = this.hits + this.misses;
717
+ return {
718
+ size: this.cache.size,
719
+ capacity: this.capacity,
720
+ hits: this.hits,
721
+ misses: this.misses,
722
+ hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
723
+ evictions: this.evictions
724
+ };
725
+ }
726
+ /**
727
+ * Reset cache statistics
728
+ */
729
+ resetStats() {
730
+ this.hits = 0;
731
+ this.misses = 0;
732
+ this.evictions = 0;
733
+ }
734
+ /**
735
+ * Get all keys in the cache
736
+ */
737
+ keys() {
738
+ return Array.from(this.cache.keys());
739
+ }
740
+ /**
741
+ * Get all values in the cache
742
+ */
743
+ values() {
744
+ return Array.from(this.cache.values()).map((entry) => entry.value);
745
+ }
746
+ /**
747
+ * Update cache capacity and evict if necessary
748
+ */
749
+ setCapacity(newCapacity) {
750
+ if (newCapacity <= 0) {
751
+ throw new Error("Cache capacity must be greater than 0");
752
+ }
753
+ this.capacity = newCapacity;
754
+ while (this.cache.size > this.capacity) {
755
+ this.evictLRU();
756
+ }
757
+ }
758
+ /**
759
+ * Evict the least recently used entry
760
+ */
761
+ evictLRU() {
762
+ const firstKey = this.cache.keys().next().value;
763
+ if (firstKey !== void 0) {
764
+ this.cache.delete(firstKey);
765
+ this.evictions++;
766
+ }
767
+ }
768
+ };
769
+ function hashContent(content) {
770
+ if (content.length > 1e4) {
771
+ const start = content.slice(0, 1e3);
772
+ const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
773
+ const end = content.slice(-1e3);
774
+ const sample = content.length + "|" + start + middle + end;
775
+ let hash2 = 2166136261;
776
+ for (let i = 0; i < sample.length; i++) {
777
+ hash2 ^= sample.charCodeAt(i);
778
+ hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
779
+ }
780
+ return (hash2 >>> 0).toString(36);
781
+ }
782
+ let hash = 2166136261;
783
+ for (let i = 0; i < content.length; i++) {
784
+ hash ^= content.charCodeAt(i);
785
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
786
+ }
787
+ return (hash >>> 0).toString(36);
788
+ }
789
+ function memoize(fn, options = {}) {
790
+ const cache = options.cache || new LRUCache(options.maxSize || 100);
791
+ const keyGenerator = options.keyGenerator || ((...args) => JSON.stringify(args));
792
+ const memoized = function(...args) {
793
+ const key = keyGenerator(...args);
794
+ const cached = cache.get(key);
795
+ if (cached !== void 0) {
796
+ return cached;
797
+ }
798
+ const result = fn.apply(this, args);
799
+ cache.set(key, result);
800
+ return result;
801
+ };
802
+ memoized.cache = cache;
803
+ memoized.clearCache = () => cache.clear();
804
+ return memoized;
805
+ }
806
+
618
807
  // src/extensions/core/blockquote.ts
619
808
  var BlockquoteExtension = {
620
809
  name: "blockquote",
@@ -1587,8 +1776,13 @@ function extractVimeoId(url) {
1587
1776
  var ChangerawrMarkdown = class {
1588
1777
  constructor(config) {
1589
1778
  this.extensions = /* @__PURE__ */ new Map();
1779
+ this.parseTime = 0;
1780
+ this.renderTime = 0;
1781
+ this.lastTokenCount = 0;
1590
1782
  this.parser = new MarkdownParser(config?.parser);
1591
1783
  this.renderer = new MarkdownRenderer(config?.renderer);
1784
+ this.parseCache = new LRUCache(100);
1785
+ this.renderCache = new LRUCache(100);
1592
1786
  this.registerCoreExtensions();
1593
1787
  this.registerFeatureExtensions();
1594
1788
  if (config?.extensions) {
@@ -1671,14 +1865,83 @@ var ChangerawrMarkdown = class {
1671
1865
  }
1672
1866
  }
1673
1867
  parse(markdown3) {
1674
- return this.parser.parse(markdown3);
1675
- }
1676
- render(tokens) {
1677
- return this.renderer.render(tokens);
1868
+ const cacheKey = hashContent(markdown3);
1869
+ const cached = this.parseCache.get(cacheKey);
1870
+ if (cached) {
1871
+ this.lastTokenCount = cached.length;
1872
+ return cached;
1873
+ }
1874
+ const startTime = performance.now();
1875
+ const tokens = this.parser.parse(markdown3);
1876
+ this.parseTime = performance.now() - startTime;
1877
+ this.lastTokenCount = tokens.length;
1878
+ this.parseCache.set(cacheKey, tokens);
1879
+ return tokens;
1880
+ }
1881
+ render(tokens, cacheKey) {
1882
+ if (cacheKey) {
1883
+ const cached = this.renderCache.get(cacheKey);
1884
+ if (cached) {
1885
+ return cached;
1886
+ }
1887
+ }
1888
+ const startTime = performance.now();
1889
+ const html = this.renderer.render(tokens);
1890
+ this.renderTime = performance.now() - startTime;
1891
+ if (cacheKey) {
1892
+ this.renderCache.set(cacheKey, html);
1893
+ }
1894
+ return html;
1678
1895
  }
1679
1896
  toHtml(markdown3) {
1897
+ const cacheKey = hashContent(markdown3);
1898
+ const cachedHtml = this.renderCache.get(cacheKey);
1899
+ if (cachedHtml) {
1900
+ return cachedHtml;
1901
+ }
1680
1902
  const tokens = this.parse(markdown3);
1681
- return this.render(tokens);
1903
+ return this.render(tokens, cacheKey);
1904
+ }
1905
+ /**
1906
+ * Render markdown with performance metrics
1907
+ */
1908
+ toHtmlWithMetrics(markdown3) {
1909
+ const startTotal = performance.now();
1910
+ const parseCacheKey = hashContent(markdown3);
1911
+ const parseCacheHit = this.parseCache.has(parseCacheKey);
1912
+ const html = this.toHtml(markdown3);
1913
+ const totalTime = performance.now() - startTotal;
1914
+ const metrics = {
1915
+ inputSize: markdown3.length,
1916
+ parseTime: this.parseTime,
1917
+ renderTime: this.renderTime,
1918
+ totalTime,
1919
+ tokenCount: this.lastTokenCount,
1920
+ cacheHit: parseCacheHit
1921
+ };
1922
+ return { html, metrics };
1923
+ }
1924
+ /**
1925
+ * Stream-render large documents in chunks for better performance
1926
+ */
1927
+ async toHtmlStreamed(markdown3, options = {}) {
1928
+ const chunkSize = options.chunkSize || 50;
1929
+ const tokens = this.parse(markdown3);
1930
+ const totalTokens = tokens.length;
1931
+ const chunks = [];
1932
+ for (let i = 0; i < tokens.length; i += chunkSize) {
1933
+ const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
1934
+ const chunkHtml = this.render(chunkTokens);
1935
+ chunks.push(chunkHtml);
1936
+ if (options.onChunk) {
1937
+ options.onChunk({
1938
+ html: chunkHtml,
1939
+ progress: Math.min(i + chunkSize, tokens.length) / totalTokens
1940
+ });
1941
+ }
1942
+ await new Promise((resolve) => setTimeout(resolve, 0));
1943
+ }
1944
+ return chunks.join("");
1682
1945
  }
1683
1946
  getExtensions() {
1684
1947
  return Array.from(this.extensions.keys());
@@ -1692,25 +1955,50 @@ var ChangerawrMarkdown = class {
1692
1955
  getDebugInfo() {
1693
1956
  return {
1694
1957
  warnings: this.getWarnings(),
1695
- parseTime: 0,
1696
- renderTime: 0,
1697
- tokenCount: 0,
1958
+ parseTime: this.parseTime,
1959
+ renderTime: this.renderTime,
1960
+ tokenCount: this.lastTokenCount,
1698
1961
  iterationCount: 0
1699
1962
  };
1700
1963
  }
1701
1964
  getPerformanceMetrics() {
1702
1965
  return {
1703
- parseTime: 0,
1704
- renderTime: 0,
1705
- totalTime: 0,
1706
- tokenCount: 0
1966
+ parseTime: this.parseTime,
1967
+ renderTime: this.renderTime,
1968
+ totalTime: this.parseTime + this.renderTime,
1969
+ tokenCount: this.lastTokenCount
1707
1970
  };
1708
1971
  }
1972
+ /**
1973
+ * Get cache statistics
1974
+ */
1975
+ getCacheStats() {
1976
+ return {
1977
+ parse: this.parseCache.getStats(),
1978
+ render: this.renderCache.getStats()
1979
+ };
1980
+ }
1981
+ /**
1982
+ * Clear all caches
1983
+ */
1984
+ clearCaches() {
1985
+ this.parseCache.clear();
1986
+ this.renderCache.clear();
1987
+ }
1988
+ /**
1989
+ * Update cache capacity
1990
+ */
1991
+ setCacheSize(size) {
1992
+ this.parseCache.setCapacity(size);
1993
+ this.renderCache.setCapacity(size);
1994
+ }
1709
1995
  rebuildParserAndRenderer() {
1710
1996
  const parserConfig = this.parser.getConfig();
1711
1997
  const rendererConfig = this.renderer.getConfig();
1712
1998
  this.parser = new MarkdownParser(parserConfig);
1713
1999
  this.renderer = new MarkdownRenderer(rendererConfig);
2000
+ this.parseCache.clear();
2001
+ this.renderCache.clear();
1714
2002
  const extensionsToRegister = Array.from(this.extensions.values());
1715
2003
  const featureExtensions = extensionsToRegister.filter(
1716
2004
  (ext) => ["alert", "button", "embed"].includes(ext.name)
@@ -2355,6 +2643,7 @@ export {
2355
2643
  ImageExtension,
2356
2644
  InlineCodeExtension,
2357
2645
  ItalicExtension,
2646
+ LRUCache,
2358
2647
  LineBreakExtension,
2359
2648
  LinkExtension,
2360
2649
  ListExtension,
@@ -2387,10 +2676,12 @@ export {
2387
2676
  generateId,
2388
2677
  getASTStats,
2389
2678
  getTokenStats,
2679
+ hashContent,
2390
2680
  isBrowser,
2391
2681
  isNode,
2392
2682
  isValidUrl,
2393
2683
  markdown2 as markdown,
2684
+ memoize,
2394
2685
  minimalClasses,
2395
2686
  parseASTFromJSON,
2396
2687
  parseCum,