4 export class TESIDCoder {
6 * Initialise a TESID coder.
8 * The key string must be made up of exactly 32 lowercase hexadecimal (0-9a-f) characters,
9 * and should have been generated randomly.
10 * Refer to external documentation for information on key generation.
13 * new TESIDCoder("000102030405060708090a0b0c0d0e0f")
15 * @param key - A string of exactly 32 lowercase hexadecimal (0-9a-f) characters
16 * @throws {RangeError} if key is the wrong length or malformed
18 constructor(key: string);
24 * let coder = new TESIDCoder("000102030405060708090a0b0c0d0e0f");
27 * console.assert(coder.encode(0) === "w2ej");
29 * // Tagging demonstration (first defining constants for consistent use):
30 * const TYPE_SPARSITY = 256; // meaning up to 256 possible types
35 * // id 0 with three different discriminants/tags:
36 * console.assert(coder.encode(0, TYPE_SPARSITY, TYPE_A) === "w2ej");
37 * console.assert(coder.encode(0, TYPE_SPARSITY, TYPE_B) === "w6um");
38 * console.assert(coder.encode(0, TYPE_SPARSITY, TYPE_C) === "x45g");
40 * // id 1 with three different discriminants/tags:
41 * console.assert(coder.encode(1, TYPE_SPARSITY, TYPE_A) === "dh2h");
42 * console.assert(coder.encode(1, TYPE_SPARSITY, TYPE_B) === "a6xy");
43 * console.assert(coder.encode(1, TYPE_SPARSITY, TYPE_C) === "7xgj");
45 * @param id - A big integer in the range [0, 2¹⁰⁰)
46 * @param sparsity - A positive integer to multiply id by (default 1, which doesn’t change id)
47 * @param discriminant - A non-negative integer to add to id after sparsening (default 0, which doesn’t change id)
48 * @returns A string of 4–20 base-32 characters, of even length
49 * @throws {TypeError} Parameter id must be a bigint (number is not accepted because of its unsuitability beyond 2⁵³)
50 * @throws {RangeError} (id * sparsity + discriminant) must be in the range [0, 2¹⁰⁰)
52 encode(id: bigint, sparsity?: bigint, discriminant?: bigint): string;
58 * let coder = new TESIDCoder("000102030405060708090a0b0c0d0e0f");
61 * console.assert(coder.decode("w2ej") === 0);
63 * // If sparsity and/or discriminant were used on encode, matching values
64 * // must be provided here, or else it will fail to decode:
65 * const TYPE_SPARSITY = 256;
69 * console.assert(coder.decode("w2ej", TYPE_SPARSITY, TYPE_A) === 0);
70 * // And coder.decode("w2ej", TYPE_SPARSITY, TYPE_C) throws RangeError
72 * @param tesid - A TESID string, which should be 4–20 characters long
73 * @param sparsity - A positive integer id was multiplied by (default 1, which doesn’t change id)
74 * @param discriminant - A non-negative integer that was added to id after sparsening (default 0, which doesn’t change id)
75 * @returns The decoded ID
76 * @throws {RangeError} if decoding fails in any way
78 decode(tesid: string, sparsity?: bigint, discriminant?: bigint): bigint;
81 * Decode an ID that was encoded with certain sparsity,
82 * separating the discriminant and returning it alongside the ID.
84 * This is useful if you want to accept various discriminants;
85 * one simple use case is better error reporting:
86 * “that’s an ID for type A, but this takes IDs for type B”.
88 * This allows *you* to identify the discriminant,
89 * but due to the encryption, anyone who has only the ID cannot;
90 * if you want users to be able to discern the discriminant,
91 * consider adding a human-friendly prefix to the ID;
92 * I like a single uppercase letter or a word followed by an underscore.
94 * This requires that the discriminant be less than the sparsity,
95 * or incorrect values will be produced.
97 * @param tesid - A TESID string, which should be 4–20 characters long
98 * @param sparsity - A positive integer id was multiplied by
99 * @returns The decoded ID and discriminant
100 * @throws {RangeError} if decoding fails in any way
102 splitDecode(tesid: string, sparsity: bigint): {id: bigint, discriminant: bigint};
105 // This is approximately a supertype for TypeScript enums.
106 // The technique has the unfortunate property that, given `D extends Enum`,
107 // `D[keyof D]` will accept numbers (*any* numbers) as well as members of the enum,
108 // but I don’t think we can do any better with generics in TypeScript at this time.
109 type Enum = {[index: number]: string};
112 * A TESID coder with type discrimination baked in.
114 * The type enum needs to provide a mapping from integers to strings, and strings to integers.
116 * If you find this type handling doesn’t quite satisfy you in any way,
117 * look at the code for this class: it’s a handful of lines of obvious code,
118 * so you can just duplicate it and apply your own slant.
120 * @example <caption>Using a TypeScript enum; methods’ examples start with this foundation.</caption>
126 * const coder = new TypedTESIDCoder(new TESIDCoder("000102030405060708090a0b0c0d0e0f"), 256n, Type);
128 * @example <caption>Using plain JavaScript, the equivalent to the previous example.</caption>
137 * const coder = new TypedTESIDCoder(new TESIDCoder("000102030405060708090a0b0c0d0e0f"), 256n, Type);
139 export class TypedTESIDCoder<D extends Enum> {
141 * Initialise a typed TESID coder.
143 * This takes a `TESIDCoder` (rather than a key) so that you can share a coder,
144 * if you don’t always use the one sparsity and type enum.
146 * `sparsity` must exceed the highest variant in `typeEnum`.
148 * @param coder - The TESID coder to use
149 * @param sparsity - A positive integer to multiply id by, which must exceed the highest variant in `typeEnum`
150 * @param typeEnum - An object for mapping discriminant names and values, using regular JavaScript indexing syntax
152 constructor(coder: TESIDCoder, sparsity: bigint, typeEnum: D);
155 * Encode an ID and type.
158 * console.assert(coder.encode(Type.A, 0n) == "w2ej");
159 * console.assert(coder.encode(Type.B, 0n) == "w6um");
160 * console.assert(coder.encode(Type.A, 1n) == "dh2h");
162 * @param type - The discriminant to use
163 * @param id - A big integer in the range [0, 2¹⁰⁰)
164 * @returns A string of 4–20 base-32 characters, of even length
165 * @throws {TypeError} Parameter type must be a string that is a valid key of the type enum
166 * @throws {TypeError} Parameter id must be a bigint (number is not accepted because of its unsuitability beyond 2⁵³)
167 * @throws {RangeError} (id * sparsity + discriminant) must be in the range [0, 2¹⁰⁰)
169 encode(type: D[keyof D], id: bigint): string;
172 * Decode an ID and type.
175 * console.assert(coder.decode(Type.A, "w2ej") == 0n);
176 * console.assert(coder.decode(Type.B, "w6um") == 0n);
177 * console.assert(coder.decode(Type.A, "dh2h") == 1n);
178 * // And coder.decode(Type.A, "w5um") throws RangeError
180 * @param type - The discriminant to use
181 * @param tesid - A TESID string, which should be 4–20 characters long
182 * @throws {TypeError} Parameter type must be a string that is a valid key of the type enum
183 * @throws {RangeError} if decoding fails in any way
184 * @returns The decoded ID
186 decode(type: D[keyof D], tesid: string): bigint;
189 * Decode an ID but separate and return its discriminant too.
191 * @param tesid - A TESID string, which should be 4–20 characters long
192 * @returns The decoded ID and discriminant; the discriminant is of the enum type, a string and not a number.
193 * @throws {RangeError} if decoding fails in any way (including if the discriminant doesn’t correspond to a variant of the type enum)
195 splitDecode(tesid: string): {id: bigint, discriminant: D[keyof D]};