Suite of AT Protocol TypeScript libraries built on web standards
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}