chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// @pdsjs/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 };
}