tdk-api-wrapper 1.6.1 → 1.7.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TDK API Node.js Wrapper
2
2
 
3
- Bu proje, Türk Dil Kurumu (TDK) sözlük verilerine Node.js ortamından doğrudan, hızlı ve güvenilir bir şekilde erişim sağlamak amacıyla geliştirilmiş, TypeScript tabanlı resmî olmayan bir sarmalayıcı (wrapper) kütüphanedir. Herhangi bir dış bağımlılığa veya ikili (binary) dosyaya ihtiyaç duymadan HTTP üzerinden güncel verileri çeker.
3
+ Bu proje, Türk Dil Kurumu (TDK) sözlük verilerine Node.js ortamından doğrudan, hızlı ve güvenilir bir şekilde erişim sağlamak amacıyla geliştirilmiş, TypeScript tabanlı resmî olmayan bir sarmalayıcı (wrapper) kütüphanedir. Herhangi bir dış bağımlılığa veya ikili (binary) dosyaya ihtiyaç duymadan HTTP üzerinden güncel verileri çeker. Kütüphanenin yanı sıra bir terminal CLI'si (`tdk`) ve dahili bir **Model Context Protocol (MCP)** sunucusu da paketle birlikte gelir.
4
4
 
5
5
  ## Kurulum
6
6
 
@@ -46,6 +46,7 @@ tdk repl
46
46
  tdk kubbealti merhaba
47
47
  tdk nisanyan merhaba
48
48
  tdk viki merhaba
