Sifa professional network API (Fastify, AT Protocol, Jetstream) sifa.id/

feat(identity): add handle/DID resolution service

Provides isHandle/isDid validation re-exports from @atproto/syntax and
a resolveHandleOrDid function that uses the public Bluesky API to
resolve handles to DIDs and vice versa. Foundation for profile CRUD.

+37
+23
src/services/identity.ts
··· 1 + import { isValidHandle, isValidDid } from '@atproto/syntax'; 2 + import { Agent } from '@atproto/api'; 3 + 4 + export const isHandle = isValidHandle; 5 + export const isDid = isValidDid; 6 + 7 + export async function resolveHandleOrDid( 8 + handleOrDid: string, 9 + ): Promise<{ did: string; handle: string }> { 10 + const agent = new Agent('https://public.api.bsky.app'); 11 + 12 + if (isDid(handleOrDid)) { 13 + const profile = await agent.getProfile({ actor: handleOrDid }); 14 + return { did: profile.data.did, handle: profile.data.handle }; 15 + } 16 + 17 + if (isHandle(handleOrDid)) { 18 + const resolved = await agent.resolveHandle({ handle: handleOrDid }); 19 + return { did: resolved.data.did, handle: handleOrDid }; 20 + } 21 + 22 + throw new Error(`Invalid handle or DID: ${handleOrDid}`); 23 + }
+14
tests/services/identity.test.ts
··· 1 + import { describe, it, expect } from 'vitest'; 2 + import { isHandle, isDid } from '../../src/services/identity.js'; 3 + 4 + describe('Identity utilities', () => { 5 + it('identifies handles', () => { 6 + expect(isHandle('alice.bsky.social')).toBe(true); 7 + expect(isHandle('did:plc:abc123')).toBe(false); 8 + }); 9 + 10 + it('identifies DIDs', () => { 11 + expect(isDid('did:plc:abc123')).toBe(true); 12 + expect(isDid('alice.bsky.social')).toBe(false); 13 + }); 14 + });