Suite of AT Protocol TypeScript libraries built on web standards
20
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 578 lines 16 kB view raw
1import { z } from "zod"; 2import type { 3 AtIdentifierString, 4 AtUriString, 5 CidString, 6 DatetimeString, 7 DidString, 8 HandleString, 9 InferMethodInputBody, 10 InferMethodParams, 11 Procedure, 12 Query, 13 RecordKeyString, 14 TidString, 15} from "@atp/lex"; 16 17export type QueryParams = Record<string, unknown>; 18export type HeadersMap = Record<string, string | undefined>; 19export type XrpcMethod = Query | Procedure; 20export type XrpcMethodNamespace<M extends XrpcMethod = XrpcMethod> = 21 | { readonly main: M } 22 | { readonly Main: M }; 23export type XrpcMethodLike<M extends XrpcMethod = XrpcMethod> = 24 | M 25 | XrpcMethodNamespace<M>; 26 27type InferXrpcMethod<M extends XrpcMethodLike> = M extends XrpcMethod ? M 28 : M extends { readonly main: infer Inner } ? Inner extends XrpcMethod ? Inner 29 : never 30 : M extends { readonly Main: infer Inner } ? Inner extends XrpcMethod ? Inner 31 : never 32 : never; 33 34type Digit = 35 | "0" 36 | "1" 37 | "2" 38 | "3" 39 | "4" 40 | "5" 41 | "6" 42 | "7" 43 | "8" 44 | "9"; 45type LowerAlpha = 46 | "a" 47 | "b" 48 | "c" 49 | "d" 50 | "e" 51 | "f" 52 | "g" 53 | "h" 54 | "i" 55 | "j" 56 | "k" 57 | "l" 58 | "m" 59 | "n" 60 | "o" 61 | "p" 62 | "q" 63 | "r" 64 | "s" 65 | "t" 66 | "u" 67 | "v" 68 | "w" 69 | "x" 70 | "y" 71 | "z"; 72type UpperAlpha = Uppercase<LowerAlpha>; 73type Alpha = LowerAlpha | UpperAlpha; 74type DidChar = Alpha | Digit | "." | "_" | ":" | "%" | "-"; 75type DidEndChar = Alpha | Digit | "." | "_" | "-"; 76type HandleChar = Alpha | Digit | "-"; 77type RecordKeyChar = Alpha | Digit | "_" | "~" | "." | ":" | "-"; 78type TidInitialChar = 79 | "2" 80 | "3" 81 | "4" 82 | "5" 83 | "6" 84 | "7" 85 | "a" 86 | "b" 87 | "c" 88 | "d" 89 | "e" 90 | "f" 91 | "g" 92 | "h" 93 | "i" 94 | "j"; 95type TidChar = 96 | "2" 97 | "3" 98 | "4" 99 | "5" 100 | "6" 101 | "7" 102 | LowerAlpha; 103type Base32Char = LowerAlpha | "2" | "3" | "4" | "5" | "6" | "7"; 104type Base58Char = 105 | "1" 106 | "2" 107 | "3" 108 | "4" 109 | "5" 110 | "6" 111 | "7" 112 | "8" 113 | "9" 114 | "A" 115 | "B" 116 | "C" 117 | "D" 118 | "E" 119 | "F" 120 | "G" 121 | "H" 122 | "J" 123 | "K" 124 | "L" 125 | "M" 126 | "N" 127 | "P" 128 | "Q" 129 | "R" 130 | "S" 131 | "T" 132 | "U" 133 | "V" 134 | "W" 135 | "X" 136 | "Y" 137 | "Z" 138 | "a" 139 | "b" 140 | "c" 141 | "d" 142 | "e" 143 | "f" 144 | "g" 145 | "h" 146 | "i" 147 | "j" 148 | "k" 149 | "m" 150 | "n" 151 | "o" 152 | "p" 153 | "q" 154 | "r" 155 | "s" 156 | "t" 157 | "u" 158 | "v" 159 | "w" 160 | "x" 161 | "y" 162 | "z"; 163 164type IsLiteralString<S extends string> = string extends S ? false : true; 165 166type IsChars<S extends string, Allowed extends string> = 167 IsLiteralString<S> extends false ? false 168 : S extends "" ? true 169 : S extends `${infer First}${infer Rest}` 170 ? First extends Allowed ? IsChars<Rest, Allowed> 171 : false 172 : false; 173 174type IsDidMethod<S extends string> = IsLiteralString<S> extends false ? false 175 : S extends `${infer First}${infer Rest}` 176 ? First extends LowerAlpha 177 ? Rest extends "" ? true : IsChars<Rest, LowerAlpha> 178 : false 179 : false; 180 181type IsDidIdentifier<S extends string> = IsLiteralString<S> extends false 182 ? false 183 : S extends `${infer First}${infer Rest}` 184 ? Rest extends "" ? First extends DidEndChar ? true : false 185 : First extends DidChar ? IsDidIdentifier<Rest> 186 : false 187 : false; 188 189type IsDidLiteral<S extends string> = S extends 190 `did:${infer Method}:${infer Id}` 191 ? IsDidMethod<Method> extends true ? IsDidIdentifier<Id> 192 : false 193 : false; 194 195type IsHandleLabel<S extends string> = IsLiteralString<S> extends false ? false 196 : S extends `${infer First}${infer Rest}` 197 ? First extends Alpha | Digit 198 ? Rest extends "" ? true : IsHandleLabelTail<Rest> 199 : false 200 : false; 201 202type IsHandleLabelTail<S extends string> = S extends 203 `${infer First}${infer Rest}` 204 ? Rest extends "" ? First extends Alpha | Digit ? true : false 205 : First extends HandleChar ? IsHandleLabelTail<Rest> 206 : false 207 : false; 208 209type IsFinalHandleLabel<S extends string> = IsLiteralString<S> extends false 210 ? false 211 : S extends `${infer First}${infer Rest}` 212 ? First extends Alpha ? Rest extends "" ? true : IsHandleLabelTail<Rest> 213 : false 214 : false; 215 216type IsHandleParts<S extends string> = S extends `${infer Label}.${infer Rest}` 217 ? IsHandleLabel<Label> extends true ? IsHandleParts<Rest> : false 218 : IsFinalHandleLabel<S>; 219 220type IsHandleLiteral<S extends string> = S extends `${string}.${string}` 221 ? IsHandleParts<S> 222 : false; 223 224type IsAtIdentifierLiteral<S extends string> = IsDidLiteral<S> extends true 225 ? true 226 : IsHandleLiteral<S>; 227 228type IsRecordKeyLiteral<S extends string> = IsLiteralString<S> extends false 229 ? false 230 : S extends "." | ".." ? false 231 : S extends `${infer _First}${infer _Rest}` ? IsChars<S, RecordKeyChar> 232 : false; 233 234type IsExactChars< 235 S extends string, 236 Allowed extends string, 237 Length extends number, 238 Count extends unknown[] = [], 239> = Count["length"] extends Length ? (S extends "" ? true : false) 240 : S extends `${infer First}${infer Rest}` 241 ? First extends Allowed 242 ? IsExactChars<Rest, Allowed, Length, [...Count, unknown]> 243 : false 244 : false; 245 246type IsTidLiteral<S extends string> = S extends `${infer First}${infer Rest}` 247 ? First extends TidInitialChar ? IsExactChars<Rest, TidChar, 12> : false 248 : false; 249 250type IsDatetimeDate<S extends string> = S extends 251 `${infer Year}-${infer Month}-${infer Day}` 252 ? IsExactChars<Year, Digit, 4> extends true 253 ? IsExactChars<Month, Digit, 2> extends true ? IsExactChars<Day, Digit, 2> 254 : false 255 : false 256 : false; 257 258type IsDatetimeTime<S extends string> = S extends 259 `${infer Hour}:${infer Minute}:${infer Second}` 260 ? IsExactChars<Hour, Digit, 2> extends true 261 ? IsExactChars<Minute, Digit, 2> extends true 262 ? IsExactChars<Second, Digit, 2> 263 : false 264 : false 265 : false; 266 267type IsDatetimeOffset<S extends string> = S extends 268 `${infer Hour}:${infer Minute}` 269 ? IsExactChars<Hour, Digit, 2> extends true ? IsExactChars<Minute, Digit, 2> 270 : false 271 : false; 272 273type IsDatetimeTail<S extends string> = S extends 274 `${infer Time}.${infer Fraction}Z` 275 ? IsDatetimeTime<Time> extends true ? IsChars<Fraction, Digit> : false 276 : S extends `${infer Time}Z` ? IsDatetimeTime<Time> 277 : S extends `${infer Time}+${infer Offset}` 278 ? IsDatetimeTime<Time> extends true ? IsDatetimeOffset<Offset> : false 279 : S extends `${infer Time}-${infer Offset}` 280 ? IsDatetimeTime<Time> extends true ? IsDatetimeOffset<Offset> : false 281 : S extends `${infer Time}.${infer Fraction}+${infer Offset}` 282 ? IsDatetimeTime<Time> extends true 283 ? IsChars<Fraction, Digit> extends true ? IsDatetimeOffset<Offset> : false 284 : false 285 : S extends `${infer Time}.${infer Fraction}-${infer Offset}` 286 ? IsDatetimeTime<Time> extends true 287 ? IsChars<Fraction, Digit> extends true ? IsDatetimeOffset<Offset> : false 288 : false 289 : false; 290 291type IsDatetimeLiteral<S extends string> = S extends 292 `${infer Date}T${infer Tail}` 293 ? IsDatetimeDate<Date> extends true ? IsDatetimeTail<Tail> : false 294 : false; 295 296type AtUriHost<S extends string> = S extends `at://${infer Host}/${string}` 297 ? Host 298 : S extends `at://${infer Host}?${string}` ? Host 299 : S extends `at://${infer Host}#${string}` ? Host 300 : S extends `at://${infer Host}` ? Host 301 : never; 302 303type IsAtUriLiteral<S extends string> = [AtUriHost<S>] extends [never] ? false 304 : AtUriHost<S> extends infer Host extends string 305 ? Host extends "" ? false : IsAtIdentifierLiteral<Host> 306 : false; 307 308type IsCidLiteral<S extends string> = S extends `b${infer Rest}` 309 ? IsChars<Rest, Base32Char> 310 : S extends `z${infer Rest}` ? IsChars<Rest, Base58Char> 311 : S extends `Qm${infer Rest}` ? IsChars<Rest, Base58Char> 312 : false; 313 314type ValidateStringLiteral<Actual, Expected> = Actual extends string 315 ? IsLiteralString<Actual> extends false ? never 316 : [Expected] extends [DidString] ? IsDidLiteral<Actual> extends true ? Actual 317 : never 318 : [Expected] extends [HandleString] 319 ? IsHandleLiteral<Actual> extends true ? Actual : never 320 : [Expected] extends [AtIdentifierString] 321 ? IsAtIdentifierLiteral<Actual> extends true ? Actual : never 322 : [Expected] extends [RecordKeyString] 323 ? IsRecordKeyLiteral<Actual> extends true ? Actual : never 324 : [Expected] extends [TidString] ? IsTidLiteral<Actual> extends true ? Actual 325 : never 326 : [Expected] extends [DatetimeString] 327 ? IsDatetimeLiteral<Actual> extends true ? Actual : never 328 : [Expected] extends [AtUriString] 329 ? IsAtUriLiteral<Actual> extends true ? Actual : never 330 : [Expected] extends [CidString] ? IsCidLiteral<Actual> extends true ? Actual 331 : never 332 : never 333 : never; 334 335type ValidateCallValue<Actual, Expected> = [Actual] extends [Expected] ? Actual 336 : undefined extends Expected 337 ? ValidateCallValue<Actual, Exclude<Expected, undefined>> 338 : Expected extends string ? ValidateStringLiteral<Actual, Expected> 339 : Expected extends readonly (infer Value)[] 340 ? Actual extends readonly unknown[] 341 ? { [K in keyof Actual]: ValidateCallValue<Actual[K], Value> } 342 : never 343 : Expected extends object 344 ? Actual extends object ? ValidateCallObject<Actual, Expected> 345 : never 346 : never; 347 348type ValidateCallObject<Actual, Expected> = 349 & { 350 [K in keyof Expected]: K extends keyof Actual 351 ? ValidateCallValue<Actual[K], Expected[K]> 352 : Expected[K]; 353 } 354 & { 355 [K in Exclude<keyof Actual, keyof Expected>]: never; 356 }; 357 358export type { 359 /** 360 * @deprecated not to be confused with the WHATWG Headers constructor. 361 * Use {@linkcode HeadersMap} instead. 362 */ 363 HeadersMap as Headers, 364}; 365 366export type Gettable<T> = T | (() => T); 367 368export interface CallOptions { 369 encoding?: string; 370 signal?: AbortSignal; 371 headers?: HeadersMap; 372} 373 374export type BinaryBodyInit = 375 | Uint8Array 376 | ArrayBuffer 377 | ArrayBufferView 378 | Blob 379 | ReadableStream<Uint8Array> 380 | AsyncIterable<Uint8Array> 381 | string; 382 383export interface XrpcCallOptions< 384 M extends XrpcMethodLike = XrpcMethod, 385> extends CallOptions { 386 params?: InferMethodParams<InferXrpcMethod<M>>; 387 body?: InferXrpcMethod<M> extends Procedure 388 ? InferMethodInputBody<InferXrpcMethod<M>, BinaryBodyInit> 389 : undefined; 390 validateRequest?: boolean; 391 validateResponse?: boolean; 392} 393 394export type XrpcCallCompatibleOptions< 395 M extends XrpcMethodLike = XrpcMethod, 396 O = XrpcCallOptions<M>, 397> = ValidateCallValue<O, XrpcCallOptions<M>>; 398 399export const errorResponseBody: z.ZodObject<{ 400 error: z.ZodOptional<z.ZodString>; 401 message: z.ZodOptional<z.ZodString>; 402}> = z.object({ 403 error: z.string().optional(), 404 message: z.string().optional(), 405}); 406export type ErrorResponseBody = z.infer<typeof errorResponseBody>; 407 408export enum ResponseType { 409 /** 410 * Network issue, unable to get response from the server. 411 */ 412 Unknown = 1, 413 /** 414 * Response failed lexicon validation. 415 */ 416 InvalidResponse = 2, 417 Success = 200, 418 InvalidRequest = 400, 419 AuthenticationRequired = 401, 420 Forbidden = 403, 421 XRPCNotSupported = 404, 422 NotAcceptable = 406, 423 PayloadTooLarge = 413, 424 UnsupportedMediaType = 415, 425 RateLimitExceeded = 429, 426 InternalServerError = 500, 427 MethodNotImplemented = 501, 428 UpstreamFailure = 502, 429 NotEnoughResources = 503, 430 UpstreamTimeout = 504, 431} 432 433export function httpResponseCodeToEnum(status: number): ResponseType { 434 if (status in ResponseType) { 435 return status; 436 } else if (status >= 100 && status < 200) { 437 return ResponseType.XRPCNotSupported; 438 } else if (status >= 200 && status < 300) { 439 return ResponseType.Success; 440 } else if (status >= 300 && status < 400) { 441 return ResponseType.XRPCNotSupported; 442 } else if (status >= 400 && status < 500) { 443 return ResponseType.InvalidRequest; 444 } else { 445 return ResponseType.InternalServerError; 446 } 447} 448 449export function httpResponseCodeToName(status: number): string { 450 return ResponseType[httpResponseCodeToEnum(status)]; 451} 452 453/** 454 * Error messages corresponding to XRPC error codes. 455 */ 456export const ResponseTypeStrings: Record<ResponseType, string> = { 457 [ResponseType.Unknown]: "Unknown", 458 [ResponseType.InvalidResponse]: "Invalid Response", 459 [ResponseType.Success]: "Success", 460 [ResponseType.InvalidRequest]: "Invalid Request", 461 [ResponseType.AuthenticationRequired]: "Authentication Required", 462 [ResponseType.Forbidden]: "Forbidden", 463 [ResponseType.XRPCNotSupported]: "XRPC Not Supported", 464 [ResponseType.NotAcceptable]: "Not Acceptable", 465 [ResponseType.PayloadTooLarge]: "Payload Too Large", 466 [ResponseType.UnsupportedMediaType]: "Unsupported Media Type", 467 [ResponseType.RateLimitExceeded]: "Rate Limit Exceeded", 468 [ResponseType.InternalServerError]: "Internal Server Error", 469 [ResponseType.MethodNotImplemented]: "Method Not Implemented", 470 [ResponseType.UpstreamFailure]: "Upstream Failure", 471 [ResponseType.NotEnoughResources]: "Not Enough Resources", 472 [ResponseType.UpstreamTimeout]: "Upstream Timeout", 473} as const satisfies Record<ResponseType, string>; 474 475export function httpResponseCodeToString(status: number): string { 476 return ResponseTypeStrings[httpResponseCodeToEnum(status)]; 477} 478 479/** 480 * Response type of a successful XRPC request. 481 */ 482export class XRPCResponse { 483 readonly success: true = true; 484 485 constructor( 486 public data: any, 487 public headers: HeadersMap, 488 ) {} 489} 490 491/** 492 * Response type of a failed XRPC request with details of the error. 493 */ 494export class XRPCError extends Error { 495 readonly success: false = false; 496 497 public status: ResponseType; 498 499 constructor( 500 statusCode: number, 501 public error: string = httpResponseCodeToName(statusCode), 502 message?: string, 503 public headers?: HeadersMap, 504 options?: ErrorOptions, 505 ) { 506 super(message || error || httpResponseCodeToString(statusCode), options); 507 508 this.status = httpResponseCodeToEnum(statusCode); 509 510 // Pre 2022 runtimes won't handle the "options" constructor argument 511 const cause = options?.cause; 512 if (this.cause === undefined && cause !== undefined) { 513 this.cause = cause; 514 } 515 } 516 517 static from(cause: unknown, fallbackStatus?: ResponseType): XRPCError { 518 if (cause instanceof XRPCError) { 519 return cause; 520 } 521 522 // Type cast the cause to an Error if it is one 523 const causeErr = cause instanceof Error ? cause : undefined; 524 525 // Try and find a Response object in the cause 526 const causeResponse: Response | undefined = cause instanceof Response 527 ? cause 528 : (cause && typeof cause === "object" && "response" in cause && 529 cause.response instanceof Response) 530 ? cause.response 531 : undefined; 532 533 const statusCode: unknown = 534 // Extract status code from "http-errors" like errors 535 (causeErr && typeof causeErr === "object" && "statusCode" in causeErr) 536 ? causeErr.statusCode 537 : (causeErr && typeof causeErr === "object" && "status" in causeErr) 538 ? causeErr.status 539 // Use the status code from the response object as fallback 540 : causeResponse?.status; 541 542 // Convert the status code to a ResponseType 543 const status: ResponseType = typeof statusCode === "number" 544 ? httpResponseCodeToEnum(statusCode) 545 : fallbackStatus ?? ResponseType.Unknown; 546 547 const message = causeErr?.message ?? String(cause); 548 549 const headers = causeResponse 550 ? Object.fromEntries(causeResponse.headers.entries()) 551 : undefined; 552 553 return new XRPCError(status, undefined, message, headers, { cause }); 554 } 555} 556 557/** 558 * Error for an invalid response from an XRPC request. 559 * Caused by a validation error with the lexicon schema 560 * matching the NSID of the endpoint. 561 */ 562export class XRPCInvalidResponseError extends XRPCError { 563 constructor( 564 public lexiconNsid: string, 565 public validationError: Error, 566 public responseBody: unknown, 567 ) { 568 super( 569 ResponseType.InvalidResponse, 570 // @NOTE: This is probably wrong and should use ResponseTypeNames instead. 571 // But it would mean a breaking change. 572 ResponseTypeStrings[ResponseType.InvalidResponse], 573 `The server gave an invalid response and may be out of date.`, 574 undefined, 575 { cause: validationError }, 576 ); 577 } 578}