49
+ tdk mcp # MCP (Model Context Protocol) stdio sunucusunu başlatır
49
50
  ```
50
51
 
51
52
  Herhangi bir komuta `--json` bayrağı eklendiğinde çıktı, insan-okunur metin yerine tek satırlık JSON olarak basılır (script/otomasyon kullanımı için):
@@ -90,7 +91,7 @@ Aşağıdaki metotlar `TDK` sınıfı üzerinden statik olarak veya `TDKClient`
90
91
  - **`TDK.checkVowelHarmony(word)`**: Kelimenin büyük ünlü uyumuna uyup uymadığını (boolean) kontrol eder.
91
92
  - **`TDK.checkLabialHarmony(word)`**: Kelimenin küçük ünlü uyumuna (düzlük-yuvarlaklık uyumu) uyup uymadığını (boolean) kontrol eder.
92
93
  - **`TDK.getPartOfSpeech(word)`**: Kelimenin sözcük türünü (isim, sıfat, zarf vb.) döndürür.
93
- - **`TDK.checkSpelling(word)`**: Kelimenin doğru yazılıp yazılmadığını kontrol eder. Önce TDK'de doğrudan arar; bulamazsa TDK'nin "sık yapılan yanlışlar" listesini kontrol eder. Ardından **morfolojik ek sıyırma (stemming) motoru** devreye girer; kelime çekimli bir biçimse (`halılarımızın`, `kitabımız`, `çocuğa`, `okuyoruz`, `hakkımızda`, `başlıyor`) kökünü tespit edip `{ isCorrect: true, isInflected: true, root: "..." }` döner. Son aşamada ise TDK'nin ~81 bin kelimelik tam madde listesi üzerinde **klavye ve düzeltme işareti farkındalıklı** bir Damerau-Levenshtein edit-distance ile en yakın kelimeyi önerir: Türkçe Q klavyede yan yana duran tuşlar (`arabs` → `araba`, `swlam` → `selam`) ve ASCII/Türkçe harf ikilileri (`ı/i`, `ş/s`, `ö/o` …) tam bir düzeltme yerine bir düzeltmenin küçük bir kesri kadar sayılır; böylece hem en yakın hem de en olası madde kazanır (`yanlız` → `yalnız`).
94
+ - **`TDK.checkSpelling(word)`**: Kelimenin doğru yazılıp yazılmadığını kontrol eder. Önce TDK'de doğrudan arar; bulamazsa kütüphanenin dahili sık yapılan yanlışlar listesini (`COMMON_MISSPELLINGS` — [referans liste](https://gist.github.com/cemyilmazzzz122/e4bdd509fe9b03a3070a685ab603aaa8)) kontrol eder. Ardından **morfolojik ek sıyırma (stemming) motoru** devreye girer; kelime çekimli bir biçimse (`halılarımızın`, `kitabımız`, `çocuğa`, `okuyoruz`, `hakkımızda`, `başlıyor`) kökünü tespit edip `{ isCorrect: true, isInflected: true, root: "..." }` döner. Son aşamada ise TDK'nin ~81 bin kelimelik tam madde listesi üzerinde **klavye ve düzeltme işareti farkındalıklı** bir Damerau-Levenshtein edit-distance ile en yakın kelimeyi önerir: Türkçe Q klavyede yan yana duran tuşlar (`arabs` → `araba`, `swlam` → `selam`) ve ASCII/Türkçe harf ikilileri (`ı/i`, `ş/s`, `ö/o` …) tam bir düzeltme yerine bir düzeltmenin küçük bir kesri kadar sayılır; böylece hem en yakın hem de en olası madde kazanır (`yanlız` → `yalnız`).
94
95
  - **`TDK.getCompoundWords(word)`**: Aranan kelime ile oluşturulmuş birleşik kelimeleri (Örn: dolma kalem) listeler.
95
96
 
96
97
  ### 3. Morfoloji ve Kök Bulma (Morphology Engine)
@@ -138,6 +139,25 @@ TDK dışındaki bu üç kaynak da her zaman kullanılabilir/dokümante edilmiş
138
139
  - **`TDK.getNisanyan(word)`**: Nişanyan Sözlük'ten kelimenin etimoloji paragrafını düz metin olarak döner; kelime bulunamazsa `null`.
139
140
  - **`TDK.getWiktionary(word)`**: Türkçe Vikisözlük'ten (`tr.wiktionary.org`) resmî MediaWiki API'si (`action=query&prop=extracts`) üzerinden veri çeker — bu üçü arasında scraping olmayan, resmî ve en kararlı olanı. `{ raw, sections }` döner; `sections` metni `== Köken ==`, `=== Söyleniş ===` gibi başlıklara göre bir sözlüğe ayırır. `getWiktionarySection(word, sectionName)` tek bir bölümü (örn. `"Köken"`) büyük/küçük harf duyarsız süzer. Bu wiki'de başlık büyütme kapalı (`$wgCapitalLinks=false` — "Türkiye" ile bir küçük harfli kelime ayrı sayfalardır), o yüzden `TDK.getWiktionary("türkiye")` gibi tam eşleşmeyen aramalar otomatik olarak ilk harfi (Türkçe kurallarına göre, örn. `istanbul` → `İstanbul`) büyütülmüş hâliyle tekrar denenir.
140
141
 
142
+ ## Model Context Protocol (MCP) Sunucusu
143
+
144
+ Paket, bir MCP stdio sunucusu içerir; böylece TDK sözlük, morfoloji, yazım denetimi ve etimoloji araçlarını Claude Desktop, Cursor, Antigravity veya herhangi bir MCP istemcisine yalnızca Node.js ile bağlayabilirsiniz — Python ya da ek bir çalışma zamanı gerekmez.
145
+
146
+ ```json
147
+ {
148
+ "mcpServers": {
149
+ "tdk": {
150
+ "command": "npx",
151
+ "args": ["-y", "tdk-api-wrapper", "mcp"]
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ Global kuruluysa `tdk mcp` (veya `tdk-mcp`) komutu da aynı sunucuyu başlatır. Sunucu programatik olarak dışa aktarılan `createMcpServer()` / `runMcpServer()` ile de gömülebilir.
158
+
159
+ Sunulan araçlar: `tdk_lookup`, `tdk_meanings`, `tdk_examples`, `tdk_proverbs`, `tdk_compound_words`, `tdk_part_of_speech`, `tdk_synonyms`, `tdk_antonyms`, `tdk_origin`, `tdk_nisanyan`, `tdk_kubbealti`, `tdk_wiktionary`, `tdk_spell_check`, `tdk_proofread`, `tdk_stem`, `tdk_analyze_text`, `tdk_syllables`, `tdk_vowel_harmony`, `tdk_autocomplete`, `tdk_pattern_search`, `tdk_anagram`, `tdk_rhymes`, `tdk_compare`, `tdk_audio_url`, `tdk_word_of_the_day`, `tdk_random_word`, `tdk_rules`. Her araç JSON metin döndürür; ağ/scraping hataları fırlatmak yerine `isError: true` ile `{ "error": ... }` olarak döner.
160
+
141
161
  ## Hata Yönetimi
142
162
 
143
163
  Kütüphane, ayırt edilebilir hata sınıfları fırlatır (hepsi `Error`'dan türer):
@@ -161,4 +181,4 @@ try {
161
181
 
162
182
  ## Lisans
163
183
 
164
- Bu proje MIT Lisansı ile lisanslanmıştır. Kullanım hakları ve kısıtlamalar için kaynak kod içerisindeki lisans metnini inceleyebilirsiniz.
184
+ Bu proje GNU General Public License v3.0 (or later) ile lisanslanmıştır. Kullanım hakları ve kısıtlamalar için kaynak kod içerisindeki [LICENSE](./LICENSE) dosyasını inceleyebilirsiniz.
@@ -25,11 +25,7 @@ var TDKNetworkError = class extends TDKError {
25
25
  }
26
26
  };
27
27
 
28
- // src/morphology.ts
29
- var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
30
- function isVowel(ch) {
31
- return TURKISH_VOWELS.includes(ch);
32
- }
28
+ // src/data/suffixes.ts
33
29
  var TURKISH_SUFFIXES = [
34
30
  // 9-letter composite suffixes
35
31
  "lerimizden",
@@ -432,6 +428,12 @@ var TURKISH_SUFFIXES = [
432
428
  "u",
433
429
  "\xFC"
434
430
  ];
431
+
432
+ // src/morphology.ts
433
+ var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
434
+ function isVowel(ch) {
435
+ return TURKISH_VOWELS.includes(ch);
436
+ }
435
437
  function restoreConsonantSoftening(stem) {
436
438
  if (stem.length < 2)
437
439
  return [];
@@ -585,12 +587,7 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
585
587
  return [...new Set(candidatesWithWeight.map((c) => c.candidate))];
586
588
  }
587
589
 
588
- // src/tdk.ts
589
- import * as fs from "fs";
590
- import * as path from "path";
591
- import * as os from "os";
592
- import * as https from "https";
593
- import * as tls from "tls";
590
+ // src/data/misspellings.ts
594
591
  var COMMON_MISSPELLINGS = {
595
592
  // -şey ile biten ve ayrı yazılması zorunlu söz öbekleri
596
593
  her\u015Fey: "her \u015Fey",
@@ -744,75 +741,10 @@ var COMMON_MISSPELLINGS = {
744
741
  babaanne: "babaanne"
745
742
  };
746
743
  var SEY_EXCEPTIONS = /* @__PURE__ */ new Set(["d\xFC\u015Fey", "e\u015Fey", "konsey", "jersey", "\u015Fey"]);
747
- var KEYBOARD_ROWS = [
748
- ["qwertyu\u0131op\u011F\xFC", 0],
749
- ["asdfghjkl\u015Fi", 0.5],
750
- ["zxcvbnm\xF6\xE7", 1]
751
- ];
752
- var KEYBOARD_COORDS = (() => {
753
- const coords = {};
754
- KEYBOARD_ROWS.forEach(([keys, offset], row) => {
755
- [...keys].forEach((key, col) => {
756
- coords[key] = [col + offset, row];
757
- });
758
- });
759
- return coords;
760
- })();
761
- var DIACRITIC_SIBLINGS = {
762
- \u0131: "i",
763
- i: "\u0131",
764
- \u00F6: "o",
765
- o: "\xF6",
766
- \u00FC: "u",
767
- u: "\xFC",
768
- \u015F: "s",
769
- s: "\u015F",
770
- \u00E7: "c",
771
- c: "\xE7",
772
- \u011F: "g",
773
- g: "\u011F",
774
- \u00E2: "a",
775
- a: "\xE2"
776
- };
777
- var KEYBOARD_ROW_SUB_COST = 0.4;
778
- var KEYBOARD_DIAGONAL_SUB_COST = 0.55;
779
- var DIACRITIC_SUB_COST = 0.3;
780
- var TRANSPOSITION_COST = 0.8;
781
- function keyboardSubCost(a, b) {
782
- if (a === b)
783
- return 0;
784
- if (DIACRITIC_SIBLINGS[a] === b)
785
- return DIACRITIC_SUB_COST;
786
- const pa = KEYBOARD_COORDS[a];
787
- const pb = KEYBOARD_COORDS[b];
788
- if (!pa || !pb)
789
- return 1;
790
- const dx = Math.abs(pa[0] - pb[0]);
791
- const dy = Math.abs(pa[1] - pb[1]);
792
- if (dy === 0 && dx <= 1 + 1e-9)
793
- return KEYBOARD_ROW_SUB_COST;
794
- if (dy === 1 && dx <= 1 + 1e-9)
795
- return KEYBOARD_DIAGONAL_SUB_COST;
796
- return 1;
797
- }
798
- var TDK = class {
799
- static BASE_URL = "https://sozluk.gov.tr";
800
- static AUDIO_API_HOST = "api.sozluk.gov.tr";
801
- static KUBBEALTI_HOST = "eski.lugatim.com";
802
- /**
803
- * `eski.lugatim.com` (Kubbealtı Lugatı's data API) sends only its leaf
804
- * certificate during the TLS handshake, omitting the intermediates a
805
- * correctly configured server would include — a server-side misconfiguration,
806
- * not something we should paper over by disabling verification. These are
807
- * the two certificates the server *should* be sending (fetched from the
808
- * leaf's own Authority Information Access URLs), supplied here so Node can
809
- * still build a full, properly verified chain up to a root it already
810
- * trusts (ISRG Root X1). If Let's Encrypt rotates this intermediate, this
811
- * stops working and every Kubbealtı call fails closed to `null` — same
812
- * fail-closed contract as the rest of this file's fragile integrations.
813
- */
814
- static KUBBEALTI_EXTRA_CA = [
815
- `-----BEGIN CERTIFICATE-----
744
+
745
+ // src/data/kubbealti-ca.ts
746
+ var KUBBEALTI_EXTRA_CA = [
747
+ `-----BEGIN CERTIFICATE-----
816
748
  MIIE2jCCAsKgAwIBAgIQTr0klH4k05SALYSlL9WzGTANBgkqhkiG9w0BAQsFADAu
817
749
  MQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZUjAe
818
750
  Fw0yNTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYw
@@ -840,7 +772,7 @@ NA3wJdl4DDUuQSV8hBgx6zoI1ZSGORprDFux7c6rhc77QZMSRrEgomBeklervEve
840
772
  86ylWmZ3WWHV6RLMi8xNvjd71r4EPIGgY7BZU/VPBkq+uA7Gb6mbJnFgV43uh3xy
841
773
  LRFgxIAphIukwTGSMZZR+AI+Qnp0BYTWovHXozOf3H8r6hozEoT02JHn0AeTfA==
842
774
  -----END CERTIFICATE-----`,
843
- `-----BEGIN CERTIFICATE-----
775
+ `-----BEGIN CERTIFICATE-----
844
776
  MIIF9DCCA9ygAwIBAgIRAPJLbRf52a18scn+p4eCaZ8wDQYJKoZIhvcNAQELBQAw
845
777
  TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
846
778
  cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjYwNTEzMDAwMDAw
@@ -874,7 +806,112 @@ RGMuHGnzS3hFIrRTfKxrzUZ9RzQWzEG3K6fJ3r2nqSltkeytis9DIBoFY9VmVyjL
874
806
  M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
875
807
  yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
876
808
  -----END CERTIFICATE-----`
877
- ];
809
+ ];
810
+
811
+ // src/data/keyboard-layout.ts
812
+ var KEYBOARD_ROWS = [
813
+ ["qwertyu\u0131op\u011F\xFC", 0],
814
+ ["asdfghjkl\u015Fi", 0.5],
815
+ ["zxcvbnm\xF6\xE7", 1]
816
+ ];
817
+ var KEYBOARD_COORDS = (() => {
818
+ const coords = {};
819
+ KEYBOARD_ROWS.forEach(([keys, offset], row) => {
820
+ [...keys].forEach((key, col) => {
821
+ coords[key] = [col + offset, row];
822
+ });
823
+ });
824
+ return coords;
825
+ })();
826
+ var DIACRITIC_SIBLINGS = {
827
+ \u0131: "i",
828
+ i: "\u0131",
829
+ \u00F6: "o",
830
+ o: "\xF6",
831
+ \u00FC: "u",
832
+ u: "\xFC",
833
+ \u015F: "s",
834
+ s: "\u015F",
835
+ \u00E7: "c",
836
+ c: "\xE7",
837
+ \u011F: "g",
838
+ g: "\u011F",
839
+ \u00E2: "a",
840
+ a: "\xE2"
841
+ };
842
+ var KEYBOARD_ROW_SUB_COST = 0.4;
843
+ var KEYBOARD_DIAGONAL_SUB_COST = 0.55;
844
+ var DIACRITIC_SUB_COST = 0.3;
845
+ var TRANSPOSITION_COST = 0.8;
846
+ function keyboardSubCost(a, b) {
847
+ if (a === b)
848
+ return 0;
849
+ if (DIACRITIC_SIBLINGS[a] === b)
850
+ return DIACRITIC_SUB_COST;
851
+ const pa = KEYBOARD_COORDS[a];
852
+ const pb = KEYBOARD_COORDS[b];
853
+ if (!pa || !pb)
854
+ return 1;
855
+ const dx = Math.abs(pa[0] - pb[0]);
856
+ const dy = Math.abs(pa[1] - pb[1]);
857
+ if (dy === 0 && dx <= 1 + 1e-9)
858
+ return KEYBOARD_ROW_SUB_COST;
859
+ if (dy === 1 && dx <= 1 + 1e-9)
860
+ return KEYBOARD_DIAGONAL_SUB_COST;
861
+ return 1;
862
+ }
863
+
864
+ // src/lib/edit-distance.ts
865
+ function damerauLevenshtein(a, b) {
866
+ const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
867
+ for (let i = 0; i <= a.length; i++)
868
+ dp[i][0] = i;
869
+ for (let j = 0; j <= b.length; j++)
870
+ dp[0][j] = j;
871
+ for (let i = 1; i <= a.length; i++) {
872
+ for (let j = 1; j <= b.length; j++) {
873
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
874
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
875
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
876
+ dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + cost);
877
+ }
878
+ }
879
+ }
880
+ return dp[a.length][b.length];
881
+ }
882
+ function keyboardAwareDistance(a, b) {
883
+ const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
884
+ for (let i = 0; i <= a.length; i++)
885
+ dp[i][0] = i;
886
+ for (let j = 0; j <= b.length; j++)
887
+ dp[0][j] = j;
888
+ for (let i = 1; i <= a.length; i++) {
889
+ for (let j = 1; j <= b.length; j++) {
890
+ const cost = keyboardSubCost(a[i - 1], b[j - 1]);
891
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
892
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
893
+ dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + TRANSPOSITION_COST);
894
+ }
895
+ }
896
+ }
897
+ return dp[a.length][b.length];
898
+ }
899
+
900
+ // src/lib/html.ts
901
+ function htmlToPlainText(html) {
902
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/gi, " ").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;|&rsquo;/gi, "'").replace(/&amp;/gi, "&").replace(/[ \t]+/g, " ").replace(/[ \t]*\n[ \t]*/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
903
+ }
904
+
905
+ // src/tdk.ts
906
+ import * as fs from "fs";
907
+ import * as path from "path";
908
+ import * as os from "os";
909
+ import * as https from "https";
910
+ import * as tls from "tls";
911
+ var TDK = class {
912
+ static BASE_URL = "https://sozluk.gov.tr";
913
+ static AUDIO_API_HOST = "api.sozluk.gov.tr";
914
+ static KUBBEALTI_HOST = "eski.lugatim.com";
878
915
  // Configuration
879
916
  static defaultTimeoutMs = 8e3;
880
917
  static defaultRetries = 1;
@@ -1421,12 +1458,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1421
1458
  continue;
1422
1459
  if (Math.abs(candidate.length - cleanWord.length) > 2)
1423
1460
  continue;
1424
- const rawDist = this.damerauLevenshtein(cleanWord, candidate);
1461
+ const rawDist = damerauLevenshtein(cleanWord, candidate);
1425
1462
  if (rawDist === 0 || rawDist > 2)
1426
1463
  continue;
1427
1464
  const firstMismatch = candidate[0] === cleanWord[0] ? 0 : 1;
1428
1465
  const lengthMismatch = candidate.length === cleanWord.length ? 0 : 1;
1429
- const score = this.keyboardAwareDistance(cleanWord, candidate) + (firstMismatch > 0 ? 1.2 : 0);
1466
+ const score = keyboardAwareDistance(cleanWord, candidate) + (firstMismatch > 0 ? 1.2 : 0);
1430
1467
  const better = !best || score < best.score - 1e-9 || Math.abs(score - best.score) < 1e-9 && firstMismatch < best.firstMismatch || Math.abs(score - best.score) < 1e-9 && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch;
1431
1468
  if (better) {
1432
1469
  best = { candidate, score, rawDist, firstMismatch, lengthMismatch };
@@ -1557,14 +1594,11 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1557
1594
  const contentEnd = html.indexOf("<footer", contentStart);
1558
1595
  if (contentEnd === -1)
1559
1596
  return null;
1560
- return this.htmlToPlainText(html.slice(contentStart, contentEnd));
1597
+ return htmlToPlainText(html.slice(contentStart, contentEnd));
1561
1598
  } catch {
1562
1599
  return null;
1563
1600
  }
1564
1601
  }
1565
- static htmlToPlainText(html) {
1566
- return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/gi, " ").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;|&rsquo;/gi, "'").replace(/&amp;/gi, "&").replace(/[ \t]+/g, " ").replace(/[ \t]*\n[ \t]*/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
1567
- }
1568
1602
  /**
1569
1603
  * GETs a JSON path from Kubbealtı Lugatı's data API (`eski.lugatim.com`),
1570
1604
  * supplying `KUBBEALTI_EXTRA_CA` to work around that host's incomplete
@@ -1578,7 +1612,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1578
1612
  hostname: this.KUBBEALTI_HOST,
1579
1613
  path: path2,
1580
1614
  method: "GET",
1581
- ca: [...tls.rootCertificates, ...this.KUBBEALTI_EXTRA_CA],
1615
+ ca: [...tls.rootCertificates, ...KUBBEALTI_EXTRA_CA],
1582
1616
  headers: {
1583
1617
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
1584
1618
  }
@@ -1670,7 +1704,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1670
1704
  const entries = await this.getKubbealti(word);
1671
1705
  if (!entries)
1672
1706
  return null;
1673
- return entries.map((e) => this.htmlToPlainText(e.anlam));
1707
+ return entries.map((e) => htmlToPlainText(e.anlam));
1674
1708
  }
1675
1709
  /**
1676
1710
  * Autocomplete suggestions from Kubbealtı Lugatı's own typeahead endpoint
@@ -1706,7 +1740,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1706
1740
  const match = html.match(/<meta name="description" content="([^"]*)"/);
1707
1741
  if (!match)
1708
1742
  return null;
1709
- const description = this.htmlToPlainText(match[1]);
1743
+ const description = htmlToPlainText(match[1]);
1710
1744
  if (description === "\xC7a\u011Fda\u015F T\xFCrk\xE7enin Etimolojisi")
1711
1745
  return null;
1712
1746
  return description;
@@ -1937,56 +1971,6 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1937
1971
  }
1938
1972
  return analyses;
1939
1973
  }
1940
- /**
1941
- * Damerau-Levenshtein edit-distance (optimal string alignment variant):
1942
- * like classic Levenshtein but also counts an adjacent-character
1943
- * transposition (e.g. "yanlız" -> "yalnız") as a single edit instead of
1944
- * two substitutions — a very common class of typo that plain Levenshtein
1945
- * otherwise misses.
1946
- */
1947
- static damerauLevenshtein(a, b) {
1948
- const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
1949
- for (let i = 0; i <= a.length; i++)
1950
- dp[i][0] = i;
1951
- for (let j = 0; j <= b.length; j++)
1952
- dp[0][j] = j;
1953
- for (let i = 1; i <= a.length; i++) {
1954
- for (let j = 1; j <= b.length; j++) {
1955
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1956
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
1957
- if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
1958
- dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + cost);
1959
- }
1960
- }
1961
- }
1962
- return dp[a.length][b.length];
1963
- }
1964
- /**
1965
- * Keyboard- and diacritic-aware edit distance: same optimal-string-alignment
1966
- * recurrence as {@link damerauLevenshtein}, but a substitution is charged by
1967
- * {@link keyboardSubCost} (a fraction of an edit when the two letters are
1968
- * adjacent on a Turkish Q keyboard or are ASCII/diacritic siblings) and a
1969
- * transposition costs {@link TRANSPOSITION_COST}. Insertions and deletions
1970
- * still cost a full 1. Used only to *rank* spelling candidates; the plain
1971
- * integer distance still gates whether a suggestion is offered at all.
1972
- */
1973
- static keyboardAwareDistance(a, b) {
1974
- const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
1975
- for (let i = 0; i <= a.length; i++)
1976
- dp[i][0] = i;
1977
- for (let j = 0; j <= b.length; j++)
1978
- dp[0][j] = j;
1979
- for (let i = 1; i <= a.length; i++) {
1980
- for (let j = 1; j <= b.length; j++) {
1981
- const cost = keyboardSubCost(a[i - 1], b[j - 1]);
1982
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
1983
- if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
1984
- dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + TRANSPOSITION_COST);
1985
- }
1986
- }
1987
- }
1988
- return dp[a.length][b.length];
1989
- }
1990
1974
  /**
1991
1975
  * Fetches multiple words concurrently with a small delay to avoid rate limiting.
1992
1976
  */
@@ -2460,9 +2444,9 @@ export {
2460
2444
  TDKError,
2461
2445
  TDKValidationError,
2462
2446
  TDKNetworkError,
2447
+ TURKISH_SUFFIXES,
2463
2448
  TURKISH_VOWELS,
2464
2449
  isVowel,
2465
- TURKISH_SUFFIXES,
2466
2450
  restoreConsonantSoftening,
2467
2451
  restoreVowelDrop,
2468
2452
  restoreGemination,