Last active
February 23, 2026 23:51
-
-
Save Cretezy/7ac7643a09295621a0f7b5d1cebfa8cf to your computer and use it in GitHub Desktop.
ID Type in TypeScript
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
| // -- Usage | |
| // Declare ID | |
| export const exampleIdUtils = createId("example") | |
| export type ExampleId = ReturnType<typeof exampleIdUtils["generate"]> | |
| // Generate IDs | |
| const generatedExampleId = exampleIdUtils.generate() // Type: ExampleId | |
| // Turn strings to IDs safely, convert them back to string transparently | |
| const exampleId = exampleIdUtils.parse("example_a1b2c3d4f5") // Type: ExampleId, throws if invalid ID | |
| const exampleIdString: string = exampleIdUtils.parse("example_a1b2c3d4f5") // Type: string | |
| // Strings aren't accepted as the ID type, maintaining type safety | |
| const invalidExampleIdType: ExampleId = "any string" // Type 'string' is not assignable to type 'ExampleId'. | |
| // -- Implementation | |
| export function createId<Prefix extends string>(prefix: Prefix) { | |
| if (!/^[a-z][a-z0-9]*$/.test(prefix)) { | |
| throw new Error(`Invalid id key "${prefix}". Expected lowercase alphanumeric key.`); | |
| } | |
| type Type = string & { readonly __id_brand: Prefix }; | |
| return { | |
| key: prefix, | |
| parse: (value: string): Type => { | |
| if (typeof value !== "string" || value.length === 0) { | |
| throw new Error(`Invalid ${prefix} id.`); | |
| } | |
| if (!testPattern(ID_PATTERN, value)) { | |
| throw new Error(`Invalid ${prefix} id format: ${value}`); | |
| } | |
| return value as Type; | |
| }, | |
| generate: (): Type => { | |
| return randomId(prefix) as Type | |
| } | |
| } as const | |
| } | |
| const ID_PATTERN = /^[a-z][a-z0-9]*_[0-9a-zA-Z]{10}$/ | |
| const ID_LENGTH = 10 | |
| const ID_CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | |
| export function randomId(prefix: string) { | |
| const b = new Uint8Array(Math.ceil(ID_LENGTH * 1.1)); | |
| crypto.getRandomValues(b); | |
| let s = `${prefix}_`, p = s.length, i = 0; | |
| while (s.length < ID_LENGTH + p) { | |
| if (i === b.length) { | |
| crypto.getRandomValues(b); | |
| i = 0; | |
| } | |
| const v = b[i++] & 63; | |
| if (v < 62) s += ID_CHARACTERS[v]; | |
| } | |
| return s; | |
| } | |
| function testPattern(regex: RegExp, value: string): boolean { | |
| regex.lastIndex = 0; | |
| return regex.test(value); | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment