cuid-range
Version:
Generate and validate CUID between two bounds. Useful for order-preserving pagination keys, gap insertion, or sequence IDs.
72 lines (54 loc) • 2.01 kB
text/typescript
const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
const MIN = 0
const MAX = ALPHABET.length - 1
function assertAllInAlphabet(s: string) {
for (const ch of s) {
if (ALPHABET.indexOf(ch) === -1) throw new Error('Invalid CUID')
}
}
function validateRange(start: string, end: string) {
if (start.length === 0) throw new Error('Invalid CUID range')
if (start.length > end.length) throw new Error('Invalid CUID range')
assertAllInAlphabet(start)
assertAllInAlphabet(end)
if (start > end) throw new Error('Invalid CUID range')
}
export function generateCuidBetween(start: string, end: string): string {
if (start === end) {
assertAllInAlphabet(start)
return start
}
validateRange(start, end)
const length = Math.max(start.length, end.length)
let res = ''
let prefixStart = true
let prefixEnd = true
for (let i = 0; i < length; i++) {
const sCh = prefixStart ? (start[i] ?? ALPHABET[MIN]) : ALPHABET[MIN]
const eCh = prefixEnd ? (end[i] ?? ALPHABET[MAX]) : ALPHABET[MAX]
const sIdx = ALPHABET.indexOf(sCh)
const eIdx = ALPHABET.indexOf(eCh)
if (sIdx === -1 || eIdx === -1 || sIdx > eIdx) {
throw new Error('Invalid CUID range')
}
const range = ALPHABET.substring(sIdx, eIdx + 1)
const pick = range[Math.floor(Math.random() * range.length)]
res += pick
if (prefixStart) {
prefixStart = (pick === (start[i] ?? ALPHABET[MIN]))
}
if (prefixEnd) {
prefixEnd = (pick === (end[i] ?? ALPHABET[MAX]))
}
}
return res
}
export function isCuidBetween(cuid: string, start: string, end: string): boolean {
assertAllInAlphabet(cuid)
if (start === end) {
assertAllInAlphabet(start)
return cuid === start
}
validateRange(start, end)
return start <= cuid && cuid <= end
}