@ekaone/entropy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eka Prasetia <ekaone3033@gmail.com> (https://prasetia.me)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @ekaone/entropy
2
+
3
+ > Under Active Development, not release yet
4
+
5
+ Primitive Shannon entropy measurement for strings.
6
+
7
+ Measures **randomness density** — best used for generated tokens, secrets, and API keys. Not intended for judging human-chosen passwords. Refer to [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory)) for details.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @ekaone/entropy
13
+ ```
14
+
15
+ ```bash
16
+ yarn add @ekaone/entropy
17
+ ```
18
+
19
+ ```bash
20
+ pnpm add @ekaone/entropy
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Basic — pure measurement
26
+
27
+ ```ts
28
+ import { calculate } from "@ekaone/entropy"
29
+
30
+ calculate("hsg3-3;gs")
31
+ // { entropy: 3.46, length: 9, unique: 8 }
32
+ ```
33
+
34
+ ### With `level` option
35
+
36
+ ```ts
37
+ calculate("hsg3-3;gs", { level: true })
38
+ // { entropy: 3.46, length: 9, unique: 8, level: "medium" }
39
+ ```
40
+
41
+ ### With `charset` option
42
+
43
+ ```ts
44
+ calculate("hsg3-3;gs", { level: true, charset: true })
45
+ // { entropy: 3.46, length: 9, unique: 8, level: "medium", charset: "mixed" }
46
+ ```
47
+
48
+ ### Custom thresholds
49
+
50
+ ```ts
51
+ calculate("hsg3-3;gs", {
52
+ level: { thresholds: { high: 5.0 } }
53
+ })
54
+ ```
55
+
56
+ ### Tree-shakeable primitives
57
+
58
+ ```ts
59
+ import { shannonEntropy } from "@ekaone/entropy"
60
+ import { classifyLevel, DEFAULT_THRESHOLDS } from "@ekaone/entropy"
61
+ import { detectCharset } from "@ekaone/entropy"
62
+ ```
63
+
64
+ ## API
65
+
66
+ ### `calculate(input, options?)`
67
+
68
+ | Field | Type | Description |
69
+ |---|---|---|
70
+ | `entropy` | `number` | Shannon bits per character |
71
+ | `length` | `number` | String length |
72
+ | `unique` | `number` | Count of distinct characters |
73
+ | `level` | `EntropyLevel` | Returned when `options.level` is set |
74
+ | `charset` | `CharSet` | Returned when `options.charset` is set |
75
+
76
+ ### Options
77
+
78
+ | Option | Type | Description |
79
+ |---|---|---|
80
+ | `level` | `boolean \| { thresholds? }` | Enable level classification |
81
+ | `charset` | `boolean` | Enable charset detection |
82
+
83
+ ### Entropy levels (defaults)
84
+
85
+ | Level | Bits | Typical example |
86
+ |---|---|---|
87
+ | `low` | < 2.5 | `"aaaa"`, `"1111"` |
88
+ | `medium` | 2.5–3.5 | `"hsg3-3;gs"` |
89
+ | `high` | 3.5–4.5 | random hex hashes |
90
+ | `critical` | ≥ 4.5 | API keys, JWT secrets |
91
+
92
+ ### Charset detection
93
+
94
+ | Charset | Rule |
95
+ |---|---|
96
+ | `numeric` | digits only |
97
+ | `alpha` | letters only |
98
+ | `hex` | `[0-9a-fA-F]` with both digits and letters, no letters outside `a-f` |
99
+ | `alphanumeric` | letters + digits, not purely hex |
100
+ | `mixed` | contains special characters |
101
+
102
+ > **Note:** `"deadbeef"` classifies as `alpha` (no digits). `"abc123"` classifies as `hex` (all chars within hex range). This is expected — see [charset ambiguity](#charset-ambiguity).
103
+
104
+ ### Charset ambiguity
105
+
106
+ Strings using only `[0-9a-fA-F]` characters are inherently ambiguous. `@ekaone/entropy` resolves this by requiring hex strings to contain **both digits and letters within the `a-f` range**. Pure-letter strings always classify as `alpha` regardless of whether their characters fall within hex range.
107
+
108
+ ## Building on top
109
+
110
+ This package is a primitive. Higher-level opinions live in consumer packages:
111
+
112
+ ```ts
113
+ import { calculate } from "@ekaone/entropy"
114
+
115
+ // your app defines "strong"
116
+ const { entropy, length } = calculate(token)
117
+ const isStrong = entropy >= 4.0 && length >= 16
118
+
119
+ // your UI defines the label
120
+ const score = Math.round((entropy / 6.55) * 100)
121
+ ```
122
+
123
+ ## License
124
+
125
+ MIT © Eka Prasetia
126
+
127
+ ## Links
128
+
129
+ - [npm Package](https://www.npmjs.com/package/@ekaone/entropy)
130
+ - [GitHub Repository](https://github.com/ekaone/entropy)
131
+ - [Issue Tracker](https://github.com/ekaone/entropy/issues)
@@ -0,0 +1,57 @@
1
+ type EntropyLevel = "low" | "medium" | "high" | "critical";
2
+ type CharSet = "alpha" | "numeric" | "alphanumeric" | "hex" | "mixed";
3
+ interface EntropyThresholds {
4
+ low: number;
5
+ medium: number;
6
+ high: number;
7
+ }
8
+ interface EntropyOptions {
9
+ level?: boolean | {
10
+ thresholds?: Partial<EntropyThresholds>;
11
+ };
12
+ charset?: boolean;
13
+ }
14
+ interface EntropyResult {
15
+ entropy: number;
16
+ length: number;
17
+ unique: number;
18
+ level?: EntropyLevel;
19
+ charset?: CharSet;
20
+ }
21
+
22
+ /**
23
+ * Calculates Shannon entropy of a string.
24
+ * Returns bits of entropy per character (0 to ~6.55 for printable ASCII).
25
+ *
26
+ * Best used for measuring randomness density of generated strings,
27
+ * tokens, secrets, and API keys — not human-chosen passwords.
28
+ *
29
+ * @see https://en.wikipedia.org/wiki/Entropy_(information_theory)
30
+ */
31
+ declare function shannonEntropy(input: string): number;
32
+
33
+ declare const DEFAULT_THRESHOLDS: EntropyThresholds;
34
+ declare function classifyLevel(entropy: number, thresholds?: EntropyThresholds): EntropyLevel;
35
+ declare function resolveThresholds(overrides?: Partial<EntropyThresholds>): EntropyThresholds;
36
+
37
+ /**
38
+ * Detects the character set family of a string.
39
+ *
40
+ * Priority:
41
+ * 1. numeric — digits only
42
+ * 2. alpha — letters only (checked before hex to avoid "deadbeef" → hex)
43
+ * 3. hex — hex chars [0-9a-fA-F] AND has both a digit and a letter AND no letters outside a-f range
44
+ * 4. alphanumeric — letters + digits, but NOT purely hex
45
+ * 5. mixed — everything else (has special chars)
46
+ */
47
+ declare function detectCharset(input: string): CharSet;
48
+
49
+ /**
50
+ * index.ts
51
+ * @description Primitive Shannon entropy measurement for strings.
52
+ * @author Eka Prasetia
53
+ */
54
+
55
+ declare function calculate(input: string, options?: EntropyOptions): EntropyResult;
56
+
57
+ export { type CharSet, DEFAULT_THRESHOLDS, type EntropyLevel, type EntropyOptions, type EntropyResult, type EntropyThresholds, calculate, classifyLevel, detectCharset, resolveThresholds, shannonEntropy };
@@ -0,0 +1,57 @@
1
+ type EntropyLevel = "low" | "medium" | "high" | "critical";
2
+ type CharSet = "alpha" | "numeric" | "alphanumeric" | "hex" | "mixed";
3
+ interface EntropyThresholds {
4
+ low: number;
5
+ medium: number;
6
+ high: number;
7
+ }
8
+ interface EntropyOptions {
9
+ level?: boolean | {
10
+ thresholds?: Partial<EntropyThresholds>;
11
+ };
12
+ charset?: boolean;
13
+ }
14
+ interface EntropyResult {
15
+ entropy: number;
16
+ length: number;
17
+ unique: number;
18
+ level?: EntropyLevel;
19
+ charset?: CharSet;
20
+ }
21
+
22
+ /**
23
+ * Calculates Shannon entropy of a string.
24
+ * Returns bits of entropy per character (0 to ~6.55 for printable ASCII).
25
+ *
26
+ * Best used for measuring randomness density of generated strings,
27
+ * tokens, secrets, and API keys — not human-chosen passwords.
28
+ *
29
+ * @see https://en.wikipedia.org/wiki/Entropy_(information_theory)
30
+ */
31
+ declare function shannonEntropy(input: string): number;
32
+
33
+ declare const DEFAULT_THRESHOLDS: EntropyThresholds;
34
+ declare function classifyLevel(entropy: number, thresholds?: EntropyThresholds): EntropyLevel;
35
+ declare function resolveThresholds(overrides?: Partial<EntropyThresholds>): EntropyThresholds;
36
+
37
+ /**
38
+ * Detects the character set family of a string.
39
+ *
40
+ * Priority:
41
+ * 1. numeric — digits only
42
+ * 2. alpha — letters only (checked before hex to avoid "deadbeef" → hex)
43
+ * 3. hex — hex chars [0-9a-fA-F] AND has both a digit and a letter AND no letters outside a-f range
44
+ * 4. alphanumeric — letters + digits, but NOT purely hex
45
+ * 5. mixed — everything else (has special chars)
46
+ */
47
+ declare function detectCharset(input: string): CharSet;
48
+
49
+ /**
50
+ * index.ts
51
+ * @description Primitive Shannon entropy measurement for strings.
52
+ * @author Eka Prasetia
53
+ */
54
+
55
+ declare function calculate(input: string, options?: EntropyOptions): EntropyResult;
56
+
57
+ export { type CharSet, DEFAULT_THRESHOLDS, type EntropyLevel, type EntropyOptions, type EntropyResult, type EntropyThresholds, calculate, classifyLevel, detectCharset, resolveThresholds, shannonEntropy };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var y=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var i=(e,t)=>{for(var o in t)y(e,o,{get:t[o],enumerable:!0})},d=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of u(t))!E.call(e,r)&&r!==o&&y(e,r,{get:()=>t[r],enumerable:!(n=p(t,r))||n.enumerable});return e};var L=e=>d(y({},"__esModule",{value:!0}),e);var O={};i(O,{DEFAULT_THRESHOLDS:()=>h,calculate:()=>_,classifyLevel:()=>c,detectCharset:()=>a,resolveThresholds:()=>f,shannonEntropy:()=>l});module.exports=L(O);function l(e){var r;if(e.length===0)return 0;let t=new Map;for(let s of e)t.set(s,((r=t.get(s))!=null?r:0)+1);let o=e.length,n=0;for(let s of t.values()){let m=s/o;n-=m*Math.log2(m)}return Math.round(n*1e3)/1e3}var h={low:2.5,medium:3.5,high:4.5};function c(e,t=h){return e<t.low?"low":e<t.medium?"medium":e<t.high?"high":"critical"}function f(e){return{...h,...e}}var T=/^[0-9]+$/,g=/^[a-zA-Z]+$/,A=/^[0-9a-fA-F]+$/,v=/^[a-zA-Z0-9]+$/,x=/[0-9]/,H=/[a-zA-Z]/,S=/[g-zG-Z]/;function a(e){return e.length===0?"mixed":T.test(e)?"numeric":g.test(e)?"alpha":A.test(e)&&x.test(e)&&H.test(e)&&!S.test(e)?"hex":v.test(e)?"alphanumeric":"mixed"}function _(e,t){let o=l(e),n={entropy:o,length:e.length,unique:new Set(e).size};if(t!=null&&t.level){let r=f(typeof t.level=="object"?t.level.thresholds:void 0);n.level=c(o,r)}return t!=null&&t.charset&&(n.charset=a(e)),n}0&&(module.exports={DEFAULT_THRESHOLDS,calculate,classifyLevel,detectCharset,resolveThresholds,shannonEntropy});
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/calculate.ts","../src/level.ts","../src/charset.ts"],"sourcesContent":["/**\n * index.ts\n * @description Primitive Shannon entropy measurement for strings.\n * @author Eka Prasetia\n */\n\nimport { shannonEntropy } from \"./calculate\";\nimport { classifyLevel, resolveThresholds } from \"./level\";\nimport { detectCharset } from \"./charset\";\nimport type { EntropyOptions, EntropyResult } from \"./types\";\n\nexport function calculate(\n input: string,\n options?: EntropyOptions,\n): EntropyResult {\n const entropy = shannonEntropy(input);\n\n const result: EntropyResult = {\n entropy,\n length: input.length,\n unique: new Set(input).size,\n };\n\n if (options?.level) {\n const thresholds = resolveThresholds(\n typeof options.level === \"object\" ? options.level.thresholds : undefined,\n );\n result.level = classifyLevel(entropy, thresholds);\n }\n\n if (options?.charset) {\n result.charset = detectCharset(input);\n }\n\n return result;\n}\n\n// named exports for tree-shaking\nexport { shannonEntropy } from \"./calculate\";\nexport { classifyLevel, resolveThresholds, DEFAULT_THRESHOLDS } from \"./level\";\nexport { detectCharset } from \"./charset\";\nexport type {\n EntropyOptions,\n EntropyResult,\n EntropyLevel,\n CharSet,\n EntropyThresholds,\n} from \"./types\";\n","/**\r\n * Calculates Shannon entropy of a string.\r\n * Returns bits of entropy per character (0 to ~6.55 for printable ASCII).\r\n *\r\n * Best used for measuring randomness density of generated strings,\r\n * tokens, secrets, and API keys — not human-chosen passwords.\r\n *\r\n * @see https://en.wikipedia.org/wiki/Entropy_(information_theory)\r\n */\r\nexport function shannonEntropy(input: string): number {\r\n if (input.length === 0) return 0;\r\n\r\n const freq = new Map<string, number>();\r\n\r\n for (const char of input) {\r\n freq.set(char, (freq.get(char) ?? 0) + 1);\r\n }\r\n\r\n const len = input.length;\r\n let entropy = 0;\r\n\r\n for (const count of freq.values()) {\r\n const p = count / len;\r\n entropy -= p * Math.log2(p);\r\n }\r\n\r\n return Math.round(entropy * 1000) / 1000; // 3 decimal precision\r\n}\r\n","import type { EntropyLevel, EntropyThresholds } from \"./types\";\r\n\r\nexport const DEFAULT_THRESHOLDS: EntropyThresholds = {\r\n low: 2.5,\r\n medium: 3.5,\r\n high: 4.5,\r\n // above 4.5 → \"critical\"\r\n};\r\n\r\nexport function classifyLevel(\r\n entropy: number,\r\n thresholds: EntropyThresholds = DEFAULT_THRESHOLDS,\r\n): EntropyLevel {\r\n if (entropy < thresholds.low) return \"low\";\r\n if (entropy < thresholds.medium) return \"medium\";\r\n if (entropy < thresholds.high) return \"high\";\r\n return \"critical\";\r\n}\r\n\r\nexport function resolveThresholds(\r\n overrides?: Partial<EntropyThresholds>,\r\n): EntropyThresholds {\r\n return { ...DEFAULT_THRESHOLDS, ...overrides };\r\n}\r\n","import type { CharSet } from \"./types\";\r\n\r\nconst ONLY_DIGITS = /^[0-9]+$/;\r\nconst ONLY_ALPHA = /^[a-zA-Z]+$/;\r\nconst ONLY_HEX = /^[0-9a-fA-F]+$/;\r\nconst ONLY_ALPHANUM = /^[a-zA-Z0-9]+$/;\r\nconst HAS_DIGIT = /[0-9]/;\r\nconst HAS_ALPHA = /[a-zA-Z]/;\r\nconst HAS_NON_HEX_LETTER = /[g-zG-Z]/; // letters outside hex range\r\n\r\n/**\r\n * Detects the character set family of a string.\r\n *\r\n * Priority:\r\n * 1. numeric — digits only\r\n * 2. alpha — letters only (checked before hex to avoid \"deadbeef\" → hex)\r\n * 3. hex — hex chars [0-9a-fA-F] AND has both a digit and a letter AND no letters outside a-f range\r\n * 4. alphanumeric — letters + digits, but NOT purely hex\r\n * 5. mixed — everything else (has special chars)\r\n */\r\nexport function detectCharset(input: string): CharSet {\r\n if (input.length === 0) return \"mixed\";\r\n\r\n if (ONLY_DIGITS.test(input)) return \"numeric\";\r\n if (ONLY_ALPHA.test(input)) return \"alpha\";\r\n\r\n // hex: must match hex pattern AND have both digits and letters AND no letters outside a-f range\r\n if (\r\n ONLY_HEX.test(input) &&\r\n HAS_DIGIT.test(input) &&\r\n HAS_ALPHA.test(input) &&\r\n !HAS_NON_HEX_LETTER.test(input)\r\n )\r\n return \"hex\";\r\n\r\n if (ONLY_ALPHANUM.test(input)) return \"alphanumeric\";\r\n\r\n return \"mixed\";\r\n}\r\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,EAAA,cAAAC,EAAA,kBAAAC,EAAA,kBAAAC,EAAA,sBAAAC,EAAA,mBAAAC,IAAA,eAAAC,EAAAR,GCSO,SAASS,EAAeC,EAAuB,CATtD,IAAAC,EAUE,GAAID,EAAM,SAAW,EAAG,MAAO,GAE/B,IAAME,EAAO,IAAI,IAEjB,QAAWC,KAAQH,EACjBE,EAAK,IAAIC,IAAOF,EAAAC,EAAK,IAAIC,CAAI,IAAb,KAAAF,EAAkB,GAAK,CAAC,EAG1C,IAAMG,EAAMJ,EAAM,OACdK,EAAU,EAEd,QAAWC,KAASJ,EAAK,OAAO,EAAG,CACjC,IAAMK,EAAID,EAAQF,EAClBC,GAAWE,EAAI,KAAK,KAAKA,CAAC,CAC5B,CAEA,OAAO,KAAK,MAAMF,EAAU,GAAI,EAAI,GACtC,CCzBO,IAAMG,EAAwC,CACnD,IAAK,IACL,OAAQ,IACR,KAAM,GAER,EAEO,SAASC,EACdC,EACAC,EAAgCH,EAClB,CACd,OAAIE,EAAUC,EAAW,IAAY,MACjCD,EAAUC,EAAW,OAAe,SACpCD,EAAUC,EAAW,KAAa,OAC/B,UACT,CAEO,SAASC,EACdC,EACmB,CACnB,MAAO,CAAE,GAAGL,EAAoB,GAAGK,CAAU,CAC/C,CCrBA,IAAMC,EAAc,WACdC,EAAa,cACbC,EAAW,iBACXC,EAAgB,iBAChBC,EAAY,QACZC,EAAY,WACZC,EAAqB,WAYpB,SAASC,EAAcC,EAAwB,CACpD,OAAIA,EAAM,SAAW,EAAU,QAE3BR,EAAY,KAAKQ,CAAK,EAAU,UAChCP,EAAW,KAAKO,CAAK,EAAU,QAIjCN,EAAS,KAAKM,CAAK,GACnBJ,EAAU,KAAKI,CAAK,GACpBH,EAAU,KAAKG,CAAK,GACpB,CAACF,EAAmB,KAAKE,CAAK,EAEvB,MAELL,EAAc,KAAKK,CAAK,EAAU,eAE/B,OACT,CH3BO,SAASC,EACdC,EACAC,EACe,CACf,IAAMC,EAAUC,EAAeH,CAAK,EAE9BI,EAAwB,CAC5B,QAAAF,EACA,OAAQF,EAAM,OACd,OAAQ,IAAI,IAAIA,CAAK,EAAE,IACzB,EAEA,GAAIC,GAAA,MAAAA,EAAS,MAAO,CAClB,IAAMI,EAAaC,EACjB,OAAOL,EAAQ,OAAU,SAAWA,EAAQ,MAAM,WAAa,MACjE,EACAG,EAAO,MAAQG,EAAcL,EAASG,CAAU,CAClD,CAEA,OAAIJ,GAAA,MAAAA,EAAS,UACXG,EAAO,QAAUI,EAAcR,CAAK,GAG/BI,CACT","names":["index_exports","__export","DEFAULT_THRESHOLDS","calculate","classifyLevel","detectCharset","resolveThresholds","shannonEntropy","__toCommonJS","shannonEntropy","input","_a","freq","char","len","entropy","count","p","DEFAULT_THRESHOLDS","classifyLevel","entropy","thresholds","resolveThresholds","overrides","ONLY_DIGITS","ONLY_ALPHA","ONLY_HEX","ONLY_ALPHANUM","HAS_DIGIT","HAS_ALPHA","HAS_NON_HEX_LETTER","detectCharset","input","calculate","input","options","entropy","shannonEntropy","result","thresholds","resolveThresholds","classifyLevel","detectCharset"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ function l(e){var n;if(e.length===0)return 0;let t=new Map;for(let s of e)t.set(s,((n=t.get(s))!=null?n:0)+1);let o=e.length,r=0;for(let s of t.values()){let y=s/o;r-=y*Math.log2(y)}return Math.round(r*1e3)/1e3}var h={low:2.5,medium:3.5,high:4.5};function c(e,t=h){return e<t.low?"low":e<t.medium?"medium":e<t.high?"high":"critical"}function f(e){return{...h,...e}}var m=/^[0-9]+$/,p=/^[a-zA-Z]+$/,u=/^[0-9a-fA-F]+$/,E=/^[a-zA-Z0-9]+$/,i=/[0-9]/,d=/[a-zA-Z]/,L=/[g-zG-Z]/;function a(e){return e.length===0?"mixed":m.test(e)?"numeric":p.test(e)?"alpha":u.test(e)&&i.test(e)&&d.test(e)&&!L.test(e)?"hex":E.test(e)?"alphanumeric":"mixed"}function S(e,t){let o=l(e),r={entropy:o,length:e.length,unique:new Set(e).size};if(t!=null&&t.level){let n=f(typeof t.level=="object"?t.level.thresholds:void 0);r.level=c(o,n)}return t!=null&&t.charset&&(r.charset=a(e)),r}export{h as DEFAULT_THRESHOLDS,S as calculate,c as classifyLevel,a as detectCharset,f as resolveThresholds,l as shannonEntropy};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/calculate.ts","../src/level.ts","../src/charset.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Calculates Shannon entropy of a string.\r\n * Returns bits of entropy per character (0 to ~6.55 for printable ASCII).\r\n *\r\n * Best used for measuring randomness density of generated strings,\r\n * tokens, secrets, and API keys — not human-chosen passwords.\r\n *\r\n * @see https://en.wikipedia.org/wiki/Entropy_(information_theory)\r\n */\r\nexport function shannonEntropy(input: string): number {\r\n if (input.length === 0) return 0;\r\n\r\n const freq = new Map<string, number>();\r\n\r\n for (const char of input) {\r\n freq.set(char, (freq.get(char) ?? 0) + 1);\r\n }\r\n\r\n const len = input.length;\r\n let entropy = 0;\r\n\r\n for (const count of freq.values()) {\r\n const p = count / len;\r\n entropy -= p * Math.log2(p);\r\n }\r\n\r\n return Math.round(entropy * 1000) / 1000; // 3 decimal precision\r\n}\r\n","import type { EntropyLevel, EntropyThresholds } from \"./types\";\r\n\r\nexport const DEFAULT_THRESHOLDS: EntropyThresholds = {\r\n low: 2.5,\r\n medium: 3.5,\r\n high: 4.5,\r\n // above 4.5 → \"critical\"\r\n};\r\n\r\nexport function classifyLevel(\r\n entropy: number,\r\n thresholds: EntropyThresholds = DEFAULT_THRESHOLDS,\r\n): EntropyLevel {\r\n if (entropy < thresholds.low) return \"low\";\r\n if (entropy < thresholds.medium) return \"medium\";\r\n if (entropy < thresholds.high) return \"high\";\r\n return \"critical\";\r\n}\r\n\r\nexport function resolveThresholds(\r\n overrides?: Partial<EntropyThresholds>,\r\n): EntropyThresholds {\r\n return { ...DEFAULT_THRESHOLDS, ...overrides };\r\n}\r\n","import type { CharSet } from \"./types\";\r\n\r\nconst ONLY_DIGITS = /^[0-9]+$/;\r\nconst ONLY_ALPHA = /^[a-zA-Z]+$/;\r\nconst ONLY_HEX = /^[0-9a-fA-F]+$/;\r\nconst ONLY_ALPHANUM = /^[a-zA-Z0-9]+$/;\r\nconst HAS_DIGIT = /[0-9]/;\r\nconst HAS_ALPHA = /[a-zA-Z]/;\r\nconst HAS_NON_HEX_LETTER = /[g-zG-Z]/; // letters outside hex range\r\n\r\n/**\r\n * Detects the character set family of a string.\r\n *\r\n * Priority:\r\n * 1. numeric — digits only\r\n * 2. alpha — letters only (checked before hex to avoid \"deadbeef\" → hex)\r\n * 3. hex — hex chars [0-9a-fA-F] AND has both a digit and a letter AND no letters outside a-f range\r\n * 4. alphanumeric — letters + digits, but NOT purely hex\r\n * 5. mixed — everything else (has special chars)\r\n */\r\nexport function detectCharset(input: string): CharSet {\r\n if (input.length === 0) return \"mixed\";\r\n\r\n if (ONLY_DIGITS.test(input)) return \"numeric\";\r\n if (ONLY_ALPHA.test(input)) return \"alpha\";\r\n\r\n // hex: must match hex pattern AND have both digits and letters AND no letters outside a-f range\r\n if (\r\n ONLY_HEX.test(input) &&\r\n HAS_DIGIT.test(input) &&\r\n HAS_ALPHA.test(input) &&\r\n !HAS_NON_HEX_LETTER.test(input)\r\n )\r\n return \"hex\";\r\n\r\n if (ONLY_ALPHANUM.test(input)) return \"alphanumeric\";\r\n\r\n return \"mixed\";\r\n}\r\n","/**\n * index.ts\n * @description Primitive Shannon entropy measurement for strings.\n * @author Eka Prasetia\n */\n\nimport { shannonEntropy } from \"./calculate\";\nimport { classifyLevel, resolveThresholds } from \"./level\";\nimport { detectCharset } from \"./charset\";\nimport type { EntropyOptions, EntropyResult } from \"./types\";\n\nexport function calculate(\n input: string,\n options?: EntropyOptions,\n): EntropyResult {\n const entropy = shannonEntropy(input);\n\n const result: EntropyResult = {\n entropy,\n length: input.length,\n unique: new Set(input).size,\n };\n\n if (options?.level) {\n const thresholds = resolveThresholds(\n typeof options.level === \"object\" ? options.level.thresholds : undefined,\n );\n result.level = classifyLevel(entropy, thresholds);\n }\n\n if (options?.charset) {\n result.charset = detectCharset(input);\n }\n\n return result;\n}\n\n// named exports for tree-shaking\nexport { shannonEntropy } from \"./calculate\";\nexport { classifyLevel, resolveThresholds, DEFAULT_THRESHOLDS } from \"./level\";\nexport { detectCharset } from \"./charset\";\nexport type {\n EntropyOptions,\n EntropyResult,\n EntropyLevel,\n CharSet,\n EntropyThresholds,\n} from \"./types\";\n"],"mappings":"AASO,SAASA,EAAeC,EAAuB,CATtD,IAAAC,EAUE,GAAID,EAAM,SAAW,EAAG,MAAO,GAE/B,IAAME,EAAO,IAAI,IAEjB,QAAWC,KAAQH,EACjBE,EAAK,IAAIC,IAAOF,EAAAC,EAAK,IAAIC,CAAI,IAAb,KAAAF,EAAkB,GAAK,CAAC,EAG1C,IAAMG,EAAMJ,EAAM,OACdK,EAAU,EAEd,QAAWC,KAASJ,EAAK,OAAO,EAAG,CACjC,IAAMK,EAAID,EAAQF,EAClBC,GAAWE,EAAI,KAAK,KAAKA,CAAC,CAC5B,CAEA,OAAO,KAAK,MAAMF,EAAU,GAAI,EAAI,GACtC,CCzBO,IAAMG,EAAwC,CACnD,IAAK,IACL,OAAQ,IACR,KAAM,GAER,EAEO,SAASC,EACdC,EACAC,EAAgCH,EAClB,CACd,OAAIE,EAAUC,EAAW,IAAY,MACjCD,EAAUC,EAAW,OAAe,SACpCD,EAAUC,EAAW,KAAa,OAC/B,UACT,CAEO,SAASC,EACdC,EACmB,CACnB,MAAO,CAAE,GAAGL,EAAoB,GAAGK,CAAU,CAC/C,CCrBA,IAAMC,EAAc,WACdC,EAAa,cACbC,EAAW,iBACXC,EAAgB,iBAChBC,EAAY,QACZC,EAAY,WACZC,EAAqB,WAYpB,SAASC,EAAcC,EAAwB,CACpD,OAAIA,EAAM,SAAW,EAAU,QAE3BR,EAAY,KAAKQ,CAAK,EAAU,UAChCP,EAAW,KAAKO,CAAK,EAAU,QAIjCN,EAAS,KAAKM,CAAK,GACnBJ,EAAU,KAAKI,CAAK,GACpBH,EAAU,KAAKG,CAAK,GACpB,CAACF,EAAmB,KAAKE,CAAK,EAEvB,MAELL,EAAc,KAAKK,CAAK,EAAU,eAE/B,OACT,CC3BO,SAASC,EACdC,EACAC,EACe,CACf,IAAMC,EAAUC,EAAeH,CAAK,EAE9BI,EAAwB,CAC5B,QAAAF,EACA,OAAQF,EAAM,OACd,OAAQ,IAAI,IAAIA,CAAK,EAAE,IACzB,EAEA,GAAIC,GAAA,MAAAA,EAAS,MAAO,CAClB,IAAMI,EAAaC,EACjB,OAAOL,EAAQ,OAAU,SAAWA,EAAQ,MAAM,WAAa,MACjE,EACAG,EAAO,MAAQG,EAAcL,EAASG,CAAU,CAClD,CAEA,OAAIJ,GAAA,MAAAA,EAAS,UACXG,EAAO,QAAUI,EAAcR,CAAK,GAG/BI,CACT","names":["shannonEntropy","input","_a","freq","char","len","entropy","count","p","DEFAULT_THRESHOLDS","classifyLevel","entropy","thresholds","resolveThresholds","overrides","ONLY_DIGITS","ONLY_ALPHA","ONLY_HEX","ONLY_ALPHANUM","HAS_DIGIT","HAS_ALPHA","HAS_NON_HEX_LETTER","detectCharset","input","calculate","input","options","entropy","shannonEntropy","result","thresholds","resolveThresholds","classifyLevel","detectCharset"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@ekaone/entropy",
3
+ "version": "0.0.1",
4
+ "description": "Primitive Shannon entropy measurement for strings.",
5
+ "keywords": [
6
+ "typescript",
7
+ "library",
8
+ "privacy",
9
+ "security",
10
+ "entropy",
11
+ "randomness",
12
+ "density"
13
+ ],
14
+ "author": {
15
+ "name": "Eka Prasetia",
16
+ "email": "ekaone3033@gmail.com",
17
+ "url": "https://prasetia.me"
18
+ },
19
+ "license": "MIT",
20
+ "sideEffects": false,
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.mjs",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.mjs",
31
+ "require": "./dist/index.js"
32
+ }
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/ekaone/entropy.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/ekaone/entropy/issues"
46
+ },
47
+ "homepage": "https://github.com/ekaone/entropy#readme",
48
+ "devDependencies": {
49
+ "@types/node": "^25.5.0",
50
+ "@vitest/coverage-v8": "^4.1.0",
51
+ "@vitest/ui": "^4.1.0",
52
+ "rimraf": "^6.1.3",
53
+ "tsup": "^8.5.1",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.1.0"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup",
59
+ "dev": "tsup --watch",
60
+ "clean": "rimraf dist",
61
+ "typecheck": "tsc --noEmit",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
64
+ "test:ui": "vitest --ui",
65
+ "test:coverage": "vitest run --coverage"
66
+ }
67
+ }