this repo has no description
1import { z } from "zod"; 2import { err, ok, type Result } from "./types/result.ts"; 3import { ApiError } from "./api.ts"; 4import type { 5 AccessToken, 6 Did, 7 Nsid, 8 RefreshToken, 9 Rkey, 10} from "./types/branded.ts"; 11import { 12 accountInfoSchema, 13 appPasswordSchema, 14 createBackupResponseSchema, 15 createdAppPasswordSchema, 16 createRecordResponseSchema, 17 didDocumentSchema, 18 enableTotpResponseSchema, 19 legacyLoginPreferenceSchema, 20 listBackupsResponseSchema, 21 listPasskeysResponseSchema, 22 listRecordsResponseSchema, 23 listSessionsResponseSchema, 24 listTrustedDevicesResponseSchema, 25 notificationPrefsSchema, 26 passwordStatusSchema, 27 reauthStatusSchema, 28 recordResponseSchema, 29 repoDescriptionSchema, 30 searchAccountsResponseSchema, 31 serverConfigSchema, 32 serverDescriptionSchema, 33 serverStatsSchema, 34 sessionSchema, 35 successResponseSchema, 36 totpSecretSchema, 37 totpStatusSchema, 38 type ValidatedAccountInfo, 39 type ValidatedAppPassword, 40 type ValidatedCreateBackupResponse, 41 type ValidatedCreatedAppPassword, 42 type ValidatedCreateRecordResponse, 43 type ValidatedDidDocument, 44 type ValidatedEnableTotpResponse, 45 type ValidatedLegacyLoginPreference, 46 type ValidatedListBackupsResponse, 47 type ValidatedListPasskeysResponse, 48 type ValidatedListRecordsResponse, 49 type ValidatedListSessionsResponse, 50 type ValidatedListTrustedDevicesResponse, 51 type ValidatedNotificationPrefs, 52 type ValidatedPasswordStatus, 53 type ValidatedReauthStatus, 54 type ValidatedRecordResponse, 55 type ValidatedRepoDescription, 56 type ValidatedSearchAccountsResponse, 57 type ValidatedServerConfig, 58 type ValidatedServerDescription, 59 type ValidatedServerStats, 60 type ValidatedSession, 61 type ValidatedSuccessResponse, 62 type ValidatedTotpSecret, 63 type ValidatedTotpStatus, 64} from "./types/schemas.ts"; 65 66const API_BASE = "/xrpc"; 67 68interface XrpcOptions { 69 method?: "GET" | "POST"; 70 params?: Record<string, string>; 71 body?: unknown; 72 token?: string; 73} 74 75class ValidationError extends Error { 76 constructor( 77 public issues: z.ZodIssue[], 78 message: string = "API response validation failed", 79 ) { 80 super(message); 81 this.name = "ValidationError"; 82 } 83} 84 85async function xrpcValidated<T>( 86 method: string, 87 schema: z.ZodType<T>, 88 options?: XrpcOptions, 89): Promise<Result<T, ApiError | ValidationError>> { 90 const { method: httpMethod = "GET", params, body, token } = options ?? {}; 91 let url = `${API_BASE}/${method}`; 92 if (params) { 93 const searchParams = new URLSearchParams(params); 94 url += `?${searchParams}`; 95 } 96 const headers: Record<string, string> = {}; 97 if (token) { 98 headers["Authorization"] = `Bearer ${token}`; 99 } 100 if (body) { 101 headers["Content-Type"] = "application/json"; 102 } 103 104 try { 105 const res = await fetch(url, { 106 method: httpMethod, 107 headers, 108 body: body ? JSON.stringify(body) : undefined, 109 }); 110 111 if (!res.ok) { 112 const errData = await res.json().catch(() => ({ 113 error: "Unknown", 114 message: res.statusText, 115 })); 116 return err(new ApiError(res.status, errData.error, errData.message)); 117 } 118 119 const data = await res.json(); 120 const parsed = schema.safeParse(data); 121 122 if (!parsed.success) { 123 return err(new ValidationError(parsed.error.issues)); 124 } 125 126 return ok(parsed.data); 127 } catch (e) { 128 if (e instanceof ApiError || e instanceof ValidationError) { 129 return err(e); 130 } 131 return err( 132 new ApiError(0, "Unknown", e instanceof Error ? e.message : String(e)), 133 ); 134 } 135} 136 137export const validatedApi = { 138 getSession( 139 token: AccessToken, 140 ): Promise<Result<ValidatedSession, ApiError | ValidationError>> { 141 return xrpcValidated("com.atproto.server.getSession", sessionSchema, { 142 token, 143 }); 144 }, 145 146 refreshSession( 147 refreshJwt: RefreshToken, 148 ): Promise<Result<ValidatedSession, ApiError | ValidationError>> { 149 return xrpcValidated("com.atproto.server.refreshSession", sessionSchema, { 150 method: "POST", 151 token: refreshJwt, 152 }); 153 }, 154 155 createSession( 156 identifier: string, 157 password: string, 158 ): Promise<Result<ValidatedSession, ApiError | ValidationError>> { 159 return xrpcValidated("com.atproto.server.createSession", sessionSchema, { 160 method: "POST", 161 body: { identifier, password }, 162 }); 163 }, 164 165 describeServer(): Promise< 166 Result<ValidatedServerDescription, ApiError | ValidationError> 167 > { 168 return xrpcValidated( 169 "com.atproto.server.describeServer", 170 serverDescriptionSchema, 171 ); 172 }, 173 174 listAppPasswords( 175 token: AccessToken, 176 ): Promise< 177 Result<{ passwords: ValidatedAppPassword[] }, ApiError | ValidationError> 178 > { 179 return xrpcValidated( 180 "com.atproto.server.listAppPasswords", 181 z.object({ passwords: z.array(appPasswordSchema) }), 182 { token }, 183 ); 184 }, 185 186 createAppPassword( 187 token: AccessToken, 188 name: string, 189 scopes?: string, 190 ): Promise<Result<ValidatedCreatedAppPassword, ApiError | ValidationError>> { 191 return xrpcValidated( 192 "com.atproto.server.createAppPassword", 193 createdAppPasswordSchema, 194 { 195 method: "POST", 196 token, 197 body: { name, scopes }, 198 }, 199 ); 200 }, 201 202 listSessions( 203 token: AccessToken, 204 ): Promise< 205 Result<ValidatedListSessionsResponse, ApiError | ValidationError> 206 > { 207 return xrpcValidated("_account.listSessions", listSessionsResponseSchema, { 208 token, 209 }); 210 }, 211 212 getTotpStatus( 213 token: AccessToken, 214 ): Promise<Result<ValidatedTotpStatus, ApiError | ValidationError>> { 215 return xrpcValidated("com.atproto.server.getTotpStatus", totpStatusSchema, { 216 token, 217 }); 218 }, 219 220 createTotpSecret( 221 token: AccessToken, 222 ): Promise<Result<ValidatedTotpSecret, ApiError | ValidationError>> { 223 return xrpcValidated( 224 "com.atproto.server.createTotpSecret", 225 totpSecretSchema, 226 { 227 method: "POST", 228 token, 229 }, 230 ); 231 }, 232 233 enableTotp( 234 token: AccessToken, 235 code: string, 236 ): Promise<Result<ValidatedEnableTotpResponse, ApiError | ValidationError>> { 237 return xrpcValidated( 238 "com.atproto.server.enableTotp", 239 enableTotpResponseSchema, 240 { 241 method: "POST", 242 token, 243 body: { code }, 244 }, 245 ); 246 }, 247 248 listPasskeys( 249 token: AccessToken, 250 ): Promise< 251 Result<ValidatedListPasskeysResponse, ApiError | ValidationError> 252 > { 253 return xrpcValidated( 254 "com.atproto.server.listPasskeys", 255 listPasskeysResponseSchema, 256 { token }, 257 ); 258 }, 259 260 listTrustedDevices( 261 token: AccessToken, 262 ): Promise< 263 Result<ValidatedListTrustedDevicesResponse, ApiError | ValidationError> 264 > { 265 return xrpcValidated( 266 "_account.listTrustedDevices", 267 listTrustedDevicesResponseSchema, 268 { token }, 269 ); 270 }, 271 272 getReauthStatus( 273 token: AccessToken, 274 ): Promise<Result<ValidatedReauthStatus, ApiError | ValidationError>> { 275 return xrpcValidated("_account.getReauthStatus", reauthStatusSchema, { 276 token, 277 }); 278 }, 279 280 getNotificationPrefs( 281 token: AccessToken, 282 ): Promise<Result<ValidatedNotificationPrefs, ApiError | ValidationError>> { 283 return xrpcValidated( 284 "_account.getNotificationPrefs", 285 notificationPrefsSchema, 286 { token }, 287 ); 288 }, 289 290 getDidDocument( 291 token: AccessToken, 292 ): Promise<Result<ValidatedDidDocument, ApiError | ValidationError>> { 293 return xrpcValidated("_account.getDidDocument", didDocumentSchema, { 294 token, 295 }); 296 }, 297 298 describeRepo( 299 token: AccessToken, 300 repo: Did, 301 ): Promise<Result<ValidatedRepoDescription, ApiError | ValidationError>> { 302 return xrpcValidated( 303 "com.atproto.repo.describeRepo", 304 repoDescriptionSchema, 305 { 306 token, 307 params: { repo }, 308 }, 309 ); 310 }, 311 312 listRecords( 313 token: AccessToken, 314 repo: Did, 315 collection: Nsid, 316 options?: { limit?: number; cursor?: string; reverse?: boolean }, 317 ): Promise<Result<ValidatedListRecordsResponse, ApiError | ValidationError>> { 318 const params: Record<string, string> = { repo, collection }; 319 if (options?.limit) params.limit = String(options.limit); 320 if (options?.cursor) params.cursor = options.cursor; 321 if (options?.reverse) params.reverse = "true"; 322 return xrpcValidated( 323 "com.atproto.repo.listRecords", 324 listRecordsResponseSchema, 325 { 326 token, 327 params, 328 }, 329 ); 330 }, 331 332 getRecord( 333 token: AccessToken, 334 repo: Did, 335 collection: Nsid, 336 rkey: Rkey, 337 ): Promise<Result<ValidatedRecordResponse, ApiError | ValidationError>> { 338 return xrpcValidated("com.atproto.repo.getRecord", recordResponseSchema, { 339 token, 340 params: { repo, collection, rkey }, 341 }); 342 }, 343 344 createRecord( 345 token: AccessToken, 346 repo: Did, 347 collection: Nsid, 348 record: unknown, 349 rkey?: Rkey, 350 ): Promise< 351 Result<ValidatedCreateRecordResponse, ApiError | ValidationError> 352 > { 353 return xrpcValidated( 354 "com.atproto.repo.createRecord", 355 createRecordResponseSchema, 356 { 357 method: "POST", 358 token, 359 body: { repo, collection, record, rkey }, 360 }, 361 ); 362 }, 363 364 getServerStats( 365 token: AccessToken, 366 ): Promise<Result<ValidatedServerStats, ApiError | ValidationError>> { 367 return xrpcValidated("_admin.getServerStats", serverStatsSchema, { token }); 368 }, 369 370 getServerConfig(): Promise< 371 Result<ValidatedServerConfig, ApiError | ValidationError> 372 > { 373 return xrpcValidated("_server.getConfig", serverConfigSchema); 374 }, 375 376 getPasswordStatus( 377 token: AccessToken, 378 ): Promise<Result<ValidatedPasswordStatus, ApiError | ValidationError>> { 379 return xrpcValidated("_account.getPasswordStatus", passwordStatusSchema, { 380 token, 381 }); 382 }, 383 384 changePassword( 385 token: AccessToken, 386 currentPassword: string, 387 newPassword: string, 388 ): Promise<Result<ValidatedSuccessResponse, ApiError | ValidationError>> { 389 return xrpcValidated("_account.changePassword", successResponseSchema, { 390 method: "POST", 391 token, 392 body: { currentPassword, newPassword }, 393 }); 394 }, 395 396 getLegacyLoginPreference( 397 token: AccessToken, 398 ): Promise< 399 Result<ValidatedLegacyLoginPreference, ApiError | ValidationError> 400 > { 401 return xrpcValidated( 402 "_account.getLegacyLoginPreference", 403 legacyLoginPreferenceSchema, 404 { token }, 405 ); 406 }, 407 408 getAccountInfo( 409 token: AccessToken, 410 did: Did, 411 ): Promise<Result<ValidatedAccountInfo, ApiError | ValidationError>> { 412 return xrpcValidated( 413 "com.atproto.admin.getAccountInfo", 414 accountInfoSchema, 415 { 416 token, 417 params: { did }, 418 }, 419 ); 420 }, 421 422 searchAccounts( 423 token: AccessToken, 424 options?: { handle?: string; cursor?: string; limit?: number }, 425 ): Promise< 426 Result<ValidatedSearchAccountsResponse, ApiError | ValidationError> 427 > { 428 const params: Record<string, string> = {}; 429 if (options?.handle) params.handle = options.handle; 430 if (options?.cursor) params.cursor = options.cursor; 431 if (options?.limit) params.limit = String(options.limit); 432 return xrpcValidated( 433 "com.atproto.admin.searchAccounts", 434 searchAccountsResponseSchema, 435 { 436 token, 437 params, 438 }, 439 ); 440 }, 441 442 listBackups( 443 token: AccessToken, 444 ): Promise<Result<ValidatedListBackupsResponse, ApiError | ValidationError>> { 445 return xrpcValidated("_backup.listBackups", listBackupsResponseSchema, { 446 token, 447 }); 448 }, 449 450 createBackup( 451 token: AccessToken, 452 ): Promise< 453 Result<ValidatedCreateBackupResponse, ApiError | ValidationError> 454 > { 455 return xrpcValidated("_backup.createBackup", createBackupResponseSchema, { 456 method: "POST", 457 token, 458 }); 459 }, 460}; 461 462export { ValidationError };