1 const ALPHABET
= "23456789abcdefghijkmnpqrstuvwxyz" ;
2 const ALPHABET_INVERSE
= [
3 , , , , , , , , , , , , , , , ,
4 , , , , , , , , , , , , , , , ,
5 , , , , , , , , , , , , , , , ,
6 , , 0 n
, 1 n
, 2 n
, 3 n
, 4 n
, 5 n
, 6 n
, 7 n
, , , , , , ,
7 , , , , , , , , , , , , , , , ,
8 , , , , , , , , , , , , , , , ,
9 , 8 n
, 9 n
, 10 n
, 11 n
, 12 n
, 13 n
, 14 n
, 15 n
, 16 n
, 17 n
, 18 n
, , 19 n
, 20 n
, ,
10 21 n
, 22 n
, 23 n
, 24 n
, 25 n
, 26 n
, 27 n
, 28 n
, 29 n
, 30 n
, 31 n
,
12 // (Yes, this JavaScript library gets worse error reporting than the Rust,
13 // because the only *real* reason for doing it so fancily in Rust was WrongDiscriminant,
14 // and that kind of data stapling to errors just doesn’t work well in JavaScript,
15 // as you’re heading outside the regular type system if using exceptions,
16 // which I think is most sensible, for fitting in with local conventions.
17 // There is also a bit of code size argument to it, but that’s secondary.
18 // If you want better error handling on wrong discriminant, use splitDecode.)
19 const throwRangeError
= ( msg
= "bad TESID" ) => {
20 throw new RangeError ( msg
);
23 export class TESIDCoder
{
25 if (! /^[0-9a-f]{32}$/ . test ( key
)) {
26 throwRangeError ( "key must be 32 hex characters" );
28 key
= new DataView ( Uint8Array
. from (
29 key
. match ( /../g ). map ( b
=> parseInt ( b
, 16 ))
31 let mask
= ( 1 n
<< 64 n
) - 1 n
;
32 let y
= key
. getBigUint64 ( 8 );
33 let x
= key
. getBigUint64 ( 0 );
37 x
= (((( x
<< 55 n
) | ( x
>> 9 n
)) + y
) & mask
) ^ BigInt ( i
);
38 k
[++ i
] = y
= ((( y
<< 2 n
) | ( y
>> 62 n
)) & mask
) ^ x
;
42 encode ( id
, sparsity
= 1 n
, discriminant
= 0 n
) {
43 // Apply discriminant and sparsity, and check range.
44 id
= ( id
* sparsity
) + discriminant
;
45 // Do you know how tempting it was to reuse the names `sparsity` and `discriminant` after this to save a few more bytes?
46 let n
= id
< 0 n
? 0n :
47 id
< ( 1 n
<< 20 n
) ? 10n :
48 id
< ( 1 n
<< 30 n
) ? 15n :
49 id
< ( 1 n
<< 40 n
) ? 20n :
50 id
< ( 1 n
<< 50 n
) ? 25n :
51 id
< ( 1 n
<< 60 n
) ? 30n :
52 id
< ( 1 n
<< 70 n
) ? 35n :
53 id
< ( 1 n
<< 80 n
) ? 40n :
54 id
< ( 1 n
<< 90 n
) ? 45n :
55 id
< ( 1 n
<< 100 n
) ? 50n :
58 throwRangeError ( "id out of range" );
63 let mask
= ( 1 n
<< n
) - 1 n
;
65 let y
= ( id
>> n
) & mask
;
69 x
= (((( x
<< ( n
- 9 n
)) | ( x
>> 9 n
)) + y
) ^ k
[ i
++]) & mask
;
70 y
= ((( y
<< 2 n
) | ( y
>> ( n
- 2 n
))) & mask
) ^ x
;
76 buf
= ALPHABET
[ id
& 31 n
] + buf
;
79 return buf
. padStart ( Number ( n
) * 0.4 , "2" /* ALPHABET[0] */ );
82 decode ( tesid
, sparsity
= 1 n
, discriminant
= 0 n
) {
87 if ( n
% 2 || n
< 4 || n
> 20 ) {
93 let c
= ALPHABET_INVERSE
[ tesid
. charCodeAt ( i
++)];
101 n
= BigInt ( n
* 2.5 ); // Done now rather than earlier because odd-length strings lead to non-integral n and BigInt() would throw a RangeError with a less friendly message
102 let mask
= ( 1 n
<< n
) - 1 n
;
104 let y
= ( id
>> n
) & mask
;
108 y
= (( y
<< ( n
- 2 n
)) | ( y
>> 2 n
)) & mask
;
109 x
= (( x
^ k
[ i
]) - y
) & mask
;
110 x
= (( x
<< 9 n
) | ( x
>> ( n
- 9 n
))) & mask
;
115 // Overly-long-encoding range check
116 ( n
> 10 && id
< ( 1 n
<< ( n
* 2 n
- 10 n
))) ||
117 // Discriminant and sparsity deapply
118 ( id
-= discriminant
) < 0 ||
123 return id
/ sparsity
;
126 splitDecode ( tesid
, sparsity
) {
127 // (Variable reuse to save the “let” keyword, just ’cos.)
128 tesid
= this . decode ( tesid
);
130 id : tesid
/ sparsity
,
131 discriminant : tesid
% sparsity
,
136 export class TypedTESIDCoder
{
137 constructor ( coder
, sparsity
, typeEnum
) {
144 return this . c
. encode ( id
, this . s
, BigInt ( type
));
147 decode ( type
, tesid
) {
148 return this . c
. decode ( tesid
, this . s
, BigInt ( type
));
152 // (Simple variable reuse, as before.)
153 tesid
= this . c
. splitDecode ( tesid
, this . s
);
154 // 1. Gotta convert to a Number as that’s what typeEnum uses
155 // (otherwise strict equality comparison (===) would fail).
156 // Assumption here is that the type enum has no variants exceeding 2⁵³ − 1.
157 // If you do… wow. I’m not even going to try to rein you in.
158 // You clearly *want* things to blow up. Don’t blame me when they do,
159 // and someone accesses an entity by two different IDs
160 // (one with discriminant 2⁵³, one with discriminant 2⁵³ + 1).
162 // 2. Gotta check it’s a valid variant, or type unsoundness would occur,
163 // most notably seen in errors in exhaustiveness checking.
164 // (Yeah, encode/decode take a typeEnum variant without checking,
165 // but that’s fine as they don’t *produce* a value of that type.)
167 // (Could have made typeEnum optional,
168 // and then you could just use a bunch of constants instead of an object,
169 // but I don’t think the unlocked functionality is *particularly* useful,
170 // and Python and Rust can’t achieve it since they use actual enum types rather than just labelling numbers,
171 // so all up I’ve decided it’s better to insist on it.)
172 if (!(( tesid
. discriminant
= Number ( tesid
. discriminant
)) in this . t
)) {