A minimal AT Protocol Personal Data Server written in JavaScript.
atproto
pds
1// @pds/core/validation - Reusable validation utilities
2
3/**
4 * DID format regex - supports did:plc and did:web
5 * did:plc uses base32 (a-z, 2-7) with 24 chars
6 */
7export const DID_REGEX = /^did:(plc:[a-z2-7]{24}|web:.+)$/;
8
9/**
10 * Handle format regex - domain-like format
11 * Each label: starts/ends alphanumeric, can have hyphens in middle, 1-63 chars
12 * Must have at least 2 labels (e.g., user.bsky.social)
13 */
14export const HANDLE_REGEX =
15 /^[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])?)+$/;
16
17/**
18 * Hex string regex
19 */
20export const HEX_REGEX = /^[0-9a-fA-F]+$/;
21
22/**
23 * Validate DID format
24 * @param {unknown} did
25 * @returns {did is string}
26 */
27export function isValidDid(did) {
28 return typeof did === 'string' && DID_REGEX.test(did);
29}
30
31/**
32 * Validate handle format
33 * @param {unknown} handle
34 * @returns {handle is string}
35 */
36export function isValidHandle(handle) {
37 return typeof handle === 'string' && HANDLE_REGEX.test(handle);
38}
39
40/**
41 * Validate hex string
42 * @param {unknown} str
43 * @returns {str is string}
44 */
45export function isValidHex(str) {
46 return typeof str === 'string' && HEX_REGEX.test(str);
47}
48
49/**
50 * Validate private key bytes (32 bytes for secp256k1)
51 * @param {Uint8Array} key
52 * @returns {boolean}
53 */
54export function isValidPrivateKeyLength(key) {
55 return key instanceof Uint8Array && key.length === 32;
56}
57
58/**
59 * Parse and validate a numeric cursor (non-negative integer)
60 * @param {string|null} cursor - Cursor string from URL params
61 * @returns {{valid: true, value: number} | {valid: false, error: string} | null}
62 */
63export function parseNumericCursor(cursor) {
64 if (cursor === null || cursor === '') return null;
65 const num = parseInt(cursor, 10);
66 if (Number.isNaN(num)) {
67 return { valid: false, error: 'cursor must be a valid integer' };
68 }
69 if (num < 0) {
70 return { valid: false, error: 'cursor must not be negative' };
71 }
72 return { valid: true, value: num };
73}