Skip to content

Instantly share code, notes, and snippets.

@iagobelo
Last active November 12, 2025 20:39
Show Gist options
  • Select an option

  • Save iagobelo/3fa4626f3c09e2d10693ba89a5c270ee to your computer and use it in GitHub Desktop.

Select an option

Save iagobelo/3fa4626f3c09e2d10693ba89a5c270ee to your computer and use it in GitHub Desktop.
Base64 encoder and decoder written with types only.
/**
* by @iagobelo
* Decoder in progress!!!
*/
type STRING_TO_CONVERT = "xablau!";
type BitResult = Base64EncodeWithoutBa64Chars<STRING_TO_CONVERT>; // 011110-000110-000101-100010-011011-000110-000101-110101-001000-010000
type Result = Base64Encode<STRING_TO_CONVERT>; // eGFibGF1IQ==
/**
* Map where the key is an ASCII char and the value
* is a tuple of string bit pairs (four pairs with 2bits each)
* that represents the char.
*/
// prettier-ignore
type AsciiTable = {
[asciiChar: string]: [bitPair: string, bitPair: string, bitPair: string, bitPair: string]
' ': ['00', '10', '00', '00'];
'!': ['00', '10', '00', '01']; '"': ['00', '10', '00', '10']; '#': ['00', '10', '00', '11'];
'$': ['00', '10', '01', '00']; '%': ['00', '10', '01', '01']; '&': ['00', '10', '01', '10'];
'\'': ['00', '10', '01', '11']; '(': ['00', '10', '10', '00']; ')': ['00', '10', '10', '01'];
'*': ['00', '10', '10', '10']; '+': ['00', '10', '10', '11']; ',': ['00', '10', '11', '00'];
'-': ['00', '10', '11', '01']; '.': ['00', '10', '11', '10']; '/': ['00', '10', '11', '11'];
'0': ['00', '11', '00', '00']; '1': ['00', '11', '00', '01']; '2': ['00', '11', '00', '10'];
'3': ['00', '11', '00', '11']; '4': ['00', '11', '01', '00']; '5': ['00', '11', '01', '01'];
'6': ['00', '11', '01', '10']; '7': ['00', '11', '01', '11']; '8': ['00', '11', '10', '00'];
'9': ['00', '11', '10', '01']; ':': ['00', '11', '10', '10']; ';': ['00', '11', '10', '11'];
'<': ['00', '11', '11', '00']; '=': ['00', '11', '11', '01']; '>': ['00', '11', '11', '10'];
'?': ['00', '11', '11', '11']; '@': ['01', '00', '00', '00']; 'A': ['01', '00', '00', '01'];
'B': ['01', '00', '00', '10']; 'C': ['01', '00', '00', '11']; 'D': ['01', '00', '01', '00'];
'E': ['01', '00', '01', '01']; 'F': ['01', '00', '01', '10']; 'G': ['01', '00', '01', '11'];
'H': ['01', '00', '10', '00']; 'I': ['01', '00', '10', '01']; 'J': ['01', '00', '10', '10'];
'K': ['01', '00', '10', '11']; 'L': ['01', '00', '11', '00']; 'M': ['01', '00', '11', '01'];
'N': ['01', '00', '11', '10']; 'O': ['01', '00', '11', '11']; 'P': ['01', '01', '00', '00'];
'Q': ['01', '01', '00', '01']; 'R': ['01', '01', '00', '10']; 'S': ['01', '01', '00', '11'];
'T': ['01', '01', '01', '00']; 'U': ['01', '01', '01', '01']; 'V': ['01', '01', '01', '10'];
'W': ['01', '01', '01', '11']; 'X': ['01', '01', '10', '00']; 'Y': ['01', '01', '10', '01'];
'Z': ['01', '01', '10', '10']; '[': ['01', '01', '10', '11']; '\\': ['01', '01', '11', '00'];
']': ['01', '01', '11', '01']; '^': ['01', '01', '11', '10']; '_': ['01', '01', '11', '11'];
'`': ['01', '10', '00', '00']; 'a': ['01', '10', '00', '01']; 'b': ['01', '10', '00', '10'];
'c': ['01', '10', '00', '11']; 'd': ['01', '10', '01', '00']; 'e': ['01', '10', '01', '01'];
'f': ['01', '10', '01', '10']; 'g': ['01', '10', '01', '11']; 'h': ['01', '10', '10', '00'];
'i': ['01', '10', '10', '01']; 'j': ['01', '10', '10', '10']; 'k': ['01', '10', '10', '11'];
'l': ['01', '10', '11', '00']; 'm': ['01', '10', '11', '01']; 'n': ['01', '10', '11', '10'];
'o': ['01', '10', '11', '11']; 'p': ['01', '11', '00', '00']; 'q': ['01', '11', '00', '01'];
'r': ['01', '11', '00', '10']; 's': ['01', '11', '00', '11']; 't': ['01', '11', '01', '00'];
'u': ['01', '11', '01', '01']; 'v': ['01', '11', '01', '10']; 'w': ['01', '11', '01', '11'];
'x': ['01', '11', '10', '00']; 'y': ['01', '11', '10', '01']; 'z': ['01', '11', '10', '10'];
'{': ['01', '11', '10', '11']; '|': ['01', '11', '11', '00']; '}': ['01', '11', '11', '01'];
'~': ['01', '11', '11', '10']; '': ['01', '11', '11', '11'];
};
/**
* Map where the key is a 6bit string representation of a valid
* Base64 char and the value is the char itself.
*/
// prettier-ignore
type Base64Chars = {
[key: string]: string
'000000': 'A'; '000001': 'B'; '000010': 'C'; '000011': 'D'; '000100': 'E';
'000101': 'F'; '000110': 'G'; '000111': 'H'; '001000': 'I'; '001001': 'J';
'001010': 'K'; '001011': 'L'; '001100': 'M'; '001101': 'N'; '001110': 'O';
'001111': 'P'; '010000': 'Q'; '010001': 'R'; '010010': 'S'; '010011': 'T';
'010100': 'U'; '010101': 'V'; '010110': 'W'; '010111': 'X'; '011000': 'Y';
'011001': 'Z'; '011010': 'a'; '011011': 'b'; '011100': 'c'; '011101': 'd';
'011110': 'e'; '011111': 'f'; '100000': 'g'; '100001': 'h'; '100010': 'i';
'100011': 'j'; '100100': 'k'; '100101': 'l'; '100110': 'm'; '100111': 'n';
'101000': 'o'; '101001': 'p'; '101010': 'q'; '101011': 'r'; '101100': 's';
'101101': 't'; '101110': 'u'; '101111': 'v'; '110000': 'w'; '110001': 'x';
'110010': 'y'; '110011': 'z'; '110100': '0'; '110101': '1'; '110110': '2';
'110111': '3'; '111000': '4'; '111001': '5'; '111010': '6'; '111011': '7';
'111100': '8'; '111101': '9'; '111110': '+'; '111111': '/';
}
/**
* Iterates over the given string and returns an array of characters
* splitted by the separetor.
*/
type Split<
S extends string,
Separator extends string = ""
> = S extends `${infer T}${Separator}${infer U}`
? U extends ""
? [S]
: [T, ...Split<U, Separator>]
: [S];
/**
* Iterates over the array of chars then get the 8 bit representation of each
* char and finally transform the 8-bit chars into 6-bit base64 chars.
*/
// prettier-ignore
type AsciiBits<C extends keyof AsciiTable> = AsciiTable[C];
type FromCharArrayTo6BitBinaryArray<A extends string[]> =
// Has 3 or more chars left
A extends [
infer C1 extends keyof AsciiTable,
infer C2 extends keyof AsciiTable,
infer C3 extends keyof AsciiTable,
...infer Rest extends string[]
]
? `${AsciiBits<C1>[0]}${AsciiBits<C1>[1]}${AsciiBits<C1>[2]}-${AsciiBits<C1>[3]}${AsciiBits<C2>[0]}${AsciiBits<C2>[1]}-${AsciiBits<C2>[2]}${AsciiBits<C2>[3]}${AsciiBits<C3>[0]}-${AsciiBits<C3>[1]}${AsciiBits<C3>[2]}${AsciiBits<C3>[3]}${Rest extends []
? ""
: `-${FromCharArrayTo6BitBinaryArray<Rest>}`}`
: // Has 2 chars left
A extends [
infer C1 extends keyof AsciiTable,
infer C2 extends keyof AsciiTable
]
? `${AsciiBits<C1>[0]}${AsciiBits<C1>[1]}${AsciiBits<C1>[2]}-${AsciiBits<C1>[3]}${AsciiBits<C2>[0]}${AsciiBits<C2>[1]}-${AsciiBits<C2>[2]}${AsciiBits<C2>[3]}00`
: // Has 1 char left
A extends [infer C1 extends keyof AsciiTable]
? `${AsciiBits<C1>[0]}${AsciiBits<C1>[1]}${AsciiBits<C1>[2]}-${AsciiBits<C1>[3]}0000`
: "";
/**
* Iterates over the array of 6bit string group and gets the Base64
* representation of each char.
*/
type FromBinaryArrayToBase64Chars<A extends string[]> = A extends [
infer Head extends string,
...infer Tail extends string[]
]
? `${Base64Chars[Head]}${FromBinaryArrayToBase64Chars<Tail>}`
: "";
type Mod3<A extends unknown[]> = A extends [
unknown,
unknown,
unknown,
...infer Rest extends unknown[]
]
? Mod3<Rest>
: A["length"];
type PaddingForArray<A extends unknown[], R = Mod3<A>> = R extends 1
? "=="
: R extends 2
? "="
: "";
type Base64Encode<
S extends string,
CharArray extends string[] = Split<S>
> = `${FromBinaryArrayToBase64Chars<
Split<FromCharArrayTo6BitBinaryArray<CharArray>, "-">
>}${PaddingForArray<CharArray>}`;
type Base64EncodeWithoutBa64Chars<S extends string> =
FromCharArrayTo6BitBinaryArray<Split<S>>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment