// @pds/core/validation - Reusable validation utilities /** * DID format regex - supports did:plc and did:web * did:plc uses base32 (a-z, 2-7) with 24 chars */ export const DID_REGEX = /^did:(plc:[a-z2-7]{24}|web:.+)$/; /** * Handle format regex - domain-like format * Each label: starts/ends alphanumeric, can have hyphens in middle, 1-63 chars * Must have at least 2 labels (e.g., user.bsky.social) */ export const HANDLE_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; /** * Hex string regex */ export const HEX_REGEX = /^[0-9a-fA-F]+$/; /** * Validate DID format * @param {unknown} did * @returns {did is string} */ export function isValidDid(did) { return typeof did === 'string' && DID_REGEX.test(did); } /** * Validate handle format * @param {unknown} handle * @returns {handle is string} */ export function isValidHandle(handle) { return typeof handle === 'string' && HANDLE_REGEX.test(handle); } /** * Validate hex string * @param {unknown} str * @returns {str is string} */ export function isValidHex(str) { return typeof str === 'string' && HEX_REGEX.test(str); } /** * Validate private key bytes (32 bytes for secp256k1) * @param {Uint8Array} key * @returns {boolean} */ export function isValidPrivateKeyLength(key) { return key instanceof Uint8Array && key.length === 32; } /** * Parse and validate a numeric cursor (non-negative integer) * @param {string|null} cursor - Cursor string from URL params * @returns {{valid: true, value: number} | {valid: false, error: string} | null} */ export function parseNumericCursor(cursor) { if (cursor === null || cursor === '') return null; const num = parseInt(cursor, 10); if (Number.isNaN(num)) { return { valid: false, error: 'cursor must be a valid integer' }; } if (num < 0) { return { valid: false, error: 'cursor must not be negative' }; } return { valid: true, value: num }; }