{"_id":"@ade_oshineye/yaket","_rev":"5-802385c311a3d07a08fb2af9185525af","name":"@ade_oshineye/yaket","dist-tags":{"latest":"0.5.3"},"versions":{"0.2.0":{"name":"@ade_oshineye/yaket","version":"0.2.0","keywords":["yake","keyword-extraction","nlp","typescript"],"license":"MIT","_id":"@ade_oshineye/yaket@0.2.0","maintainers":[{"name":"ade_oshineye","email":"adewale@gmail.com"}],"homepage":"https://github.com/adewale/yaket#readme","bugs":{"url":"https://github.com/adewale/yaket/issues"},"bin":{"yaket":"dist/cli.js"},"dist":{"shasum":"d8817822d36f5358142084ae46b530e3784b7811","tarball":"https://registry.npmjs.org/@ade_oshineye/yaket/-/yaket-0.2.0.tgz","fileCount":62,"integrity":"sha512-J9QmSEaeQevbZGTkb0ryiAkSHBb/bgCBPJxD2HT/rhFmWHmmICmcunhBjHu/TseEeysrPe/zwFqocgbh60bulQ==","signatures":[{"sig":"MEQCIGxL4NbNJzgCBWdG6hay3z87GmBrZ/+c57QLS0cLhMkCAiBeewKTTIydDWaIpCj9DQMAP178KxbCY4FRL3amWLA9ow==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":330890},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./worker":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./browser":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"8c1ae2b162ab86f8c6e6e739eab8a2dfd840003a","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","verify":"npm run typecheck && npm test && npm run test:cloudflare && npm run build && npm run check:package","prepack":"npm run build","test:all":"npm test && npm run test:cloudflare","benchmark":"tsx scripts/benchmark.ts","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","check:package":"npm pack --dry-run && publint","test:mutation":"stryker run","test:cloudflare":"vitest run --config vitest.cloudflare.config.ts","test:cli:coverage":"vitest run --config vitest.cli-coverage.config.ts"},"_npmUser":{"name":"ade_oshineye","email":"adewale@gmail.com"},"repository":{"url":"git+https://github.com/adewale/yaket.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript port of the YAKE keyword extraction core pipeline","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.6","vitest":"^4.1.2","esbuild":"^0.25.9","publint":"^0.3.15","wrangler":"^4.80.0","fast-check":"^4.3.0","typescript":"^5.9.3","@types/node":"^24.5.2","@vitest/coverage-v8":"^4.1.4","@stryker-mutator/core":"^9.3.0","@cloudflare/workers-types":"^4.20260405.1","@stryker-mutator/vitest-runner":"^9.3.0","@cloudflare/vitest-pool-workers":"^0.14.1","@stryker-mutator/typescript-checker":"^9.3.0"},"_npmOperationalInternal":{"tmp":"tmp/yaket_0.2.0_1776368667500_0.8082214603451763","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ade_oshineye/yaket","version":"0.3.0","keywords":["yake","keyword-extraction","nlp","typescript"],"license":"MIT","_id":"@ade_oshineye/yaket@0.3.0","maintainers":[{"name":"ade_oshineye","email":"adewale@gmail.com"}],"homepage":"https://github.com/adewale/yaket#readme","bugs":{"url":"https://github.com/adewale/yaket/issues"},"bin":{"yaket":"dist/cli.js"},"dist":{"shasum":"dd3e65933d54b8c2ede266cea5eafee52ff16a98","tarball":"https://registry.npmjs.org/@ade_oshineye/yaket/-/yaket-0.3.0.tgz","fileCount":62,"integrity":"sha512-j3mWFXuuVuHf959jhFnMjVb/DhzECVPSnk27ijHhMBrgHbI/Hm1X/5h3pMhLVsdlnWKs9VRWQY5S8FgRMFm5Ag==","signatures":[{"sig":"MEYCIQDfwaZsxunwCTtmJQApvndkBDYZ8GRfJKgw/rYl26rgxAIhAMPgnAt38kHxhNDPKMfJXjQWOdKoXs8PWTtrf3sAn0RU","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":330871},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./worker":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./browser":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"05b941a6404187a26e4463bfcab6f2f6be1ab3dc","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","verify":"npm run typecheck && npm test && npm run test:cloudflare && npm run build && npm run check:package","prepack":"npm run build","test:all":"npm test && npm run test:cloudflare","benchmark":"tsx scripts/benchmark.ts","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","check:package":"npm pack --dry-run && publint","test:mutation":"stryker run","test:cloudflare":"vitest run --config vitest.cloudflare.config.ts","test:cli:coverage":"vitest run --config vitest.cli-coverage.config.ts"},"_npmUser":{"name":"ade_oshineye","email":"adewale@gmail.com"},"repository":{"url":"git+https://github.com/adewale/yaket.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript port of the YAKE keyword extraction core pipeline","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.6","vitest":"^4.1.2","esbuild":"^0.28.0","publint":"^0.3.15","wrangler":"^4.80.0","fast-check":"^4.3.0","typescript":"^5.9.3","@types/node":"^24.5.2","@vitest/coverage-v8":"^4.1.4","@stryker-mutator/core":"^9.3.0","@cloudflare/workers-types":"^4.20260405.1","@stryker-mutator/vitest-runner":"^9.3.0","@cloudflare/vitest-pool-workers":"^0.14.1","@stryker-mutator/typescript-checker":"^9.3.0"},"_npmOperationalInternal":{"tmp":"tmp/yaket_0.3.0_1776373747577_0.6505704564004928","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@ade_oshineye/yaket","version":"0.4.0","keywords":["yake","keyword-extraction","nlp","typescript"],"license":"MIT","_id":"@ade_oshineye/yaket@0.4.0","maintainers":[{"name":"ade_oshineye","email":"adewale@gmail.com"}],"homepage":"https://github.com/adewale/yaket#readme","bugs":{"url":"https://github.com/adewale/yaket/issues"},"bin":{"yaket":"dist/cli.js"},"dist":{"shasum":"f8577afa822598cca0bee4326598d5492f5913f5","tarball":"https://registry.npmjs.org/@ade_oshineye/yaket/-/yaket-0.4.0.tgz","fileCount":63,"integrity":"sha512-+okvVfIrm3UVdD3snfOd+LekQ4wJIMiQ3r7fVoyfWFrd6mDFKblLUTikvDS81SJ0ebyT46tbqcZ8xYjMH0OCdQ==","signatures":[{"sig":"MEUCIDwdN6VtnnmhtKQBjXxHRqm9xQqJasbyysI/w+WBTPiQAiEAnYspBem/WeGS6kCCPH/Iqqx/DHNX5ATLJmDa96Y5hXA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":356339},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./worker":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./browser":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"d3aac6fe8ae6a4f74da62f8cb3bbcfd661de94e5","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","verify":"npm run typecheck && npm test && npm run test:cloudflare && npm run build && npm run check:package","prepack":"npm run build","test:all":"npm test && npm run test:cloudflare","benchmark":"tsx scripts/benchmark.ts","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","check:package":"npm pack --dry-run && publint","test:mutation":"stryker run","test:cloudflare":"vitest run --config vitest.cloudflare.config.ts","test:cli:coverage":"vitest run --config vitest.cli-coverage.config.ts","benchmark:datasets":"tsx scripts/benchmark-datasets.ts"},"_npmUser":{"name":"ade_oshineye","email":"adewale@gmail.com"},"repository":{"url":"git+https://github.com/adewale/yaket.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript port of the YAKE keyword extraction core pipeline","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.6","vitest":"^4.1.2","esbuild":"^0.28.0","publint":"^0.3.15","wrangler":"^4.80.0","fast-check":"^4.3.0","typescript":"^5.9.3","@types/node":"^24.5.2","@vitest/coverage-v8":"^4.1.4","@stryker-mutator/core":"^9.3.0","@cloudflare/workers-types":"^4.20260405.1","@stryker-mutator/vitest-runner":"^9.3.0","@cloudflare/vitest-pool-workers":"^0.14.1","@stryker-mutator/typescript-checker":"^9.3.0"},"_npmOperationalInternal":{"tmp":"tmp/yaket_0.4.0_1776384793400_0.5643749064448351","host":"s3://npm-registry-packages-npm-production"}},"0.5.2":{"name":"@ade_oshineye/yaket","version":"0.5.2","keywords":["yake","keyword-extraction","nlp","typescript"],"license":"MIT","_id":"@ade_oshineye/yaket@0.5.2","maintainers":[{"name":"ade_oshineye","email":"adewale@gmail.com"}],"homepage":"https://github.com/adewale/yaket#readme","bugs":{"url":"https://github.com/adewale/yaket/issues"},"bin":{"yaket":"dist/cli.js"},"dist":{"shasum":"9ca020e064a28b380d7a16ad2f2d5db24de60bf5","tarball":"https://registry.npmjs.org/@ade_oshineye/yaket/-/yaket-0.5.2.tgz","fileCount":63,"integrity":"sha512-kG0rBULnhbYXJvYmd8c7Oo/y/e3cK0kbQqiTKO2kypOGpb1FrxetH/ySMAlA/QGuZoH4K+FFSIwZKn5n5zO1Kw==","signatures":[{"sig":"MEYCIQDYdzi3QveBAgO7OOREhQw//c+aPyMJamUCC2P4xOY5aQIhAK5gw+KPTEnfA0oo0nSVe/R/Tf3f7iGdpz2hkFUvcvBG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":371173},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./worker":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./browser":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"7949c8d0209ac74d70df52a3caf16ea074269f8f","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","verify":"npm run typecheck && npm test && npm run test:cloudflare && npm run build && npm run check:package","prepack":"npm run build","test:all":"npm test && npm run test:cloudflare","benchmark":"tsx scripts/benchmark.ts","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","check:package":"npm pack --dry-run && publint","test:mutation":"stryker run","test:cloudflare":"vitest run --config vitest.cloudflare.config.ts","test:cli:coverage":"vitest run --config vitest.cli-coverage.config.ts","benchmark:datasets":"tsx scripts/benchmark-datasets.ts"},"_npmUser":{"name":"ade_oshineye","email":"adewale@gmail.com"},"repository":{"url":"git+https://github.com/adewale/yaket.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript port of the YAKE keyword extraction core pipeline","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.6","vitest":"^4.1.2","esbuild":"^0.28.0","publint":"^0.3.15","wrangler":"^4.80.0","fast-check":"^4.3.0","typescript":"^5.9.3","@types/node":"^24.5.2","@vitest/coverage-v8":"^4.1.4","@stryker-mutator/core":"^9.3.0","@cloudflare/workers-types":"^4.20260405.1","@stryker-mutator/vitest-runner":"^9.3.0","@cloudflare/vitest-pool-workers":"^0.14.1","@stryker-mutator/typescript-checker":"^9.3.0"},"_npmOperationalInternal":{"tmp":"tmp/yaket_0.5.2_1776540925911_0.03637228602866749","host":"s3://npm-registry-packages-npm-production"}},"0.5.3":{"name":"@ade_oshineye/yaket","version":"0.5.3","description":"TypeScript port of the YAKE keyword extraction core pipeline","repository":{"type":"git","url":"git+https://github.com/adewale/yaket.git"},"homepage":"https://github.com/adewale/yaket#readme","bugs":{"url":"https://github.com/adewale/yaket/issues"},"engines":{"node":">=20"},"publishConfig":{"access":"public"},"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"yaket":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./browser":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./worker":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.json","benchmark":"tsx scripts/benchmark.ts","benchmark:datasets":"tsx scripts/benchmark-datasets.ts","check:package":"npm pack --dry-run && publint","prepack":"npm run build","test:cli:coverage":"vitest run --config vitest.cli-coverage.config.ts","test":"vitest run","test:cloudflare":"vitest run --config vitest.cloudflare.config.ts","test:all":"npm test && npm run test:cloudflare","test:mutation":"stryker run","test:watch":"vitest","typecheck":"tsc -p tsconfig.json --noEmit","verify":"npm run typecheck && npm test && npm run test:cloudflare && npm run build && npm run check:package"},"keywords":["yake","keyword-extraction","nlp","typescript"],"license":"MIT","devDependencies":{"@stryker-mutator/core":"^9.3.0","@stryker-mutator/typescript-checker":"^9.3.0","@stryker-mutator/vitest-runner":"^9.3.0","@cloudflare/vitest-pool-workers":"^0.14.1","@cloudflare/workers-types":"^4.20260405.1","@types/node":"^24.5.2","@vitest/coverage-v8":"^4.1.4","fast-check":"^4.3.0","esbuild":"^0.28.0","publint":"^0.3.15","tsx":"^4.20.6","typescript":"^5.9.3","vitest":"^4.1.2","wrangler":"^4.80.0"},"gitHead":"57aa5fbf63b39e5e5d014cc45019223f361518ad","_id":"@ade_oshineye/yaket@0.5.3","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-SVhmTleoqHFNAuvneaPd6bzVA5gN+PrYrXU79jYjCQG/DvZiKrHWvvj0B9K6XBE6cYsn4h7wbPve/Tg3TFAkJg==","shasum":"f1ccd6773a1af50481ed102eee64cf4a2da80393","tarball":"https://registry.npmjs.org/@ade_oshineye/yaket/-/yaket-0.5.3.tgz","fileCount":63,"unpackedSize":371173,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIQC5sq8ud/Et4GIUyrOrm2tFy7x1zt5OVOu3zxgwlqNX0AIfZG1dowZX5bv3N5Kxz81bRItbJVdjazWQ+lZZ99qlRQ=="}]},"_npmUser":{"name":"ade_oshineye","email":"adewale@gmail.com"},"directories":{},"maintainers":[{"name":"ade_oshineye","email":"adewale@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/yaket_0.5.3_1776541449652_0.5192183114138671"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-16T19:44:27.387Z","modified":"2026-04-18T19:44:10.046Z","0.2.0":"2026-04-16T19:44:27.636Z","0.3.0":"2026-04-16T21:09:07.727Z","0.4.0":"2026-04-17T00:13:13.529Z","0.5.2":"2026-04-18T19:35:26.051Z","0.5.3":"2026-04-18T19:44:09.919Z"},"bugs":{"url":"https://github.com/adewale/yaket/issues"},"license":"MIT","homepage":"https://github.com/adewale/yaket#readme","keywords":["yake","keyword-extraction","nlp","typescript"],"repository":{"type":"git","url":"git+https://github.com/adewale/yaket.git"},"description":"TypeScript port of the YAKE keyword extraction core pipeline","maintainers":[{"name":"ade_oshineye","email":"adewale@gmail.com"}],"readme":"# Yaket\n\n`Yaket` is a TypeScript keyword extraction library that ports the core YAKE pipeline into a form that works in Node, browser-style bundles, and Cloudflare Workers.\n\nIt is designed for teams that want upstream-like YAKE behavior, deterministic results, and a typed API that can plug into ingestion pipelines such as [Bobbin](https://github.com/adewale/bobbin) or future consumers such as `flux-search`.\n\n## Attribution\n\nYaket is an independent TypeScript port/reimplementation of the YAKE approach and is based on the upstream Python YAKE project:\n\n- Original project: [INESCTEC/yake](https://github.com/INESCTEC/yake)\n\nThe underlying research is:\n\n- Ricardo Campos, Vitor Mangaravite, Arian Pasquali, Alipio Jorge, Celine N. S. Santos, and Adam Jatowt.\n  *YAKE! Keyword Extraction from Single Documents using Multiple Local Features*.\n  Information Sciences 509 (2020), 257-289.\n  DOI: [`10.1016/j.ins.2019.09.013`](https://doi.org/10.1016/j.ins.2019.09.013)\n\nYaket aims to preserve the core YAKE behavior where practical, while adapting the implementation to JavaScript/TypeScript runtimes and edge-compatible packaging.\n\n## Why use it\n\n- **Upstream-shaped YAKE core**: `KeywordExtractor`, `DataCore`, `SingleWord`, `ComposedWord`, and the core scoring/dedup flow are implemented in TypeScript.\n- **Edge-safe extraction path**: stopwords are bundled, and the extraction core avoids Node-only runtime dependencies.\n- **Pipeline-friendly API**: one-shot extraction, reusable extractor instances, [Bobbin](https://github.com/adewale/bobbin)-compatible adapter output, and document-oriented helpers are all available.\n- **Surface-form preserving results**: returned `keyword` values keep the observed case from the source text, while `normalizedKeyword` carries the normalized matching form for downstream logic.\n- **Verification-heavy**: regression fixtures, Python parity checks, property-based tests, Cloudflare runtime tests, and a benchmark harness are checked in.\n\n## 30-Second Summary\n\nYaket extracts weighted keywords from a single document using a YAKE-style local-feature pipeline.\n\nIt is designed for cases where you want:\n\n1. a deterministic keyword extractor\n2. no LLM dependency\n3. browser/edge compatibility\n4. a practical JavaScript alternative to the Python YAKE package\n\n## Quick Start\n\n> Requires Node.js 20+\n\n```bash\nnpm install @ade_oshineye/yaket\n```\n\n```ts\nimport { extract } from \"@ade_oshineye/yaket\";\n\nconst keywords = extract(\n  \"Google is acquiring data science community Kaggle.\",\n  { language: \"en\", n: 3, top: 5 },\n);\n\nconsole.log(keywords);\n```\n\nExpected shape:\n\n```ts\n[\n  [\"science community Kaggle\", 0.022868570857866696],\n  [\"community Kaggle\", 0.04778970771086575],\n]\n```\n\n## Installation\n\nInstall from npm:\n\n```bash\nnpm install @ade_oshineye/yaket\n```\n\nThe package ships ESM output and exposes Worker/browser-safe entry points:\n\n- `@ade_oshineye/yaket`\n- `@ade_oshineye/yaket/browser`\n- `@ade_oshineye/yaket/worker`\n\n## Usage\n\n## Algorithm Summary\n\nAt a high level, Yaket:\n\n1. preprocesses text into sentences and tokens\n2. generates single-word and multi-word candidates up to `n`\n3. scores single words using local YAKE-style features such as frequency, spread, position, casing, and co-occurrence-derived relations\n4. scores composed phrases from those single-word scores\n5. deduplicates the ranked list with `seqm`, `levs`, or `jaro`\n\nSee `docs/architecture.md` for the pipeline structure and `docs/algorithm-drift.md` for known deviations from upstream YAKE.\n\n## Options Reference\n\nCommon options:\n\n| Option | Meaning | Default |\n|---|---|---|\n| `language` | language code | `en` |\n| `n` | maximum n-gram size | `3` |\n| `top` | number of results to return | `20` |\n| `dedupFunc` | dedup function (`seqm`, `levs`, `jaro`) | `seqm` |\n| `dedupLim` | dedup threshold | `0.9` |\n| `windowSize` | co-occurrence window | `1` |\n| `stopwords` | explicit stopword iterable override | bundled set for `lan` |\n\nFor the complete public API, see `docs/api-reference.md`.\n\nCanonical option names are:\n\n- `language`\n- `dedupLim`\n- `dedupFunc`\n- `windowSize`\n\nLegacy aliases such as `lan`, `dedup_lim`, `windowsSize`, and `window_size` are still accepted for backward compatibility, but new code should prefer the canonical names.\n\nIf you prefer the most concise one-shot API, `extract()` is an alias for `extractKeywords()`.\n\n### Reusable extractor\n\n```ts\nimport { KeywordExtractor } from \"@ade_oshineye/yaket\";\n\nconst extractor = new KeywordExtractor({\n  language: \"en\",\n  n: 3,\n  top: 10,\n});\n\nconst keywords = extractor.extractKeywords(\n  \"Cloudflare Workers process requests close to users.\",\n);\n```\n\n### Detailed keyword results\n\n```ts\nimport { extractKeywordDetails } from \"@ade_oshineye/yaket\";\n\nconst details = extractKeywordDetails(\"Machine learning improves software delivery.\", {\n  language: \"en\",\n  n: 2,\n  top: 5,\n});\n```\n\n`extractKeywordDetails()` returns:\n\n```ts\ntype KeywordResult = {\n  keyword: string;\n  normalizedKeyword: string;\n  score: number;\n  ngramSize: number;\n  occurrences: number;\n  sentenceIds: number[];\n};\n```\n\n`keyword` preserves the source-text surface form; `normalizedKeyword` is the normalized comparison key used for deduplication and downstream matching.\n\n### Document-oriented pipelines\n\n```ts\nimport { extractFromDocument, serializeDocumentKeywordResult } from \"@ade_oshineye/yaket\";\n\nconst result = extractFromDocument({\n  id: \"doc-1\",\n  language: \"en\",\n  title: \"Edge runtimes\",\n  body: \"Cloudflare Workers process requests close to users.\",\n});\n\nconst serialized = serializeDocumentKeywordResult(result);\n```\n\nDocument helpers also support lightweight pipeline hooks:\n\n- `beforeExtractText(text, context)` for pre-normalization before extraction\n- `afterExtractKeywords(keywords, context)` for post-ranking pipeline shaping\n\n### [Bobbin](https://github.com/adewale/bobbin)-compatible adapter\n\n```ts\nimport { extractYakeKeywords } from \"@ade_oshineye/yaket\";\n\nconst keywords = extractYakeKeywords(\n  \"Platform ecosystems reward integration.\",\n  5,\n  3,\n);\n```\n\nThis preserves [Bobbin's](https://github.com/adewale/bobbin) current output shape:\n\n```ts\ntype BobbinYakeResult = {\n  keyword: string;\n  score: number;\n};\n```\n\n### Custom hooks\n\n```ts\nimport { extractKeywordDetails } from \"@ade_oshineye/yaket\";\n\nconst details = extractKeywordDetails(\"models model models\", {\n  n: 1,\n  candidateNormalizer: {\n    normalize(token) {\n      return token.endsWith(\"s\") ? token.slice(0, -1) : token;\n    },\n  },\n  lemmatizer: {\n    lemmatize(token) {\n      return token;\n    },\n  },\n});\n```\n\nAvailable extension points:\n\n- `TextProcessor`\n- `StopwordProvider`\n- `SimilarityStrategy`\n- `CandidateNormalizer`\n- `Lemmatizer`\n- `SingleWordScorer`\n- `MultiWordScorer`\n- `KeywordScorer`\n- `candidateFilter`\n\n`lemmatizer` stays hook-based in Yaket. Upstream-style string backends such as `\"spacy\"` or `\"nltk\"` are intentionally not implemented in the extraction core.\n\nYaket also exports:\n\n- `YakeResult`\n- `YakeOptions`\n\nThe two first-class internal scoring hooks are:\n\n1. `singleWordScorer` for replacing the internal YAKE single-word score formula\n2. `multiWordScorer` for replacing the internal YAKE multi-word score formula\n\nExample:\n\n```ts\nimport { extractKeywordDetails } from \"@ade_oshineye/yaket\";\n\nconst details = extractKeywordDetails(\"agent swarms coordinate teams\", {\n  language: \"en\",\n  n: 2,\n  multiWordScorer: {\n    score(candidate) {\n      return candidate.size === 2 ? 0.001 : 10;\n    },\n  },\n});\n```\n\n### Stopwords and languages\n\n```ts\nimport { STOPWORDS, getStopwordText, supportedLanguages } from \"@ade_oshineye/yaket\";\n\nconsole.log(supportedLanguages.includes(\"en\"));\nconsole.log(getStopwordText(\"en\").split(\"\\n\").length > 0);\nconsole.log(STOPWORDS.en.includes(\"the\"));\n```\n\nLanguage lookup uses the first two letters of the requested language code.\nIf a specific stopword list is unavailable, Yaket currently resolves to an empty stopword list.\n\nTo extend or replace stopwords without mutating global state:\n\n```ts\nimport { createStaticStopwordProvider, createStopwordSet } from \"@ade_oshineye/yaket\";\n\nconst stopwords = createStopwordSet(\"en\", { add: [\"yaket\"], remove: [\"the\"] });\n\nconst provider = createStaticStopwordProvider({\n  en: stopwords,\n  pt: [\"um\", \"uma\"],\n});\n```\n\n`STOPWORDS` is exported as a frozen map of bundled raw stopword text for users who want direct access to the packaged lists.\n\n### Highlighting\n\n```ts\nimport { TextHighlighter, extractKeywords } from \"@ade_oshineye/yaket\";\n\nconst keywords = extractKeywords(\"Machine learning improves software delivery.\");\nconst highlighted = new TextHighlighter().highlight(\n  \"Machine learning improves software delivery.\",\n  keywords,\n);\n```\n\n## CLI\n\n```bash\nyaket --text-input \"Google is acquiring Kaggle\" --language en --ngram-size 3 --top 5 --verbose\n```\n\nSupported flags:\n\n- `--text-input`\n- `--input-file`\n- `--language`\n- `--ngram-size`\n- `--dedup-func`\n- `--dedup-lim`\n- `--window-size`\n- `--top`\n- `--verbose`\n- `--help`\n\n## Cloudflare Compatibility\n\nYaket keeps the extraction core free of runtime filesystem access and Node-only extraction dependencies.\n\nVerification currently includes:\n\n- source guards for extraction modules\n- browser-target bundling smoke tests\n- a real Cloudflare Workers test lane via `@cloudflare/vitest-pool-workers`\n\nRun it with:\n\n```bash\nnpm run test:cloudflare\n```\n\n## Benchmarks\n\nThe repository includes a benchmark harness that compares:\n\n- Yaket\n- upstream Python YAKE\n- the original [Bobbin](https://github.com/adewale/bobbin) YAKE-like implementation\n- a simple TF-IDF baseline\n\nCurrent checked-in report:\n\n- `docs/benchmarks/komoroske-2026-04-06.md`\n\nAdditional dataset-oriented benchmark support is available for Inspec and SemEval-style evaluation via `scripts/benchmark-datasets.ts`.\n\n```bash\nnpm run benchmark:datasets\n```\n\nRun it with:\n\n```bash\nnpm run benchmark\n```\n\n## Architecture\n\n- Architecture overview: `docs/architecture.md`\n- API reference: `docs/api-reference.md`\n- Use cases: `docs/use-cases.md`\n- Algorithm drift: `docs/algorithm-drift.md`\n- Dataset benchmarks: `docs/benchmarks/inspec-semeval.md`\n- [Bobbin](https://github.com/adewale/bobbin) integration guide: `docs/integrations/bobbin.md`\n- Generic pipeline guide: `docs/integrations/pipelines.md`\n- Releasing guide: `docs/releasing.md`\n- Contributing: `CONTRIBUTING.md`\n- Roadmap: `docs/roadmap.md`\n- Deferred work: `TODO.md`\n- Audit notes: `docs/audits/implementation-audit-2026-04-16.md`\n\n## Limitations\n\n- The tokenizer is close to YAKE, but still not a literal `segtok` port.\n- Dedup `seqm` behavior is still approximate rather than a byte-for-byte Python clone.\n- Multilingual support exists through bundled stopwords, but broad multilingual parity coverage is deferred.\n- Bobbin adapter validation now covers the Bobbin YAKE, topic-extractor, topic-system, and extraction-quality tests in the reference Bobbin checkout, but that validation still needs to be kept current as Bobbin evolves.\n\n## Comparison To Alternatives\n\n| Tool | Strength | Tradeoff vs Yaket |\n|---|---|---|\n| TF-IDF | simple, cheap, corpus-aware | less phrase-aware and less YAKE-like on single documents |\n| RAKE | simple phrase extraction | weaker local-feature scoring and usually cruder ranking |\n| KeyBERT | embedding-based semantic relevance | larger dependency/runtime cost and often slower |\n| Yaket | deterministic YAKE-style local-feature extraction in JS | still has some drift from upstream Python YAKE in tokenization and some heuristic edge cases |\n\nFor a concrete checked-in comparison, see the Komoroske benchmark report.\n\n## Main Use Cases\n\nYaket is especially well-suited for:\n\n1. blog/CMS/knowledge-base tagging\n2. newsletter and article topic extraction\n3. search indexing and hybrid retrieval metadata\n4. RAG chunk enrichment without an LLM call\n5. browser extensions and client-side page analysis\n6. chat and Slack bot topic tagging\n\nSee `docs/use-cases.md` for more detail.\n\n## Live Demo\n\nAn interactive demo page lives in `demo/index.html` and is intended to be served through GitHub Pages.\n\nGitHub Pages URL:\n\n- `https://adewale.github.io/yaket/`\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm test\nnpm run test:cli:coverage\nnpm run test:cloudflare\nnpm run build\nnpm run check:package\nnpm run benchmark\nnpm run verify\n```\n\n`test/python-parity.test.ts` performs a live comparison against upstream Python YAKE when `PYTHONPATH` points at a YAKE checkout. The default path used during local development is `/tmp/yake`.\n\nMutation testing is configured via `Stryker` and can be run separately when you want a slower audit-focused pass:\n\n```bash\nnpm run test:mutation\n```\n\n## When Not To Use Yaket\n\n- If you need corpus-wide topic modeling rather than single-document keyword extraction.\n- If you need production-grade lemmatization out of the box today.\n- If exact upstream Python tokenization parity across all languages is a hard requirement right now.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}