Files
lingxi/packages/shared/src/ulid.ts
T
Stev_Wang 23067edb8f feat(api): 搭建 API 与 Worker 骨架及契约基线
NestJS 11 + Fastify 统一错误体与 trace_id,/health 真实探活 DB/Redis,OpenAPI 3.1 契约含 20 个错误码;shared 包提供错误码、权限点、ULID、分页与通用 schema;worker 完成队列进程骨架。
2026-11-02 17:20:46 +08:00

57 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ULID 生成与解析(Crockford Base32,26 字符 = 48bit 时间 + 80bit 随机)
* 主键统一由应用侧生成 char(26) ULID(工程规范 05)。
*/
import { ULID_LENGTH } from './constants.js';
const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
const ENCODING_LEN = 32;
export const ULID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
function randomBytes(length: number): Uint8Array {
const bytes = new Uint8Array(length);
globalThis.crypto.getRandomValues(bytes);
return bytes;
}
/** 生成 ULID;同一毫秒内依靠 80bit 随机数避免碰撞 */
export function ulid(timestamp: number = Date.now()): string {
if (!Number.isInteger(timestamp) || timestamp < 0 || timestamp > 0xffffffffffff) {
throw new RangeError('ULID 时间戳必须是 48bit 非负整数');
}
let time = timestamp;
const timeChars: string[] = new Array<string>(10);
for (let i = 9; i >= 0; i -= 1) {
timeChars[i] = ENCODING[time % ENCODING_LEN] ?? '0';
time = Math.floor(time / ENCODING_LEN);
}
const randomChars = Array.from(randomBytes(16), (byte) => ENCODING[byte & 31] ?? '0');
return `${timeChars.join('')}${randomChars.join('')}`;
}
export function isUlid(value: string): boolean {
return value.length === ULID_LENGTH && ULID_PATTERN.test(value);
}
/** 解析 ULID 前 10 位得到毫秒时间戳 */
export function ulidToTimestamp(id: string): number {
if (!isUlid(id)) {
throw new TypeError(`不是合法 ULID:${id}`);
}
let timestamp = 0;
for (let i = 0; i < 10; i += 1) {
const char = id[i] ?? '';
const value = ENCODING.indexOf(char);
if (value < 0) {
throw new TypeError(`ULID 含非法字符:${char}`);
}
timestamp = timestamp * ENCODING_LEN + value;
}
return timestamp;
}
export function ulidToDate(id: string): Date {
return new Date(ulidToTimestamp(id));
}