From 0265e6d796cb2e3411918b1dfa1c5f997d149069 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Sun, 23 Aug 2026 20:04:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20REST=20v1=20(OpenAPI=203.1)=20=E2=80=94?= =?UTF-8?q?=20maps+models=20discovery,=20transliterate,=20detect,=20ML=20i?= =?UTF-8?q?nference=20proxy;=20v1.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++ dist/src/generated/models.json | 312 +++++++++++++++++++++++++++++++++ dist/src/index.js | 4 +- dist/src/server.js | 4 + package.json | 5 +- scripts/gen-models.mjs | 21 +++ src/generated/models.json | 312 +++++++++++++++++++++++++++++++++ src/models.ts | 44 +++++ src/openapi.ts | 240 +++++++++++++++++++++++++ src/rest.ts | 172 ++++++++++++++++++ src/server.ts | 5 + test/rest.test.ts | 149 ++++++++++++++++ 12 files changed, 1280 insertions(+), 4 deletions(-) create mode 100644 dist/src/generated/models.json create mode 100644 scripts/gen-models.mjs create mode 100644 src/generated/models.json create mode 100644 src/models.ts create mode 100644 src/openapi.ts create mode 100644 src/rest.ts create mode 100644 test/rest.test.ts diff --git a/README.md b/README.md index 8442c5a..76e6432 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,19 @@ ruby -e '... see scripts/build-maps.rb ...' `test/fixtures/parity.json` holds queries recorded from the production Ruby API; CI replays them against this implementation. Zero-diff is the release gate. + + +## REST v1 (OpenAPI) + +REST endpoints serve alongside GraphQL (which remains at `POST /graphql`): + +- `GET /openapi.json` — the OpenAPI 3.1 document +- `GET /v1/info` — version + capability summary +- `GET /v1/maps` / `GET /v1/maps/{code}` — bundled transliteration systems +- `POST /v1/transliterate` — `{system, input}` → `{output}` +- `POST /v1/detect` — `{input, output}` → ranked systems +- `GET /v1/models` / `GET /v1/models/{id}` — the neural model index (IMF v1, generated from interscript-ml's models.yaml via `npm run gen:models`) +- `POST /v1/infer` — `{model, input}` → `{output}`; proxies to the + inference service configured with `ML_ENDPOINT` + `ML_TOKEN` env vars + (Interscript runs `src/api/inference.py` on Modal: the same + parity-verified ONNX decode that gates every release) diff --git a/dist/src/generated/models.json b/dist/src/generated/models.json new file mode 100644 index 0000000..d4e498c --- /dev/null +++ b/dist/src/generated/models.json @@ -0,0 +1,312 @@ +{ + "version": 1, + "models": [ + { + "id": "khm-latn-1.0", + "task": "translit", + "scripts": [ + "Khmr", + "Latn" + ], + "precision": "fp32", + "filename": "khm-latn-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/khm-latn-1.0/khm-latn-1.0-fp32.zip", + "parts": null, + "sha256": "55993d473a2ed9489058779cad7e115db05e4020085b71616468cfae4f2f65cb", + "size": 1418009977, + "metrics": [ + { + "name": "cer", + "value": 27.42, + "source": "secryst/docs/RESULTS.md#khmer-transliteration-2026-08-14" + }, + { + "name": "em", + "value": 59.66, + "source": "secryst/docs/RESULTS.md#khmer-transliteration-2026-08-14" + } + ], + "parity": { + "samples": 895, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "urd-g2p-1.0", + "task": "g2p", + "scripts": [ + "Arab", + "IPA" + ], + "precision": "fp32", + "filename": "urd-g2p-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/urd-g2p-1.0/urd-g2p-1.0-fp32.zip", + "parts": null, + "sha256": "9f0a248c7116aa7f6b9ac125fb9684a3556f3b8ce8b64567c05a5e7e7a018ebf", + "size": 1418039605, + "metrics": [ + { + "name": "cer", + "value": 14.77, + "source": "rababa-urdu/docs/RESULTS.md#g2p-urdu-text--ipa" + }, + { + "name": "em", + "value": 33.6, + "source": "rababa-urdu/docs/RESULTS.md#g2p-urdu-text--ipa" + } + ], + "parity": { + "samples": 12699, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "urd-diac-1.0", + "task": "diacritization", + "scripts": [ + "Arab", + "Arab" + ], + "precision": "fp32", + "filename": "urd-diac-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/urd-diac-1.0/urd-diac-1.0-fp32.zip", + "parts": null, + "sha256": "c299ea44a4da4851a52283f8e42d2fb6f8c7a9c443ba7eb0270f97372e10b8ea", + "size": 1417851421, + "metrics": [ + { + "name": "cer", + "value": 3.74, + "source": "rababa-urdu/docs/RESULTS.md#diacritization-urdu-text--text--haraqat" + } + ], + "parity": { + "samples": 11940, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "heb-diac-1.0", + "task": "diacritization", + "scripts": [ + "Hebr", + "Hebr" + ], + "precision": "fp32", + "filename": "heb-diac-1.0-fp32.zip", + "url": null, + "parts": [ + { + "url": "https://github.com/interscript/interscript-ml/releases/download/heb-diac-1.0/heb-diac-1.0-fp32.zip.part-00", + "sha256": "5497f70cc3c96829e65f1eb0cc401c2d1ffeb4fa909acd4def824c2e019cbc37", + "size": 1500000000 + }, + { + "url": "https://github.com/interscript/interscript-ml/releases/download/heb-diac-1.0/heb-diac-1.0-fp32.zip.part-01", + "sha256": "a7d2011ab4f9326a86e234cc49cb50daf82949e51efbf03a21d5796c46465e9d", + "size": 1281139753 + } + ], + "sha256": "b32fa239e0c56708b6004942de4b9e6e9f265e7cc3828f41d560e96386e20dd8", + "size": 2781139753, + "metrics": [ + { + "name": "der_greedy", + "value": 29.0, + "source": "rababa/docs/RESULTS.md#hebrew-diacritization" + }, + { + "name": "der_beam4", + "value": 17.46, + "source": "rababa/docs/RESULTS.md#hebrew-diacritization" + } + ], + "parity": { + "samples": 600, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "tha-g2p-base-1.0", + "task": "g2p", + "scripts": [ + "Thai", + "IPA" + ], + "precision": "fp32", + "filename": "tha-g2p-base-1.0-fp32.zip", + "url": null, + "parts": [ + { + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-base-1.0/tha-g2p-base-1.0-fp32.zip.part-00", + "sha256": "c4525414e5dec91a54d055ca9f1acd51c198f81fbfc55977aa82c1fb038d58d7", + "size": 1500000000 + }, + { + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-base-1.0/tha-g2p-base-1.0-fp32.zip.part-01", + "sha256": "f5d11fbd75f92e8b5471add867418e2dd4f49a5e258b9ded2f3b5ffa630abe24", + "size": 1281174343 + } + ], + "sha256": "f341a5868867a9847d3f897d1fcc4d1d63b9a59b8913871bcf4ecab48f4fa895", + "size": 2781174343, + "metrics": [ + { + "name": "per_teacher", + "value": 4.43, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-base-1.0" + }, + { + "name": "per_student", + "value": 9.19, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-base-1.0" + } + ], + "parity": { + "samples": 1219, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "tha-g2p-small-1.0", + "task": "g2p", + "scripts": [ + "Thai", + "IPA" + ], + "precision": "int8", + "filename": "tha-g2p-small-1.0-int8.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-small-1.0/tha-g2p-small-1.0-int8.zip", + "parts": null, + "sha256": "f2cfa93a42d98aabf08fdc82dfc6bd1574e21993f36558aa581fbe29e0588a94", + "size": 257468517, + "metrics": [ + { + "name": "per_teacher", + "value": 4.43, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + }, + { + "name": "per_student", + "value": 12.06, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + } + ], + "parity": { + "samples": 1219, + "cer_delta": 0.0803 + }, + "license": "BSD-3-Clause" + }, + { + "id": "tha-g2p-small-1.0-int4", + "task": "g2p", + "scripts": [ + "Thai", + "IPA" + ], + "precision": "int4", + "filename": "tha-g2p-small-1.0-int4.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-small-1.0/tha-g2p-small-1.0-int4.zip", + "parts": null, + "sha256": "e5d12a3d65c6ac39fa08f52c975c99ff7a51c3e7b7aca7fbb461d2de2345e4cb", + "size": 201958073, + "metrics": [ + { + "name": "per_teacher", + "value": 4.43, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + }, + { + "name": "per_student", + "value": 12.06, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + } + ], + "parity": { + "samples": 600, + "cer_delta": 0.0734 + }, + "license": "BSD-3-Clause" + }, + { + "id": "heb-diac-small-1.0", + "task": "diacritization", + "scripts": [ + "Hebr", + "Hebr" + ], + "precision": "fp32", + "filename": "heb-diac-small-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/heb-diac-small-1.0/heb-diac-small-1.0-fp32.zip", + "parts": null, + "sha256": "905c53873d5a62bbe6252c6ec0ecef5f37e96e78cb01e8cd99b6b24c4d4d74ad", + "size": 1418663344, + "metrics": [ + { + "name": "der_teacher_greedy", + "value": 24.79, + "source": "interscript/interscript-ml docs/RESULTS.md#heb-diac-small-1.0" + }, + { + "name": "der_student_greedy", + "value": 30.37, + "source": "interscript/interscript-ml docs/RESULTS.md#heb-diac-small-1.0" + } + ], + "parity": { + "samples": 1864, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "fas-g2p-1.0", + "task": "g2p", + "scripts": [ + "Arab", + "Latn" + ], + "precision": "fp32", + "filename": "fas-g2p-1.0-fp32.zip", + "url": null, + "parts": [ + { + "url": "https://github.com/interscript/interscript-ml/releases/download/fas-g2p-1.0/fas-g2p-1.0-fp32.zip.part-00", + "sha256": "6c475e53f3c7ef3edcef5a24d2d81f51d13ddf4c417de98440943c9a32b03e55", + "size": 1500000000 + }, + { + "url": "https://github.com/interscript/interscript-ml/releases/download/fas-g2p-1.0/fas-g2p-1.0-fp32.zip.part-01", + "sha256": "c5656c649412f41e201c567e002514d31390b636d4650d9dcb6dfed1973fb3d5", + "size": 1280254270 + } + ], + "sha256": "b53fd69196127efcc596935d24e135b3c897006872c1646c674e70f3c0d9a0e3", + "size": 2780254270, + "metrics": [ + { + "name": "cer", + "value": 1.6, + "source": "interscript/interscript-ml docs/RESULTS.md#fas-g2p-1.0" + }, + { + "name": "sb_homograph_ezafe_norm", + "value": 77.34, + "source": "interscript/interscript-ml docs/RESULTS.md#fas-g2p-1.0" + } + ], + "parity": { + "samples": 1000, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + } + ] +} diff --git a/dist/src/index.js b/dist/src/index.js index 4ca2d2c..75da27f 100644 --- a/dist/src/index.js +++ b/dist/src/index.js @@ -1,4 +1,4 @@ -var Pe=Object.defineProperty;var d=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(a){throw n=[a],a}};var E=(e,t)=>{for(var n in t)Pe(e,n,{get:t[n],enumerable:!0})};var Rt={};E(Rt,{createLitertSession:()=>qe});async function Ge(){return F||(F=(async()=>{try{return await import("@litertjs/core")}catch(e){throw F=void 0,new Error("@litertjs/core is required for LiteRT inference. Install with: npm install @litertjs/core",{cause:e})}})()),F}async function qe(e,t={}){let n=t.accelerator??"webgpu",a=t.wasmPath??He,r=t.inputNames??["src","lengths"],o=t.outputNames??["output"],s=await Ge();await s.loadLiteRt(a,{jspi:n==="webnn"});let c=new Blob([new Uint8Array(e)],{type:"application/octet-stream"}),i=URL.createObjectURL(c),l;return l=await s.loadAndCompile(i,{accelerator:n}),new rt(l,s,r,o,i)}function We(e){return e instanceof Float32Array?"float32":e instanceof Int32Array?"int32":e instanceof BigInt64Array?"int64":e instanceof Uint8Array?"uint8":"float32"}var He,F,rt,Ot=d(()=>{"use strict";He="https://cdn.jsdelivr.net/npm/@litertjs/core@2.5.2/wasm/";rt=class{inputNames;outputNames;model;core;blobUrl;disposed=!1;constructor(t,n,a,r,o){this.model=t,this.core=n,this.inputNames=a,this.outputNames=r,this.blobUrl=o}async run(t){if(this.disposed)throw new Error("Session disposed");let n=[];for(let o of this.inputNames){let s=t[o];if(!s)throw new Error(`Missing LiteRT input: ${o}`);n.push(new this.core.Tensor(s.data,s.dims))}let a=await this.model.run(n.length===1?n[0]:n),r={};try{for(let o=0;oQe});async function Ke(){try{return await import("onnxruntime-node")}catch(e){throw new Error("onnxruntime-node is required for ML inference in Node. Install with: npm install onnxruntime-node",{cause:e})}}async function Qe(e){return ot.create(e)}var ot,Bt=d(()=>{"use strict";ot=class e{session;ort;inputNames;outputNames;inputMetadata;constructor(t,n){this.session=t,this.ort=n,this.inputNames=t.inputNames,this.outputNames=t.outputNames;let a=t.inputMetadata;Array.isArray(a)&&(this.inputMetadata=a)}static async create(t){let n=await Ke(),a=await n.InferenceSession.create(t);return new e(a,n)}async run(t){let n={};for(let[o,s]of Object.entries(t))n[o]=new this.ort.Tensor(s.type,s.data,s.dims);let a=await this.session.run(n),r={};for(let[o,s]of Object.entries(a)){let c=s;r[o]={name:o,type:c.type,data:c.data,dims:c.dims}}return r}async dispose(){await this.session.release()}}});var Pt={};E(Pt,{createWebSession:()=>Ye});async function Ve(){return U||(U=(async()=>{try{let e=await import("onnxruntime-web");return e.env.wasm.simd=!0,e}catch(e){throw U=void 0,new Error("onnxruntime-web is required for ML inference in the browser. Install with: npm install onnxruntime-web",{cause:e})}})()),U}async function Ye(e,t={}){return st.create(e,t)}var U,st,zt=d(()=>{"use strict";st=class e{session;ort;inputNames;outputNames;constructor(t,n){this.session=t,this.ort=n,this.inputNames=t.inputNames,this.outputNames=t.outputNames}static async create(t,n={}){let a=await Ve(),r=n.webgpu===!1?["wasm"]:["webgpu","wasm"],o=await a.InferenceSession.create(t,{executionProviders:r});return new e(o,a)}async run(t){let n={};for(let[o,s]of Object.entries(t))n[o]=new this.ort.Tensor(s.type,s.data,s.dims);let a=await this.session.run(n),r={};for(let[o,s]of Object.entries(a)){let c=s;r[o]={name:o,type:c.type,data:c.data,dims:c.dims}}return r}async dispose(){await this.session.release()}}});function it(){let e=globalThis;return e.process?.versions?.node?"node":e.self!==void 0?"web":"unknown"}async function v(e,t={}){if((t.runtime??"onnx")==="litert"){let o={};return t.litertAccelerator!==void 0&&(o.accelerator=t.litertAccelerator),t.inputNames!==void 0&&(o.inputNames=t.inputNames),t.outputNames!==void 0&&(o.outputNames=t.outputNames),(await Promise.resolve().then(()=>(Ot(),Rt))).createLitertSession(e,o)}if((t.backend??it()==="node"?"node":"web")==="node")return(await Promise.resolve().then(()=>(Bt(),jt))).createNodeSession(e);let r=t.webgpu===void 0?{}:{webgpu:t.webgpu};return(await Promise.resolve().then(()=>(zt(),Pt))).createWebSession(e,r)}var H=d(()=>{"use strict"});var Je,Ze,Ft,Ut,ea,Ht,na,Xe,Gt,tn,ct,qt,lt,G,D=d(()=>{"use strict";Je=["\u0652","\u0651","\u064C","\u064D","\u0650","\u064B","\u064E","\u064F"],Ze="\u0649\u0639\u0638\u062D\u0631\u0633\u064A\u0634\u0636\u0642 \u062B\u0644\u0635\u0637\u0643\u0622\u0645\u0627\u0625\u0647\u0632\u0621\u0623\u0641\u0624\u063A\u062C\u0626\u062F\u0629\u062E\u0648\u0628\u0630\u062A\u0646",Ft="\u0649\u0639\u0638\u062D\u0631\u0633\u064A\u0634\u0636\u0642\u062B\u0644\u0635\u0637\u0643\u0622\u0645\u0627\u0625\u0647\u0632\u0621\u0623\u0641\u0624\u063A\u062C\u0626\u062F\u0629\u062E\u0648\u0628\u0630\u062A\u0646",Ut=[...Je,...Ze],ea=Object.freeze({"\u064E":"Fatha","\u064B":"Fathatah","\u064F":"Damma","\u064C":"Dammatan","\u0650":"Kasra","\u064D":"Kasratan","\u0652":"Sukun","\u0651":"Shaddah"}),Ht=Object.freeze({"":"No Diacritic","\u064E":"Fatha","\u064B":"Fathatah","\u064F":"Damma","\u064C":"Dammatan","\u0650":"Kasra","\u064D":"Kasratan","\u0652":"Sukun","\u0651":"Shaddah","\u0651\u064E":"Shaddah + Fatha","\u0651\u064B":"Shaddah + Fathatah","\u0651\u064F":"Shaddah + Damma","\u0651\u064C":"Shaddah + Dammatan","\u0651\u0650":"Shaddah + Kasra","\u0651\u064D":"Shaddah + Kasratan"}),na=Object.freeze(Object.fromEntries(Object.keys(Ht).map((e,t)=>[e,t]))),Xe="P",Gt=[Xe,...Object.keys(Ht),""],tn="\u0628\u0636.\u063A\u0649\u0647\u0638\u062E\u0629\u061F:\u0637\u0633\u060C\u061B\u0641\u0646\u062F\u0624\u0644\u0648\u0626\u0622\u0643-\u064A\u0630\u0627\u0635\u0634\u062D\u0632\u0621\u0645\u0623\u062C\u0625 \u062A\u0631\u0642\u0639\u062B",ct="P",qt=[ct,...tn],lt=Object.freeze(Object.fromEntries(qt.map((e,t)=>[e,t]))),G=qt});function en(e){return e.replace(/\s+/g," ")}function ut(e){return en(e).trim()}function Wt(e){return ut(Array.from(e).filter(t=>nn.has(t)).join(""))}var nn,Kt=d(()=>{"use strict";D();nn=new Set(Ut)});var q,Qt=d(()=>{"use strict";D();Kt();q=class{inputSymbolToId=lt;inputIdToSymbol=G;inputPadId;startSymbolId;cleanerType;reverse;constructor(t={}){this.cleanerType=t.cleaner??"arabic",this.reverse=t.reverseInput??!1,this.inputPadId=lt[ct],this.startSymbolId=t.startSymbol?0:null}clean(t){return this.cleanerType==="arabic"?Wt(t):ut(t)}removeDiacritics(t){return Array.from(t).filter(n=>!(!(n in this.inputSymbolToId)&&/[ً-ْ]/.test(n))).join("")}inputToSequence(t){let n=this.reverse?Array.from(t).reverse():Array.from(t),a=[];for(let r of n){let o=this.inputSymbolToId[r];o!==void 0&&a.push(o)}return a}}});function on(e,t){let n=[],a=0;for(let r=0;r!an.has(i)),a=Array.from(t),r=on(n,a),o="",s=0,c=0;for(let{idxDia:i,idxOri:l}of r){if(c{"use strict";D();an=new Set(["\u0652","\u0651","\u064C","\u064D","\u0650","\u064B","\u064E","\u064F"]),rn=new Set(Ft)});function sn(e,t){let n=e.dims,a=n[n.length-1],r=[],o=e.data;for(let s=0;si&&(i=f,c=u)}r.push(c)}return r}function cn(e,t,n){let a="";for(let r=0;r{"use strict";Qt();Yt();D();M={maxLen:200,batchSize:1,textEncoder:"ArabicEncoderWithStartSymbol",textCleaner:"valid_arabic_cleaners"},pt=class{kind="rababa";session;encoder;config;constructor(t,n){this.session=t,this.config=n,this.encoder=new q({cleaner:n.textCleaner==="valid_arabic_cleaners"?"arabic":"basic",startSymbol:n.textEncoder==="ArabicEncoderWithStartSymbol"})}async diacritize(t){let n=t;n.length>this.config.maxLen&&(n=n.slice(0,this.config.maxLen));let a=this.encoder.clean(n),r=this.stripDiacritics(a),o=this.encoder.inputToSequence(r);if(o.length===0)return n;let s=this.config.batchSize,c=this.config.maxLen,i=this.encoder.inputPadId,l=[];for(let T=0;TBigInt(T))),dims:[s,c]},f={name:"lengths",type:"int64",data:new BigInt64Array(Array(s).fill(BigInt(c))),dims:[s]},y=(await this.session.run({src:u,lengths:f}))[this.session.outputNames[0]];if(!y)throw new Error("Rababa model produced no output");let et=sn(y,o.length),_=cn(o,et,this.encoder.inputPadId);return Vt(a,_)}async transform(t){return this.diacritize(t)}stripDiacritics(t){return Array.from(t).filter(n=>!/[ً-ْ]/.test(n)).join("")}async dispose(){await this.session.dispose()}}});var te={};E(te,{rababa:()=>gn,rababaReverse:()=>Ln,resetRababaConfigs:()=>pn,setRababaConfig:()=>un});function un(e,t){dt.set(e,t)}function pn(){dt=new Map(Object.entries(Xt))}function fn(e){let t=dt.get(e);if(!t)throw new Error(`No rababa config registered under '${e}'. Register one via setRababaConfig() before invoking rababa.`);return t}function dn(e){return{maxLen:Number(e.max_len??e.maxLen??200),batchSize:Number(e.batch_size??e.batchSize??32),textEncoder:e.text_encoder??"ArabicEncoderWithStartSymbol",textCleaner:e.text_cleaner??"valid_arabic_cleaners"}}async function mn(e){let t=ft.get(e);if(!t){let n=fn(e);t=(async()=>{let a=await hn(n.model),r=await v(a),o=dn(n.config);return Jt({session:r,artifacts:{},config:o})})(),ft.set(e,t),t.catch(()=>ft.delete(e))}return t}async function hn(e){if(e.startsWith("file:")||e.startsWith(".")){let{readFile:n}=await import("node:fs/promises"),a=e.startsWith("file:")?e.slice(5):e;return new Uint8Array(await n(a))}let t=await fetch(e);if(!t.ok)throw new Error(`Failed to fetch ${e}: ${t.status}`);return new Uint8Array(await t.arrayBuffer())}async function gn(e,t={}){let n=t.config??"200";return(await mn(n)).diacritize(e)}function Ln(e){return e.replace(/[ًٌٍَُِّْ]/g,"")}var Xt,dt,ft,ee=d(()=>{"use strict";H();Zt();Xt=Object.freeze({200:Object.freeze({model:"https://github.com/secryst/rababa-models/releases/download/0.1/diacritization_model_arabic.onnx",config:Object.freeze({session_name:"base",text_encoder:"ArabicEncoderWithStartSymbol",text_cleaner:"valid_arabic_cleaners",max_len:200,batch_size:32})})}),dt=new Map(Object.entries(Xt));ft=new Map});function ae(e){ne=e.replace(/\/$/,"")}function W(){return ne}var ne,mt=d(()=>{"use strict";ne="https://cdn.jsdelivr.net/gh/interscript/ml-models@main/npm/models"});function oe(e){ht=e,A=null}function se(e){re=e,A=null}function wn(){return`${W()}/npm/models/manifest.json`}async function gt(){if(A)return A;if(ht)return A=ht,A;let e=re??wn(),t=await fetch(e);if(!t.ok)throw new Error(`Failed to load model manifest from ${e}: ${t.status} ${t.statusText}`);let n=await t.json();return A=n,n}async function R(e,t){let n=await gt(),a=t.startsWith(`${e}_`)?t:`${e}_${t}`;return n.models[a]??n.models[t]??null}function O(e,t="q8",n="onnx"){let a=ie(e,t,n),r=e.cdn_base.replace("{version}",e.version),o=e.github_base.replace("{version}",e.version);return{primary:`${r}${a}`,fallback:`${o}${a}`,assetName:a}}function K(e,t="q8",n="onnx"){return[`${ie(e,t,n)}.sha256`,"vocab.json","config.json"]}function ie(e,t,n="onnx"){return`${An(e)}${yn[t]}${bn[n]}`}function An(e){let n=e.github_base.match(/([^/]+)-v\{version\}\/$/)?.[1];if(n)return n;throw new Error(`Cannot extract task name from manifest entry. github_base=${e.github_base}`)}var yn,bn,A,ht,re,Lt=d(()=>{"use strict";mt();yn={fp32:"",q8:"-q8",q4:"-q4",fp16:"-fp16"},bn={onnx:".onnx",tflite:".tflite"},A=null,ht=null,re=null});async function le(e,t={}){let n=t.variant??"q8",a=t.format??"onnx",r=t.webgpu??!0,o=e.url??await xn(e,n,a),s=await ce(o),c=await kn(e,n,a),i={};await Promise.all(c.map(async({filename:f,url:b})=>{try{let y=await ce(b);f.endsWith(".json")||f.endsWith(".yaml")||f.endsWith(".yml")||f.endsWith(".sha256")?i[f]=new TextDecoder().decode(y):i[f]=y}catch{}}));let l={webgpu:r};return a==="tflite"&&(l.runtime="litert",t.litertAccelerator!==void 0&&(l.litertAccelerator=t.litertAccelerator),t.inputNames!==void 0&&(l.inputNames=t.inputNames),t.outputNames!==void 0&&(l.outputNames=t.outputNames)),{session:await v(s,l),artifacts:i}}async function xn(e,t,n){let a=await R(e.kind,e.id);if(!a)throw new Error(`No manifest entry for kind=${e.kind} id=${e.id}. Pass an explicit \`url\` on the ModelRef or pin a task version in the manifest.`);let{primary:r}=O(a,t,n);return r}async function kn(e,t,n){if(e.url)return[];let a=await R(e.kind,e.id);if(!a)return[];let{primary:r,assetName:o}=O(a,t,n),s=r.slice(0,r.lastIndexOf("/")+1);return K(a,t,n).map(c=>({filename:c,url:`${s}${c.startsWith(o),c}`}))}async function ce(e){if(e.startsWith("file:")||e.startsWith(".")&&typeof process<"u"&&process.versions?.node){let{readFile:n}=await import("node:fs/promises"),a=e.startsWith("file:")?e.slice(5):e;return new Uint8Array(await n(a))}let t=await fetch(e);if(!t.ok)throw new Error(`Failed to fetch ${e}: ${t.status} ${t.statusText}`);return new Uint8Array(await t.arrayBuffer())}var ue=d(()=>{"use strict";H();Lt()});function pe(e,t){if(Q.has(e))throw new Error(`Model kind "${e}" is already registered`);Q.set(e,t)}function Cn(e){return`${e.kind}/${e.id}/${e.url??"_default"}`}async function fe(e){let t=Cn(e),n=j.get(t);if(n)return n;let a=Q.get(e.kind);if(!a)throw new Error(`Unknown model kind: "${e.kind}". Register a factory via registerModel("${e.kind}", factory).`);let r=(async()=>{let{session:o,artifacts:s}=await le(e);return a({session:o,artifacts:s})})();return j.set(t,r),r.catch(()=>j.delete(t)),r}function de(){return[...Q.keys()]}async function me(){let e=[...j.values()];j.clear(),await Promise.allSettled(e.map(async t=>{try{await(await t).dispose()}catch{}}))}var Q,j,he=d(()=>{"use strict";ue();Q=new Map;j=new Map});var ge={};E(ge,{artifactUrls:()=>O,createSession:()=>v,detectBackend:()=>it,getModelBase:()=>W,loadManifest:()=>gt,loadModel:()=>fe,registerModel:()=>pe,registeredKinds:()=>de,resetModels:()=>me,resolveManifestEntry:()=>R,setInlineManifest:()=>oe,setManifestUrl:()=>se,setModelBase:()=>ae,sidecarFilenames:()=>K});var Le=d(()=>{"use strict";he();H();mt();Lt()});import{createYoga as Rn,createSchema as On}from"graphql-yoga";import{Hono as jn}from"hono";import{cors as Bn}from"hono/cors";var nt=` +var Wt=Object.defineProperty;var m=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(r){throw n=[r],r}};var O=(e,t)=>{for(var n in t)Wt(e,n,{get:t[n],enumerable:!0})};var Ge={};O(Ge,{createLitertSession:()=>Zt});async function Xt(){return Q||(Q=(async()=>{try{return await import("@litertjs/core")}catch(e){throw Q=void 0,new Error("@litertjs/core is required for LiteRT inference. Install with: npm install @litertjs/core",{cause:e})}})()),Q}async function Zt(e,t={}){let n=t.accelerator??"webgpu",r=t.wasmPath??Jt,a=t.inputNames??["src","lengths"],s=t.outputNames??["output"],o=await Xt();await o.loadLiteRt(r,{jspi:n==="webnn"});let c=new Blob([new Uint8Array(e)],{type:"application/octet-stream"}),i=URL.createObjectURL(c),l;return l=await o.loadAndCompile(i,{accelerator:n}),new de(l,o,a,s,i)}function en(e){return e instanceof Float32Array?"float32":e instanceof Int32Array?"int32":e instanceof BigInt64Array?"int64":e instanceof Uint8Array?"uint8":"float32"}var Jt,Q,de,He=m(()=>{"use strict";Jt="https://cdn.jsdelivr.net/npm/@litertjs/core@2.5.2/wasm/";de=class{inputNames;outputNames;model;core;blobUrl;disposed=!1;constructor(t,n,r,a,s){this.model=t,this.core=n,this.inputNames=r,this.outputNames=a,this.blobUrl=s}async run(t){if(this.disposed)throw new Error("Session disposed");let n=[];for(let s of this.inputNames){let o=t[s];if(!o)throw new Error(`Missing LiteRT input: ${s}`);n.push(new this.core.Tensor(o.data,o.dims))}let r=await this.model.run(n.length===1?n[0]:n),a={};try{for(let s=0;snn});async function tn(){try{return await import("onnxruntime-node")}catch(e){throw new Error("onnxruntime-node is required for ML inference in Node. Install with: npm install onnxruntime-node",{cause:e})}}async function nn(e){return fe.create(e)}var fe,We=m(()=>{"use strict";fe=class e{session;ort;inputNames;outputNames;inputMetadata;constructor(t,n){this.session=t,this.ort=n,this.inputNames=t.inputNames,this.outputNames=t.outputNames;let r=t.inputMetadata;Array.isArray(r)&&(this.inputMetadata=r)}static async create(t){let n=await tn(),r=await n.InferenceSession.create(t);return new e(r,n)}async run(t){let n={};for(let[s,o]of Object.entries(t))n[s]=new this.ort.Tensor(o.type,o.data,o.dims);let r=await this.session.run(n),a={};for(let[s,o]of Object.entries(r)){let c=o;a[s]={name:s,type:c.type,data:c.data,dims:c.dims}}return a}async dispose(){await this.session.release()}}});var Ve={};O(Ve,{createWebSession:()=>an});async function rn(){return Y||(Y=(async()=>{try{let e=await import("onnxruntime-web");return e.env.wasm.simd=!0,e}catch(e){throw Y=void 0,new Error("onnxruntime-web is required for ML inference in the browser. Install with: npm install onnxruntime-web",{cause:e})}})()),Y}async function an(e,t={}){return me.create(e,t)}var Y,me,Qe=m(()=>{"use strict";me=class e{session;ort;inputNames;outputNames;constructor(t,n){this.session=t,this.ort=n,this.inputNames=t.inputNames,this.outputNames=t.outputNames}static async create(t,n={}){let r=await rn(),a=n.webgpu===!1?["wasm"]:["webgpu","wasm"],s=await r.InferenceSession.create(t,{executionProviders:a});return new e(s,r)}async run(t){let n={};for(let[s,o]of Object.entries(t))n[s]=new this.ort.Tensor(o.type,o.data,o.dims);let r=await this.session.run(n),a={};for(let[s,o]of Object.entries(r)){let c=o;a[s]={name:s,type:c.type,data:c.data,dims:c.dims}}return a}async dispose(){await this.session.release()}}});function he(){let e=globalThis;return e.process?.versions?.node?"node":e.self!==void 0?"web":"unknown"}async function T(e,t={}){if((t.runtime??"onnx")==="litert"){let s={};return t.litertAccelerator!==void 0&&(s.accelerator=t.litertAccelerator),t.inputNames!==void 0&&(s.inputNames=t.inputNames),t.outputNames!==void 0&&(s.outputNames=t.outputNames),(await Promise.resolve().then(()=>(He(),Ge))).createLitertSession(e,s)}if((t.backend??he()==="node"?"node":"web")==="node")return(await Promise.resolve().then(()=>(We(),Ke))).createNodeSession(e);let a=t.webgpu===void 0?{}:{webgpu:t.webgpu};return(await Promise.resolve().then(()=>(Qe(),Ve))).createWebSession(e,a)}var J=m(()=>{"use strict"});var sn,on,Ye,Je,dr,Xe,fr,cn,Ze,ln,ge,et,be,X,P=m(()=>{"use strict";sn=["\u0652","\u0651","\u064C","\u064D","\u0650","\u064B","\u064E","\u064F"],on="\u0649\u0639\u0638\u062D\u0631\u0633\u064A\u0634\u0636\u0642 \u062B\u0644\u0635\u0637\u0643\u0622\u0645\u0627\u0625\u0647\u0632\u0621\u0623\u0641\u0624\u063A\u062C\u0626\u062F\u0629\u062E\u0648\u0628\u0630\u062A\u0646",Ye="\u0649\u0639\u0638\u062D\u0631\u0633\u064A\u0634\u0636\u0642\u062B\u0644\u0635\u0637\u0643\u0622\u0645\u0627\u0625\u0647\u0632\u0621\u0623\u0641\u0624\u063A\u062C\u0626\u062F\u0629\u062E\u0648\u0628\u0630\u062A\u0646",Je=[...sn,...on],dr=Object.freeze({"\u064E":"Fatha","\u064B":"Fathatah","\u064F":"Damma","\u064C":"Dammatan","\u0650":"Kasra","\u064D":"Kasratan","\u0652":"Sukun","\u0651":"Shaddah"}),Xe=Object.freeze({"":"No Diacritic","\u064E":"Fatha","\u064B":"Fathatah","\u064F":"Damma","\u064C":"Dammatan","\u0650":"Kasra","\u064D":"Kasratan","\u0652":"Sukun","\u0651":"Shaddah","\u0651\u064E":"Shaddah + Fatha","\u0651\u064B":"Shaddah + Fathatah","\u0651\u064F":"Shaddah + Damma","\u0651\u064C":"Shaddah + Dammatan","\u0651\u0650":"Shaddah + Kasra","\u0651\u064D":"Shaddah + Kasratan"}),fr=Object.freeze(Object.fromEntries(Object.keys(Xe).map((e,t)=>[e,t]))),cn="P",Ze=[cn,...Object.keys(Xe),""],ln="\u0628\u0636.\u063A\u0649\u0647\u0638\u062E\u0629\u061F:\u0637\u0633\u060C\u061B\u0641\u0646\u062F\u0624\u0644\u0648\u0626\u0622\u0643-\u064A\u0630\u0627\u0635\u0634\u062D\u0632\u0621\u0645\u0623\u062C\u0625 \u062A\u0631\u0642\u0639\u062B",ge="P",et=[ge,...ln],be=Object.freeze(Object.fromEntries(et.map((e,t)=>[e,t]))),X=et});function pn(e){return e.replace(/\s+/g," ")}function ye(e){return pn(e).trim()}function tt(e){return ye(Array.from(e).filter(t=>un.has(t)).join(""))}var un,nt=m(()=>{"use strict";P();un=new Set(Je)});var Z,rt=m(()=>{"use strict";P();nt();Z=class{inputSymbolToId=be;inputIdToSymbol=X;inputPadId;startSymbolId;cleanerType;reverse;constructor(t={}){this.cleanerType=t.cleaner??"arabic",this.reverse=t.reverseInput??!1,this.inputPadId=be[ge],this.startSymbolId=t.startSymbol?0:null}clean(t){return this.cleanerType==="arabic"?tt(t):ye(t)}removeDiacritics(t){return Array.from(t).filter(n=>!(!(n in this.inputSymbolToId)&&/[ً-ْ]/.test(n))).join("")}inputToSequence(t){let n=this.reverse?Array.from(t).reverse():Array.from(t),r=[];for(let a of n){let s=this.inputSymbolToId[a];s!==void 0&&r.push(s)}return r}}});function mn(e,t){let n=[],r=0;for(let a=0;a!dn.has(i)),r=Array.from(t),a=mn(n,r),s="",o=0,c=0;for(let{idxDia:i,idxOri:l}of a){if(c{"use strict";P();dn=new Set(["\u0652","\u0651","\u064C","\u064D","\u0650","\u064B","\u064E","\u064F"]),fn=new Set(Ye)});function hn(e,t){let n=e.dims,r=n[n.length-1],a=[],s=e.data;for(let o=0;oi&&(i=d,c=p)}a.push(c)}return a}function gn(e,t,n){let r="";for(let a=0;a{"use strict";rt();st();P();E={maxLen:200,batchSize:1,textEncoder:"ArabicEncoderWithStartSymbol",textCleaner:"valid_arabic_cleaners"},Le=class{kind="rababa";session;encoder;config;constructor(t,n){this.session=t,this.config=n,this.encoder=new Z({cleaner:n.textCleaner==="valid_arabic_cleaners"?"arabic":"basic",startSymbol:n.textEncoder==="ArabicEncoderWithStartSymbol"})}async diacritize(t){let n=t;n.length>this.config.maxLen&&(n=n.slice(0,this.config.maxLen));let r=this.encoder.clean(n),a=this.stripDiacritics(r),s=this.encoder.inputToSequence(a);if(s.length===0)return n;let o=this.config.batchSize,c=this.config.maxLen,i=this.encoder.inputPadId,l=[];for(let j=0;jBigInt(j))),dims:[o,c]},d={name:"lengths",type:"int64",data:new BigInt64Array(Array(o).fill(BigInt(c))),dims:[o]},w=(await this.session.run({src:p,lengths:d}))[this.session.outputNames[0]];if(!w)throw new Error("Rababa model produced no output");let le=hn(w,s.length),$=gn(s,le,this.encoder.inputPadId);return at(r,$)}async transform(t){return this.diacritize(t)}stripDiacritics(t){return Array.from(t).filter(n=>!/[ً-ْ]/.test(n)).join("")}async dispose(){await this.session.dispose()}}});var lt={};O(lt,{rababa:()=>kn,rababaReverse:()=>xn,resetRababaConfigs:()=>Ln,setRababaConfig:()=>yn});function yn(e,t){Se.set(e,t)}function Ln(){Se=new Map(Object.entries(ct))}function wn(e){let t=Se.get(e);if(!t)throw new Error(`No rababa config registered under '${e}'. Register one via setRababaConfig() before invoking rababa.`);return t}function Sn(e){return{maxLen:Number(e.max_len??e.maxLen??200),batchSize:Number(e.batch_size??e.batchSize??32),textEncoder:e.text_encoder??"ArabicEncoderWithStartSymbol",textCleaner:e.text_cleaner??"valid_arabic_cleaners"}}async function vn(e){let t=we.get(e);if(!t){let n=wn(e);t=(async()=>{let r=await An(n.model),a=await T(r),s=Sn(n.config);return ot({session:a,artifacts:{},config:s})})(),we.set(e,t),t.catch(()=>we.delete(e))}return t}async function An(e){if(e.startsWith("file:")||e.startsWith(".")){let{readFile:n}=await import("node:fs/promises"),r=e.startsWith("file:")?e.slice(5):e;return new Uint8Array(await n(r))}let t=await fetch(e);if(!t.ok)throw new Error(`Failed to fetch ${e}: ${t.status}`);return new Uint8Array(await t.arrayBuffer())}async function kn(e,t={}){let n=t.config??"200";return(await vn(n)).diacritize(e)}function xn(e){return e.replace(/[ًٌٍَُِّْ]/g,"")}var ct,Se,we,pt=m(()=>{"use strict";J();it();ct=Object.freeze({200:Object.freeze({model:"https://github.com/secryst/rababa-models/releases/download/0.1/diacritization_model_arabic.onnx",config:Object.freeze({session_name:"base",text_encoder:"ArabicEncoderWithStartSymbol",text_cleaner:"valid_arabic_cleaners",max_len:200,batch_size:32})})}),Se=new Map(Object.entries(ct));we=new Map});function dt(e){ut=e.replace(/\/$/,"")}function ee(){return ut}var ut,ve=m(()=>{"use strict";ut="https://cdn.jsdelivr.net/gh/interscript/ml-models@main/npm/models"});function mt(e){Ae=e,k=null}function ht(e){ft=e,k=null}function Mn(){return`${ee()}/npm/models/manifest.json`}async function ke(){if(k)return k;if(Ae)return k=Ae,k;let e=ft??Mn(),t=await fetch(e);if(!t.ok)throw new Error(`Failed to load model manifest from ${e}: ${t.status} ${t.statusText}`);let n=await t.json();return k=n,n}async function U(e,t){let n=await ke(),r=t.startsWith(`${e}_`)?t:`${e}_${t}`;return n.models[r]??n.models[t]??null}function F(e,t="q8",n="onnx"){let r=gt(e,t,n),a=e.cdn_base.replace("{version}",e.version),s=e.github_base.replace("{version}",e.version);return{primary:`${a}${r}`,fallback:`${s}${r}`,assetName:r}}function te(e,t="q8",n="onnx"){return[`${gt(e,t,n)}.sha256`,"vocab.json","config.json"]}function gt(e,t,n="onnx"){return`${Tn(e)}${_n[t]}${Cn[n]}`}function Tn(e){let n=e.github_base.match(/([^/]+)-v\{version\}\/$/)?.[1];if(n)return n;throw new Error(`Cannot extract task name from manifest entry. github_base=${e.github_base}`)}var _n,Cn,k,Ae,ft,xe=m(()=>{"use strict";ve();_n={fp32:"",q8:"-q8",q4:"-q4",fp16:"-fp16"},Cn={onnx:".onnx",tflite:".tflite"},k=null,Ae=null,ft=null});async function yt(e,t={}){let n=t.variant??"q8",r=t.format??"onnx",a=t.webgpu??!0,s=e.url??await En(e,n,r),o=await bt(s),c=await Nn(e,n,r),i={};await Promise.all(c.map(async({filename:d,url:v})=>{try{let w=await bt(v);d.endsWith(".json")||d.endsWith(".yaml")||d.endsWith(".yml")||d.endsWith(".sha256")?i[d]=new TextDecoder().decode(w):i[d]=w}catch{}}));let l={webgpu:a};return r==="tflite"&&(l.runtime="litert",t.litertAccelerator!==void 0&&(l.litertAccelerator=t.litertAccelerator),t.inputNames!==void 0&&(l.inputNames=t.inputNames),t.outputNames!==void 0&&(l.outputNames=t.outputNames)),{session:await T(o,l),artifacts:i}}async function En(e,t,n){let r=await U(e.kind,e.id);if(!r)throw new Error(`No manifest entry for kind=${e.kind} id=${e.id}. Pass an explicit \`url\` on the ModelRef or pin a task version in the manifest.`);let{primary:a}=F(r,t,n);return a}async function Nn(e,t,n){if(e.url)return[];let r=await U(e.kind,e.id);if(!r)return[];let{primary:a,assetName:s}=F(r,t,n),o=a.slice(0,a.lastIndexOf("/")+1);return te(r,t,n).map(c=>({filename:c,url:`${o}${c.startsWith(s),c}`}))}async function bt(e){if(e.startsWith("file:")||e.startsWith(".")&&typeof process<"u"&&process.versions?.node){let{readFile:n}=await import("node:fs/promises"),r=e.startsWith("file:")?e.slice(5):e;return new Uint8Array(await n(r))}let t=await fetch(e);if(!t.ok)throw new Error(`Failed to fetch ${e}: ${t.status} ${t.statusText}`);return new Uint8Array(await t.arrayBuffer())}var Lt=m(()=>{"use strict";J();xe()});function wt(e,t){if(ne.has(e))throw new Error(`Model kind "${e}" is already registered`);ne.set(e,t)}function In(e){return`${e.kind}/${e.id}/${e.url??"_default"}`}async function St(e){let t=In(e),n=q.get(t);if(n)return n;let r=ne.get(e.kind);if(!r)throw new Error(`Unknown model kind: "${e.kind}". Register a factory via registerModel("${e.kind}", factory).`);let a=(async()=>{let{session:s,artifacts:o}=await yt(e);return r({session:s,artifacts:o})})();return q.set(t,a),a.catch(()=>q.delete(t)),a}function vt(){return[...ne.keys()]}async function At(){let e=[...q.values()];q.clear(),await Promise.allSettled(e.map(async t=>{try{await(await t).dispose()}catch{}}))}var ne,q,kt=m(()=>{"use strict";Lt();ne=new Map;q=new Map});var xt={};O(xt,{artifactUrls:()=>F,createSession:()=>T,detectBackend:()=>he,getModelBase:()=>ee,loadManifest:()=>ke,loadModel:()=>St,registerModel:()=>wt,registeredKinds:()=>vt,resetModels:()=>At,resolveManifestEntry:()=>U,setInlineManifest:()=>mt,setManifestUrl:()=>ht,setModelBase:()=>dt,sidecarFilenames:()=>te});var _t=m(()=>{"use strict";kt();J();ve();xe()});import{createYoga as Kn,createSchema as Wn}from"graphql-yoga";import{Hono as Vn}from"hono";import{cors as Qn}from"hono/cors";var pe=` type DetectionResult { mapName: String distance: Float @@ -10,4 +10,4 @@ var Pe=Object.defineProperty;var d=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&( transliterate(systemCode: String!, input: String!): String detect(input: String!, output: String!): [DetectionResult] } -`;var h=class extends Error{cause;constructor(t,n){super(t,n),this.name=new.target.name,this.cause=n?.cause}},m=class extends h{systemCode;constructor(t){super(`Map not found: ${t}`),this.systemCode=t}},k=class extends h{},L=class extends h{},C=class extends h{dependency;constructor(t){super(`Map dependency missing: ${t}`),this.dependency=t}};var P=class{strategies;cache=new Map;known=new Map;options;constructor(t,n={}){this.strategies=t,this.options=n}load(t){let n=this.cache.get(t);if(n)return n;let a=this.known.get(t);if(a)return this.cache.set(t,a),a;for(let r of this.strategies){let o=r(t);if(o&&typeof o.then!="function"){let s=o;return this.cache.set(t,s),this.known.set(t,s),this.options.onLoaded?.(t,s),s}}throw new m(t)}async loadAsync(t){let n=this.cache.get(t);if(n)return n;let a=this.known.get(t);if(a)return this.cache.set(t,a),a;for(let r of this.strategies){let o=await r(t);if(o)return this.cache.set(t,o),this.known.set(t,o),this.options.onLoaded?.(t,o),o}throw new m(t)}clear(){this.cache.clear()}loadedMaps(){return Array.from(this.known.keys())}register(t,n){this.known.set(t,n),this.cache.set(t,n)}};var N=class e{current;map;loader;aliasCache=new Map;functions;constructor(t,n,a){this.map=t,this.current=n,this.functions=t.functions,this.loader=a}resolveAlias(t){if(this.aliasCache.has(t))return this.aliasCache.get(t);let n=this.map.aliases.get(t);if(!n&&this.loader)for(let a of this.map.dependencies)try{let o=this.loader.load(a).aliases.get(t);if(o){n=o;break}}catch{}return n&&this.aliasCache.set(t,n),n}withMap(t){return new e(t,this.current,this.loader)}loadDependency(t){if(!this.loader)throw new Error(`Cannot resolve dependency ${t}: no loader configured`);return this.loader.load(t)}};function at(){return{children:new Map,match:null}}function kt(e){let t=at();for(let[n,a]of e){if(n.length===0)continue;let r=t;for(let c=0;c0?(n+=c,r+=s):(n+=e[r],r+=1)}return n}function I(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function St(e){return e.toLowerCase()}function vt(e){return e.toUpperCase()}function Mt(e,t={}){let n=t.wordSeparator??" ";return n===""?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():e.split(n).map(a=>a.length===0?a:a.charAt(0).toUpperCase()+a.slice(1).toLowerCase()).join(n)}function _t(e,t={}){let n=t.separator??" ";return e.split("").join(n)}function Tt(e){return e.normalize("NFC")}function Et(e){return e.normalize("NFD")}function Nt(e,t){if(t.length===0)return e;let n=t.map((r,o)=>`(?<__r${o}>${r.pattern})`),a=new RegExp(n.join("|"),"gmu");return e.replace(a,(...r)=>{let o=r[r.length-1];for(let s=0;sp(r,t).re),a=e.of.length>0?p(e.of[0],t).literal:"";return{re:`(?:${n.join("|")})`,literal:a}}case"any_char_class":{if(e.range){let[n,a]=e.range;return{re:`[${I(n)}-${I(a)}]`,literal:n??""}}return e.chars&&e.chars.length>0?{re:`[${e.chars.map(a=>I(a)).join("")}]`,literal:e.chars[0]}:{re:"[^]",literal:""}}case"group":{let n=e.items.map(o=>p(o,t)),a=n.map(o=>o.re).join(""),r=n.map(o=>o.literal).join("");return{re:`(?:${a})`,literal:r}}case"repeat":{let n=p(e.item,t).re,{min:a,max:r}=e,o=r===null?1/0:r,s=o===1/0?a===0?"*":"+":`{${a},${o}}`;return{re:`(?:${n})${s}`,literal:""}}case"stage_ref":return{re:"",literal:""}}}function $(e,t){switch(e.kind){case"string":return e.value;case"alias":{let n=z[e.name];if(n)return n.literal===""?null:n.literal;let a=t.resolveAlias(e.name);return a?$(a,t):null}case"capture_group":case"capture_ref":case"repeat":case"stage_ref":return null;case"any_char_class":return e.chars&&e.chars.length>0?e.chars[0]:e.range?e.range[0]:null;case"any":return e.of.length===0?null:$(e.of[0],t);case"group":{let n="";for(let a of e.items){let r=$(a,t);if(r===null)return null;n+=r}return n}}}function S(e,t){switch(e.kind){case"string":return[e.value];case"alias":{if(z[e.name])return null;let a=t.resolveAlias(e.name);return a?S(a,t):null}case"any_char_class":{if(e.chars)return[...e.chars];if(e.range){let[n,a]=e.range,r=n.codePointAt(0),o=a.codePointAt(0),s=[];for(let c=r;c<=o;c++)s.push(String.fromCodePoint(c));return s}return null}case"any":{let n=[];for(let a of e.of){let r=S(a,t);if(r===null)return null;n.push(...r)}return n}case"group":{let n=[""];for(let a of e.items){let r=S(a,t);if(r===null)return null;let o=[];for(let s of n)for(let c of r)o.push(s+c);n=o}return n}default:return null}}function g(e,t){switch(e.kind){case"string":return e.value.length;case"capture_group":return g(e.data,t);case"capture_ref":return 1;case"alias":{if(e.name==="none")return 0;if(z[e.name])return 1;let n=t.resolveAlias(e.name);return n?g(n,t):1}case"any":return e.of.reduce((n,a)=>Math.max(n,g(a,t)),0);case"any_char_class":return 1;case"group":return e.items.reduce((n,a)=>n+g(a,t),0);case"repeat":return g(e.item,t);case"stage_ref":return 1}}var z=Object.freeze({any_character:{re:".",literal:""},none:{re:"",literal:""},space:{re:" ",literal:" "},whitespace:{re:"\\s+",literal:" "},boundary:{re:"(?:(?Mt(e,t??{}),separate:(e,t)=>_t(e,t??{}),compose:Tt,decompose:Et};function It(e,t){let n=e.functions.get(t)?.impl;if(n)return n;let a=ze[t];if(a)return a}var Fe={sub:(e,t)=>Ue(e,t),run:(e,t)=>{let n=e.docName?t.loadDependency(e.docName):t.map,a=n.stages.find(o=>o.name===e.stage);if(!a)throw new L(`Stage not found: ${e.stage}`);let r=t.withMap(n);for(let o of a.rules)w(o,r);t.current=r.current},funcall:(e,t)=>{let n=It(t,e.name);if(!n)throw new L(`Unknown function: ${e.name}`);t.current=n(t.current,e.kwargs??{})},parallel:(e,t)=>{let n=[],a=[];for(let s of e.rules)s.kind==="sub"&&s.from?n.push(s):a.push(s);let r=s=>!s.before&&!s.after&&!s.notBefore&&!s.notAfter&&S(s.from,t)!==null;if(n.some(s=>!r(s))){let c=n.map((i,l)=>({rule:i,idx:l,len:g(i.from,t)+(i.before?g(i.before,t):0)+(i.after?g(i.after,t):0)+(i.notBefore?g(i.notBefore,t):0)+(i.notAfter?g(i.notAfter,t):0)+(i.priority??0)})).sort((i,l)=>l.len-i.len||i.idx-l.idx).map(({rule:i})=>{let l=p(i.from,t),u=i.before?p(i.before,t).re:"",f=i.after?p(i.after,t).re:"",b=i.notBefore?p(i.notBefore,t).re:"",y=i.notAfter?p(i.notAfter,t).re:"",et=[u?`(?<=${u})`:"",b?`(?$t(_,x,Be):_}});t.current=Nt(t.current,c)}else{let s=[];for(let c of n){if(c.to&&c.to.kind==="funcall_inline")continue;let i=c.to?$(c.to,t):"";if(i===null)continue;let l=S(c.from,t);if(l!==null)for(let u of l)s.push([u,i])}s.length>0&&(t.current=Ct(t.current,kt(s)))}for(let s of a)w(s,t)},sequential:(e,t)=>{for(let n of e.rules)w(n,t)}};function Ue(e,t){if(!e.from)throw new L("Sub rule missing 'from'");let n=p(e.from,t),a=e.before?p(e.before,t).re:"",r=e.after?p(e.after,t).re:"",o=e.notBefore?p(e.notBefore,t).re:"",s=e.notAfter?p(e.notAfter,t).re:"",c=[];a&&c.push(`(?<=${a})`),o&&c.push(`(?{let u=l;return(f,...b)=>$t(u,f,b)})():l)}function $t(e,t,n){return e.replace(/\$(\d+)/g,(a,r)=>{let o=parseInt(r,10);return n[o-1]??t})}function Dt(e,t){if(!e)return"";if(e.kind==="funcall_inline"){let a=It(t,e.name);if(!a)throw new L(`Unknown inline function: ${e.name}`);return r=>a(r)}return p(e,t).literal}function w(e,t){let n=Fe[e.kind];n(e,t)}var yt=new Set(["rababa","secryst"]);function ye(e){for(let t of e){if(t.kind==="funcall"&&yt.has(t.name))return!0;if(t.kind==="parallel"||t.kind==="sequential"){for(let n of t.rules)if(n.kind==="funcall"&&yt.has(n.name))return!0}}return!1}function B(e,t,n,a){let r=e.stages.find(s=>s.name===t);if(!r)return n;if(ye(r.rules))throw new Error(`Stage "${t}" contains ML function calls (rababa/secryst). Use transliterateAsync() instead of transliterate().`);let o=new N(e,n,a);for(let s of r.rules)w(s,o);return o.current}async function be(e,t,n,a){let r=e.stages.find(s=>s.name===t);if(!r)return n;if(!ye(r.rules))return B(e,t,n,a);let o=new N(e,n,a);for(let s of r.rules)await Sn(s,o);return o.current}async function Sn(e,t){if(e.kind==="funcall"){await vn(e,t);return}w(e,t)}async function vn(e,t){if(e.name==="rababa"){let{rababa:n}=await Promise.resolve().then(()=>(ee(),te));t.current=await n(t.current,e.kwargs);return}if(yt.has(e.name)){let{loadModel:n}=await Promise.resolve().then(()=>(Le(),ge)),a=e.kwargs?.config??e.kwargs?.model??"default",r=await n({kind:e.name,id:a});t.current=await r.transform(t.current);return}w({kind:"funcall",name:e.name,kwargs:e.kwargs},t)}function Mn(e,t){if(e===t)return 0;if(e.length===0)return t.length;if(t.length===0)return e.length;let n=new Array(t.length+1),a=new Array(t.length+1);for(let r=0;r<=t.length;r++)n[r]=r;for(let r=1;r<=e.length;r++){a[0]=r;for(let o=1;o<=t.length;o++){let s=e[r-1]===t[o-1]?0:1;a[o]=Math.min(n[o]+1,a[o-1]+1,n[o-1]+s)}[n,a]=[a,n]}return n[t.length]}function _n(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${t}$`)}function we(e,t,n,a={},r){let o=[],s=a.mapPattern?_n(a.mapPattern):null,c=r??n.loadedMaps();for(let i of c){if(s&&!s.test(i))continue;let l;try{let u=n.load(i);l=B(u,"main",e,n)}catch(u){if(u instanceof h)continue;throw u}o.push({mapName:i,distance:Mn(l,t)})}return o.sort((i,l)=>i.distance-l.distance)}function bt(e){let t={schemaVersion:e.schemaVersion,systemCode:e.systemCode,dependencies:e.dependencies,stages:e.stages,aliases:new Map(Object.entries(e.aliases)),functions:new Map};return e.metadata&&(t.metadata=e.metadata),t}var Tn="main",Y=class{loader;defaultStage;constructor(t={}){this.loader=new P(t.strategies??[]),this.defaultStage=t.defaultStage??Tn}loadMap(t){let n=this.loader.load(t);for(let a of n.dependencies)try{this.loader.load(a)}catch(r){throw r instanceof m?new C(a):r}return n}async loadMapAsync(t){let n=await this.loader.loadAsync(t),a=new Set,r=[...n.dependencies];for(;r.length>0;){let o=r.shift();if(!a.has(o)){a.add(o);try{let s=await this.loader.loadAsync(o);for(let c of s.dependencies)a.has(c)||r.push(c)}catch(s){throw s instanceof m?new C(o):s}}}return n}transliterate(t,n,a){try{let r=this.loadMap(t),o=a??this.defaultStage;return B(r,o,n,this.loader)}catch(r){throw r instanceof h?r:new k(`Transliteration failed for ${t}: ${r.message}`,{cause:r})}}async transliterateAsync(t,n,a){try{let r=await this.loadMapAsync(t),o=a??this.defaultStage;return await be(r,o,n,this.loader)}catch(r){throw r instanceof h?r:new k(`Transliteration failed for ${t}: ${r.message}`,{cause:r})}}loadedMaps(){return this.loader.loadedMaps()}getLoader(){return this.loader}detect(t,n,a={},r){return we(t,n,this.loader,a,r)}},V;function Ae(e){V=new Y(e)}function En(){return V||(V=new Y),V}function wt(e,t,n){return En().transliterateAsync(e,t,n)}var xe=["acadsin-zho-Hani-Latn-2002","alalc-amh-Ethi-Latn-1997","alalc-amh-Ethi-Latn-2011","alalc-ara-Arab-Latn-1997","alalc-asm-Deva-Latn-1997","alalc-asm-Deva-Latn-2012","alalc-aze-Arab-Latn-1997","alalc-aze-Cyrl-Latn-1997","alalc-bel-Cyrl-Latn-1997","alalc-ben-Beng-Latn-1997","alalc-ben-Beng-Latn-2017","alalc-bul-Cyrl-Latn-1997","alalc-div-Thaa-Latn-1997","alalc-div-Thaa-Latn-2012","alalc-ell-Grek-Latn-1997","alalc-ell-Grek-Latn-2010","alalc-fas-Arab-Latn-1997","alalc-guj-Gujr-Latn-1997","alalc-guj-Gujr-Latn-2011","alalc-hin-Deva-Latn-1997","alalc-hin-Deva-Latn-2011","alalc-kan-Kana-Latn-1997","alalc-kan-Kana-Latn-2011","alalc-kat-Geok-Latn-1997","alalc-kat-Geok-Latn-2011","alalc-kat-Geor-Latn-1997","alalc-kat-Geor-Latn-2011","alalc-kor-Hang-Latn-1997","alalc-mal-Mlym-Latn-1997","alalc-mal-Mlym-Latn-2012","alalc-mar-Deva-Latn-1997","alalc-mar-Deva-Latn-2011","alalc-mkd-Cyrl-Latn-1997","alalc-mkd-Cyrl-Latn-2013","alalc-mon-Cyrl-Latn-1997","alalc-ori-Orya-Latn-1997","alalc-ori-Orya-Latn-2011","alalc-pan-Guru-Latn-1997","alalc-pan-Guru-Latn-2011","alalc-pli-Deva-Latn-2012","alalc-pra-Deva-Latn-2012","alalc-rus-Cyrl-Latn-1997","alalc-rus-Cyrl-Latn-2012","alalc-san-Deva-Latn-2012","alalc-sin-Sinh-Latn-1997","alalc-sin-Sinh-Latn-2011","alalc-srp-Cyrl-Latn-1997","alalc-srp-Cyrl-Latn-2013","alalc-tam-Taml-Latn-1997","alalc-tam-Taml-Latn-2011","alalc-tel-Telu-Latn-1997","alalc-tel-Telu-Latn-2011","alalc-tir-Ethi-Latn-1997","alalc-tir-Ethi-Latn-2011","alalc-ukr-Cyrl-Latn-1997","alalc-ukr-Cyrl-Latn-2011","apcbg-bul-Cyrl-Latn-1995","az-aze-Cyrl-Latn-1939","az-aze-Cyrl-Latn-1958","bas-rus-Cyrl-Latn-2017-bss","bas-rus-Cyrl-Latn-2017-oss","bgn-jpn-Hrkt-Latn-1962","bgn-kor-Hang-Latn-1943","bgn-kor-Kore-Latn-1943","bgna-bul-Cyrl-Latn-2006","bgna-bul-Cyrl-Latn-2009","bgnpcgn-amh-Ethi-Latn-1967","bgnpcgn-ara-Arab-Latn-1956","bgnpcgn-arm-Armn-Latn-1981","bgnpcgn-aze-Cyrl-Latn-1993","bgnpcgn-bak-Cyrl-Latn-2007","bgnpcgn-bal-Arab-Latn-2008","bgnpcgn-bel-Cyrl-Latn-1979","bgnpcgn-bul-Cyrl-Latn-1952","bgnpcgn-bul-Cyrl-Latn-2013","bgnpcgn-che-Cyrl-Latn-2008","bgnpcgn-deu-Latn-Latn-2000","bgnpcgn-div-Thaa-Latn-1972","bgnpcgn-div-Thaa-Latn-1988","bgnpcgn-ell-Grek-Latn-1962","bgnpcgn-ell-Grek-Latn-1996","bgnpcgn-fao-Latn-Latn-1964","bgnpcgn-fao-Latn-Latn-1968","bgnpcgn-fas-Arab-Latn-1956","bgnpcgn-isl-Latn-Latn-1964","bgnpcgn-isl-Latn-Latn-1968","bgnpcgn-jpn-Hrkt-Latn-1976","bgnpcgn-kat-Geor-Latn-1981","bgnpcgn-kat-Geor-Latn-2009","bgnpcgn-kaz-Cyrl-Latn-1979","bgnpcgn-kir-Cyrl-Latn-1979","bgnpcgn-kor-Hang-Latn-kn-1945","bgnpcgn-kor-Hang-Latn-rok-2011","bgnpcgn-kor-Kore-Latn-rok-2011","bgnpcgn-kur-Arab-Latn-2007","bgnpcgn-mkd-Cyrl-Latn-1981","bgnpcgn-mkd-Cyrl-Latn-2013","bgnpcgn-mon-Cyrl-Latn-1964","bgnpcgn-nep-Deva-Latn-2011","bgnpcgn-per-Arab-Latn-1958","bgnpcgn-prs-Arab-Latn-2007","bgnpcgn-prs-Arab-Latn-yaghoubi","bgnpcgn-pus-Arab-Latn-1968","bgnpcgn-ron-Cyrl-Latn-2002","bgnpcgn-rue-Cyrl-Latn-2016","bgnpcgn-rus-Cyrl-Latn-1947","bgnpcgn-sme-Latn-Latn-1984","bgnpcgn-srp-Cyrl-Latn-1962","bgnpcgn-srp-Cyrl-Latn-2005","bgnpcgn-tat-Cyrl-Latn-2007","bgnpcgn-tgk-Cyrl-Latn-1994","bgnpcgn-tir-Ethi-Latn-2007","bgnpcgn-ukr-Cyrl-Latn-1965","bgnpcgn-ukr-Cyrl-Latn-2019","bgnpcgn-urd-Arab-Latn-2007","bgnpcgn-uzb-Cyrl-Latn-1979","bgnpcgn-uzb-Cyrl-Latn-2000","bgnpcgn-zho-Hans-Latn-1979","bis-asm-Beng-Latn-13194-1991","bis-ben-Beng-Latn-13194-1991","bis-dev-Deva-Latn-13194-1991","bis-guj-Gujr-Latn-13194-1991","bis-kan-Kana-Latn-13194-1991","bis-mlm-Mlym-Latn-13194-1991","bis-ori-Orya-Latn-13194-1991","bis-pnj-Guru-Latn-13194-1991","bis-tel-Telu-Latn-13194-1991","bis-tml-Taml-Latn-13194-1991","by-bel-Cyrl-Latn-1998","by-bel-Cyrl-Latn-2007","din-grc-Grek-Latn-31634-2011-t1","din-hin-Deva-Latn-33904-2018","din-kat-Geor-Latn-32707-2010","din-mar-Deva-Latn-33904-2018","din-nep-Deva-Latn-33904-2018","din-pli-Deva-Latn-33904-2018","din-pra-Deva-Latn-33904-2018","din-san-Deva-Latn-33904-2018","din-tam-Taml-Latn-33903-2016","dos-nep-Deva-Latn-1997","elot-ell-Grek-Latn-743-1982-tl","elot-ell-Grek-Latn-743-1982-ts","elot-ell-Grek-Latn-743-2001-tl","elot-ell-Grek-Latn-743-2001-ts","ggg-kat-Geor-Latn-2002","gki-bel-Cyrl-Latn-1992","gki-bel-Cyrl-Latn-2000","gost-rus-Cyrl-Latn-16876-71-1983","gost-rus-Cyrl-Latn-7.79-2000-2002","hk-yue-Hani-Latn-1888","icao-bel-Cyrl-Latn-9303","icao-bul-Cyrl-Latn-9303","icao-fas-Arab-Latn-9303","icao-heb-Hebr-Latn-9303","icao-mkd-Cyrl-Latn-9303","icao-rus-Cyrl-Latn-9303","icao-srp-Cyrl-Latn-9303","icao-ukr-Cyrl-Latn-9303","iso-ara-Arab-Latn-233-1984","iso-ara-Arab-Latn-233-2-1993","iso-asm-Beng-Latn-15919-2001","iso-ben-Beng-Latn-15919-2001","iso-ell-Grek-Latn-843-1997-t1","iso-ell-Grek-Latn-843-1997-t2","iso-guj-Gujr-Latn-15919-2001","iso-hin-Deva-Latn-15919-2001","iso-inc-Deva-Latn-15919-2001","iso-jpn-Hrkt-Latn-3602-1989","iso-kan-Kana-Latn-15919-2001","iso-kat-Geor-Latn-9984-1996","iso-kor-Hang-Latn-1996-method1","iso-kor-Hang-Latn-1996-method2","iso-mal-Mlym-Latn-15919-2001","iso-mar-Deva-Latn-15919-2001","iso-nep-Deva-Latn-15919-2001","iso-ori-Orya-Latn-15919-2001","iso-pan-Guru-Latn-15919-2001","iso-pli-Beng-Latn-15919-2001","iso-pli-Deva-Latn-15919-2001","iso-pli-Sinh-Latn-15919-2001","iso-pli-Thai-Latn-15919-2001","iso-pra-Deva-Latn-15919-2001","iso-prs-Arab-Latn-233-3-1999","iso-rus-Cyrl-Latn-9-1995","iso-san-Deva-Latn-15919-2001","iso-tam-Taml-Latn-15919-2001","iso-tel-Telu-Latn-15919-2001","iso-tha-Thai-Latn-11940-1998","kp-kor-Hang-Latn-2002","lshk-yue-Hani-Latn-jyutping-1993","masm-mon-Cyrl-Latn-5217-2012","masm-mon-Latn-Cyrl-5217-2012","mext-jpn-Hrkt-Latn-1954","moct-kor-Hang-Latn-2000","mofa-jpn-Hrkt-Latn-1989","mv-div-Thaa-Latn-1987","mvd-bel-Cyrl-Latn-2008","mvd-bel-Cyrl-Latn-2010","mvd-rus-Cyrl-Latn-2008","mvd-rus-Cyrl-Latn-2010","odni-ara-Arab-Latn-2004","odni-ara-Arab-Latn-2015","odni-aze-Cyrl-Latn-2015","odni-bel-Cyrl-Latn-2015","odni-bul-Cyrl-Latn-2005","odni-bul-Cyrl-Latn-2015","odni-che-Cyrl-Latn-2015","odni-fas-Arab-Latn-2004","odni-fas-Arab-Latn-2015","odni-hin-Deva-Latn-2004","odni-hin-Deva-Latn-2015","odni-kat-Geor-Latn-2015","odni-kaz-Cyrl-Latn-2015","odni-kir-Cyrl-Latn-2015","odni-kor-Hang-Latn-2015","odni-mkd-Cyrl-Latn-2005","odni-mkd-Cyrl-Latn-2015","odni-prs-Arab-Latn-2004","odni-prs-Arab-Latn-2015","odni-pus-Arab-Latn-2011","odni-rus-Cyrl-Latn-2015","odni-srp-Cyrl-Latn-2005","odni-srp-Cyrl-Latn-2015","odni-tat-Cyrl-Latn-2015","odni-tgk-Cyrl-Latn-2015","odni-tuk-Cyrl-Latn-2015","odni-uig-Cyrl-Latn-2015","odni-ukr-Cyrl-Latn-2015","odni-urd-Arab-Latn-2015","odni-uzb-Cyrl-Latn-2015","posix","sac-zho-Hans-Latn-1979","sasm-mon-Mong-Latn-general-1978","sasm-mon-Mong-Latn-phonetic-1978","ses-ara-Arab-Latn-1930","stategeocadastre-ukr-Cyrl-Latn-1993","ua-ukr-Cyrl-Latn-1996","ua-ukr-Cyrl-Latn-2007","ua-ukr-Cyrl-Latn-2010","un-amh-Ethi-Latn-2016","un-ara-Arab-Latn-1971","un-ara-Arab-Latn-1972","un-ara-Arab-Latn-2017","un-asm-Beng-Latn-1972","un-bel-Cyrl-Latn-2007","un-ben-Beng-Latn-2016","un-bul-Cyrl-Latn-1977","un-ell-Grek-Latn-1987-phonetic","un-ell-Grek-Latn-1987-tl","un-ell-Grek-Latn-1987-ts","un-guj-Gujr-Latn-1972","un-hin-Deva-Latn-2016","un-kan-Kana-Latn-2016","un-mal-Mlym-Latn-1972","un-mar-Deva-Latn-2016","un-mkd-Cyrl-Latn-1977","un-mon-Mong-Latn-general-2013","un-mon-Mong-Latn-phonetic-2013","un-nep-Deva-Latn-1972","un-nep-Deva-Latn-2013","un-ori-Orya-Latn-1972","un-pan-Guru-Latn-1972","un-prs-Arab-Latn-1967","un-rus-Cyrl-Latn-1987","un-sin-Sinh-Latn-1972","un-srp-Cyrl-Latn-1997","un-tam-Taml-Latn-1972","un-tel-Telu-Latn-1972","un-ukr-Cyrl-Latn-1998","un-ukr-Cyrl-Latn-2012","un-urd-Arab-Latn-1972","unicode","var-Cyrl","var-amh-Ethi-Latn-eae-2003","var-ara-Arab-Arab-rababa","var-gez-Ethi-Latn-eae-2003","var-hin-Deva-Latn-hunterian-1872","var-jpn-Hrkt-Latn-hepburn-1886","var-jpn-Hrkt-Latn-hepburn-1954","var-kor","var-kor-Hang-Hang-jamo","var-kor-Hang-Latn-mr-1939","var-kor-Kore-Hang-2013","var-kor-Kore-Latn-mr-1939","var-mar-Deva-Latn-hunterian-1872","var-mon-Mong-Latn-1930","var-mon-Mong-Latn-lessing","var-mon-Mong-Latn-vpmc","var-pra-Deva-Latn-iast-1912","var-san-Deva-Latn-iast-1912","var-zho-Hani-Latn-wd-1979"];var Nn=new Set(["unicode","posix","var-Cyrl","var-kor"]),In=new Set(["var-ara-Arab-Arab-rababa"]),Ce=xe.filter(e=>!Nn.has(e)),$n=Ce.filter(e=>!In.has(e)),ke=new Map;function At(){return Ce}function Se(){return $n}async function ve(e,t){let n=ke.get(t);if(n)return n;let a=await e.fetch(new Request(`https://assets.internal/${t}.json`));if(!a.ok)throw new m(t);let r=await a.json(),o=bt(r);return ke.set(t,o),o}var Me;function _e(e){if(Me===e)return;Ae({strategies:[n=>ve(e,n)]}),Me=e}async function Te(e,t,n){return _e(e),wt(t,n)}function Dn(e,t){if(e===t)return 0;if(!e.length)return t.length;if(!t.length)return e.length;let n=Array.from({length:t.length+1},(a,r)=>r);for(let a=1;a<=e.length;a++){let r=[a];for(let o=1;o<=t.length;o++)r[o]=Math.min(n[o]+1,r[o-1]+1,n[o-1]+(e[a-1]===t[o-1]?0:1));n=r}return n[t.length]}async function Ee(e,t,n){_e(e);let a=[];for(let r of Se())try{let o=await wt(r,t);a.push({mapName:r,distance:Dn(o,n)})}catch{continue}return a.sort((r,o)=>r.distance-o.distance)}var J=class extends Error{constructor(n){super(`Input is too long (max ${n} characters)`);this.max=n;this.name="InputTooLongError"}max},Z=class extends Error{constructor(n){super(`Couldn't locate ${n}`);this.systemCode=n;this.name="MapNotFoundError"}systemCode};var X={input_max_size:1e6};var Ne="3.0.0-cloudflare.1",xt;function Ie(e){xt=e}function $e(){if(!xt)throw new Error("ASSETS binding missing \u2014 see README deployment notes");return xt}async function De(){return JSON.stringify({version:Ne,engine:"interscript-ts@native-interpreter"})}function Re(){return[...At()]}async function Oe(e,t){if(t.length>X.input_max_size)throw new J(X.input_max_size);try{return await Te($e(),e,t)}catch(n){throw n instanceof m?new Z(e):n}}async function je(e,t){return Ee($e(),e,t)}var Pn=Rn({schema:On({typeDefs:nt,resolvers:{Query:{info:()=>De(),systemCodes:()=>Re(),transliterate:(e,t)=>Oe(t.systemCode,t.input),detect:(e,t)=>je(t.input,t.output)}}}),graphqlEndpoint:"*",landingPage:!1,maskedErrors:!1}),tt=new jn;tt.use("*",Bn({origin:"*",allowMethods:["GET","POST","OPTIONS"]}));tt.all("*",e=>{let t=e.env;t?.ASSETS&&Ie(t.ASSETS);let n;try{n=e.executionCtx}catch{n=void 0}return Pn.fetch(e.req.raw,e.env,n)});var zn=tt;export{Ne as API_VERSION,X as LIMITS,tt as app,zn as default,nt as typeDefs}; +`;import{Hono as Gn}from"hono";var g=class extends Error{cause;constructor(t,n){super(t,n),this.name=new.target.name,this.cause=n?.cause}},h=class extends g{systemCode;constructor(t){super(`Map not found: ${t}`),this.systemCode=t}},_=class extends g{},L=class extends g{},C=class extends g{dependency;constructor(t){super(`Map dependency missing: ${t}`),this.dependency=t}};var W=class{strategies;cache=new Map;known=new Map;options;constructor(t,n={}){this.strategies=t,this.options=n}load(t){let n=this.cache.get(t);if(n)return n;let r=this.known.get(t);if(r)return this.cache.set(t,r),r;for(let a of this.strategies){let s=a(t);if(s&&typeof s.then!="function"){let o=s;return this.cache.set(t,o),this.known.set(t,o),this.options.onLoaded?.(t,o),o}}throw new h(t)}async loadAsync(t){let n=this.cache.get(t);if(n)return n;let r=this.known.get(t);if(r)return this.cache.set(t,r),r;for(let a of this.strategies){let s=await a(t);if(s)return this.cache.set(t,s),this.known.set(t,s),this.options.onLoaded?.(t,s),s}throw new h(t)}clear(){this.cache.clear()}loadedMaps(){return Array.from(this.known.keys())}register(t,n){this.known.set(t,n),this.cache.set(t,n)}};var z=class e{current;map;loader;aliasCache=new Map;functions;constructor(t,n,r){this.map=t,this.current=n,this.functions=t.functions,this.loader=r}resolveAlias(t){if(this.aliasCache.has(t))return this.aliasCache.get(t);let n=this.map.aliases.get(t);if(!n&&this.loader)for(let r of this.map.dependencies)try{let s=this.loader.load(r).aliases.get(t);if(s){n=s;break}}catch{}return n&&this.aliasCache.set(t,n),n}withMap(t){return new e(t,this.current,this.loader)}loadDependency(t){if(!this.loader)throw new Error(`Cannot resolve dependency ${t}: no loader configured`);return this.loader.load(t)}};function ue(){return{children:new Map,match:null}}function Ie(e){let t=ue();for(let[n,r]of e){if(n.length===0)continue;let a=t;for(let c=0;c0?(n+=c,a+=o):(n+=e[a],a+=1)}return n}function D(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function $e(e){return e.toLowerCase()}function je(e){return e.toUpperCase()}function Oe(e,t={}){let n=t.wordSeparator??" ";return n===""?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():e.split(n).map(r=>r.length===0?r:r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join(n)}function ze(e,t={}){let n=t.separator??" ";return e.split("").join(n)}function De(e){return e.normalize("NFC")}function Be(e){return e.normalize("NFD")}function Pe(e,t){if(t.length===0)return e;let n=t.map((a,s)=>`(?<__r${s}>${a.pattern})`),r=new RegExp(n.join("|"),"gmu");return e.replace(r,(...a)=>{let s=a[a.length-1];for(let o=0;ou(a,t).re),r=e.of.length>0?u(e.of[0],t).literal:"";return{re:`(?:${n.join("|")})`,literal:r}}case"any_char_class":{if(e.range){let[n,r]=e.range;return{re:`[${D(n)}-${D(r)}]`,literal:n??""}}return e.chars&&e.chars.length>0?{re:`[${e.chars.map(r=>D(r)).join("")}]`,literal:e.chars[0]}:{re:"[^]",literal:""}}case"group":{let n=e.items.map(s=>u(s,t)),r=n.map(s=>s.re).join(""),a=n.map(s=>s.literal).join("");return{re:`(?:${r})`,literal:a}}case"repeat":{let n=u(e.item,t).re,{min:r,max:a}=e,s=a===null?1/0:a,o=s===1/0?r===0?"*":"+":`{${r},${s}}`;return{re:`(?:${n})${o}`,literal:""}}case"stage_ref":return{re:"",literal:""}}}function B(e,t){switch(e.kind){case"string":return e.value;case"alias":{let n=V[e.name];if(n)return n.literal===""?null:n.literal;let r=t.resolveAlias(e.name);return r?B(r,t):null}case"capture_group":case"capture_ref":case"repeat":case"stage_ref":return null;case"any_char_class":return e.chars&&e.chars.length>0?e.chars[0]:e.range?e.range[0]:null;case"any":return e.of.length===0?null:B(e.of[0],t);case"group":{let n="";for(let r of e.items){let a=B(r,t);if(a===null)return null;n+=a}return n}}}function M(e,t){switch(e.kind){case"string":return[e.value];case"alias":{if(V[e.name])return null;let r=t.resolveAlias(e.name);return r?M(r,t):null}case"any_char_class":{if(e.chars)return[...e.chars];if(e.range){let[n,r]=e.range,a=n.codePointAt(0),s=r.codePointAt(0),o=[];for(let c=a;c<=s;c++)o.push(String.fromCodePoint(c));return o}return null}case"any":{let n=[];for(let r of e.of){let a=M(r,t);if(a===null)return null;n.push(...a)}return n}case"group":{let n=[""];for(let r of e.items){let a=M(r,t);if(a===null)return null;let s=[];for(let o of n)for(let c of a)s.push(o+c);n=s}return n}default:return null}}function y(e,t){switch(e.kind){case"string":return e.value.length;case"capture_group":return y(e.data,t);case"capture_ref":return 1;case"alias":{if(e.name==="none")return 0;if(V[e.name])return 1;let n=t.resolveAlias(e.name);return n?y(n,t):1}case"any":return e.of.reduce((n,r)=>Math.max(n,y(r,t)),0);case"any_char_class":return 1;case"group":return e.items.reduce((n,r)=>n+y(r,t),0);case"repeat":return y(e.item,t);case"stage_ref":return 1}}var V=Object.freeze({any_character:{re:".",literal:""},none:{re:"",literal:""},space:{re:" ",literal:" "},whitespace:{re:"\\s+",literal:" "},boundary:{re:"(?:(?Oe(e,t??{}),separate:(e,t)=>ze(e,t??{}),compose:De,decompose:Be};function Ue(e,t){let n=e.functions.get(t)?.impl;if(n)return n;let r=Vt[t];if(r)return r}var Qt={sub:(e,t)=>Yt(e,t),run:(e,t)=>{let n=e.docName?t.loadDependency(e.docName):t.map,r=n.stages.find(s=>s.name===e.stage);if(!r)throw new L(`Stage not found: ${e.stage}`);let a=t.withMap(n);for(let s of r.rules)A(s,a);t.current=a.current},funcall:(e,t)=>{let n=Ue(t,e.name);if(!n)throw new L(`Unknown function: ${e.name}`);t.current=n(t.current,e.kwargs??{})},parallel:(e,t)=>{let n=[],r=[];for(let o of e.rules)o.kind==="sub"&&o.from?n.push(o):r.push(o);let a=o=>!o.before&&!o.after&&!o.notBefore&&!o.notAfter&&M(o.from,t)!==null;if(n.some(o=>!a(o))){let c=n.map((i,l)=>({rule:i,idx:l,len:y(i.from,t)+(i.before?y(i.before,t):0)+(i.after?y(i.after,t):0)+(i.notBefore?y(i.notBefore,t):0)+(i.notAfter?y(i.notAfter,t):0)+(i.priority??0)})).sort((i,l)=>l.len-i.len||i.idx-l.idx).map(({rule:i})=>{let l=u(i.from,t),p=i.before?u(i.before,t).re:"",d=i.after?u(i.after,t).re:"",v=i.notBefore?u(i.notBefore,t).re:"",w=i.notAfter?u(i.notAfter,t).re:"",le=[p?`(?<=${p})`:"",v?`(?Fe($,x,Kt):$}});t.current=Pe(t.current,c)}else{let o=[];for(let c of n){if(c.to&&c.to.kind==="funcall_inline")continue;let i=c.to?B(c.to,t):"";if(i===null)continue;let l=M(c.from,t);if(l!==null)for(let p of l)o.push([p,i])}o.length>0&&(t.current=Re(t.current,Ie(o)))}for(let o of r)A(o,t)},sequential:(e,t)=>{for(let n of e.rules)A(n,t)}};function Yt(e,t){if(!e.from)throw new L("Sub rule missing 'from'");let n=u(e.from,t),r=e.before?u(e.before,t).re:"",a=e.after?u(e.after,t).re:"",s=e.notBefore?u(e.notBefore,t).re:"",o=e.notAfter?u(e.notAfter,t).re:"",c=[];r&&c.push(`(?<=${r})`),s&&c.push(`(?{let p=l;return(d,...v)=>Fe(p,d,v)})():l)}function Fe(e,t,n){return e.replace(/\$(\d+)/g,(r,a)=>{let s=parseInt(a,10);return n[s-1]??t})}function qe(e,t){if(!e)return"";if(e.kind==="funcall_inline"){let r=Ue(t,e.name);if(!r)throw new L(`Unknown inline function: ${e.name}`);return a=>r(a)}return u(e,t).literal}function A(e,t){let n=Qt[e.kind];n(e,t)}var _e=new Set(["rababa","secryst"]);function Ct(e){for(let t of e){if(t.kind==="funcall"&&_e.has(t.name))return!0;if(t.kind==="parallel"||t.kind==="sequential"){for(let n of t.rules)if(n.kind==="funcall"&&_e.has(n.name))return!0}}return!1}function G(e,t,n,r){let a=e.stages.find(o=>o.name===t);if(!a)return n;if(Ct(a.rules))throw new Error(`Stage "${t}" contains ML function calls (rababa/secryst). Use transliterateAsync() instead of transliterate().`);let s=new z(e,n,r);for(let o of a.rules)A(o,s);return s.current}async function Mt(e,t,n,r){let a=e.stages.find(o=>o.name===t);if(!a)return n;if(!Ct(a.rules))return G(e,t,n,r);let s=new z(e,n,r);for(let o of a.rules)await Rn(o,s);return s.current}async function Rn(e,t){if(e.kind==="funcall"){await $n(e,t);return}A(e,t)}async function $n(e,t){if(e.name==="rababa"){let{rababa:n}=await Promise.resolve().then(()=>(pt(),lt));t.current=await n(t.current,e.kwargs);return}if(_e.has(e.name)){let{loadModel:n}=await Promise.resolve().then(()=>(_t(),xt)),r=e.kwargs?.config??e.kwargs?.model??"default",a=await n({kind:e.name,id:r});t.current=await a.transform(t.current);return}A({kind:"funcall",name:e.name,kwargs:e.kwargs},t)}function jn(e,t){if(e===t)return 0;if(e.length===0)return t.length;if(t.length===0)return e.length;let n=new Array(t.length+1),r=new Array(t.length+1);for(let a=0;a<=t.length;a++)n[a]=a;for(let a=1;a<=e.length;a++){r[0]=a;for(let s=1;s<=t.length;s++){let o=e[a-1]===t[s-1]?0:1;r[s]=Math.min(n[s]+1,r[s-1]+1,n[s-1]+o)}[n,r]=[r,n]}return n[t.length]}function On(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${t}$`)}function Tt(e,t,n,r={},a){let s=[],o=r.mapPattern?On(r.mapPattern):null,c=a??n.loadedMaps();for(let i of c){if(o&&!o.test(i))continue;let l;try{let p=n.load(i);l=G(p,"main",e,n)}catch(p){if(p instanceof g)continue;throw p}s.push({mapName:i,distance:jn(l,t)})}return s.sort((i,l)=>i.distance-l.distance)}function Ce(e){let t={schemaVersion:e.schemaVersion,systemCode:e.systemCode,dependencies:e.dependencies,stages:e.stages,aliases:new Map(Object.entries(e.aliases)),functions:new Map};return e.metadata&&(t.metadata=e.metadata),t}var zn="main",ae=class{loader;defaultStage;constructor(t={}){this.loader=new W(t.strategies??[]),this.defaultStage=t.defaultStage??zn}loadMap(t){let n=this.loader.load(t);for(let r of n.dependencies)try{this.loader.load(r)}catch(a){throw a instanceof h?new C(r):a}return n}async loadMapAsync(t){let n=await this.loader.loadAsync(t),r=new Set,a=[...n.dependencies];for(;a.length>0;){let s=a.shift();if(!r.has(s)){r.add(s);try{let o=await this.loader.loadAsync(s);for(let c of o.dependencies)r.has(c)||a.push(c)}catch(o){throw o instanceof h?new C(s):o}}}return n}transliterate(t,n,r){try{let a=this.loadMap(t),s=r??this.defaultStage;return G(a,s,n,this.loader)}catch(a){throw a instanceof g?a:new _(`Transliteration failed for ${t}: ${a.message}`,{cause:a})}}async transliterateAsync(t,n,r){try{let a=await this.loadMapAsync(t),s=r??this.defaultStage;return await Mt(a,s,n,this.loader)}catch(a){throw a instanceof g?a:new _(`Transliteration failed for ${t}: ${a.message}`,{cause:a})}}loadedMaps(){return this.loader.loadedMaps()}getLoader(){return this.loader}detect(t,n,r={},a){return Tt(t,n,this.loader,r,a)}},re;function Et(e){re=new ae(e)}function Dn(){return re||(re=new ae),re}function Me(e,t,n){return Dn().transliterateAsync(e,t,n)}var Nt=["acadsin-zho-Hani-Latn-2002","alalc-amh-Ethi-Latn-1997","alalc-amh-Ethi-Latn-2011","alalc-ara-Arab-Latn-1997","alalc-asm-Deva-Latn-1997","alalc-asm-Deva-Latn-2012","alalc-aze-Arab-Latn-1997","alalc-aze-Cyrl-Latn-1997","alalc-bel-Cyrl-Latn-1997","alalc-ben-Beng-Latn-1997","alalc-ben-Beng-Latn-2017","alalc-bul-Cyrl-Latn-1997","alalc-div-Thaa-Latn-1997","alalc-div-Thaa-Latn-2012","alalc-ell-Grek-Latn-1997","alalc-ell-Grek-Latn-2010","alalc-fas-Arab-Latn-1997","alalc-guj-Gujr-Latn-1997","alalc-guj-Gujr-Latn-2011","alalc-hin-Deva-Latn-1997","alalc-hin-Deva-Latn-2011","alalc-kan-Kana-Latn-1997","alalc-kan-Kana-Latn-2011","alalc-kat-Geok-Latn-1997","alalc-kat-Geok-Latn-2011","alalc-kat-Geor-Latn-1997","alalc-kat-Geor-Latn-2011","alalc-kor-Hang-Latn-1997","alalc-mal-Mlym-Latn-1997","alalc-mal-Mlym-Latn-2012","alalc-mar-Deva-Latn-1997","alalc-mar-Deva-Latn-2011","alalc-mkd-Cyrl-Latn-1997","alalc-mkd-Cyrl-Latn-2013","alalc-mon-Cyrl-Latn-1997","alalc-ori-Orya-Latn-1997","alalc-ori-Orya-Latn-2011","alalc-pan-Guru-Latn-1997","alalc-pan-Guru-Latn-2011","alalc-pli-Deva-Latn-2012","alalc-pra-Deva-Latn-2012","alalc-rus-Cyrl-Latn-1997","alalc-rus-Cyrl-Latn-2012","alalc-san-Deva-Latn-2012","alalc-sin-Sinh-Latn-1997","alalc-sin-Sinh-Latn-2011","alalc-srp-Cyrl-Latn-1997","alalc-srp-Cyrl-Latn-2013","alalc-tam-Taml-Latn-1997","alalc-tam-Taml-Latn-2011","alalc-tel-Telu-Latn-1997","alalc-tel-Telu-Latn-2011","alalc-tir-Ethi-Latn-1997","alalc-tir-Ethi-Latn-2011","alalc-ukr-Cyrl-Latn-1997","alalc-ukr-Cyrl-Latn-2011","apcbg-bul-Cyrl-Latn-1995","az-aze-Cyrl-Latn-1939","az-aze-Cyrl-Latn-1958","bas-rus-Cyrl-Latn-2017-bss","bas-rus-Cyrl-Latn-2017-oss","bgn-jpn-Hrkt-Latn-1962","bgn-kor-Hang-Latn-1943","bgn-kor-Kore-Latn-1943","bgna-bul-Cyrl-Latn-2006","bgna-bul-Cyrl-Latn-2009","bgnpcgn-amh-Ethi-Latn-1967","bgnpcgn-ara-Arab-Latn-1956","bgnpcgn-arm-Armn-Latn-1981","bgnpcgn-aze-Cyrl-Latn-1993","bgnpcgn-bak-Cyrl-Latn-2007","bgnpcgn-bal-Arab-Latn-2008","bgnpcgn-bel-Cyrl-Latn-1979","bgnpcgn-bul-Cyrl-Latn-1952","bgnpcgn-bul-Cyrl-Latn-2013","bgnpcgn-che-Cyrl-Latn-2008","bgnpcgn-deu-Latn-Latn-2000","bgnpcgn-div-Thaa-Latn-1972","bgnpcgn-div-Thaa-Latn-1988","bgnpcgn-ell-Grek-Latn-1962","bgnpcgn-ell-Grek-Latn-1996","bgnpcgn-fao-Latn-Latn-1964","bgnpcgn-fao-Latn-Latn-1968","bgnpcgn-fas-Arab-Latn-1956","bgnpcgn-isl-Latn-Latn-1964","bgnpcgn-isl-Latn-Latn-1968","bgnpcgn-jpn-Hrkt-Latn-1976","bgnpcgn-kat-Geor-Latn-1981","bgnpcgn-kat-Geor-Latn-2009","bgnpcgn-kaz-Cyrl-Latn-1979","bgnpcgn-kir-Cyrl-Latn-1979","bgnpcgn-kor-Hang-Latn-kn-1945","bgnpcgn-kor-Hang-Latn-rok-2011","bgnpcgn-kor-Kore-Latn-rok-2011","bgnpcgn-kur-Arab-Latn-2007","bgnpcgn-mkd-Cyrl-Latn-1981","bgnpcgn-mkd-Cyrl-Latn-2013","bgnpcgn-mon-Cyrl-Latn-1964","bgnpcgn-nep-Deva-Latn-2011","bgnpcgn-per-Arab-Latn-1958","bgnpcgn-prs-Arab-Latn-2007","bgnpcgn-prs-Arab-Latn-yaghoubi","bgnpcgn-pus-Arab-Latn-1968","bgnpcgn-ron-Cyrl-Latn-2002","bgnpcgn-rue-Cyrl-Latn-2016","bgnpcgn-rus-Cyrl-Latn-1947","bgnpcgn-sme-Latn-Latn-1984","bgnpcgn-srp-Cyrl-Latn-1962","bgnpcgn-srp-Cyrl-Latn-2005","bgnpcgn-tat-Cyrl-Latn-2007","bgnpcgn-tgk-Cyrl-Latn-1994","bgnpcgn-tir-Ethi-Latn-2007","bgnpcgn-ukr-Cyrl-Latn-1965","bgnpcgn-ukr-Cyrl-Latn-2019","bgnpcgn-urd-Arab-Latn-2007","bgnpcgn-uzb-Cyrl-Latn-1979","bgnpcgn-uzb-Cyrl-Latn-2000","bgnpcgn-zho-Hans-Latn-1979","bis-asm-Beng-Latn-13194-1991","bis-ben-Beng-Latn-13194-1991","bis-dev-Deva-Latn-13194-1991","bis-guj-Gujr-Latn-13194-1991","bis-kan-Kana-Latn-13194-1991","bis-mlm-Mlym-Latn-13194-1991","bis-ori-Orya-Latn-13194-1991","bis-pnj-Guru-Latn-13194-1991","bis-tel-Telu-Latn-13194-1991","bis-tml-Taml-Latn-13194-1991","by-bel-Cyrl-Latn-1998","by-bel-Cyrl-Latn-2007","din-grc-Grek-Latn-31634-2011-t1","din-hin-Deva-Latn-33904-2018","din-kat-Geor-Latn-32707-2010","din-mar-Deva-Latn-33904-2018","din-nep-Deva-Latn-33904-2018","din-pli-Deva-Latn-33904-2018","din-pra-Deva-Latn-33904-2018","din-san-Deva-Latn-33904-2018","din-tam-Taml-Latn-33903-2016","dos-nep-Deva-Latn-1997","elot-ell-Grek-Latn-743-1982-tl","elot-ell-Grek-Latn-743-1982-ts","elot-ell-Grek-Latn-743-2001-tl","elot-ell-Grek-Latn-743-2001-ts","ggg-kat-Geor-Latn-2002","gki-bel-Cyrl-Latn-1992","gki-bel-Cyrl-Latn-2000","gost-rus-Cyrl-Latn-16876-71-1983","gost-rus-Cyrl-Latn-7.79-2000-2002","hk-yue-Hani-Latn-1888","icao-bel-Cyrl-Latn-9303","icao-bul-Cyrl-Latn-9303","icao-fas-Arab-Latn-9303","icao-heb-Hebr-Latn-9303","icao-mkd-Cyrl-Latn-9303","icao-rus-Cyrl-Latn-9303","icao-srp-Cyrl-Latn-9303","icao-ukr-Cyrl-Latn-9303","iso-ara-Arab-Latn-233-1984","iso-ara-Arab-Latn-233-2-1993","iso-asm-Beng-Latn-15919-2001","iso-ben-Beng-Latn-15919-2001","iso-ell-Grek-Latn-843-1997-t1","iso-ell-Grek-Latn-843-1997-t2","iso-guj-Gujr-Latn-15919-2001","iso-hin-Deva-Latn-15919-2001","iso-inc-Deva-Latn-15919-2001","iso-jpn-Hrkt-Latn-3602-1989","iso-kan-Kana-Latn-15919-2001","iso-kat-Geor-Latn-9984-1996","iso-kor-Hang-Latn-1996-method1","iso-kor-Hang-Latn-1996-method2","iso-mal-Mlym-Latn-15919-2001","iso-mar-Deva-Latn-15919-2001","iso-nep-Deva-Latn-15919-2001","iso-ori-Orya-Latn-15919-2001","iso-pan-Guru-Latn-15919-2001","iso-pli-Beng-Latn-15919-2001","iso-pli-Deva-Latn-15919-2001","iso-pli-Sinh-Latn-15919-2001","iso-pli-Thai-Latn-15919-2001","iso-pra-Deva-Latn-15919-2001","iso-prs-Arab-Latn-233-3-1999","iso-rus-Cyrl-Latn-9-1995","iso-san-Deva-Latn-15919-2001","iso-tam-Taml-Latn-15919-2001","iso-tel-Telu-Latn-15919-2001","iso-tha-Thai-Latn-11940-1998","kp-kor-Hang-Latn-2002","lshk-yue-Hani-Latn-jyutping-1993","masm-mon-Cyrl-Latn-5217-2012","masm-mon-Latn-Cyrl-5217-2012","mext-jpn-Hrkt-Latn-1954","moct-kor-Hang-Latn-2000","mofa-jpn-Hrkt-Latn-1989","mv-div-Thaa-Latn-1987","mvd-bel-Cyrl-Latn-2008","mvd-bel-Cyrl-Latn-2010","mvd-rus-Cyrl-Latn-2008","mvd-rus-Cyrl-Latn-2010","odni-ara-Arab-Latn-2004","odni-ara-Arab-Latn-2015","odni-aze-Cyrl-Latn-2015","odni-bel-Cyrl-Latn-2015","odni-bul-Cyrl-Latn-2005","odni-bul-Cyrl-Latn-2015","odni-che-Cyrl-Latn-2015","odni-fas-Arab-Latn-2004","odni-fas-Arab-Latn-2015","odni-hin-Deva-Latn-2004","odni-hin-Deva-Latn-2015","odni-kat-Geor-Latn-2015","odni-kaz-Cyrl-Latn-2015","odni-kir-Cyrl-Latn-2015","odni-kor-Hang-Latn-2015","odni-mkd-Cyrl-Latn-2005","odni-mkd-Cyrl-Latn-2015","odni-prs-Arab-Latn-2004","odni-prs-Arab-Latn-2015","odni-pus-Arab-Latn-2011","odni-rus-Cyrl-Latn-2015","odni-srp-Cyrl-Latn-2005","odni-srp-Cyrl-Latn-2015","odni-tat-Cyrl-Latn-2015","odni-tgk-Cyrl-Latn-2015","odni-tuk-Cyrl-Latn-2015","odni-uig-Cyrl-Latn-2015","odni-ukr-Cyrl-Latn-2015","odni-urd-Arab-Latn-2015","odni-uzb-Cyrl-Latn-2015","posix","sac-zho-Hans-Latn-1979","sasm-mon-Mong-Latn-general-1978","sasm-mon-Mong-Latn-phonetic-1978","ses-ara-Arab-Latn-1930","stategeocadastre-ukr-Cyrl-Latn-1993","ua-ukr-Cyrl-Latn-1996","ua-ukr-Cyrl-Latn-2007","ua-ukr-Cyrl-Latn-2010","un-amh-Ethi-Latn-2016","un-ara-Arab-Latn-1971","un-ara-Arab-Latn-1972","un-ara-Arab-Latn-2017","un-asm-Beng-Latn-1972","un-bel-Cyrl-Latn-2007","un-ben-Beng-Latn-2016","un-bul-Cyrl-Latn-1977","un-ell-Grek-Latn-1987-phonetic","un-ell-Grek-Latn-1987-tl","un-ell-Grek-Latn-1987-ts","un-guj-Gujr-Latn-1972","un-hin-Deva-Latn-2016","un-kan-Kana-Latn-2016","un-mal-Mlym-Latn-1972","un-mar-Deva-Latn-2016","un-mkd-Cyrl-Latn-1977","un-mon-Mong-Latn-general-2013","un-mon-Mong-Latn-phonetic-2013","un-nep-Deva-Latn-1972","un-nep-Deva-Latn-2013","un-ori-Orya-Latn-1972","un-pan-Guru-Latn-1972","un-prs-Arab-Latn-1967","un-rus-Cyrl-Latn-1987","un-sin-Sinh-Latn-1972","un-srp-Cyrl-Latn-1997","un-tam-Taml-Latn-1972","un-tel-Telu-Latn-1972","un-ukr-Cyrl-Latn-1998","un-ukr-Cyrl-Latn-2012","un-urd-Arab-Latn-1972","unicode","var-Cyrl","var-amh-Ethi-Latn-eae-2003","var-ara-Arab-Arab-rababa","var-gez-Ethi-Latn-eae-2003","var-hin-Deva-Latn-hunterian-1872","var-jpn-Hrkt-Latn-hepburn-1886","var-jpn-Hrkt-Latn-hepburn-1954","var-kor","var-kor-Hang-Hang-jamo","var-kor-Hang-Latn-mr-1939","var-kor-Kore-Hang-2013","var-kor-Kore-Latn-mr-1939","var-mar-Deva-Latn-hunterian-1872","var-mon-Mong-Latn-1930","var-mon-Mong-Latn-lessing","var-mon-Mong-Latn-vpmc","var-pra-Deva-Latn-iast-1912","var-san-Deva-Latn-iast-1912","var-zho-Hani-Latn-wd-1979"];var Bn=new Set(["unicode","posix","var-Cyrl","var-kor"]),Pn=new Set(["var-ara-Arab-Arab-rababa"]),Rt=Nt.filter(e=>!Bn.has(e)),Un=Rt.filter(e=>!Pn.has(e)),It=new Map;function S(){return Rt}function $t(){return Un}async function jt(e,t){let n=It.get(t);if(n)return n;let r=await e.fetch(new Request(`https://assets.internal/${t}.json`));if(!r.ok)throw new h(t);let a=await r.json(),s=Ce(a);return It.set(t,s),s}var Ot;function zt(e){if(Ot===e)return;Et({strategies:[n=>jt(e,n)]}),Ot=e}async function Dt(e,t,n){return zt(e),Me(t,n)}function Fn(e,t){if(e===t)return 0;if(!e.length)return t.length;if(!t.length)return e.length;let n=Array.from({length:t.length+1},(r,a)=>a);for(let r=1;r<=e.length;r++){let a=[r];for(let s=1;s<=t.length;s++)a[s]=Math.min(n[s]+1,a[s-1]+1,n[s-1]+(e[r-1]===t[s-1]?0:1));n=a}return n[t.length]}async function Bt(e,t,n){zt(e);let r=[];for(let a of $t())try{let s=await Me(a,t);r.push({mapName:a,distance:Fn(s,n)})}catch{continue}return r.sort((a,s)=>a.distance-s.distance)}var N=class extends Error{constructor(n){super(`Input is too long (max ${n} characters)`);this.max=n;this.name="InputTooLongError"}max},I=class extends Error{constructor(n){super(`Couldn't locate ${n}`);this.systemCode=n;this.name="MapNotFoundError"}systemCode};var R={input_max_size:1e6};var se="3.0.0-cloudflare.1",Te;function H(e){Te=e}function Pt(){if(!Te)throw new Error("ASSETS binding missing \u2014 see README deployment notes");return Te}async function Ut(){return JSON.stringify({version:se,engine:"interscript-ts@native-interpreter"})}function Ft(){return[...S()]}async function oe(e,t){if(t.length>R.input_max_size)throw new N(R.input_max_size);try{return await Dt(Pt(),e,t)}catch(n){throw n instanceof h?new I(e):n}}async function ie(e,t){return Bt(Pt(),e,t)}var qt={version:1,models:[{id:"khm-latn-1.0",task:"translit",scripts:["Khmr","Latn"],precision:"fp32",filename:"khm-latn-1.0-fp32.zip",url:"https://github.com/interscript/interscript-ml/releases/download/khm-latn-1.0/khm-latn-1.0-fp32.zip",parts:null,sha256:"55993d473a2ed9489058779cad7e115db05e4020085b71616468cfae4f2f65cb",size:1418009977,metrics:[{name:"cer",value:27.42,source:"secryst/docs/RESULTS.md#khmer-transliteration-2026-08-14"},{name:"em",value:59.66,source:"secryst/docs/RESULTS.md#khmer-transliteration-2026-08-14"}],parity:{samples:895,cer_delta:0},license:"BSD-3-Clause"},{id:"urd-g2p-1.0",task:"g2p",scripts:["Arab","IPA"],precision:"fp32",filename:"urd-g2p-1.0-fp32.zip",url:"https://github.com/interscript/interscript-ml/releases/download/urd-g2p-1.0/urd-g2p-1.0-fp32.zip",parts:null,sha256:"9f0a248c7116aa7f6b9ac125fb9684a3556f3b8ce8b64567c05a5e7e7a018ebf",size:1418039605,metrics:[{name:"cer",value:14.77,source:"rababa-urdu/docs/RESULTS.md#g2p-urdu-text--ipa"},{name:"em",value:33.6,source:"rababa-urdu/docs/RESULTS.md#g2p-urdu-text--ipa"}],parity:{samples:12699,cer_delta:0},license:"BSD-3-Clause"},{id:"urd-diac-1.0",task:"diacritization",scripts:["Arab","Arab"],precision:"fp32",filename:"urd-diac-1.0-fp32.zip",url:"https://github.com/interscript/interscript-ml/releases/download/urd-diac-1.0/urd-diac-1.0-fp32.zip",parts:null,sha256:"c299ea44a4da4851a52283f8e42d2fb6f8c7a9c443ba7eb0270f97372e10b8ea",size:1417851421,metrics:[{name:"cer",value:3.74,source:"rababa-urdu/docs/RESULTS.md#diacritization-urdu-text--text--haraqat"}],parity:{samples:11940,cer_delta:0},license:"BSD-3-Clause"},{id:"heb-diac-1.0",task:"diacritization",scripts:["Hebr","Hebr"],precision:"fp32",filename:"heb-diac-1.0-fp32.zip",url:null,parts:[{url:"https://github.com/interscript/interscript-ml/releases/download/heb-diac-1.0/heb-diac-1.0-fp32.zip.part-00",sha256:"5497f70cc3c96829e65f1eb0cc401c2d1ffeb4fa909acd4def824c2e019cbc37",size:15e8},{url:"https://github.com/interscript/interscript-ml/releases/download/heb-diac-1.0/heb-diac-1.0-fp32.zip.part-01",sha256:"a7d2011ab4f9326a86e234cc49cb50daf82949e51efbf03a21d5796c46465e9d",size:1281139753}],sha256:"b32fa239e0c56708b6004942de4b9e6e9f265e7cc3828f41d560e96386e20dd8",size:2781139753,metrics:[{name:"der_greedy",value:29,source:"rababa/docs/RESULTS.md#hebrew-diacritization"},{name:"der_beam4",value:17.46,source:"rababa/docs/RESULTS.md#hebrew-diacritization"}],parity:{samples:600,cer_delta:0},license:"BSD-3-Clause"},{id:"tha-g2p-base-1.0",task:"g2p",scripts:["Thai","IPA"],precision:"fp32",filename:"tha-g2p-base-1.0-fp32.zip",url:null,parts:[{url:"https://github.com/interscript/interscript-ml/releases/download/tha-g2p-base-1.0/tha-g2p-base-1.0-fp32.zip.part-00",sha256:"c4525414e5dec91a54d055ca9f1acd51c198f81fbfc55977aa82c1fb038d58d7",size:15e8},{url:"https://github.com/interscript/interscript-ml/releases/download/tha-g2p-base-1.0/tha-g2p-base-1.0-fp32.zip.part-01",sha256:"f5d11fbd75f92e8b5471add867418e2dd4f49a5e258b9ded2f3b5ffa630abe24",size:1281174343}],sha256:"f341a5868867a9847d3f897d1fcc4d1d63b9a59b8913871bcf4ecab48f4fa895",size:2781174343,metrics:[{name:"per_teacher",value:4.43,source:"interscript/interscript-ml docs/RESULTS.md#tha-g2p-base-1.0"},{name:"per_student",value:9.19,source:"interscript/interscript-ml docs/RESULTS.md#tha-g2p-base-1.0"}],parity:{samples:1219,cer_delta:0},license:"BSD-3-Clause"},{id:"tha-g2p-small-1.0",task:"g2p",scripts:["Thai","IPA"],precision:"int8",filename:"tha-g2p-small-1.0-int8.zip",url:"https://github.com/interscript/interscript-ml/releases/download/tha-g2p-small-1.0/tha-g2p-small-1.0-int8.zip",parts:null,sha256:"f2cfa93a42d98aabf08fdc82dfc6bd1574e21993f36558aa581fbe29e0588a94",size:257468517,metrics:[{name:"per_teacher",value:4.43,source:"interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0"},{name:"per_student",value:12.06,source:"interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0"}],parity:{samples:1219,cer_delta:.0803},license:"BSD-3-Clause"},{id:"tha-g2p-small-1.0-int4",task:"g2p",scripts:["Thai","IPA"],precision:"int4",filename:"tha-g2p-small-1.0-int4.zip",url:"https://github.com/interscript/interscript-ml/releases/download/tha-g2p-small-1.0/tha-g2p-small-1.0-int4.zip",parts:null,sha256:"e5d12a3d65c6ac39fa08f52c975c99ff7a51c3e7b7aca7fbb461d2de2345e4cb",size:201958073,metrics:[{name:"per_teacher",value:4.43,source:"interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0"},{name:"per_student",value:12.06,source:"interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0"}],parity:{samples:600,cer_delta:.0734},license:"BSD-3-Clause"},{id:"heb-diac-small-1.0",task:"diacritization",scripts:["Hebr","Hebr"],precision:"fp32",filename:"heb-diac-small-1.0-fp32.zip",url:"https://github.com/interscript/interscript-ml/releases/download/heb-diac-small-1.0/heb-diac-small-1.0-fp32.zip",parts:null,sha256:"905c53873d5a62bbe6252c6ec0ecef5f37e96e78cb01e8cd99b6b24c4d4d74ad",size:1418663344,metrics:[{name:"der_teacher_greedy",value:24.79,source:"interscript/interscript-ml docs/RESULTS.md#heb-diac-small-1.0"},{name:"der_student_greedy",value:30.37,source:"interscript/interscript-ml docs/RESULTS.md#heb-diac-small-1.0"}],parity:{samples:1864,cer_delta:0},license:"BSD-3-Clause"},{id:"fas-g2p-1.0",task:"g2p",scripts:["Arab","Latn"],precision:"fp32",filename:"fas-g2p-1.0-fp32.zip",url:null,parts:[{url:"https://github.com/interscript/interscript-ml/releases/download/fas-g2p-1.0/fas-g2p-1.0-fp32.zip.part-00",sha256:"6c475e53f3c7ef3edcef5a24d2d81f51d13ddf4c417de98440943c9a32b03e55",size:15e8},{url:"https://github.com/interscript/interscript-ml/releases/download/fas-g2p-1.0/fas-g2p-1.0-fp32.zip.part-01",sha256:"c5656c649412f41e201c567e002514d31390b636d4650d9dcb6dfed1973fb3d5",size:1280254270}],sha256:"b53fd69196127efcc596935d24e135b3c897006872c1646c674e70f3c0d9a0e3",size:2780254270,metrics:[{name:"cer",value:1.6,source:"interscript/interscript-ml docs/RESULTS.md#fas-g2p-1.0"},{name:"sb_homograph_ezafe_norm",value:77.34,source:"interscript/interscript-ml docs/RESULTS.md#fas-g2p-1.0"}],parity:{samples:1e3,cer_delta:0},license:"BSD-3-Clause"}]};var Ee=qt;function ce(){return Ee.models}function Ne(e){return Ee.models.find(t=>t.id===e)}var Gt=Ee.version;var Ht={openapi:"3.1.0",info:{title:"Interscript API",version:"1.0.0",description:"Transliteration and neural text processing. Maps run in-edge on the interscript-ts native interpreter (289 systems); neural models (diacritization, grapheme-to-phoneme) run on Interscript's inference service. GraphQL remains available at POST /graphql.",license:{name:"BSD-3-Clause"}},servers:[{url:"https://api.interscript.org"}],tags:[{name:"service"},{name:"maps"},{name:"transliteration"},{name:"models"},{name:"inference"}],paths:{"/":{get:{tags:["service"],summary:"Service index",responses:{200:{$ref:"#/components/responses/Ok"}}}},"/v1/info":{get:{tags:["service"],summary:"Version and capability summary",responses:{200:{$ref:"#/components/responses/Ok"}}}},"/v1/maps":{get:{tags:["maps"],summary:"Every bundled transliteration system code",responses:{200:{$ref:"#/components/responses/Ok"}}}},"/v1/maps/{code}":{get:{tags:["maps"],summary:"One system's compiled map (JSON IR)",parameters:[{name:"code",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{$ref:"#/components/responses/Ok"},404:{$ref:"#/components/responses/NotFound"}}}},"/v1/transliterate":{post:{tags:["transliteration"],summary:"Transliterate text with a system",requestBody:{required:!0,content:{"application/json":{schema:{$ref:"#/components/schemas/TransliterateRequest"},examples:{gek:{value:{system:"ungegn-kor-Kore-Latn-1939",input:"\uD3C9\uC591"}}}}}},responses:{200:{description:"Transliterated output",content:{"application/json":{schema:{$ref:"#/components/schemas/TransliterateResponse"}}}},404:{$ref:"#/components/responses/NotFound"},413:{$ref:"#/components/responses/Error"}}}},"/v1/detect":{post:{tags:["transliteration"],summary:"Rank systems by how well they map input to output",requestBody:{required:!0,content:{"application/json":{schema:{$ref:"#/components/schemas/DetectRequest"}}}},responses:{200:{$ref:"#/components/responses/Ok"}}}},"/v1/models":{get:{tags:["models"],summary:"The neural model index (IMF v1)",responses:{200:{$ref:"#/components/responses/Ok"}}}},"/v1/models/{id}":{get:{tags:["models"],summary:"One model's full entry (metrics, parity, download)",parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{$ref:"#/components/responses/Ok"},404:{$ref:"#/components/responses/NotFound"}}}},"/v1/infer":{post:{tags:["inference"],summary:"Run a neural model (diacritization or G2P)",description:"Proxy to Interscript's inference service. Only models whose task is diacritization or g2p are served; see GET /v1/models for the catalog.",requestBody:{required:!0,content:{"application/json":{schema:{$ref:"#/components/schemas/InferRequest"},examples:{hebrew:{value:{model:"heb-diac-1.0",input:"\u05E9\u05DC\u05D5\u05DD \u05E2\u05DC\u05D9\u05DB\u05DD"},summary:"Hebrew nikud restoration"},urdu:{value:{model:"urd-g2p-1.0",input:"\u0627\u0631\u062F\u0648"},summary:"Urdu grapheme-to-phoneme"}}}}},responses:{200:{description:"Model output",content:{"application/json":{schema:{$ref:"#/components/schemas/InferResponse"}}}},404:{$ref:"#/components/responses/NotFound"},502:{$ref:"#/components/responses/Error"},503:{$ref:"#/components/responses/Error"}}}}},components:{schemas:{Error:{type:"object",properties:{error:{type:"object",properties:{code:{type:"string"},message:{type:"string"}}}}},TransliterateRequest:{type:"object",required:["system","input"],properties:{system:{type:"string",description:"A system code from GET /v1/maps",examples:["ungegn-kor-Kore-Latn-1939"]},input:{type:"string",maxLength:1e6}}},TransliterateResponse:{type:"object",properties:{system:{type:"string"},input:{type:"string"},output:{type:"string"}}},DetectRequest:{type:"object",required:["input","output"],properties:{input:{type:"string"},output:{type:"string"}}},InferRequest:{type:"object",required:["model","input"],properties:{model:{type:"string",description:"A model id from GET /v1/models",examples:["heb-diac-1.0"]},input:{type:"string",maxLength:4e3}}},InferResponse:{type:"object",properties:{model:{type:"string"},task:{type:"string",enum:["diacritization","g2p"]},input:{type:"string"},output:{type:"string"}}}},responses:{Ok:{description:"Success",content:{"application/json":{schema:{type:"object"}}}},NotFound:{description:"Unknown system or model",content:{"application/json":{schema:{$ref:"#/components/schemas/Error"}}}},Error:{description:"Error",content:{"application/json":{schema:{$ref:"#/components/schemas/Error"}}}}}}};var Hn=12e4;function f(e,t,n){return Response.json({error:{code:t,message:n}},{status:e})}var b=new Gn;b.get("/",e=>e.json({name:"Interscript API",version:"v1",openapi:"/openapi.json",endpoints:["GET /v1/info","GET /v1/maps","GET /v1/maps/{code}","POST /v1/transliterate","POST /v1/detect","GET /v1/models","GET /v1/models/{id}","POST /v1/infer","POST /graphql"]}));b.get("/openapi.json",e=>e.json(Ht));b.get("/v1/info",e=>e.json({api_version:"1.0.0",engine_version:se,engine:"interscript-ts@native-interpreter",maps:S().length,models:ce().length,models_index_version:Gt,limits:R}));b.get("/v1/maps",e=>e.json({count:S().length,maps:S()}));b.get("/v1/maps/:code",async e=>{let t=e.env?.ASSETS;if(!t)return f(503,"assets_missing","ASSETS binding not configured");let n=e.req.param("code");if(!S().includes(n))return f(404,"map_not_found",`Couldn't locate ${n}`);let r=await t.fetch(`https://assets.internal/${n}.json`);return r.ok?new Response(r.body,{headers:{"content-type":"application/json; charset=utf-8"}}):f(404,"map_not_found",`Couldn't locate ${n}`)});b.post("/v1/transliterate",async e=>{let t=e.env?.ASSETS;if(!t)return f(503,"assets_missing","ASSETS binding not configured");H(t);let n=await e.req.json().catch(()=>null);if(!n||typeof n!="object")return f(400,"bad_request","body must be JSON {system, input}");let{system:r,input:a}=n;if(typeof r!="string"||typeof a!="string")return f(400,"bad_request","system and input must be strings");try{let s=await oe(r,a);return e.json({system:r,input:a,output:s})}catch(s){return s instanceof N?f(413,"input_too_long",s.message):s instanceof I?f(404,"map_not_found",s.message):f(500,"engine_error",String(s))}});b.post("/v1/detect",async e=>{let t=e.env?.ASSETS;if(!t)return f(503,"assets_missing","ASSETS binding not configured");H(t);let n=await e.req.json().catch(()=>null),{input:r,output:a}=n??{};if(typeof r!="string"||typeof a!="string")return f(400,"bad_request","input and output must be strings");let s=await ie(r,a);return e.json({count:s.length,results:s})});b.get("/v1/models",e=>e.json({count:ce().length,models:ce()}));b.get("/v1/models/:id",e=>{let t=Ne(e.req.param("id"));return t?e.json(t):f(404,"model_not_found",`Couldn't locate ${e.req.param("id")}`)});b.post("/v1/infer",async e=>{let t=await e.req.json().catch(()=>null),{model:n,input:r}=t??{};if(typeof n!="string"||typeof r!="string")return f(400,"bad_request","body must be JSON {model, input}");if(!Ne(n))return f(404,"model_not_found",`Couldn't locate ${n}`);let{ML_ENDPOINT:a,ML_TOKEN:s}=e.env??{};if(!a||!s)return f(503,"inference_unconfigured","ML_ENDPOINT/ML_TOKEN are not set on this deployment");let o=await fetch(`${a}/infer`,{method:"POST",headers:{"content-type":"application/json","x-api-key":s},body:JSON.stringify({model:n,input:r}),signal:AbortSignal.timeout(Hn)}).catch(()=>null);if(!o||!o.ok){let i=o?await o.text().catch(()=>""):"upstream unreachable";return f(502,"inference_upstream",i.slice(0,500))}let c=await o.json();return e.json({model:n,task:c.task,input:r,output:c.output})});var Yn=Kn({schema:Wn({typeDefs:pe,resolvers:{Query:{info:()=>Ut(),systemCodes:()=>Ft(),transliterate:(e,t)=>oe(t.systemCode,t.input),detect:(e,t)=>ie(t.input,t.output)}}}),graphqlEndpoint:"*",landingPage:!1,maskedErrors:!1}),K=new Vn;K.use("*",Qn({origin:"*",allowMethods:["GET","POST","OPTIONS"]}));K.route("/",b);K.all("*",e=>{let t=e.env;t?.ASSETS&&H(t.ASSETS);let n;try{n=e.executionCtx}catch{n=void 0}return Yn.fetch(e.req.raw,e.env,n)});var Jn=K;export{se as API_VERSION,R as LIMITS,K as app,Jn as default,pe as typeDefs}; diff --git a/dist/src/server.js b/dist/src/server.js index 5b55053..c7d4099 100644 --- a/dist/src/server.js +++ b/dist/src/server.js @@ -8,6 +8,7 @@ import { createYoga, createSchema } from "graphql-yoga"; import { Hono } from "hono"; import { cors } from "hono/cors"; import { typeDefs } from "./schema.js"; +import { rest } from "./rest.js"; import { detectResolver, info, setAssets, systemCodesResolver, transliterateResolver, } from "./resolvers.js"; const yoga = createYoga({ schema: createSchema({ @@ -29,6 +30,9 @@ const yoga = createYoga({ }); export const app = new Hono(); app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"] })); +// REST v1 (OpenAPI: GET /openapi.json) — mounted before the GraphQL +// catch-all so /v1/* and / serve REST; everything else stays GraphQL. +app.route("/", rest); app.all("*", (c) => { const env = c.env; if (env?.ASSETS) diff --git a/package.json b/package.json index 7596372..383eadf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@interscript/api-worker", - "version": "0.1.0", + "version": "1.0.0", "description": "Interscript API software \u2014 GraphQL API for transliteration on Cloudflare Workers (or any Workers-compatible runtime). Deploy your own endpoint: see README.", "type": "module", "main": "./dist/src/index.js", @@ -28,7 +28,8 @@ "format": "prettier --write src test scripts", "format:check": "prettier --check src test scripts", "typecheck": "tsc --noEmit", - "fixtures:record": "node scripts/record-fixtures.mjs" + "fixtures:record": "node scripts/record-fixtures.mjs", + "gen:models": "node scripts/gen-models.mjs" }, "dependencies": { "hono": "^4.0.0", diff --git a/scripts/gen-models.mjs b/scripts/gen-models.mjs new file mode 100644 index 0000000..f81e470 --- /dev/null +++ b/scripts/gen-models.mjs @@ -0,0 +1,21 @@ +// Regenerate src/generated/models.json from a checkout of +// interscript/interscript-ml (default path ../ml-models is the +// historical local name; override with INTSCRIPT_ML_DIR). +import { execSync } from "node:child_process" +import { mkdirSync, writeFileSync } from "node:fs" + +const dir = process.env.INTERSCRIPT_ML_DIR ?? "../ml-models" +const yaml = `${dir}/models.yaml` +mkdirSync("src/generated", { recursive: true }) +const out = execSync( + `python3 -c '${[ + "import json,yaml", + "d=yaml.safe_load(open(" + JSON.stringify(yaml) + "))", + "print(json.dumps({'version':d.get('version',1),'models':[{k:m.get(k) for k in", + "('id','task','scripts','precision','filename','url','parts','sha256','size','metrics','parity','license')}", + "for m in d['models'].values()]}))", + ].join(" ")}'`, +).toString() +const parsed = JSON.parse(out) +writeFileSync("src/generated/models.json", JSON.stringify(parsed, null, 1) + "\n") +console.log(`wrote ${parsed.models.length} models`) diff --git a/src/generated/models.json b/src/generated/models.json new file mode 100644 index 0000000..04553af --- /dev/null +++ b/src/generated/models.json @@ -0,0 +1,312 @@ +{ + "version": 1, + "models": [ + { + "id": "khm-latn-1.0", + "task": "translit", + "scripts": [ + "Khmr", + "Latn" + ], + "precision": "fp32", + "filename": "khm-latn-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/khm-latn-1.0/khm-latn-1.0-fp32.zip", + "parts": null, + "sha256": "55993d473a2ed9489058779cad7e115db05e4020085b71616468cfae4f2f65cb", + "size": 1418009977, + "metrics": [ + { + "name": "cer", + "value": 27.42, + "source": "secryst/docs/RESULTS.md#khmer-transliteration-2026-08-14" + }, + { + "name": "em", + "value": 59.66, + "source": "secryst/docs/RESULTS.md#khmer-transliteration-2026-08-14" + } + ], + "parity": { + "samples": 895, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "urd-g2p-1.0", + "task": "g2p", + "scripts": [ + "Arab", + "IPA" + ], + "precision": "fp32", + "filename": "urd-g2p-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/urd-g2p-1.0/urd-g2p-1.0-fp32.zip", + "parts": null, + "sha256": "9f0a248c7116aa7f6b9ac125fb9684a3556f3b8ce8b64567c05a5e7e7a018ebf", + "size": 1418039605, + "metrics": [ + { + "name": "cer", + "value": 14.77, + "source": "rababa-urdu/docs/RESULTS.md#g2p-urdu-text--ipa" + }, + { + "name": "em", + "value": 33.6, + "source": "rababa-urdu/docs/RESULTS.md#g2p-urdu-text--ipa" + } + ], + "parity": { + "samples": 12699, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "urd-diac-1.0", + "task": "diacritization", + "scripts": [ + "Arab", + "Arab" + ], + "precision": "fp32", + "filename": "urd-diac-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/urd-diac-1.0/urd-diac-1.0-fp32.zip", + "parts": null, + "sha256": "c299ea44a4da4851a52283f8e42d2fb6f8c7a9c443ba7eb0270f97372e10b8ea", + "size": 1417851421, + "metrics": [ + { + "name": "cer", + "value": 3.74, + "source": "rababa-urdu/docs/RESULTS.md#diacritization-urdu-text--text--haraqat" + } + ], + "parity": { + "samples": 11940, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "heb-diac-1.0", + "task": "diacritization", + "scripts": [ + "Hebr", + "Hebr" + ], + "precision": "fp32", + "filename": "heb-diac-1.0-fp32.zip", + "url": null, + "parts": [ + { + "url": "https://github.com/interscript/interscript-ml/releases/download/heb-diac-1.0/heb-diac-1.0-fp32.zip.part-00", + "sha256": "5497f70cc3c96829e65f1eb0cc401c2d1ffeb4fa909acd4def824c2e019cbc37", + "size": 1500000000 + }, + { + "url": "https://github.com/interscript/interscript-ml/releases/download/heb-diac-1.0/heb-diac-1.0-fp32.zip.part-01", + "sha256": "a7d2011ab4f9326a86e234cc49cb50daf82949e51efbf03a21d5796c46465e9d", + "size": 1281139753 + } + ], + "sha256": "b32fa239e0c56708b6004942de4b9e6e9f265e7cc3828f41d560e96386e20dd8", + "size": 2781139753, + "metrics": [ + { + "name": "der_greedy", + "value": 29.0, + "source": "rababa/docs/RESULTS.md#hebrew-diacritization" + }, + { + "name": "der_beam4", + "value": 17.46, + "source": "rababa/docs/RESULTS.md#hebrew-diacritization" + } + ], + "parity": { + "samples": 600, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "tha-g2p-base-1.0", + "task": "g2p", + "scripts": [ + "Thai", + "IPA" + ], + "precision": "fp32", + "filename": "tha-g2p-base-1.0-fp32.zip", + "url": null, + "parts": [ + { + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-base-1.0/tha-g2p-base-1.0-fp32.zip.part-00", + "sha256": "c4525414e5dec91a54d055ca9f1acd51c198f81fbfc55977aa82c1fb038d58d7", + "size": 1500000000 + }, + { + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-base-1.0/tha-g2p-base-1.0-fp32.zip.part-01", + "sha256": "f5d11fbd75f92e8b5471add867418e2dd4f49a5e258b9ded2f3b5ffa630abe24", + "size": 1281174343 + } + ], + "sha256": "f341a5868867a9847d3f897d1fcc4d1d63b9a59b8913871bcf4ecab48f4fa895", + "size": 2781174343, + "metrics": [ + { + "name": "per_teacher", + "value": 4.43, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-base-1.0" + }, + { + "name": "per_student", + "value": 9.19, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-base-1.0" + } + ], + "parity": { + "samples": 1219, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "tha-g2p-small-1.0", + "task": "g2p", + "scripts": [ + "Thai", + "IPA" + ], + "precision": "int8", + "filename": "tha-g2p-small-1.0-int8.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-small-1.0/tha-g2p-small-1.0-int8.zip", + "parts": null, + "sha256": "f2cfa93a42d98aabf08fdc82dfc6bd1574e21993f36558aa581fbe29e0588a94", + "size": 257468517, + "metrics": [ + { + "name": "per_teacher", + "value": 4.43, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + }, + { + "name": "per_student", + "value": 12.06, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + } + ], + "parity": { + "samples": 1219, + "cer_delta": 0.0803 + }, + "license": "BSD-3-Clause" + }, + { + "id": "tha-g2p-small-1.0-int4", + "task": "g2p", + "scripts": [ + "Thai", + "IPA" + ], + "precision": "int4", + "filename": "tha-g2p-small-1.0-int4.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/tha-g2p-small-1.0/tha-g2p-small-1.0-int4.zip", + "parts": null, + "sha256": "e5d12a3d65c6ac39fa08f52c975c99ff7a51c3e7b7aca7fbb461d2de2345e4cb", + "size": 201958073, + "metrics": [ + { + "name": "per_teacher", + "value": 4.43, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + }, + { + "name": "per_student", + "value": 12.06, + "source": "interscript/interscript-ml docs/RESULTS.md#tha-g2p-small-1.0" + } + ], + "parity": { + "samples": 600, + "cer_delta": 0.0734 + }, + "license": "BSD-3-Clause" + }, + { + "id": "heb-diac-small-1.0", + "task": "diacritization", + "scripts": [ + "Hebr", + "Hebr" + ], + "precision": "fp32", + "filename": "heb-diac-small-1.0-fp32.zip", + "url": "https://github.com/interscript/interscript-ml/releases/download/heb-diac-small-1.0/heb-diac-small-1.0-fp32.zip", + "parts": null, + "sha256": "905c53873d5a62bbe6252c6ec0ecef5f37e96e78cb01e8cd99b6b24c4d4d74ad", + "size": 1418663344, + "metrics": [ + { + "name": "der_teacher_greedy", + "value": 24.79, + "source": "interscript/interscript-ml docs/RESULTS.md#heb-diac-small-1.0" + }, + { + "name": "der_student_greedy", + "value": 30.37, + "source": "interscript/interscript-ml docs/RESULTS.md#heb-diac-small-1.0" + } + ], + "parity": { + "samples": 1864, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + }, + { + "id": "fas-g2p-1.0", + "task": "g2p", + "scripts": [ + "Arab", + "Latn" + ], + "precision": "fp32", + "filename": "fas-g2p-1.0-fp32.zip", + "url": null, + "parts": [ + { + "url": "https://github.com/interscript/interscript-ml/releases/download/fas-g2p-1.0/fas-g2p-1.0-fp32.zip.part-00", + "sha256": "6c475e53f3c7ef3edcef5a24d2d81f51d13ddf4c417de98440943c9a32b03e55", + "size": 1500000000 + }, + { + "url": "https://github.com/interscript/interscript-ml/releases/download/fas-g2p-1.0/fas-g2p-1.0-fp32.zip.part-01", + "sha256": "c5656c649412f41e201c567e002514d31390b636d4650d9dcb6dfed1973fb3d5", + "size": 1280254270 + } + ], + "sha256": "b53fd69196127efcc596935d24e135b3c897006872c1646c674e70f3c0d9a0e3", + "size": 2780254270, + "metrics": [ + { + "name": "cer", + "value": 1.6, + "source": "interscript/interscript-ml docs/RESULTS.md#fas-g2p-1.0" + }, + { + "name": "sb_homograph_ezafe_norm", + "value": 77.34, + "source": "interscript/interscript-ml docs/RESULTS.md#fas-g2p-1.0" + } + ], + "parity": { + "samples": 1000, + "cer_delta": 0.0 + }, + "license": "BSD-3-Clause" + } + ] +} \ No newline at end of file diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 0000000..0859418 --- /dev/null +++ b/src/models.ts @@ -0,0 +1,44 @@ +/** + * The neural model index: a generated projection of interscript-ml's + * models.yaml (the IMF v1 contract). Regenerate with + * `npm run gen:models` from a checkout of interscript/interscript-ml. + */ +import index from "./generated/models.json" + +export interface ModelMetric { + name: string + value: number + source: string +} + +export interface ModelEntry { + id: string + task: "g2p" | "diacritization" | "translit" + scripts: string[] + precision: string + filename: string | null + url: string | null + parts: unknown + sha256: string | null + size: number | null + metrics: ModelMetric[] + parity: { samples: number; cer_delta: number } | null + license: string +} + +interface ModelIndex { + version: number + models: ModelEntry[] +} + +const data = index as unknown as ModelIndex + +export function listModels(): ModelEntry[] { + return data.models +} + +export function getModel(id: string): ModelEntry | undefined { + return data.models.find((m) => m.id === id) +} + +export const MODELS_INDEX_VERSION = data.version diff --git a/src/openapi.ts b/src/openapi.ts new file mode 100644 index 0000000..4984d11 --- /dev/null +++ b/src/openapi.ts @@ -0,0 +1,240 @@ +/** + * The OpenAPI 3.1 document for REST v1 — served at /openapi.json. + * Hand-maintained to mirror src/rest.ts exactly. + */ +export const OPENAPI = { + openapi: "3.1.0", + info: { + title: "Interscript API", + version: "1.0.0", + description: + "Transliteration and neural text processing. Maps run in-edge on the interscript-ts native interpreter (289 systems); neural models (diacritization, grapheme-to-phoneme) run on Interscript's inference service. GraphQL remains available at POST /graphql.", + license: { name: "BSD-3-Clause" }, + }, + servers: [{ url: "https://api.interscript.org" }], + tags: [ + { name: "service" }, + { name: "maps" }, + { name: "transliteration" }, + { name: "models" }, + { name: "inference" }, + ], + paths: { + "/": { + get: { + tags: ["service"], + summary: "Service index", + responses: { "200": { $ref: "#/components/responses/Ok" } }, + }, + }, + "/v1/info": { + get: { + tags: ["service"], + summary: "Version and capability summary", + responses: { "200": { $ref: "#/components/responses/Ok" } }, + }, + }, + "/v1/maps": { + get: { + tags: ["maps"], + summary: "Every bundled transliteration system code", + responses: { "200": { $ref: "#/components/responses/Ok" } }, + }, + }, + "/v1/maps/{code}": { + get: { + tags: ["maps"], + summary: "One system's compiled map (JSON IR)", + parameters: [ + { name: "code", in: "path", required: true, schema: { type: "string" } }, + ], + responses: { + "200": { $ref: "#/components/responses/Ok" }, + "404": { $ref: "#/components/responses/NotFound" }, + }, + }, + }, + "/v1/transliterate": { + post: { + tags: ["transliteration"], + summary: "Transliterate text with a system", + requestBody: { + required: true, + content: { + "application/json": { + schema: { $ref: "#/components/schemas/TransliterateRequest" }, + examples: { + gek: { + value: { system: "ungegn-kor-Kore-Latn-1939", input: "평양" }, + }, + }, + }, + }, + }, + responses: { + "200": { + description: "Transliterated output", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/TransliterateResponse" }, + }, + }, + }, + "404": { $ref: "#/components/responses/NotFound" }, + "413": { $ref: "#/components/responses/Error" }, + }, + }, + }, + "/v1/detect": { + post: { + tags: ["transliteration"], + summary: "Rank systems by how well they map input to output", + requestBody: { + required: true, + content: { + "application/json": { + schema: { $ref: "#/components/schemas/DetectRequest" }, + }, + }, + }, + responses: { "200": { $ref: "#/components/responses/Ok" } }, + }, + }, + "/v1/models": { + get: { + tags: ["models"], + summary: "The neural model index (IMF v1)", + responses: { "200": { $ref: "#/components/responses/Ok" } }, + }, + }, + "/v1/models/{id}": { + get: { + tags: ["models"], + summary: "One model's full entry (metrics, parity, download)", + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], + responses: { + "200": { $ref: "#/components/responses/Ok" }, + "404": { $ref: "#/components/responses/NotFound" }, + }, + }, + }, + "/v1/infer": { + post: { + tags: ["inference"], + summary: "Run a neural model (diacritization or G2P)", + description: + "Proxy to Interscript's inference service. Only models whose task is diacritization or g2p are served; see GET /v1/models for the catalog.", + requestBody: { + required: true, + content: { + "application/json": { + schema: { $ref: "#/components/schemas/InferRequest" }, + examples: { + hebrew: { + value: { model: "heb-diac-1.0", input: "שלום עליכם" }, + summary: "Hebrew nikud restoration", + }, + urdu: { + value: { model: "urd-g2p-1.0", input: "اردو" }, + summary: "Urdu grapheme-to-phoneme", + }, + }, + }, + }, + }, + responses: { + "200": { + description: "Model output", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/InferResponse" }, + }, + }, + }, + "404": { $ref: "#/components/responses/NotFound" }, + "502": { $ref: "#/components/responses/Error" }, + "503": { $ref: "#/components/responses/Error" }, + }, + }, + }, + }, + components: { + schemas: { + Error: { + type: "object", + properties: { + error: { + type: "object", + properties: { + code: { type: "string" }, + message: { type: "string" }, + }, + }, + }, + }, + TransliterateRequest: { + type: "object", + required: ["system", "input"], + properties: { + system: { + type: "string", + description: "A system code from GET /v1/maps", + examples: ["ungegn-kor-Kore-Latn-1939"], + }, + input: { type: "string", maxLength: 1000000 }, + }, + }, + TransliterateResponse: { + type: "object", + properties: { + system: { type: "string" }, + input: { type: "string" }, + output: { type: "string" }, + }, + }, + DetectRequest: { + type: "object", + required: ["input", "output"], + properties: { + input: { type: "string" }, + output: { type: "string" }, + }, + }, + InferRequest: { + type: "object", + required: ["model", "input"], + properties: { + model: { + type: "string", + description: "A model id from GET /v1/models", + examples: ["heb-diac-1.0"], + }, + input: { type: "string", maxLength: 4000 }, + }, + }, + InferResponse: { + type: "object", + properties: { + model: { type: "string" }, + task: { type: "string", enum: ["diacritization", "g2p"] }, + input: { type: "string" }, + output: { type: "string" }, + }, + }, + }, + responses: { + Ok: { + description: "Success", + content: { "application/json": { schema: { type: "object" } } }, + }, + NotFound: { + description: "Unknown system or model", + content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }, + }, + Error: { + description: "Error", + content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }, + }, + }, + }, +} as const diff --git a/src/rest.ts b/src/rest.ts new file mode 100644 index 0000000..4bbaec1 --- /dev/null +++ b/src/rest.ts @@ -0,0 +1,172 @@ +/** + * REST v1 — the OpenAPI-first interface to Interscript. + * + * GET /openapi.json the OpenAPI 3.1 document + * GET / service index (JSON) + * GET /v1/info version + capability summary + * GET /v1/maps every bundled transliteration system code + * GET /v1/maps/:code one system's compiled map (JSON IR) + * POST /v1/transliterate {system, input} -> {output} + * POST /v1/detect {input, output} -> ranked systems + * GET /v1/models the neural model index (IMF v1) + * GET /v1/models/:id one model's full entry + * POST /v1/infer {model, input} -> {output} (Modal proxy) + * + * GraphQL remains at POST /graphql (and any other path, for + * compatibility with the Ruby API's {proxy+} behavior). + */ +import { Hono } from "hono" +import { API_VERSION, detectResolver, setAssets, transliterateResolver, type Env } from "./resolvers.js" +import { InputTooLongError, MapNotFoundError } from "./errors.js" +import { LIMITS } from "./limits.js" +import { bundledSystemCodes } from "./engine.js" +import { getModel, listModels, MODELS_INDEX_VERSION } from "./models.js" +import { OPENAPI } from "./openapi.js" + +const INFER_TIMEOUT_MS = 120_000 + +function errorResponse(status: number, code: string, message: string) { + return Response.json({ error: { code, message } }, { status }) +} + +export const rest = new Hono<{ Bindings: Env }>() + +rest.get("/", (c) => + c.json({ + name: "Interscript API", + version: "v1", + openapi: "/openapi.json", + endpoints: [ + "GET /v1/info", + "GET /v1/maps", + "GET /v1/maps/{code}", + "POST /v1/transliterate", + "POST /v1/detect", + "GET /v1/models", + "GET /v1/models/{id}", + "POST /v1/infer", + "POST /graphql", + ], + }), +) + +rest.get("/openapi.json", (c) => c.json(OPENAPI)) + +rest.get("/v1/info", (c) => + c.json({ + api_version: "1.0.0", + engine_version: API_VERSION, + engine: "interscript-ts@native-interpreter", + maps: bundledSystemCodes().length, + models: listModels().length, + models_index_version: MODELS_INDEX_VERSION, + limits: LIMITS, + }), +) + +rest.get("/v1/maps", (c) => + c.json({ count: bundledSystemCodes().length, maps: bundledSystemCodes() }), +) + +rest.get("/v1/maps/:code", async (c) => { + const assets = c.env?.ASSETS + if (!assets) return errorResponse(503, "assets_missing", "ASSETS binding not configured") + const code = c.req.param("code") + if (!bundledSystemCodes().includes(code as never)) { + return errorResponse(404, "map_not_found", `Couldn't locate ${code}`) + } + // The assets directory IS the maps directory (no /maps/ prefix) and + // requires absolute URLs. + const response = await assets.fetch(`https://assets.internal/${code}.json`) + if (!response.ok) { + return errorResponse(404, "map_not_found", `Couldn't locate ${code}`) + } + return new Response(response.body, { + headers: { "content-type": "application/json; charset=utf-8" }, + }) +}) + +rest.post("/v1/transliterate", async (c) => { + const assets = c.env?.ASSETS + if (!assets) return errorResponse(503, "assets_missing", "ASSETS binding not configured") + setAssets(assets) + const body = await c.req.json().catch(() => null) + if (!body || typeof body !== "object") { + return errorResponse(400, "bad_request", "body must be JSON {system, input}") + } + const { system, input } = body as { system?: unknown; input?: unknown } + if (typeof system !== "string" || typeof input !== "string") { + return errorResponse(400, "bad_request", "system and input must be strings") + } + try { + const output = await transliterateResolver(system, input) + return c.json({ system, input, output }) + } catch (e) { + if (e instanceof InputTooLongError) { + return errorResponse(413, "input_too_long", e.message) + } + if (e instanceof MapNotFoundError) { + return errorResponse(404, "map_not_found", e.message) + } + return errorResponse(500, "engine_error", String(e)) + } +}) + +rest.post("/v1/detect", async (c) => { + const assets = c.env?.ASSETS + if (!assets) return errorResponse(503, "assets_missing", "ASSETS binding not configured") + setAssets(assets) + const body = await c.req.json().catch(() => null) + const { input, output } = (body ?? {}) as { input?: unknown; output?: unknown } + if (typeof input !== "string" || typeof output !== "string") { + return errorResponse(400, "bad_request", "input and output must be strings") + } + const results = await detectResolver(input, output) + return c.json({ count: results.length, results }) +}) + +rest.get("/v1/models", (c) => + c.json({ count: listModels().length, models: listModels() }), +) + +rest.get("/v1/models/:id", (c) => { + const model = getModel(c.req.param("id")) + if (!model) return errorResponse(404, "model_not_found", `Couldn't locate ${c.req.param("id")}`) + return c.json(model) +}) + +rest.post("/v1/infer", async (c) => { + const body = await c.req.json().catch(() => null) + const { model, input } = (body ?? {}) as { model?: unknown; input?: unknown } + if (typeof model !== "string" || typeof input !== "string") { + return errorResponse(400, "bad_request", "body must be JSON {model, input}") + } + if (!getModel(model)) { + return errorResponse(404, "model_not_found", `Couldn't locate ${model}`) + } + const { ML_ENDPOINT, ML_TOKEN } = (c.env ?? {}) as Record + if (!ML_ENDPOINT || !ML_TOKEN) { + return errorResponse( + 503, + "inference_unconfigured", + "ML_ENDPOINT/ML_TOKEN are not set on this deployment", + ) + } + const upstream = await fetch(`${ML_ENDPOINT}/infer`, { + method: "POST", + headers: { "content-type": "application/json", "x-api-key": ML_TOKEN }, + body: JSON.stringify({ model, input }), + signal: AbortSignal.timeout(INFER_TIMEOUT_MS), + }).catch(() => null) + if (!upstream || !upstream.ok) { + const detail = upstream ? await upstream.text().catch(() => "") : "upstream unreachable" + return errorResponse(502, "inference_upstream", detail.slice(0, 500)) + } + const result = (await upstream.json()) as Record + return c.json({ + model, + task: result["task"], + input, + output: result["output"], + }) +}) diff --git a/src/server.ts b/src/server.ts index 2be0f8b..676cac8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -8,6 +8,7 @@ import { createYoga, createSchema } from "graphql-yoga" import { Hono } from "hono" import { cors } from "hono/cors" import { typeDefs } from "./schema.js" +import { rest } from "./rest.js" import { detectResolver, info, @@ -40,6 +41,10 @@ export const app = new Hono() app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"] })) +// REST v1 (OpenAPI: GET /openapi.json) — mounted before the GraphQL +// catch-all so /v1/* and / serve REST; everything else stays GraphQL. +app.route("/", rest) + app.all("*", (c) => { const env = c.env as Env | undefined if (env?.ASSETS) setAssets(env.ASSETS) diff --git a/test/rest.test.ts b/test/rest.test.ts new file mode 100644 index 0000000..dff31ad --- /dev/null +++ b/test/rest.test.ts @@ -0,0 +1,149 @@ +/** + * REST v1 behavior tests — same fetch-interface harness as the GraphQL + * suite (maps served from the local maps/ directory). + */ +import { readFileSync } from "node:fs" +import { describe, expect, it } from "vitest" +import { app } from "../src/server.js" + +const assets = { + fetch: async (url: RequestInfo): Promise => { + const path = typeof url === "string" ? url : url.url + const name = path.substring(path.lastIndexOf("/") + 1) + const code = name.replace(".json", "") + try { + const body = readFileSync(`maps/${code}.json`) + return new Response(body, { status: 200 }) + } catch { + return new Response(null, { status: 404 }) + } + }, +} +const env = { ASSETS: assets } +const JSON_HEADERS = { "content-type": "application/json" } + +async function get(path: string, extra: Record = {}): Promise { + return await app.request(`https://example.org${path}`, { headers: { ...JSON_HEADERS, ...extra } }, env) +} + +async function post(path: string, body: unknown, extra: Record = {}): Promise { + return await app.request( + `https://example.org${path}`, + { method: "POST", headers: { ...JSON_HEADERS, ...extra }, body: JSON.stringify(body) }, + env, + ) +} + +describe("REST index + docs", () => { + it("GET / lists endpoints", async () => { + const res = await get("/") + expect(res.status).toBe(200) + const body = (await res.json()) as { endpoints: string[] } + expect(body.endpoints).toContain("POST /v1/infer") + }) + + it("GET /openapi.json serves a 3.1 document covering all routes", async () => { + const res = await get("/openapi.json") + expect(res.status).toBe(200) + const spec = (await res.json()) as { openapi: string; paths: Record } + expect(spec.openapi).toBe("3.1.0") + for (const p of [ + "/v1/info", + "/v1/maps", + "/v1/maps/{code}", + "/v1/transliterate", + "/v1/detect", + "/v1/models", + "/v1/models/{id}", + "/v1/infer", + ]) { + expect(spec.paths[p], p).toBeDefined() + } + }) + + it("GET /v1/info reports map and model counts", async () => { + const res = await get("/v1/info") + const body = (await res.json()) as { maps: number; models: number } + expect(body.maps).toBeGreaterThan(200) + expect(body.models).toBeGreaterThanOrEqual(9) + }) +}) + +describe("REST maps", () => { + it("GET /v1/maps lists systems", async () => { + const res = await get("/v1/maps") + const body = (await res.json()) as { count: number; maps: string[] } + expect(body.count).toBe(body.maps.length) + expect(body.maps).toContain("alalc-kat-Geor-Latn-1997") + }) + + it("GET /v1/maps/:code returns the compiled map; unknown codes 404", async () => { + const ok = await get("/v1/maps/alalc-kat-Geor-Latn-1997") + expect(ok.status).toBe(200) + expect(await ok.json()).toBeTruthy() + const missing = await get("/v1/maps/does-not-exist") + expect(missing.status).toBe(404) + expect(((await missing.json()) as { error: { code: string } }).error.code).toBe( + "map_not_found", + ) + }) +}) + +describe("REST transliteration", () => { + it("POST /v1/transliterate mirrors the GraphQL engine", async () => { + const res = await post("/v1/transliterate", { + system: "alalc-kat-Geor-Latn-1997", + input: "ქართული", + }) + expect(res.status).toBe(200) + const body = (await res.json()) as { output: string } + expect(body.output).toBe("k\u02bbart\u02bbuli") + }) + + it("rejects unknown systems and over-long input", async () => { + const missing = await post("/v1/transliterate", { system: "nope", input: "x" }) + expect(missing.status).toBe(404) + const tooLong = await post("/v1/transliterate", { + system: "alalc-kat-Geor-Latn-1997", + input: "a".repeat(1_000_001), + }) + expect(tooLong.status).toBe(413) + }) +}) + +describe("REST detect", () => { + it("ranks systems", async () => { + const res = await post("/v1/detect", { input: "ქართული", output: "k\u02bbart\u02bbuli" }) + expect(res.status).toBe(200) + const body = (await res.json()) as { count: number; results: { mapName: string }[] } + expect(body.count).toBeGreaterThan(0) + expect(body.results[0]?.mapName).toContain("kat") + }) +}) + +describe("REST models + inference", () => { + it("GET /v1/models lists the IMF index", async () => { + const res = await get("/v1/models") + const body = (await res.json()) as { count: number; models: { id: string; task: string }[] } + expect(body.count).toBeGreaterThanOrEqual(9) + expect(body.models.map((m) => m.id)).toContain("heb-diac-1.0") + }) + + it("GET /v1/models/:id returns the entry; unknown 404", async () => { + const ok = await get("/v1/models/heb-diac-1.0") + expect(ok.status).toBe(200) + const missing = await get("/v1/models/nope-9.9") + expect(missing.status).toBe(404) + }) + + it("POST /v1/infer validates without an upstream configured", async () => { + const unconfigured = await post("/v1/infer", { model: "heb-diac-1.0", input: "x" }) + expect(unconfigured.status).toBe(503) + + const unknownModel = await post("/v1/infer", { model: "nope-9.9", input: "x" }) + expect(unknownModel.status).toBe(404) + + const badBody = await post("/v1/infer", { model: 1, input: "x" }) + expect(badBody.status).toBe(400) + }) +})