{"_id":"@agentix-e/drain-ts","_rev":"5-b2ec1c75fcc72068034aa79573a397ec","name":"@agentix-e/drain-ts","dist-tags":{"latest":"1.1.1"},"versions":{"0.0.0":{"name":"@agentix-e/drain-ts","version":"0.0.0","keywords":["oidc","trusted-publishing","setup"],"_id":"@agentix-e/drain-ts@0.0.0","maintainers":[{"name":"lambertyan","email":"853901691@qq.com"}],"dist":{"shasum":"a6900fbdf07e4aeff1dd1c764d7f9f859a11bda6","tarball":"https://registry.npmjs.org/@agentix-e/drain-ts/-/drain-ts-0.0.0.tgz","fileCount":2,"integrity":"sha512-/UpaKxvuJvy0o4wV0tIz9/uRI9dfNRxc8qALol/KV0dWIWD/IwGhXdEbwz6QxGrtqxLoHkyDYKRD3UQeU5ob0Q==","signatures":[{"sig":"MEUCIQDCGvDQ8yihJd6kKys+mZCtN3Pt0lKqsE+UtyjjR7ILnQIgHSvjrJuwSjmFuJ4rFIJik1oSLK3aUT/TXcK+R2vPNrs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2039},"_npmUser":{"name":"lambertyan","email":"853901691@qq.com"},"_npmVersion":"11.9.0","description":"OIDC trusted publishing setup package for @agentix-e/drain-ts","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/drain-ts_0.0.0_1784591211060_0.9483805995573535","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@agentix-e/drain-ts","version":"1.0.0","keywords":["drain","drain3","log-parsing","log-template","log-miner","template-mining","log-analysis","observability","typescript","streaming"],"author":{"name":"Lambertyan","email":"lambertyan@agentix-e.dev"},"license":"MIT","_id":"@agentix-e/drain-ts@1.0.0","maintainers":[{"name":"lambertyan","email":"853901691@qq.com"}],"homepage":"https://github.com/AgentiX-E/drain-ts#readme","bugs":{"url":"https://github.com/AgentiX-E/drain-ts/issues"},"dist":{"shasum":"09dc0f1e2122a74399fbd7397815a907093baf05","tarball":"https://registry.npmjs.org/@agentix-e/drain-ts/-/drain-ts-1.0.0.tgz","fileCount":38,"integrity":"sha512-bhpMl+CAQpUJTKdJyzk4udIJAV03ZAQKj3X3Q+jf2ITcqNkFZlbdvpsfa0NjxZnPnUmDz6QpWV6DsLEaPw2wjg==","signatures":[{"sig":"MEQCIGbldFM5S813k0u25RQ+KzckAfLr3Tcd+Ib4SJLZgda3AiAk4tM8Fv5p9t64Zwq0DOXryd3y2+ep4jhxEPxTD54oJw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentix-e%2fdrain-ts@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":571994},"main":"./dist/index.cjs","pnpm":{"onlyBuiltDependencies":["esbuild"]},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=22.0.0","pnpm":">=10.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./presets":{"types":"./dist/presets.d.ts","import":"./dist/presets.mjs","require":"./dist/presets.cjs"}},"gitHead":"ef557cd5d5a6ac78fbdd08ba49c784e3021a77a6","scripts":{"ci":"pnpm build && pnpm typecheck && pnpm lint && pnpm format:check && pnpm test:coverage","dev":"tsup --watch","lint":"eslint src/ tests/ 2>/dev/null || echo '[lint] ESLint not configured'","test":"vitest run","build":"tsup","format":"prettier --write 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","ci:local":"pnpm build && pnpm typecheck && pnpm test:coverage","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","docs:generate":"typedoc --out docs/api src/index.ts","test:coverage":"vitest run --coverage","prepublishOnly":"pnpm build","test:benchmark":"vitest run --config vitest.benchmark.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:24025deb-2d4e-40a1-b41c-ae75daa77aeb"}},"repository":{"url":"git+https://github.com/AgentiX-E/drain-ts.git","type":"git"},"_npmVersion":"11.16.0","description":"A TypeScript/Node.js streaming log template miner based on the Drain algorithm, ported with high fidelity from the official Python Drain3 implementation.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","eslint":"^9.0.0","vitest":"^2.0.0","prettier":"^3.0.0","typescript":"^5.5.0","@types/node":"^22.0.0","@vitest/coverage-v8":"^2.0.0"},"peerDependencies":{},"peerDependenciesMeta":{},"_npmOperationalInternal":{"tmp":"tmp/drain-ts_1.0.0_1784595434201_0.33235445358412874","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@agentix-e/drain-ts","version":"1.0.1","keywords":["drain","drain3","log-parsing","log-template","log-miner","template-mining","log-analysis","observability","typescript","streaming","online-learning","log-clustering","structured-logging","nodejs","zero-dependency"],"author":{"name":"Lambertyan","email":"lambertyan@agentix-e.dev"},"license":"MIT","_id":"@agentix-e/drain-ts@1.0.1","maintainers":[{"name":"lambertyan","email":"853901691@qq.com"}],"homepage":"https://github.com/AgentiX-E/drain-ts#readme","bugs":{"url":"https://github.com/AgentiX-E/drain-ts/issues"},"dist":{"shasum":"a0ac9f1710f2f09d7bba5d823b5de13d10e891d1","tarball":"https://registry.npmjs.org/@agentix-e/drain-ts/-/drain-ts-1.0.1.tgz","fileCount":38,"integrity":"sha512-XxQTvqp9qKZ8Chu0NTyd0tCUj6cLogxK6xYzYM8SGwastsWtJ2JCFR29cCnBTL/dc2FuNMsGpVQ65GPc9CwGVg==","signatures":[{"sig":"MEQCIF8Jvi06RzsYIOIx/SvyyTpeRqljnuL9Xm/Igkfu0mOTAiBPSXyRdy3AFykCObSpTxz0vqMiHMomhtFIVOGaEYzi8A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentix-e%2fdrain-ts@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":572492},"main":"./dist/index.cjs","pnpm":{"onlyBuiltDependencies":["esbuild"]},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=22.0.0","pnpm":">=10.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./presets":{"types":"./dist/presets.d.ts","import":"./dist/presets.mjs","require":"./dist/presets.cjs"}},"gitHead":"fcaf157fda4d334af3fbc5229cdd6f9d0db9a7aa","scripts":{"ci":"pnpm build && pnpm typecheck && pnpm lint && pnpm format:check && pnpm test:coverage","dev":"tsup --watch","lint":"eslint src/ tests/ 2>/dev/null || echo '[lint] ESLint not configured'","test":"vitest run","build":"tsup","format":"prettier --write 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","ci:local":"pnpm build && pnpm typecheck && pnpm test:coverage","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","docs:generate":"typedoc --out docs/api src/index.ts","test:coverage":"vitest run --coverage","prepublishOnly":"pnpm build","test:benchmark":"vitest run --config vitest.benchmark.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:24025deb-2d4e-40a1-b41c-ae75daa77aeb"}},"repository":{"url":"git+https://github.com/AgentiX-E/drain-ts.git","type":"git"},"_npmVersion":"11.16.0","description":"A production-ready TypeScript/Node.js streaming log template miner — 1:1 port of the official Python Drain3 algorithm. Extracts structured templates from unstructured logs in real time using fixed-depth prefix tree clustering with zero runtime dependencie","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","eslint":"^9.0.0","vitest":"^2.0.0","prettier":"^3.0.0","typescript":"^5.5.0","@types/node":"^22.0.0","@vitest/coverage-v8":"^2.0.0"},"peerDependencies":{},"peerDependenciesMeta":{},"_npmOperationalInternal":{"tmp":"tmp/drain-ts_1.0.1_1784597463537_0.730265545670526","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@agentix-e/drain-ts","version":"1.1.0","keywords":["drain","drain3","log-parsing","log-template","log-miner","template-mining","log-analysis","observability","typescript","streaming","online-learning","log-clustering","structured-logging","nodejs","zero-dependency"],"author":{"name":"Lambertyan","email":"lambertyan@agentix-e.dev"},"license":"MIT","_id":"@agentix-e/drain-ts@1.1.0","maintainers":[{"name":"lambertyan","email":"853901691@qq.com"}],"homepage":"https://github.com/AgentiX-E/drain-ts#readme","bugs":{"url":"https://github.com/AgentiX-E/drain-ts/issues"},"dist":{"shasum":"b110aa184eb10a3fdfde1c31a2e90056b92f621a","tarball":"https://registry.npmjs.org/@agentix-e/drain-ts/-/drain-ts-1.1.0.tgz","fileCount":46,"integrity":"sha512-psH9uDu7BUgL1mAIUi9wqprHnJNPkp2AkC5vxiWRjRyeCO7ILD11Raw9LXvqSM8RIKmVjXYKogTVbPoFaZ4fUA==","signatures":[{"sig":"MEYCIQDKEfRUe1jDDhxUghZXfwHQTGcflik0V6khUC+kbOqXIgIhAN2c8W31UiQt6TLs3YV4WXbpi3/RE8P0mAqgryDBWEW3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentix-e%2fdrain-ts@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1267582},"main":"./dist/index.cjs","pnpm":{"overrides":{"esbuild":"0.28.1"},"onlyBuiltDependencies":["esbuild"]},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22.0.0","pnpm":">=10.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./presets":{"types":"./dist/presets.d.ts","import":"./dist/presets.js","require":"./dist/presets.cjs"}},"gitHead":"6e9836ff3f19aeab11a6f935261dfea7be3bece5","scripts":{"ci":"pnpm build && pnpm typecheck && pnpm lint && pnpm format:check && pnpm test:coverage","dev":"tsup --watch","lint":"eslint src/ tests/ 2>/dev/null || echo '[lint] ESLint not configured'","test":"vitest run","build":"tsup","format":"prettier --write 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","ci:local":"pnpm build && pnpm typecheck && pnpm test:coverage","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","test:browser":"pnpm build:browser && playwright test --config=playwright.config.ts","build:browser":"tsup src/browser-test.ts --format iife --out-dir dist-browser --target es2022 --platform browser --global-name DrainTs --clean","docs:generate":"typedoc --out docs/api src/index.ts","test:coverage":"vitest run --coverage","prepublishOnly":"pnpm build","test:benchmark":"vitest run --config vitest.benchmark.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:24025deb-2d4e-40a1-b41c-ae75daa77aeb"}},"repository":{"url":"git+https://github.com/AgentiX-E/drain-ts.git","type":"git"},"_npmVersion":"11.16.0","description":"A production-ready TypeScript/Node.js streaming log template miner — 1:1 port of the official Python Drain3 algorithm. Extracts structured templates from unstructured logs in real time using fixed-depth prefix tree clustering with zero runtime dependencie","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","vite":"^8.1.5","eslint":"^9.39.5","vitest":"^4.1.10","esbuild":"^0.28.1","prettier":"^3.9.6","playwright":"^1.62.0","typescript":"^5.9.3","@types/node":"^22.20.1","@playwright/test":"^1.62.0","@vitest/coverage-v8":"^4.1.10"},"peerDependenciesMeta":{},"_npmOperationalInternal":{"tmp":"tmp/drain-ts_1.1.0_1785253372245_0.9274870428454343","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@agentix-e/drain-ts","version":"1.1.1","description":"A production-ready TypeScript/Node.js streaming log template miner — 1:1 port of the official Python Drain3 algorithm. Extracts structured templates from unstructured logs in real time using fixed-depth prefix tree clustering with zero runtime dependencie","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./presets":{"types":"./dist/presets.d.ts","import":"./dist/presets.js","require":"./dist/presets.cjs"}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","test:browser":"pnpm build:browser && playwright test --config=playwright.config.ts","build:browser":"tsup src/browser-test.ts --format iife --out-dir dist-browser --target es2022 --platform browser --global-name DrainTs --clean","test:coverage":"vitest run --coverage","test:benchmark":"vitest run --config vitest.benchmark.config.ts","typecheck":"tsc --noEmit","lint":"eslint src/ tests/ 2>/dev/null || echo '[lint] ESLint not configured'","format":"prettier --write 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","format:check":"prettier --check 'src/**/*.ts' 'tests/**/*.ts' 'benchmark/**/*.ts' 'docs/**/*.md'","ci":"pnpm build && pnpm typecheck && pnpm lint && pnpm format:check && pnpm test:coverage","ci:local":"pnpm build && pnpm typecheck && pnpm test:coverage","docs:generate":"typedoc --out docs/api src/index.ts","prepublishOnly":"pnpm build"},"keywords":["drain","drain3","log-parsing","log-template","log-miner","template-mining","log-analysis","observability","typescript","streaming","online-learning","log-clustering","structured-logging","nodejs","zero-dependency"],"repository":{"type":"git","url":"git+https://github.com/AgentiX-E/drain-ts.git"},"license":"MIT","author":{"name":"Lambertyan","email":"lambertyan@agentix-e.dev"},"sideEffects":false,"engines":{"node":">=22.0.0","pnpm":">=10.0.0"},"peerDependenciesMeta":{},"devDependencies":{"@playwright/test":"^1.62.0","@types/node":"^22.20.1","@vitest/coverage-v8":"^4.1.10","esbuild":"^0.28.1","eslint":"^9.39.5","playwright":"^1.62.0","prettier":"^3.9.6","tsup":"^8.5.1","typescript":"^5.9.3","vite":"^8.1.5","vitest":"^4.1.10"},"pnpm":{"onlyBuiltDependencies":["esbuild"],"overrides":{"esbuild":"0.28.1"}},"gitHead":"ca2f3045a3ed6c1789d4eaf9d9da40c2f74e6eff","_id":"@agentix-e/drain-ts@1.1.1","bugs":{"url":"https://github.com/AgentiX-E/drain-ts/issues"},"homepage":"https://github.com/AgentiX-E/drain-ts#readme","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-c9oPaDwgG1dkRIUfDxT64q518iRcno5VGMwlmNu90U/qVCfJfBs6p1PMZG197wrL/ISx+l3c9S3RRH8se2746A==","shasum":"4981f8adfe70cd37298ccaa533a3f02a15864cd2","tarball":"https://registry.npmjs.org/@agentix-e/drain-ts/-/drain-ts-1.1.1.tgz","fileCount":46,"unpackedSize":1267583,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentix-e%2fdrain-ts@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID7AdH2zK7ctncSfpGlbJoPvhkmcBqeHWTQ28yzplGp2AiB+E0XzSoIFaXwHIj92KEQutjK52ENYMpYqrLN+bmlb9Q=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:24025deb-2d4e-40a1-b41c-ae75daa77aeb"}},"directories":{},"maintainers":[{"name":"lambertyan","email":"853901691@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/drain-ts_1.1.1_1785459603096_0.8861114245951134"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-20T23:46:50.814Z","modified":"2026-07-31T01:00:03.708Z","0.0.0":"2026-07-20T23:46:51.195Z","1.0.0":"2026-07-21T00:57:14.373Z","1.0.1":"2026-07-21T01:31:03.681Z","1.1.0":"2026-07-28T15:42:52.403Z","1.1.1":"2026-07-31T01:00:03.305Z"},"bugs":{"url":"https://github.com/AgentiX-E/drain-ts/issues"},"author":{"name":"Lambertyan","email":"lambertyan@agentix-e.dev"},"license":"MIT","homepage":"https://github.com/AgentiX-E/drain-ts#readme","keywords":["drain","drain3","log-parsing","log-template","log-miner","template-mining","log-analysis","observability","typescript","streaming","online-learning","log-clustering","structured-logging","nodejs","zero-dependency"],"repository":{"type":"git","url":"git+https://github.com/AgentiX-E/drain-ts.git"},"description":"A production-ready TypeScript/Node.js streaming log template miner — 1:1 port of the official Python Drain3 algorithm. Extracts structured templates from unstructured logs in real time using fixed-depth prefix tree clustering with zero runtime dependencie","maintainers":[{"name":"lambertyan","email":"853901691@qq.com"}],"readme":"# @agentix-e/drain-ts\n\n> TypeScript/Node.js streaming log template miner — 1:1 port of the official Python [Drain3](https://github.com/logpai/Drain3) v0.9.11, with zero runtime dependencies.\n\n[![CI](https://github.com/AgentiX-E/drain-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/AgentiX-E/drain-ts/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@agentix-e/drain-ts?color=blue)](https://www.npmjs.com/package/@agentix-e/drain-ts)\n[![Coverage](https://img.shields.io/badge/coverage-97%25-blue)](https://agentix-e.github.io/drain-ts/coverage-report/)\n[![Benchmark 2k](https://img.shields.io/badge/benchmark-Loghub%202k-blue)](https://agentix-e.github.io/drain-ts/benchmark-report/2k/)\n[![Benchmark Full](https://img.shields.io/badge/benchmark-Loghub%20full-blue)](https://agentix-e.github.io/drain-ts/benchmark-report/full/)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)](https://www.typescriptlang.org/)\n[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22-green)](https://nodejs.org/)\n\n---\n\n**What it does**: Turns raw logs like `\"connection from 192.168.1.1 port 8080\"` into structured templates like `\"connection from <IP> port <NUM>\"` — online, in a single pass, with no training required.\n\n**Why it matters**: Log parsing is the critical first step in any observability, anomaly detection, or log analytics pipeline. drain-ts gives you the same algorithm that powers [LogPAI's benchmark-leading Drain3](https://github.com/logpai/logparser) — in pure TypeScript, with zero Python dependency.\n\n```ts\nimport { TemplateMiner, TemplateMinerConfig, DEFAULT_MASKING_INSTRUCTIONS } from \"@agentix-e/drain-ts\";\n\nconst miner = new TemplateMiner({\n  config: TemplateMinerConfig.from({ maskingInstructions: DEFAULT_MASKING_INSTRUCTIONS }),\n});\n\nconst r1 = miner.addLogMessage(\"connection from 192.168.1.1 port 8080\");\nconsole.log(r1.templateMined); // \"connection from <IP> port <NUM>\"\n\nconst r2 = miner.addLogMessage(\"connection from 10.0.0.1 port 443\");\nconsole.log(r2.templateMined); // \"connection from <IP> port <NUM>\"\nconsole.log(r2.changeType);    // \"none\" — no template change needed\n```\n\n## Quick Install\n\n```bash\nnpm install @agentix-e/drain-ts\n# or\npnpm add @agentix-e/drain-ts\n```\n\n**Requirements**: Node.js ≥ 22\n\n## Why drain-ts?\n\n| | Drain3 (Python) | drain-ts (this project) |\n|---|---|---|\n| **Algorithm** | ✅ Fixed-depth prefix tree | ✅ 1:1 port, same tree structure |\n| **JaccardDrain** | ✅ | ✅ Same algorithm |\n| **match() inference** | ✅ 3 search strategies | ✅ 3 search strategies |\n| **extractParameters()** | ✅ | ✅ Exact + inexact matching |\n| **LRU eviction** | ✅ | ✅ Same eviction policy |\n| **Persistence** | ✅ File/Redis/Kafka | ✅ File/Memory + plugin interface |\n| **Profiling** | ✅ | ✅ Same section names + batch rates |\n| **Streaming** | ❌ | ✅ Node.js Transform stream |\n| **Round-robin pool** | ❌ | ✅ RoundRobinPool in-process instances |\n| **Browser support** | ❌ | ✅ Playwright-verified |\n| **Zero deps** | ❌ Requires pip | ✅ No runtime dependencies |\n| **Type safety** | ❌ Dynamic | ✅ Full TypeScript, strict mode |\n| **Run anywhere** | Python only | Node, Deno, Bun, Browser |\n| **Benchmark CI** | ❌ | ✅ Loghub 16-dataset CI + Pages |\n\n### Performance vs Drain3\n\nMeasured on the same HDFS dataset (2,000 messages, Node 22 LTS / Python 3.11):\n\n| Metric | Drain3 (Python) | drain-ts (Node.js) |\n|--------|----------------|-------------------|\n| Processing time | ~9 ms | ~5 ms |\n| Throughput | ~228k logs/sec | ~420k logs/sec |\n| Clusters (HDFS) | 17 | 16 |\n\n## Key Features\n\n- **Streaming**: Process logs one at a time — no batching, no training phase\n- **Online learning**: Templates evolve automatically as new log patterns appear\n- **Pre-built masks**: IP addresses, numbers, hex values, UUIDs, emails detected out of the box\n- **Custom masks**: Add your own regex patterns for domain-specific variables\n- **State persistence**: Save/restore the model to disk, Redis, S3 — or any custom backend\n- **Inference mode**: Classify new logs without modifying the model\n- **Parameter extraction**: Pull out the variable parts (IP, user ID, port) from matched logs\n\n## 60-Second Tutorials\n\n### Tutorial 1: Cluster Similar Logs\n\n```ts\nimport { TemplateMiner } from \"@agentix-e/drain-ts\";\n\nconst miner = new TemplateMiner();\n\n// Feed 3 similar messages — they'll be grouped together\nminer.addLogMessage(\"user alice logged in\");\nminer.addLogMessage(\"user bob logged in\");\nminer.addLogMessage(\"user carol logged in\");\n\n// Template automatically generalized to: \"user <*> logged in\"\nconst result = miner.addLogMessage(\"user dave logged in\");\nconsole.log(result.templateMined); // \"user <*> logged in\"\n```\n\n### Tutorial 2: Mask IPs and Numbers\n\n```ts\nimport { TemplateMiner, TemplateMinerConfig, DEFAULT_MASKING_INSTRUCTIONS } from \"@agentix-e/drain-ts\";\n\nconst config = TemplateMinerConfig.from({\n  maskingInstructions: DEFAULT_MASKING_INSTRUCTIONS,\n});\nconst miner = new TemplateMiner({ config });\n\nminer.addLogMessage(\"error code 42 at 192.168.1.1\");\nminer.addLogMessage(\"error code 500 at 10.0.0.1\");\n// Both map to: \"error code <NUM> at <IP>\"\n```\n\n### Tutorial 3: Classify Without Changing the Model\n\n```ts\n// After training, use match() for read-only classification\nconst cluster = miner.match(\"error code 99 at 172.16.0.1\");\nif (cluster) {\n  console.log(cluster.getTemplate()); // \"error code <NUM> at <IP>\"\n}\n```\n\n### Tutorial 4: Save and Restore State\n\n```ts\nimport { TemplateMiner, FilePersistence } from \"@agentix-e/drain-ts\";\n\nconst handler = new FilePersistence(\"./snapshot.json\");\nconst miner = new TemplateMiner({ persistenceHandler: handler });\n// State auto-saves on template changes\n// On restart: model loads from snapshot.json automatically\n```\n\n### Tutorial 5: Add Custom Masking Rules\n\n```ts\nimport { MaskingInstruction, TemplateMinerConfig } from \"@agentix-e/drain-ts\";\n\nconst sha1Mask = new MaskingInstruction(\n  String.raw`\\b[a-f0-9]{40}\\b`,\n  \"SHA1\"\n);\nconst config = TemplateMinerConfig.from({\n  maskingInstructions: [sha1Mask],\n});\n```\n\n## Configuration Reference\n\nAll parameters match Drain3 v0.9.11 defaults:\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `simTh` | `number` | `0.4` | Similarity threshold (0 = merge everything, 1 = exact only) |\n| `depth` | `number` | `4` | Parse tree depth (minimum 3) |\n| `maxChildren` | `number` | `100` | Max child nodes per tree level |\n| `maxClusters` | `number \\| null` | `null` | Max clusters; oldest evicted via LRU when limit exceeded |\n| `maskPrefix` / `maskSuffix` | `string` | `\"<\"` / `\">\"` | Delimiters for masked parameters in templates |\n| `maskingInstructions` | `MaskingInstruction[]` | `[]` | Regex patterns to apply before clustering |\n| `snapshotIntervalMinutes` | `number` | `1` | Minutes between periodic state snapshots |\n| `profilingEnabled` | `boolean` | `false` | Enable per-stage timing reports |\n| `enableAffixPreserving` | `boolean` | `false` | Token-level prefix/suffix param detection (e.g. \"bytes<*>sent\") |\n| `enableAdjacentFusion` | `boolean` | `false` | Auto-fuse adjacent constant tokens into compound tokens |\n| `enableClusterMerge` | `boolean` | `false` | Post-training cluster merge (AEL reconcile) |\n| `enableAELSimilarity` | `boolean` | `false` | Use AEL-style diff-ratio similarity instead of position-wise |\n\n## Architecture\n\n```\nTemplateMiner (public API)\n├── Drain (fixed-depth prefix tree clustering)\n│   ├── Node (tree nodes)\n│   ├── LogCluster (template + hit count)\n│   ├── LogClusterCache (LRU eviction when maxClusters reached)\n│   ├── SimilarityStrategyChain (pluggable similarity: PositionWise, AEL DiffRatio, Jaccard, TermPair)\n│   ├── TemplatePatternStrategyChain (pluggable token param: ExactMatch, AffixPreserving, Regex, FullToken)\n│   └── ClusterMergePipeline (post-training AEL reconcile for cluster consolidation)\n├── TokenNormalizerPipeline (pre-clustering: RegexSubstitution, RegexCollapse, AdjacentConstantFusion)\n├── LogMasker (pre-processing: replace variables with <PLACEHOLDER>)\n│   └── MaskingInstruction[] (IP, NUM, HEX, UUID, EMAIL, HOST_PORT, BLOCK_ID, PATH presets)\n├── PersistenceHandler (framework-agnostic save/load interface)\n│   ├── FilePersistence (built-in, zero deps)\n│   └── MemoryPersistence (built-in, zero deps)\n├── Profiler (optional wall-clock instrumentation)\n└── LRUCache<K,V> (generic cache for parameter extraction regex)\n```\n\n## Benchmark Results\n\n### Loghub-2k (16 datasets × 2,000 messages)\n\ndrain-ts is validated against all 16 [Loghub 2k](https://github.com/logpai/logparser) standard benchmark datasets using the four official metrics: GA, FGA, PTA, FTA.\n\n→ **[View Loghub-2k Benchmark →](https://agentix-e.github.io/drain-ts/benchmark-report/2k/)**\n\nAverage across all 16 datasets: **GA: 0.991, PTA: 0.828** (70k–420k logs/sec).\n\n```bash\nnpx tsx benchmark/run.ts --all       # All 16 Loghub 2k datasets\nnpx tsx benchmark/run.ts HDFS        # Single dataset\n```\n\n### Loghub-2.0 Full (14 datasets × up to 16.6M messages)\n\nRuns on-demand via per-dataset GitHub Actions workflows. Each dataset downloads from Zenodo and benchmarks independently.\n\n→ **[View Full Benchmark →](https://agentix-e.github.io/drain-ts/benchmark-report/full/)**\n→ **[Latest Full Run →](https://github.com/AgentiX-E/drain-ts/actions/workflows/benchmark-full.yml)**\n\n```bash\n# CI (on-demand, all 14 datasets in parallel)\ngh workflow run benchmark-full.yml -f datasets=all\n\n# Run locally (requires Zenodo download)\nnpx tsx benchmark/run-full.ts Proxifier --data-dir /path/to/dataset\nnpx tsx benchmark/run-full.ts --all --data-dir /path/to/datasets\n```\n\n## API Quick Reference\n\n| Method | Returns | Description |\n|---|---|---|\n| `new TemplateMiner(opts?)` | `TemplateMiner` | Create instance. Use `TemplateMiner.create()` for async persistence. |\n| `.addLogMessage(line)` | `AddLogResult` | Train: cluster a log line, may update templates |\n| `.match(line, strategy?)` | `LogCluster \\| null` | Inference: classify without modifying state |\n| `.extractParameters(tmpl, msg, exact?)` | `ExtractedParameter[]` | Get variable values from a log |\n| `TemplateMinerConfig.from(opts)` | `TemplateMinerConfig` | Create config with defaults + overrides |\n\n## Development\n\n```bash\ngit clone https://github.com/AgentiX-E/drain-ts.git\ncd drain-ts\npnpm install\npnpm test          # 548 tests\npnpm test:coverage # [Coverage report](https://agentix-e.github.io/drain-ts/coverage-report/) (enforced thresholds)\npnpm typecheck     # Strict TypeScript check\npnpm build         # ESM + CJS output\npnpm benchmark     # Run Loghub 2k benchmark (all 16 datasets)\n```\n\n## External Persistence\n\ndrain-ts ships with `FilePersistence` and `MemoryPersistence`. Implement the `PersistenceHandler` interface (~15 lines) for any backend:\n\n<details>\n<summary><b>Redis (ioredis)</b></summary>\n\n```typescript\nimport type { PersistenceHandler } from \"@agentix-e/drain-ts\";\nimport type { Redis } from \"ioredis\";\n\nclass RedisPersistence implements PersistenceHandler {\n  constructor(private redis: Redis, private key: string) {}\n  async saveState(state: Uint8Array) { await this.redis.set(this.key, Buffer.from(state)); }\n  async loadState() { const d = await this.redis.getBuffer(this.key); return d ? new Uint8Array(d) : null; }\n}\n```\n</details>\n\n<details>\n<summary><b>Kafka (kafkajs)</b></summary>\n\n```typescript\nimport type { PersistenceHandler } from \"@agentix-e/drain-ts\";\nimport type { Kafka, Producer } from \"kafkajs\";\n\nclass KafkaPersistence implements PersistenceHandler {\n  private producer: Producer;\n  constructor(kafka: Kafka, private topic: string) { this.producer = kafka.producer(); }\n  async saveState(state: Uint8Array) {\n    await this.producer.connect();\n    await this.producer.send({ topic: this.topic, messages: [{ value: Buffer.from(state) }] });\n    await this.producer.disconnect();\n  }\n  async loadState(): Promise<Uint8Array | null> { /* poll last message from topic */ return null; }\n}\n```\n</details>\n\n<details>\n<summary><b>S3 (@aws-sdk/client-s3)</b></summary>\n\n```typescript\nimport type { PersistenceHandler } from \"@agentix-e/drain-ts\";\nimport { S3Client, PutObjectCommand, GetObjectCommand } from \"@aws-sdk/client-s3\";\n\nclass S3Persistence implements PersistenceHandler {\n  constructor(private s3: S3Client, private bucket: string, private key: string) {}\n  async saveState(state: Uint8Array) { await this.s3.send(new PutObjectCommand({ Bucket: this.bucket, Key: this.key, Body: state })); }\n  async loadState() {\n    try { const r = await this.s3.send(new GetObjectCommand({ Bucket: this.bucket, Key: this.key })); return await r.Body?.transformToByteArray() ?? null; }\n    catch { return null; }\n  }\n}\n```\n</details>\n\n## License\n\nMIT © [Lambertyan](https://github.com/Lambertyan) / [AgentiX-E](https://github.com/AgentiX-E)\n\n## References\n\n- He et al. **\"Drain: An Online Log Parsing Approach with Fixed Depth Tree.\"** *IEEE ICWS 2017.*\n- [logpai/Drain3](https://github.com/logpai/Drain3) — Official Python implementation by IBM Research\n- [logpai/logparser](https://github.com/logpai/logparser) — Benchmark framework (ICSE 2019)\n- [logpai/loghub](https://github.com/logpai/loghub) — Standard log datasets (ISSRE 2023)\n","readmeFilename":"README.md"}