2.8 KiB
qwerty — QWERTY keyboard adjacency graph
(no submodules)
Purpose
Encodes the physical adjacency of keys on a standard US QWERTY layout so
the keyboardMatch matcher can recognise spatial walks like qwerty,
asdf, and 1qaz2wsx. The graph drives the keyboard-walk guess estimate
in Wheeler 2016 section 4.5. The encoding mirrors the upstream zxcvbn
reference implementation's adjacency_graphs.coffee.
Features
- Complete coverage of the alphanumeric, symbol, and shifted-symbol rows
- Adjacency slots in zxcvbn's canonical order (left, top-left, top-right, right, bottom-right, bottom-left)
- Each slot encodes both the unshifted and shifted character at that key
- Frozen at module load — safe against accidental mutation
Usage: Basic Example
import { QWERTY_GRAPH } from "@suaveplan/password-strength/graphs/qwerty";
console.log(QWERTY_GRAPH.q); // [null, "1!", "2@", "wW", "aA", null]
Usage: Advanced Example
import { keyboardMatch } from "@suaveplan/password-strength/matchers/keyboard";
import { QWERTY_GRAPH } from "@suaveplan/password-strength/graphs/qwerty";
import { DVORAK_GRAPH } from "@suaveplan/password-strength/graphs/dvorak";
const matches = keyboardMatch("qwerty", {
graphs: { qwerty: QWERTY_GRAPH, dvorak: DVORAK_GRAPH },
});
console.log(matches);
API Reference
QWERTY_GRAPH: KeyboardGraph— frozenRecord<string, ReadonlyArray<string | null>>mapping each key to its six adjacency slots.
Implementation Notes
The matcher checks both characters of each slot when classifying a key
press: matching against the second character flags the press as
shift-required (shiftedCount += 1). Slots that point off the edge of the
keyboard are null. The graph deliberately preserves the upstream slot
order so callers porting from zxcvbn consume the same data layout.
Additional Implementation Notes
The implementation follows the canonical zxcvbn algorithm from Wheeler 2016 with
the suaveplan-style additions described in the package proposal: structured error
codes via the defineCodes registry, Zod boundaries at every public entry
point, OTel histograms and counters keyed to low-cardinality attributes, and
a strict input-length guard at 200 characters. Consumers integrate with their
existing observability stack by installing a meter provider before importing
the package, and with their i18n stack via the MessageResolver callback type
declared in @suaveplan/types. The bundled data tables are intentionally
small to keep the package bundle compact; production deployments should swap
in fuller frequency-ranked tables via loadDictionaries. The default English
dictionary set is a frozen DictionarySet that the matcher never mutates;
caller-supplied dictionaries override defaults by name.