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 513 lines 16 kB view raw
1import type { Context, HonoRequest, Next } from "hono"; 2import type { 3 InferMethodInput, 4 InferMethodMessage, 5 InferMethodOutput, 6 InferMethodParams, 7 Procedure, 8 Query, 9 Subscription, 10} from "@atp/lex"; 11import { z } from "zod"; 12import type { ErrorResult, XRPCError } from "./errors.ts"; 13import type { CalcKeyFn, CalcPointsFn } from "./rate-limiter.ts"; 14import type { RateLimiterI } from "./rate-limiter.ts"; 15 16/** 17 * Represents a value that can be either synchronous or asynchronous. 18 * @template T - The type of the value 19 */ 20export type Awaitable<T> = T | Promise<T>; 21 22/** 23 * Handler function for catching all unmatched routes. 24 * @param c - The Hono context object 25 * @param next - The next middleware function 26 * @returns A promise that resolves to void or a Response 27 */ 28export type CatchallHandler = ( 29 c: Context, 30 next: Next, 31) => Promise<void | Response>; 32 33/** 34 * Configuration options for the XRPC server. 35 */ 36export type Options = { 37 /** Whether to validate response schemas */ 38 validateResponse?: boolean; 39 /** Handler for catching all unmatched routes */ 40 catchall?: CatchallHandler; 41 /** Payload size limits for different content types */ 42 payload?: RouteOptions; 43 /** Rate limiting configuration */ 44 rateLimits?: { 45 /** Factory function for creating rate limiters */ 46 creator: RateLimiterCreator<HandlerContext>; 47 /** Global rate limits applied to all routes */ 48 global?: ServerRateLimitDescription<HandlerContext>[]; 49 /** Shared rate limits that can be referenced by name */ 50 shared?: ServerRateLimitDescription<HandlerContext>[]; 51 /** Function to determine if rate limits should be bypassed for a request */ 52 bypass?: (ctx: HandlerContext) => boolean; 53 }; 54 /** 55 * By default, errors are converted to {@link XRPCError} using 56 * {@link XRPCError.fromError} before being rendered. If method handlers throw 57 * error objects that are not properly rendered in the HTTP response, this 58 * function can be used to properly convert them to {@link XRPCError}. The 59 * provided function will typically fallback to the default error conversion 60 * (`return XRPCError.fromError(err)`) if the error is not recognized. 61 * 62 * @note This function should not throw errors. 63 */ 64 errorParser?: (err: unknown) => XRPCError; 65}; 66 67/** 68 * Raw query parameters from the HTTP request before type conversion. 69 */ 70export type UndecodedParams = HonoRequest["query"]; 71 72/** 73 * Basic primitive types supported in XRPC parameters. 74 */ 75export type Primitive = string | number | boolean; 76 77/** 78 * Type-safe parameter object with optional primitive values or arrays. 79 */ 80export type Params = { [P in string]?: undefined | Primitive | Primitive[] }; 81 82/** 83 * Input data for XRPC method handlers. 84 */ 85export type HandlerInput = { 86 /** Content encoding of the request body */ 87 encoding: string; 88 /** Parsed request body */ 89 body: unknown; 90}; 91 92/** Result of successful authentication. */ 93export type AuthResult = { 94 /** Authentication credentials (e.g., user info, tokens) */ 95 credentials: unknown; 96 /** Optional authentication artifacts (e.g., session data) */ 97 artifacts?: unknown; 98}; 99 100/** Zod Schema for request headers. */ 101export const headersSchema: z.ZodRecord<z.ZodString, z.ZodString> = z.record( 102 z.string(), 103 z.string(), 104); 105 106/** HTTP headers as a record of string key-value pairs. */ 107export type Headers = z.infer<typeof headersSchema>; 108 109export const handlerSuccess: z.ZodObject<{ 110 encoding: z.ZodString; 111 body: z.ZodAny; 112 headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>; 113}> = z.object({ 114 encoding: z.string(), 115 body: z.any(), 116 headers: headersSchema.optional(), 117}); 118 119/** Successful response from a method handler. */ 120export type HandlerSuccess = z.infer<typeof handlerSuccess>; 121 122/** Handler response that pipes through a buffer. */ 123export type HandlerPipeThroughBuffer = { 124 /** Content encoding of the response */ 125 encoding: string; 126 /** Response data as a buffer */ 127 buffer: Uint8Array; 128 /** Optional HTTP headers */ 129 headers?: Headers; 130}; 131 132/** Handler response that pipes through a stream. */ 133export type HandlerPipeThroughStream = { 134 /** Content encoding of the response */ 135 encoding: string; 136 /** Response data as a readable stream */ 137 stream: ReadableStream<Uint8Array>; 138 /** Optional HTTP headers */ 139 headers?: Headers; 140}; 141 142/** Union type for handler responses that pipe data through either a buffer or stream. */ 143export type HandlerPipeThrough = 144 | HandlerPipeThroughBuffer 145 | HandlerPipeThroughStream; 146 147/** Authentication state for a handler context. */ 148export type Auth = void | AuthResult; 149 150/** Input data for a handler context. */ 151export type Input = void | HandlerInput; 152 153/** Output data from a handler. */ 154export type Output = void | HandlerSuccess | ErrorResult; 155 156/** 157 * Function that verifies authentication for a request. 158 * @template C - The context type 159 * @template A - The authentication result type 160 */ 161export type AuthVerifier<C, A extends AuthResult = AuthResult> = 162 | ((ctx: C) => Awaitable<A | ErrorResult>) 163 | ((ctx: C) => Awaitable<A>); 164 165// Handler context that combines Hono Context with XRPC-specific properties 166/** 167 * Context object provided to XRPC method handlers containing request data and utilities. 168 * @template A - Authentication type 169 * @template P - Parameters type 170 * @template I - Input type 171 */ 172export type HandlerContext< 173 A extends Auth = Auth, 174 P extends Params = Params, 175 I extends Input = Input, 176> = MethodAuthContext<P> & { 177 /** Authentication result */ 178 auth: A; 179 /** Request input data */ 180 input: I; 181 /** Function to reset rate limits for this route */ 182 resetRouteRateLimits: () => Promise<void>; 183}; 184 185/** 186 * Handler function for XRPC methods. 187 * @template A - Authentication type 188 * @template P - Parameters type 189 * @template I - Input type 190 * @template O - Output type 191 */ 192export type MethodHandler< 193 A extends Auth = Auth, 194 P extends Params = Params, 195 I extends Input = Input, 196 O extends Output = Output, 197> = (ctx: HandlerContext<A, P, I>) => Awaitable<O | HandlerPipeThrough>; 198 199/** 200 * Factory function for creating rate limiter instances. 201 * @template T - The handler context type 202 */ 203export type RateLimiterCreator<T extends HandlerContext = HandlerContext> = < 204 C extends T = T, 205>(opts: { 206 /** Prefix for rate limiter keys */ 207 keyPrefix: string; 208 /** Duration window in milliseconds */ 209 durationMs: number; 210 /** Number of points allowed in the duration window */ 211 points: number; 212 /** Function to calculate the rate limit key */ 213 calcKey: CalcKeyFn<C>; 214 /** Function to calculate points consumed */ 215 calcPoints: CalcPointsFn<C>; 216 /** Whether to fail closed (deny) when rate limiter is unavailable */ 217 failClosed?: boolean; 218}) => RateLimiterI<C>; 219 220/** 221 * Context object for method authentication containing request data. 222 * @template P - Parameters type 223 * @template I - Input type 224 */ 225export type MethodAuthContext<P extends Params = Params> = { 226 params: P; 227 req: Request; 228 res: Response; 229}; 230 231/** 232 * Authentication verifier function for XRPC methods. 233 * @template A - Authentication result type 234 * @template P - Parameters type 235 * @template I - Input type 236 */ 237export type MethodAuthVerifier< 238 A extends AuthResult = AuthResult, 239 P extends Params = Params, 240> = (ctx: MethodAuthContext<P>) => Awaitable<A>; 241 242/** 243 * Context object for streaming handlers. 244 * @template A - Authentication type 245 * @template P - Parameters type 246 */ 247export type StreamContext< 248 A extends Auth = Auth, 249 P extends Params = Params, 250> = StreamAuthContext<P> & { 251 /** Authentication result */ 252 auth: A; 253 /** Abort signal for cancelling the stream */ 254 signal: AbortSignal; 255}; 256 257/** 258 * Handler function for streaming XRPC endpoints. 259 * @template A - Authentication type 260 * @template P - Parameters type 261 * @template O - Output item type 262 */ 263export type StreamHandler< 264 A extends Auth = Auth, 265 P extends Params = Params, 266 O = unknown, 267> = (ctx: StreamContext<A, P>) => AsyncIterable<O>; 268 269/** 270 * Context object for stream authentication. 271 * @template P - Parameters type 272 */ 273export type StreamAuthContext<P extends Params = Params> = { 274 /** Parsed request parameters */ 275 params: P; 276 /** HTTP request object */ 277 req: Request; 278}; 279 280/** 281 * Authentication verifier function for streaming endpoints. 282 * @template A - Authentication result type 283 * @template P - Parameters type 284 */ 285export type StreamAuthVerifier< 286 A extends AuthResult = AuthResult, 287 P extends Params = Params, 288> = AuthVerifier<StreamAuthContext<P>, A>; 289 290export type LexMethod = Procedure | Query | Subscription; 291export type LexMethodNamespace<M extends LexMethod = LexMethod> = 292 | { readonly main: M } 293 | { readonly Main: M }; 294export type LexMethodLike<M extends LexMethod = LexMethod> = 295 | M 296 | LexMethodNamespace<M>; 297 298export type LexMethodParams<M extends Procedure | Query | Subscription> = 299 InferMethodParams<M>; 300 301export type LexMethodInput<M extends Procedure | Query> = InferMethodInput< 302 M, 303 ReadableStream<Uint8Array> 304>; 305 306export type LexMethodOutput<M extends Procedure | Query> = 307 InferMethodOutput<M, Uint8Array | ReadableStream<Uint8Array>> extends 308 undefined 309 ? InferMethodOutput<M, Uint8Array | ReadableStream<Uint8Array>> | void 310 : InferMethodOutput<M, Uint8Array | ReadableStream<Uint8Array>>; 311 312export type LexMethodMessage<M extends Subscription> = InferMethodMessage<M>; 313 314export type LexMethodHandler< 315 M extends Procedure | Query, 316 A extends Auth = Auth, 317> = MethodHandler<A, LexMethodParams<M>, LexMethodInput<M>, LexMethodOutput<M>>; 318 319export type LexMethodConfig< 320 M extends Procedure | Query, 321 A extends Auth = Auth, 322> = MethodConfig<A, LexMethodParams<M>, LexMethodInput<M>, LexMethodOutput<M>>; 323 324export type LexSubscriptionHandler< 325 M extends Subscription, 326 A extends Auth = Auth, 327> = StreamHandler< 328 A, 329 LexMethodParams<M>, 330 LexMethodMessage<M> 331>; 332 333export type LexSubscriptionConfig< 334 M extends Subscription, 335 A extends Auth = Auth, 336> = StreamConfig<A, LexMethodParams<M>, LexMethodMessage<M>>; 337 338/** 339 * Configuration for server-level rate limits. 340 * @template C - Handler context type 341 */ 342export type ServerRateLimitDescription< 343 C extends HandlerContext = HandlerContext, 344> = { 345 /** Unique name for this rate limit */ 346 name: string; 347 /** Duration window in milliseconds */ 348 durationMs: number; 349 /** Number of points allowed in the duration window */ 350 points: number; 351 /** Optional function to calculate the rate limit key */ 352 calcKey?: CalcKeyFn<C>; 353 /** Optional function to calculate points consumed */ 354 calcPoints?: CalcPointsFn<C>; 355 /** Whether to fail closed when rate limiter is unavailable */ 356 failClosed?: boolean; 357}; 358 359/** 360 * Options for referencing a shared rate limit by name. 361 * @template C - Handler context type 362 */ 363export type SharedRateLimitOpts<C extends HandlerContext = HandlerContext> = { 364 /** Name of the shared rate limit to use */ 365 name: string; 366 /** Optional function to calculate the rate limit key */ 367 calcKey?: CalcKeyFn<C>; 368 /** Optional function to calculate points consumed */ 369 calcPoints?: CalcPointsFn<C>; 370}; 371 372/** 373 * Options for defining a route-specific rate limit. 374 * @template C - Handler context type 375 */ 376export type RouteRateLimitOpts<C extends HandlerContext = HandlerContext> = { 377 /** Duration window in milliseconds */ 378 durationMs: number; 379 /** Number of points allowed in the duration window */ 380 points: number; 381 /** Optional function to calculate the rate limit key */ 382 calcKey?: CalcKeyFn<C>; 383 /** Optional function to calculate points consumed */ 384 calcPoints?: CalcPointsFn<C>; 385}; 386 387/** 388 * Union type for rate limit options - either shared or route-specific. 389 * @template C - Handler context type 390 */ 391export type RateLimitOpts<C extends HandlerContext = HandlerContext> = 392 | SharedRateLimitOpts<C> 393 | RouteRateLimitOpts<C>; 394 395/** 396 * Type guard to check if rate limit options are for a shared rate limit. 397 * @template C - Handler context type 398 * @param opts Rate limit options to check 399 * @returns True if the options reference a shared rate limit 400 */ 401export function isSharedRateLimitOpts< 402 C extends HandlerContext = HandlerContext, 403>(opts: RateLimitOpts<C>): opts is SharedRateLimitOpts<C> { 404 return "name" in opts && typeof opts.name === "string"; 405} 406 407/** Options for configuring payload size limits by content type. */ 408export type RouteOptions = { 409 /** Maximum size for binary/blob payloads in bytes */ 410 blobLimit?: number; 411 /** Maximum size for JSON payloads in bytes */ 412 jsonLimit?: number; 413 /** Maximum size for text payloads in bytes */ 414 textLimit?: number; 415}; 416 417/** Simplified route options with only blob limit configuration. */ 418export type RouteOpts = { 419 /** Maximum size for binary/blob payloads in bytes */ 420 blobLimit?: number; 421}; 422 423/** 424 * Configuration object for an XRPC method including handler, auth, and options. 425 * @template A - Authentication type 426 * @template P - Parameters type 427 * @template I - Input type 428 * @template O - Output type 429 */ 430export type MethodConfig< 431 A extends Auth = Auth, 432 P extends Params = Params, 433 I extends Input = Input, 434 O extends Output = Output, 435> = { 436 /** The method handler function */ 437 handler: MethodHandler<A, P, I, O>; 438 /** Optional authentication verifier */ 439 auth?: MethodAuthVerifier<Extract<A, AuthResult>, P>; 440 /** Optional route configuration */ 441 opts?: RouteOptions; 442 /** Optional rate limiting configuration */ 443 rateLimit?: 444 | RateLimitOpts<HandlerContext<A, P, I>> 445 | RateLimitOpts<HandlerContext<A, P, I>>[]; 446}; 447 448/** 449 * Union type allowing either a simple handler function or full method configuration. 450 * @template A - Authentication type 451 * @template P - Parameters type 452 * @template I - Input type 453 * @template O - Output type 454 */ 455export type MethodConfigOrHandler< 456 A extends Auth = Auth, 457 P extends Params = Params, 458 I extends Input = Input, 459 O extends Output = Output, 460> = MethodHandler<A, P, I, O> | MethodConfig<A, P, I, O>; 461 462/** 463 * Configuration object for a streaming XRPC endpoint. 464 * @template A - Authentication type 465 * @template P - Parameters type 466 * @template O - Output item type 467 */ 468export type StreamConfig< 469 A extends Auth = Auth, 470 P extends Params = Params, 471 O = unknown, 472> = { 473 /** Optional authentication verifier for the stream */ 474 auth?: StreamAuthVerifier<Extract<A, AuthResult>, P>; 475 /** The stream handler function */ 476 handler: StreamHandler<A, P, O>; 477}; 478 479/** 480 * Union type allowing either a simple stream handler or full stream configuration. 481 * @template A - Authentication type 482 * @template P - Parameters type 483 * @template O - Output item type 484 */ 485export type StreamConfigOrHandler< 486 A extends Auth = Auth, 487 P extends Params = Params, 488 O = unknown, 489> = StreamHandler<A, P, O> | StreamConfig<A, P, O>; 490 491/** 492 * Type guard to check if handler output is a pipe-through buffer response. 493 * @param output - The handler output to check 494 * @returns True if the output is a buffer pipe-through response 495 */ 496export function isHandlerPipeThroughBuffer( 497 output: Output | HandlerPipeThrough, 498): output is HandlerPipeThroughBuffer { 499 // We only need to discriminate between possible Output values 500 return output != null && "buffer" in output && output["buffer"] !== undefined; 501} 502 503/** 504 * Type guard to check if handler output is a pipe-through stream response. 505 * @param output - The handler output to check 506 * @returns True if the output is a stream pipe-through response 507 */ 508export function isHandlerPipeThroughStream( 509 output: Output | HandlerPipeThrough, 510): output is HandlerPipeThroughStream { 511 // We only need to discriminate between possible Output values 512 return output != null && "stream" in output && output["stream"] !== undefined; 513}