first commit
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# `validate-graph` — keyboard graph integrity guard
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [azerty](./azerty/azerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_ - [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_ - [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [validate-graph](./validate-graph.md) — <!-- begin:auto-docs --> _(no submodules)_- azerty — <!-- begin:auto-docs --> _(no submodules)_…
|
||||
- [azerty](./azerty/azerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [validate-graph](./validate-graph.md) — <!-- begin:auto-docs --> _(no submodules)_- [azerty](./azerty/azerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [validate-graph](./validate-graph.md) — <!-- begin:auto-docs --> _(no submodules)_- azerty — <!-- begin:auto-doc…
|
||||
- [azerty](./azerty/azerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [validate-graph](./validate-graph.md) — <!-- begin:auto-docs --> _(no submodules)_- azerty — <!-- begin:auto-docs --> _(no submodules)_…
|
||||
- [azerty](./azerty/azerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_ - [dvorak](./dvorak/dvorak.md) — <!-- begin:auto-docs --> _(no submodules)_- [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_ - [qwerty](./qwerty/qwerty.md) — <!-- begin:auto-docs --> _(no submodules)_- [validate-graph](./validate-graph.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
Validates that a user-supplied keyboard adjacency graph is well-formed
|
||||
before the `keyboardMatch` matcher walks it. A malformed graph would
|
||||
either produce nonsensical matches (self-loops) or cause inconsistent
|
||||
guess estimates across users typing the same word.
|
||||
|
||||
## Features
|
||||
|
||||
- Detects self-adjacency (a key listed as adjacent to itself)
|
||||
- Detects asymmetric edges (`a` lists `b` but `b` does not list `a`)
|
||||
- Throws `PasswordStrengthError` with code `PWSTRENGTH.GRAPH_INVALID`
|
||||
- Pure data validation; no side effects
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { validateGraph } from "@suaveplan/password-strength";
|
||||
import { QWERTY_GRAPH } from "@suaveplan/password-strength/graphs/qwerty";
|
||||
|
||||
validateGraph("qwerty", QWERTY_GRAPH); // returns; throws if malformed
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { validateGraph, PWSTRENGTH_CODES, PasswordStrengthError } from "@suaveplan/password-strength";
|
||||
|
||||
const broken = { q: ["q"] };
|
||||
try {
|
||||
validateGraph("broken", broken);
|
||||
} catch (e) {
|
||||
if (e instanceof PasswordStrengthError && e.code === PWSTRENGTH_CODES.GRAPH_INVALID) {
|
||||
console.log("graph rejected");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `validateGraph(name: string, graph: KeyboardGraph): void`
|
||||
- Throws `PasswordStrengthError` with code `PWSTRENGTH.GRAPH_INVALID` when
|
||||
the graph contains a self-loop or an asymmetric edge. The error message
|
||||
names the offending key for fast debugging.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The symmetry check is one-directional per pair — if `a` claims adjacency to
|
||||
`b`, then `b`'s adjacency slot list must contain `a` in at least one of its
|
||||
slots' character pairs (covering both the shifted and unshifted halves).
|
||||
Slots set to `null` are skipped. The walk is `O(n)` over the number of
|
||||
edges in the graph; running it on a 60-key graph is negligible.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user