Last active
November 12, 2025 20:39
-
-
Save iagobelo/3fa4626f3c09e2d10693ba89a5c270ee to your computer and use it in GitHub Desktop.
Base64 encoder and decoder written with types only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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