@cloudyventures/baseh 1.1.0 → 2.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/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @cloudyventures/baseh
2
+
3
+ TypeScript implementation of the baseH (Human Reference Code) codec. Encodes
4
+ integer IDs as checksummed, human-friendly reference codes — short codes that
5
+ grow automatically in expandable mode (recommended), or fixed-length codes on
6
+ the classic tiers — with a feistel-v1 permutation on every tier and profanity
7
+ safety. The normative spec is `spec/IMPLEMENTATION_CODEC.md` in the
8
+ [monorepo](https://github.com/cloudyventures/baseh).
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install @cloudyventures/baseh
14
+ ```
15
+
16
+ One runtime dependency (`@noble/hashes`, auditable and dependency-free itself). Requires Node 18 or later (native `BigInt`).
17
+
18
+ ## Expandable mode (recommended)
19
+
20
+ Every profile carries a `mode` field: `"expandable"` or `"fixed"`. Expandable
21
+ is the recommended default for new users: codes start short (minimum 4
22
+ characters, profile field `minLength`) and grow automatically as the ID
23
+ sequence climbs past each length's capacity — transparently, with no
24
+ migration and no re-issue. Shorter codes already issued keep decoding
25
+ forever; the code's length selects the generation on decode.
26
+
27
+ The recommended starting tier is `baseh-expandable-v1`:
28
+
29
+ ```typescript
30
+ import { Baseh, basehExpandableV1 } from "@cloudyventures/baseh";
31
+
32
+ const codec = new Baseh(basehExpandableV1());
33
+
34
+ const code = codec.encode(123456n); // short code; grows as ids climb
35
+
36
+ const result = codec.decode(code);
37
+ result.id; // 123456n
38
+ ```
39
+
40
+ Expandable mode differs from the fixed tiers as follows:
41
+
42
+ - The default body alphabet is the 27 symbols left after the medium visual
43
+ and spoken safety strips and the `0`/`O` zero ban, so an issued code
44
+ never emits a visual or spoken confusable. A custom alphabet that includes
45
+ `0`/`O` has those symbols silently removed during profile preparation.
46
+ - The checksum alphabet is the body alphabet plus `0` (28 symbols by
47
+ default). The `O -> 0` input alias remains, so a misread `O` in a checksum
48
+ position resolves to `0`; a `0` or `O` in a body position is simply an
49
+ invalid character.
50
+ - The short checksum is on by default (codec spec 22): one checksum symbol
51
+ through 5 characters, two beyond; generation 4 holds 19,683 ids (3 body +
52
+ 1 checksum) instead of 729. Configure with `shortChecksumLength` and
53
+ `shortChecksumUntil`; `shortChecksumUntil: 0` turns it off.
54
+ - There is no left-padding; codes use exactly the length of the current
55
+ generation.
56
+ - The Feistel permutation stays on, applied per generation with the code
57
+ length mixed into the key derivation alongside the profile id. Codes within
58
+ each length look random even though issuance is a sequential counter.
59
+ Presentation only, not encryption — same caveat as the fixed tiers.
60
+ - Separators only appear once codes reach `separatorMinLength` characters
61
+ (6 in the shipped tier). Below that threshold there is no separator and no
62
+ grouping. Above it the split is the balanced rule of codec spec 19.5 —
63
+ a pure function of the code length (`XXX-XXX` at 6, `XXXX-XXX` at 7,
64
+ `XXXX-XXXX` at 8) — so expandable profiles carry no `grouping` field.
65
+ - All other profile options — visual/spoken safety levels, profanity modes,
66
+ blocklists — compose with expandable unchanged.
67
+ - The repetition filter is on by default here too: the tier ships
68
+ `maxRepetition: 4`, so a code with a run of four or more identical symbols
69
+ is never issued (any floor of 3 or more is configurable; 0 turns it off).
70
+
71
+ A keyed private-mapping variant `baseh-expandable-p-v1` mirrors the `-p`
72
+ fixed tiers:
73
+
74
+ ```typescript
75
+ import { basehExpandablePV1 } from "@cloudyventures/baseh";
76
+
77
+ const codec = new Baseh(
78
+ basehExpandablePV1({ keyBytes, keyId: "prod-01" })
79
+ );
80
+ ```
81
+
82
+ Security posture is unchanged: a code is a reference alias, never an
83
+ authorization token. The smallest expandable generations are a small
84
+ namespace, so rate-limit public lookups and enforce authorization after
85
+ decode.
86
+
87
+ ## Frozen tiers (fixed mode)
88
+
89
+ The classic frozen tiers are all `mode: "fixed"`: constant-width codes for
90
+ when you need a stable printed length. Four frozen tiers ship with the
91
+ package, built from the full alphanumeric set with cumulative visual and
92
+ spoken strips. All four encode 6 body symbols, are case-insensitive,
93
+ hyphen-delimit at the midpoint, run the default profanity blocklist, block
94
+ runs of four or more identical symbols (the repetition filter,
95
+ `maxRepetition: 4` — configurable to any floor of 3 or more, or 0 to turn it
96
+ off) and permute with the published frozen key.
97
+
98
+ | Tier | Helper | Body symbols | Checksum | Format | Capacity |
99
+ | ---- | ------ | ------------ | -------- | ------ | -------- |
100
+ | Minimum | `basehMinimumV1` | 36 | none | `XXX-XXX` | 2,176,782,336 |
101
+ | Light | `basehLightV1` | 31 | 2 | `XXXX-XXXX` | 887,503,681 |
102
+ | Medium | `basehMediumV1` | 28 | 2 | `XXXX-XXXX` | 481,890,304 |
103
+ | Heavy | `basehHeavyV1` | 26 | 2 | `XXXX-XXXX` | 308,915,776 |
104
+
105
+ Medium is the default fixed tier. The frozen key is public by design: it
106
+ hides sequence, not records. See the spec, section 7.5.
107
+
108
+ ## Usage
109
+
110
+ ```typescript
111
+ import { Baseh, basehExpandableV1 } from "@cloudyventures/baseh";
112
+
113
+ const codec = new Baseh(basehExpandableV1());
114
+
115
+ const code = codec.encode(123456n); // short code; grows as ids climb
116
+
117
+ const result = codec.decode(code);
118
+ result.id; // 123456n
119
+ result.canonicalCode; // canonical form
120
+ result.corrected; // true when input needed correction
121
+
122
+ codec.capacity; // capacity of the current generation
123
+
124
+ const check = codec.validate("00000000");
125
+ check.valid; // false
126
+ check.reason; // "INVALID_CHECKSUM"
127
+
128
+ // Spoken-confusion correction
129
+ codec.decode("TB14QDFU", { tryCorrection: true, confusionProfile: "light" });
130
+ ```
131
+
132
+ Fixed mode works the same way, through a fixed tier helper:
133
+
134
+ ```typescript
135
+ import { basehMediumV1 } from "@cloudyventures/baseh";
136
+
137
+ const fixed = new Baseh(basehMediumV1());
138
+ const code = fixed.encode(123456n); // fixed-width hyphenated code
139
+ ```
140
+
141
+ IDs are `bigint`, so every capacity and ID operation is exact at any size.
142
+
143
+ ## Permutation
144
+
145
+ The plain tiers permute with `FROZEN_KEY_BYTES` (the published frozen key).
146
+ The `P` variants take a caller-supplied key instead; keep that key in a
147
+ secret manager and never change it for a live profile:
148
+
149
+ ```typescript
150
+ import { basehMediumPV1 } from "@cloudyventures/baseh";
151
+
152
+ const codec = new Baseh(
153
+ basehMediumPV1({ keyBytes, keyId: "prod-01" })
154
+ );
155
+ ```
156
+
157
+ ## Errors
158
+
159
+ All failures raise `BasehError` with a `code` from the spec:
160
+ `INVALID_PROFILE`, `OUT_OF_RANGE`, `PERMUTATION_FAILURE`, `INVALID_LENGTH`,
161
+ `INVALID_CHARACTER`, `INVALID_CHECKSUM`, `AMBIGUOUS_INPUT`,
162
+ `TOO_MANY_CANDIDATES` and `BLOCKED_CODE`. `validate` never raises on user
163
+ input.
164
+
165
+ ## License
166
+
167
+ AGPL-3.0. Commercial licensing available; see
168
+ [COMMERCIAL.md](https://github.com/cloudyventures/baseh/blob/main/COMMERCIAL.md).
package/dist/basen.d.ts CHANGED
@@ -3,3 +3,5 @@ export declare function encodeBaseN(value: bigint, alphabet: string, length: num
3
3
  /** Spec 5.2. */
4
4
  export declare function decodeBaseN(text: string, alphabet: string, index: Map<string, bigint>): bigint;
5
5
  export declare function alphabetIndex(alphabet: string): Map<string, bigint>;
6
+ /** Integer power for bigint bases; shared by profile, codec and checksum. */
7
+ export declare function powBigInt(base: bigint, exp: number): bigint;
package/dist/basen.js CHANGED
@@ -32,3 +32,10 @@ export function alphabetIndex(alphabet) {
32
32
  [...alphabet].forEach((ch, i) => m.set(ch, BigInt(i)));
33
33
  return m;
34
34
  }
35
+ /** Integer power for bigint bases; shared by profile, codec and checksum. */
36
+ export function powBigInt(base, exp) {
37
+ let result = 1n;
38
+ for (let i = 0; i < exp; i += 1)
39
+ result *= base;
40
+ return result;
41
+ }
package/dist/blocklist.js CHANGED
@@ -5,6 +5,9 @@ export const DEFAULT_BLOCKLIST = [
5
5
  "SHT", "CNT", "TWT", "DCK", "AZZ", "BCH"
6
6
  ];
7
7
  const WORD = /^[A-Za-z]{2,32}$/;
8
+ // JS `$` also matches before a trailing newline, so the charset test alone
9
+ // accepts "abc\n"; an explicit newline check restores Ruby's \A...\z strictness.
10
+ const NEWLINE = /[\r\n]/;
8
11
  function fail(reason) {
9
12
  throw new BasehError("INVALID_PROFILE", `Invalid baseH profile: ${reason}`, false);
10
13
  }
@@ -14,7 +17,7 @@ export function effectiveBlocklist(profanity) {
14
17
  const list = [...base, ...(profanity.extraWords ?? [])];
15
18
  const out = [];
16
19
  for (const word of list) {
17
- if (typeof word !== "string" || !WORD.test(word)) {
20
+ if (typeof word !== "string" || !WORD.test(word) || NEWLINE.test(word)) {
18
21
  fail("blocklist entries must be 2 through 32 ASCII letters");
19
22
  }
20
23
  const upper = word.toUpperCase();
@@ -2,7 +2,9 @@ import type { PreparedProfile } from "./profile.js";
2
2
  /**
3
3
  * Spec 6.2. Rolling polynomial checksum over symbol values.
4
4
  * Returns the checksum value in [0, modulus).
5
+ * Spec 22: expandable generations may pass a shorter effective checksum
6
+ * length; the modulus is then S^length instead of the profile default.
5
7
  */
6
- export declare function checksumValue(profile: PreparedProfile, body: string, bodyIndex: Map<string, bigint>): bigint;
8
+ export declare function checksumValue(profile: PreparedProfile, body: string, bodyIndex: Map<string, bigint>, checksumLength?: number): bigint;
7
9
  /** Compute the expected checksum string for a normalized body. */
8
- export declare function calculateChecksum(profile: PreparedProfile, body: string): string;
10
+ export declare function calculateChecksum(profile: PreparedProfile, body: string, checksumLength?: number, bodyIndex?: Map<string, bigint>): string;
package/dist/checksum.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import { BasehError } from "./errors.js";
2
- import { alphabetIndex, encodeBaseN } from "./basen.js";
2
+ import { alphabetIndex, encodeBaseN, powBigInt } from "./basen.js";
3
3
  /**
4
4
  * Spec 6.2. Rolling polynomial checksum over symbol values.
5
5
  * Returns the checksum value in [0, modulus).
6
+ * Spec 22: expandable generations may pass a shorter effective checksum
7
+ * length; the modulus is then S^length instead of the profile default.
6
8
  */
7
- export function checksumValue(profile, body, bodyIndex) {
8
- const modulus = profile.checksumModulus;
9
+ export function checksumValue(profile, body, bodyIndex, checksumLength = profile.checksumLength) {
10
+ const modulus = checksumLength === profile.checksumLength
11
+ ? profile.checksumModulus
12
+ : powBigInt(BigInt(profile.checksumAlphabetNorm.length || 1), checksumLength);
9
13
  let state = 17n;
10
14
  for (let i = 0; i < profile.profileId.length; i += 1) {
11
15
  state = (state * 37n + BigInt(profile.profileId.charCodeAt(i)) + 1n) % modulus;
@@ -21,10 +25,12 @@ export function checksumValue(profile, body, bodyIndex) {
21
25
  return state;
22
26
  }
23
27
  /** Compute the expected checksum string for a normalized body. */
24
- export function calculateChecksum(profile, body) {
25
- if (profile.checksumLength === 0)
28
+ export function calculateChecksum(profile, body, checksumLength = profile.checksumLength, bodyIndex) {
29
+ if (checksumLength === 0)
26
30
  return "";
27
- const index = alphabetIndex(profile.bodyAlphabetNorm);
28
- const value = checksumValue(profile, body, index);
29
- return encodeBaseN(value, profile.checksumAlphabetNorm, profile.checksumLength);
31
+ // Correction loops call this up to 64 times; callers with a prepared index
32
+ // pass it in rather than rebuilding one per call.
33
+ const index = bodyIndex ?? alphabetIndex(profile.bodyAlphabetNorm);
34
+ const value = checksumValue(profile, body, index, checksumLength);
35
+ return encodeBaseN(value, profile.checksumAlphabetNorm, checksumLength);
30
36
  }
@@ -0,0 +1,7 @@
1
+ /** Spec 5.1. Fixed-length base-N encode, most significant digit first. */
2
+ export declare function encodeBaseN(value: bigint, alphabet: string, length: number): string;
3
+ /** Spec 5.2. */
4
+ export declare function decodeBaseN(text: string, alphabet: string, index: Map<string, bigint>): bigint;
5
+ export declare function alphabetIndex(alphabet: string): Map<string, bigint>;
6
+ /** Integer power for bigint bases; shared by profile, codec and checksum. */
7
+ export declare function powBigInt(base: bigint, exp: number): bigint;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeBaseN = encodeBaseN;
4
+ exports.decodeBaseN = decodeBaseN;
5
+ exports.alphabetIndex = alphabetIndex;
6
+ exports.powBigInt = powBigInt;
7
+ const errors_js_1 = require("./errors.js");
8
+ /** Spec 5.1. Fixed-length base-N encode, most significant digit first. */
9
+ function encodeBaseN(value, alphabet, length) {
10
+ const base = BigInt(alphabet.length);
11
+ const out = new Array(length);
12
+ let v = value;
13
+ for (let pos = length - 1; pos >= 0; pos -= 1) {
14
+ const digit = Number(v % base);
15
+ const ch = alphabet[digit];
16
+ if (ch === undefined)
17
+ throw new errors_js_1.BasehError("OUT_OF_RANGE", "digit outside alphabet");
18
+ out[pos] = ch;
19
+ v = v / base;
20
+ }
21
+ return out.join("");
22
+ }
23
+ /** Spec 5.2. */
24
+ function decodeBaseN(text, alphabet, index) {
25
+ const base = BigInt(alphabet.length);
26
+ let value = 0n;
27
+ for (const ch of text) {
28
+ const digit = index.get(ch);
29
+ if (digit === undefined) {
30
+ throw new errors_js_1.BasehError("INVALID_CHARACTER", `Symbol ${JSON.stringify(ch)} is not in the alphabet`);
31
+ }
32
+ value = value * base + digit;
33
+ }
34
+ return value;
35
+ }
36
+ function alphabetIndex(alphabet) {
37
+ const m = new Map();
38
+ [...alphabet].forEach((ch, i) => m.set(ch, BigInt(i)));
39
+ return m;
40
+ }
41
+ /** Integer power for bigint bases; shared by profile, codec and checksum. */
42
+ function powBigInt(base, exp) {
43
+ let result = 1n;
44
+ for (let i = 0; i < exp; i += 1)
45
+ result *= base;
46
+ return result;
47
+ }
@@ -0,0 +1,15 @@
1
+ export type BasehProfanityMode = "none" | "no-vowels" | "blocklist";
2
+ /** Spec 18. Optional profanity safety configuration. */
3
+ export interface BasehProfanity {
4
+ mode: BasehProfanityMode;
5
+ /** Replaces the default list when present (mode "blocklist" only). */
6
+ words?: string[];
7
+ /** Appended to the effective list (mode "blocklist" only). */
8
+ extraWords?: string[];
9
+ }
10
+ /** Spec 18.2 default list. Deliberately small; applications extend it. */
11
+ export declare const DEFAULT_BLOCKLIST: readonly string[];
12
+ /** Spec 18.2: replacement semantics, then augmentation, uppercased and deduplicated. */
13
+ export declare function effectiveBlocklist(profanity: BasehProfanity): string[];
14
+ /** Spec 18.1: vowels removed for no-vowels mode, applied after case normalization. */
15
+ export declare function stripVowels(alphabetNorm: string): string;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_BLOCKLIST = void 0;
4
+ exports.effectiveBlocklist = effectiveBlocklist;
5
+ exports.stripVowels = stripVowels;
6
+ const errors_js_1 = require("./errors.js");
7
+ /** Spec 18.2 default list. Deliberately small; applications extend it. */
8
+ exports.DEFAULT_BLOCKLIST = [
9
+ "CRAP", "TWAT", "SHAG", "DAMN", "FCK", "FUC",
10
+ "SHT", "CNT", "TWT", "DCK", "AZZ", "BCH"
11
+ ];
12
+ const WORD = /^[A-Za-z]{2,32}$/;
13
+ // JS `$` also matches before a trailing newline, so the charset test alone
14
+ // accepts "abc\n"; an explicit newline check restores Ruby's \A...\z strictness.
15
+ const NEWLINE = /[\r\n]/;
16
+ function fail(reason) {
17
+ throw new errors_js_1.BasehError("INVALID_PROFILE", `Invalid baseH profile: ${reason}`, false);
18
+ }
19
+ /** Spec 18.2: replacement semantics, then augmentation, uppercased and deduplicated. */
20
+ function effectiveBlocklist(profanity) {
21
+ const base = profanity.words ? [...profanity.words] : [...exports.DEFAULT_BLOCKLIST];
22
+ const list = [...base, ...(profanity.extraWords ?? [])];
23
+ const out = [];
24
+ for (const word of list) {
25
+ if (typeof word !== "string" || !WORD.test(word) || NEWLINE.test(word)) {
26
+ fail("blocklist entries must be 2 through 32 ASCII letters");
27
+ }
28
+ const upper = word.toUpperCase();
29
+ if (!out.includes(upper))
30
+ out.push(upper);
31
+ }
32
+ return out;
33
+ }
34
+ /** Spec 18.1: vowels removed for no-vowels mode, applied after case normalization. */
35
+ function stripVowels(alphabetNorm) {
36
+ return [...alphabetNorm].filter((c) => !"AEIOU".includes(c)).join("");
37
+ }
@@ -0,0 +1,10 @@
1
+ import type { PreparedProfile } from "./profile.js";
2
+ /**
3
+ * Spec 6.2. Rolling polynomial checksum over symbol values.
4
+ * Returns the checksum value in [0, modulus).
5
+ * Spec 22: expandable generations may pass a shorter effective checksum
6
+ * length; the modulus is then S^length instead of the profile default.
7
+ */
8
+ export declare function checksumValue(profile: PreparedProfile, body: string, bodyIndex: Map<string, bigint>, checksumLength?: number): bigint;
9
+ /** Compute the expected checksum string for a normalized body. */
10
+ export declare function calculateChecksum(profile: PreparedProfile, body: string, checksumLength?: number, bodyIndex?: Map<string, bigint>): string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checksumValue = checksumValue;
4
+ exports.calculateChecksum = calculateChecksum;
5
+ const errors_js_1 = require("./errors.js");
6
+ const basen_js_1 = require("./basen.js");
7
+ /**
8
+ * Spec 6.2. Rolling polynomial checksum over symbol values.
9
+ * Returns the checksum value in [0, modulus).
10
+ * Spec 22: expandable generations may pass a shorter effective checksum
11
+ * length; the modulus is then S^length instead of the profile default.
12
+ */
13
+ function checksumValue(profile, body, bodyIndex, checksumLength = profile.checksumLength) {
14
+ const modulus = checksumLength === profile.checksumLength
15
+ ? profile.checksumModulus
16
+ : (0, basen_js_1.powBigInt)(BigInt(profile.checksumAlphabetNorm.length || 1), checksumLength);
17
+ let state = 17n;
18
+ for (let i = 0; i < profile.profileId.length; i += 1) {
19
+ state = (state * 37n + BigInt(profile.profileId.charCodeAt(i)) + 1n) % modulus;
20
+ }
21
+ state = (state * 37n) % modulus;
22
+ for (let pos = 0; pos < body.length; pos += 1) {
23
+ const symValue = bodyIndex.get(body[pos]);
24
+ if (symValue === undefined) {
25
+ throw new errors_js_1.BasehError("INVALID_CHARACTER", `Body symbol ${JSON.stringify(body[pos])} is not in the body alphabet`);
26
+ }
27
+ state = (state * 37n + symValue + BigInt(pos + 1)) % modulus;
28
+ }
29
+ return state;
30
+ }
31
+ /** Compute the expected checksum string for a normalized body. */
32
+ function calculateChecksum(profile, body, checksumLength = profile.checksumLength, bodyIndex) {
33
+ if (checksumLength === 0)
34
+ return "";
35
+ // Correction loops call this up to 64 times; callers with a prepared index
36
+ // pass it in rather than rebuilding one per call.
37
+ const index = bodyIndex ?? (0, basen_js_1.alphabetIndex)(profile.bodyAlphabetNorm);
38
+ const value = checksumValue(profile, body, index, checksumLength);
39
+ return (0, basen_js_1.encodeBaseN)(value, profile.checksumAlphabetNorm, checksumLength);
40
+ }
@@ -0,0 +1,94 @@
1
+ import { type BasehErrorCode } from "./errors.js";
2
+ import { type BasehProfile, type PreparedProfile } from "./profile.js";
3
+ export type ConfusionProfileName = "none" | "light" | "medium" | "heavy";
4
+ /** Built-in spoken-confusion candidate maps. Spec 3.3; pairs apply to body symbols only. */
5
+ export declare const CONFUSION_MAPS: Record<Exclude<ConfusionProfileName, "none">, Record<string, string[]>>;
6
+ export interface DecodeOptions {
7
+ acceptSpaces?: boolean;
8
+ tryCorrection?: boolean;
9
+ confusionProfile?: ConfusionProfileName;
10
+ maxCorrections?: 0 | 1;
11
+ }
12
+ export interface DecodeResult {
13
+ id: bigint;
14
+ canonicalCode: string;
15
+ corrected: boolean;
16
+ }
17
+ export interface ValidateResult {
18
+ valid: boolean;
19
+ canonicalCode?: string;
20
+ reason?: BasehErrorCode;
21
+ }
22
+ /**
23
+ * Spec 12.5. Live as-you-type feedback for a code entry field. `typing`
24
+ * carries the normalized typed symbols with separators inserted as far as
25
+ * the groups go, plus a progress fraction; `invalid` carries the
26
+ * BasehErrorCode from validate; `valid` is only ever reported for a
27
+ * complete code.
28
+ */
29
+ export type InspectResult = {
30
+ state: "empty";
31
+ } | {
32
+ state: "typing";
33
+ typed: string;
34
+ progress: number;
35
+ } | {
36
+ state: "bad-char";
37
+ } | {
38
+ state: "too-long";
39
+ } | {
40
+ state: "invalid";
41
+ reason: BasehErrorCode;
42
+ } | {
43
+ state: "valid";
44
+ id: bigint;
45
+ canonicalCode: string;
46
+ };
47
+ /** Spec 3.1 normalization, steps 1-7. Returns the raw unformatted string. */
48
+ export declare function normalize(input: string, profile: PreparedProfile, acceptSpaces?: boolean): string;
49
+ export declare function formatRaw(raw: string, profile: PreparedProfile): string;
50
+ /**
51
+ * Spec 19.5. Balanced grouping: the split is a pure function of the total
52
+ * length — `g = max(2, ceil(L / 5))` groups differing in size by at most
53
+ * one, larger groups to the left. There is no configurable pattern in
54
+ * expandable mode (`grouping` must be empty, section 2.2).
55
+ */
56
+ export declare function expandableGrouping(length: number): number[];
57
+ /**
58
+ * Spec 19.1/22.3. First id of generation L: the sum of each generation's
59
+ * capacity A^(k - effectiveK(k)) for k from minLength through L-1. The
60
+ * effective checksum length is per-generation (spec 22), so the sum is not
61
+ * a single geometric series when the short checksum is on.
62
+ */
63
+ export declare function generationBase(profile: PreparedProfile, length: number): bigint;
64
+ /** Spec 19.1/22.3. Ids held by generation L: A^(L - effectiveK(L)). */
65
+ export declare function generationCapacity(profile: PreparedProfile, length: number): bigint;
66
+ /** Smallest generation whose range holds id, per spec 19.6. */
67
+ export declare function generationForId(profile: PreparedProfile, id: bigint): number;
68
+ /** Spec 10. Substitution-only candidate generation, capped and deduplicated. */
69
+ export declare function generateCandidates(body: string, confusionMap: Record<string, string[]>, maxEdits?: number): string[];
70
+ export declare class Baseh {
71
+ readonly profile: PreparedProfile;
72
+ private readonly bodyIndex;
73
+ constructor(profile: BasehProfile);
74
+ capacity(): bigint;
75
+ private permKey;
76
+ private checkBlocked;
77
+ /** Spec 8 (fixed mode). */
78
+ private encodeFixed;
79
+ /** Spec 19.6. */
80
+ private encodeExpandable;
81
+ /** Spec 8/19.6. */
82
+ encode(id: bigint | number): string;
83
+ /** Spec 9/19.7. */
84
+ decode(input: string, options?: DecodeOptions): DecodeResult;
85
+ /** Spec 12.4. Never throws on user input. */
86
+ validate(input: string, options?: DecodeOptions): ValidateResult;
87
+ /**
88
+ * Spec 12.5. Live as-you-type inspection. Gates on the typed length before
89
+ * validating, so spec 3.4 re-padding can never paint an incomplete fixed-mode
90
+ * code `valid` (or `invalid`): a short fixed input is `typing`, never
91
+ * checked. Never throws on user input.
92
+ */
93
+ inspect(input: string): InspectResult;
94
+ }