Entropy-based password validator for TypeScript. Pluggable character sets and pattern collapsers, no character-type mandates, no dictionaries, no network calls. Ships dual ESM + CJS with type declarations and runtime input validation via Zod.
The algorithm is a TypeScript port of wagslane/go-password-validator
- same character pools, same sequence/repeat penalties, same
log2(base^length)formula.
npm install @tsforge7/ts-password-validatorimport { validatePassword } from '@tsforge7/ts-password-validator';
const result = validatePassword('Ololo_123!');
// {
// valid: true,
// entropy: 55.5,
// base: 72,
// length: 10,
// effectiveLength: 9,
// strength: 'fair',
// minEntropy: 50,
// message: 'Password entropy 55.5 bits meets the required 50 bits (strength: fair).'
// }validatePassword always returns a full IValidationResult - boolean,
entropy, strength label, and a ready-to-show message.
import { validatePassword } from '@tsforge7/ts-password-validator';
validatePassword('Ololo_123!', { minEntropy: 70 });
// { valid: false, strength: 'fair', message: '... below the required 70 bits ...' }import { createPasswordValidator } from '@tsforge7/ts-password-validator';
const validator = createPasswordValidator({ minEntropy: 70 });
validator.check('Ololo_123!'); // -> IValidationResult
validator.validate('Ololo_123!'); // -> boolean
validator.getEntropy('Ololo_123!'); // -> number
validator.details('Ololo_123!'); // -> IEntropyDetailsimport {
classifyStrength,
getEntropyDetails,
PASSWORD_STRENGTH,
} from '@tsforge7/ts-password-validator';
classifyStrength(42); // 'weak'
getEntropyDetails('xK#9!mLp_2');
// { entropy: 65.5, base: 94, length: 10, effectiveLength: 10 }
// Compare against the const object, not magic strings
if (classifyStrength(42) === PASSWORD_STRENGTH.weak) {
// ...
}PASSWORD_STRENGTH is an as const object - its values are exactly the
strings in IValidationResult.strength, and TPasswordStrength is the
derived type.
import {
getPasswordEntropy,
validatePasswordEntropy,
} from '@tsforge7/ts-password-validator';
getPasswordEntropy('Ololo_123!'); // 55.5 (bits)
validatePasswordEntropy('Ololo_123!'); // truecreatePasswordValidator accepts a partial options bag - pass only what you
want to override.
import {
createPasswordValidator,
CharacterSet,
RepeatCollapser,
SequenceCollapser,
PASSWORD_STRENGTH,
} from '@tsforge7/ts-password-validator';
const validator = createPasswordValidator({
minEntropy: 60,
characterSets: [
new CharacterSet('абвгдеёжзийклмнопрстуфхцчшщъыьэюя'),
new CharacterSet('АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'),
],
sequences: ['абвгдеёжзийклмнопрстуфхцчшщъыьэюя'],
strengthThresholds: [
{ entropy: 80, strength: PASSWORD_STRENGTH.veryStrong },
{ entropy: 60, strength: PASSWORD_STRENGTH.strong },
{ entropy: 40, strength: PASSWORD_STRENGTH.fair },
{ entropy: 20, strength: PASSWORD_STRENGTH.weak },
{ entropy: 0, strength: PASSWORD_STRENGTH.veryWeak },
],
});
validator.check('Прекрасный_Пароль42');| Option | What it overrides | Default |
|---|---|---|
minEntropy |
Threshold for valid: true |
50 bits |
characterSets |
Character categories for the base | DEFAULT_CHARACTER_SETS |
sequences |
Sequences for the default collapser pipeline | DEFAULT_SEQUENCES |
collapsers |
Full collapser pipeline (wins over sequences) |
DEFAULT_COLLAPSERS |
strengthThresholds |
Mapping from entropy to label | DEFAULT_STRENGTH_THRESHOLDS |
Every public function and every constructor of a publicly-instantiable class
validates its inputs with Zod and throws a friendly
TypeError if the contract is broken. You get the same safety nets when
calling from JavaScript or with any-typed data.
validatePassword(123 as any);
// -> TypeError: Invalid password: password must be a string (got number).
validatePassword('x', { minEntropy: -1 });
// -> TypeError: Invalid options: minEntropy: Number must be greater than or equal to 0
validatePassword('x', { foo: 'bar' } as any);
// -> TypeError: Invalid options: Unrecognized key(s) in object: 'foo'
new CharacterSet('');
// -> TypeError: Invalid CharacterSet chars: CharacterSet chars must be a non-empty string
new RepeatCollapser(-1);
// -> TypeError: Invalid maxStreak: maxStreak must be a positive integerThe validatorOptionsSchema is .strict(), so typos in option keys are
caught immediately rather than silently ignored.
Schemas live in src/commands/ as TypeScript namespaces that bundle the
Zod schema with its derived TS type:
import { z } from 'zod';
export namespace PasswordCommand {
export const schema = z.string({ ... });
export type Type = z.infer<typeof schema>;
}Available commands: PasswordCommand, CharacterSetCommand,
CollapserCommand, StrengthCommand, ValidatorOptionsCommand. The
assertSchema(schema, value, label) helper does the safeParse +
TypeError wrap if you want to validate something yourself.
interface IValidationResult {
valid: boolean;
entropy: number; // bits
base: number; // pool size
length: number; // raw code points
effectiveLength: number; // after collapsing repeats/sequences
strength: TPasswordStrength;
minEntropy: number;
message: string;
}
// PASSWORD_STRENGTH is the source of truth - TPasswordStrength is derived
const PASSWORD_STRENGTH = {
veryWeak: 'very-weak', // 0 - 34 bits
weak: 'weak', // 35 - 49 bits
fair: 'fair', // 50 - 69 bits
strong: 'strong', // 70 - 99 bits
veryStrong: 'very-strong', // 100+ bits
} as const;
type TPasswordStrength =
(typeof PASSWORD_STRENGTH)[keyof typeof PASSWORD_STRENGTH];entropy(password) = effectiveLength · log2(base)
Sum of the sizes of every character category present in the password, plus 1 for each unique character outside those categories (e.g. unicode letters).
| Category | Default characters | Size |
|---|---|---|
| Replace | !@$&* |
5 |
| Separator | _-., (incl. space) |
5 |
| Other special | ", #, %, ', (, ), +, /, :, ;, <, =, >, ?, [, \, ], ^, {, |, }, ~ |
22 |
| Lowercase | a-z |
26 |
| Uppercase | A-Z |
26 |
| Digits | 0-9 |
10 |
| Unknown (each) | anything else (per unique character) | +1 |
Maximum base with all six categories is 94.
The length is reduced before the entropy calculation to avoid overestimating predictable patterns:
- Repeats - runs of three or more identical characters count as two.
aaaa-> effectiveaa. - Sequences - three or more consecutive characters from a known sequence
count as two, checked case-insensitively in both directions. Default
sequences:
0123456789,qwertyuiop,asdfghjkl,zxcvbnm,abcdefghijklmnopqrstuvwxyz. Example:qwerty->qw,9876->98.
DEFAULT_MIN_ENTROPY = 50 bits. The upstream Go project recommends 50-70
depending on threat model.
| Export | Returns |
|---|---|
validatePassword(password, options?) |
IValidationResult |
createPasswordValidator(options?) |
PasswordValidator |
getEntropyDetails(password) |
IEntropyDetails |
classifyStrength(entropy, thresholds?) |
TPasswordStrength |
getPasswordEntropy(password) |
number (bits) |
validatePasswordEntropy(password) |
boolean |
assertSchema(schema, value, label) |
parsed value |
| Export | Purpose |
|---|---|
PasswordValidator |
validate, check, getEntropy, details |
PasswordEntropyCalculator |
calculate, details |
BaseCalculator |
Character-pool size from a list of CharacterSets |
EffectiveLengthCalculator |
Reduces length through a pipeline of IPatternCollapsers |
CharacterSet |
A character pool with contains / size |
RepeatCollapser |
aaa… -> aa |
SequenceCollapser |
Forward/reverse sequence collapsing |
| Namespace | Members |
|---|---|
PasswordCommand |
schema, Type |
CharacterSetCommand |
charsSchema, Chars |
CollapserCommand |
sequenceSchema, maxStreakSchema, patternSchema, Sequence, MaxStreak, Pattern |
StrengthCommand |
passwordStrengthSchema, entropySchema, thresholdSchema, thresholdsSchema, Entropy, Threshold, Thresholds |
ValidatorOptionsCommand |
schema, Type |
| Export | Kind |
|---|---|
IValidationResult |
interface |
IEntropyDetails |
interface |
IPatternCollapser |
interface |
TPasswordStrength |
type |
PASSWORD_STRENGTH |
const |
DEFAULT_CHARACTER_SETS |
const |
DEFAULT_SEQUENCES |
const |
DEFAULT_COLLAPSERS |
const |
DEFAULT_MIN_ENTROPY |
const |
DEFAULT_STRENGTH_THRESHOLDS |
const |
defaultEntropyCalculator |
instance |
defaultPasswordValidator |
instance |
Output-only shapes (IValidationResult, IEntropyDetails,
IPatternCollapser) live in src/interfaces/ with an I prefix. Input
shapes that come with a runtime schema live in src/commands/ as
namespaces. Top-level type aliases (e.g. TPasswordStrength) get a T
prefix.
src/
├── base-calculator.ts
├── character-set.ts
├── defaults.ts
├── effective-length-calculator.ts
├── factory.ts
├── index.ts # public entry
├── password-entropy-calculator.ts
├── password-entropy.ts
├── password-validator.ts
├── strength.ts
├── validate.ts
├── collapsers/
│ ├── index.ts
│ ├── repeat-collapser.ts
│ └── sequence-collapser.ts
├── commands/ # Zod schemas + inferred types
│ ├── index.ts
│ ├── assert.ts
│ ├── character-set.command.ts
│ ├── collapser.command.ts
│ ├── password.command.ts
│ ├── strength.command.ts
│ └── validator-options.command.ts
├── constants/
│ ├── index.ts
│ ├── character-sets.constant.ts
│ ├── collapsers.constant.ts
│ ├── min-entropy.constant.ts
│ ├── password-strength.constant.ts
│ ├── sequences.constant.ts
│ └── strength-thresholds.constant.ts
└── interfaces/
├── index.ts
├── entropy-details.interface.ts
├── pattern-collapser.interface.ts
└── validation-result.interface.ts
Imports across the codebase go through folder barrels - from './constants',
from './interfaces', from './collapsers', from './commands' - never
through a specific *.constant.ts / *.interface.ts / *.command.ts file
from outside its folder.
npm test # vitest (86 tests covering logic + input validation)
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run format # prettier --write
npm run build # tsup -> dist/index.js (ESM) + dist/index.cjs + typesThe build (tsup + esbuild + rollup-plugin-dts) emits a flat dist/:
dist/
├── index.js # ESM bundle
├── index.cjs # CJS bundle
├── index.d.ts # ESM types
├── index.d.cts # CJS types
└── *.map # sourcemaps
For interactive download graphs, package comparisons and bundle-size analysis:
- 📈 Download chart - npm-stat.com
- 🔄 Compare with other validators - npmtrends
- 📦 Bundle analysis - bundlephobia
- 🔒 Security advisor - Snyk
MIT © 2026 tsforge7