@atproto/lex-server 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +38 -21
  3. package/dist/errors.d.ts +28 -58
  4. package/dist/errors.d.ts.map +1 -1
  5. package/dist/errors.js +72 -72
  6. package/dist/errors.js.map +1 -1
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -4
  10. package/dist/index.js.map +1 -1
  11. package/dist/{lex-server.d.ts → lex-router.d.ts} +55 -21
  12. package/dist/lex-router.d.ts.map +1 -0
  13. package/dist/{lex-server.js → lex-router.js} +169 -73
  14. package/dist/lex-router.js.map +1 -0
  15. package/dist/lib/drain-websocket.d.ts +7 -0
  16. package/dist/lib/drain-websocket.d.ts.map +1 -1
  17. package/dist/lib/drain-websocket.js +11 -0
  18. package/dist/lib/drain-websocket.js.map +1 -1
  19. package/dist/lib/www-authenticate.d.ts +4 -3
  20. package/dist/lib/www-authenticate.d.ts.map +1 -1
  21. package/dist/lib/www-authenticate.js +29 -16
  22. package/dist/lib/www-authenticate.js.map +1 -1
  23. package/dist/nodejs.d.ts +1 -1
  24. package/dist/nodejs.d.ts.map +1 -1
  25. package/dist/nodejs.js +1 -1
  26. package/dist/nodejs.js.map +1 -1
  27. package/dist/service-auth.d.ts +1 -1
  28. package/dist/service-auth.d.ts.map +1 -1
  29. package/dist/service-auth.js.map +1 -1
  30. package/package.json +9 -8
  31. package/src/errors.test.ts +262 -0
  32. package/src/errors.ts +103 -78
  33. package/src/index.ts +1 -7
  34. package/src/{lex-server.test.ts → lex-router.test.ts} +591 -24
  35. package/src/{lex-server.ts → lex-router.ts} +275 -119
  36. package/src/lib/drain-websocket.ts +11 -0
  37. package/src/lib/www-authenticate.test.ts +134 -0
  38. package/src/lib/www-authenticate.ts +36 -17
  39. package/src/nodejs.ts +2 -2
  40. package/src/service-auth.ts +1 -1
  41. package/dist/lex-server.d.ts.map +0 -1
  42. package/dist/lex-server.js.map +0 -1
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { formatWWWAuthenticateHeader } from './www-authenticate.js'
3
+
4
+ describe(formatWWWAuthenticateHeader, () => {
5
+ describe('single scheme with params object', () => {
6
+ it('formats a Bearer challenge with params', () => {
7
+ const result = formatWWWAuthenticateHeader({
8
+ Bearer: { realm: 'api.example.com', error: 'InvalidToken' },
9
+ })
10
+ expect(result).toBe(
11
+ 'Bearer realm="api.example.com", error="InvalidToken"',
12
+ )
13
+ })
14
+
15
+ it('omits undefined param values', () => {
16
+ const result = formatWWWAuthenticateHeader({
17
+ Bearer: { realm: 'api', error: undefined },
18
+ })
19
+ expect(result).toBe('Bearer realm="api"')
20
+ })
21
+
22
+ it('omits null param values', () => {
23
+ const result = formatWWWAuthenticateHeader({
24
+ Bearer: { realm: 'api', error: null as any },
25
+ })
26
+ expect(result).toBe('Bearer realm="api"')
27
+ })
28
+
29
+ it('outputs only the scheme when all params are undefined', () => {
30
+ const result = formatWWWAuthenticateHeader({
31
+ Bearer: {},
32
+ })
33
+ expect(result).toBe('Bearer')
34
+ })
35
+ })
36
+
37
+ describe('single scheme with token68 string', () => {
38
+ it('formats a token68 value', () => {
39
+ const result = formatWWWAuthenticateHeader({
40
+ Bearer: 'base64encodedvalue==',
41
+ })
42
+ expect(result).toBe('Bearer base64encodedvalue==')
43
+ })
44
+ })
45
+
46
+ describe('multiple schemes', () => {
47
+ it('joins multiple different schemes with a comma', () => {
48
+ const result = formatWWWAuthenticateHeader({
49
+ Bearer: { realm: 'api' },
50
+ Basic: { realm: 'api' },
51
+ })
52
+ expect(result).toBe('Bearer realm="api", Basic realm="api"')
53
+ })
54
+ })
55
+
56
+ describe('array of challenges for the same scheme (new feature)', () => {
57
+ it('emits one challenge per array element', () => {
58
+ const result = formatWWWAuthenticateHeader({
59
+ Bearer: [
60
+ { realm: 'first', error: 'TokenExpired' },
61
+ { realm: 'second', error: 'TokenRevoked' },
62
+ ],
63
+ })
64
+ expect(result).toBe(
65
+ 'Bearer realm="first", error="TokenExpired", Bearer realm="second", error="TokenRevoked"',
66
+ )
67
+ })
68
+
69
+ it('handles an array mixing token68 strings and param objects', () => {
70
+ const result = formatWWWAuthenticateHeader({
71
+ Bearer: ['token68value', { realm: 'api', error: 'BadToken' }],
72
+ })
73
+ expect(result).toBe(
74
+ 'Bearer token68value, Bearer realm="api", error="BadToken"',
75
+ )
76
+ })
77
+
78
+ it('handles an array of token68 strings', () => {
79
+ const result = formatWWWAuthenticateHeader({
80
+ Bearer: ['firstToken', 'secondToken'],
81
+ })
82
+ expect(result).toBe('Bearer firstToken, Bearer secondToken')
83
+ })
84
+
85
+ it('handles an array with a single element', () => {
86
+ const result = formatWWWAuthenticateHeader({
87
+ Bearer: [{ realm: 'api' }],
88
+ })
89
+ expect(result).toBe('Bearer realm="api"')
90
+ })
91
+
92
+ it('handles array challenges alongside other schemes', () => {
93
+ const result = formatWWWAuthenticateHeader({
94
+ Bearer: [
95
+ { realm: 'r1', error: 'Err1' },
96
+ { realm: 'r2', error: 'Err2' },
97
+ ],
98
+ Basic: { realm: 'fallback' },
99
+ })
100
+ expect(result).toBe(
101
+ 'Bearer realm="r1", error="Err1", Bearer realm="r2", error="Err2", Basic realm="fallback"',
102
+ )
103
+ })
104
+ })
105
+
106
+ describe('null / undefined scheme values', () => {
107
+ it('skips schemes with null value', () => {
108
+ const result = formatWWWAuthenticateHeader({
109
+ Bearer: null as any,
110
+ Basic: { realm: 'api' },
111
+ })
112
+ expect(result).toBe('Basic realm="api"')
113
+ })
114
+
115
+ it('skips schemes with undefined value', () => {
116
+ const result = formatWWWAuthenticateHeader({
117
+ Bearer: undefined,
118
+ Basic: { realm: 'api' },
119
+ })
120
+ expect(result).toBe('Basic realm="api"')
121
+ })
122
+
123
+ it('returns an empty string when all schemes are null/undefined', () => {
124
+ const result = formatWWWAuthenticateHeader({
125
+ Bearer: undefined,
126
+ })
127
+ expect(result).toBe('')
128
+ })
129
+ })
130
+
131
+ it('returns an empty string for an empty object', () => {
132
+ expect(formatWWWAuthenticateHeader({})).toBe('')
133
+ })
134
+ })
@@ -39,9 +39,12 @@
39
39
  export type WWWAuthenticate = {
40
40
  [authScheme in string]?:
41
41
  | string // token68
42
- | { [authParam in string]?: string }
42
+ | WWWAuthenticateParams
43
+ | (string | WWWAuthenticateParams)[]
43
44
  }
44
45
 
46
+ export type WWWAuthenticateParams = { [authParam in string]?: string }
47
+
45
48
  /**
46
49
  * Formats a WWWAuthenticate object into an HTTP header string.
47
50
  *
@@ -73,20 +76,36 @@ export type WWWAuthenticate = {
73
76
  export function formatWWWAuthenticateHeader(
74
77
  wwwAuthenticate: WWWAuthenticate,
75
78
  ): string {
76
- return Object.entries(wwwAuthenticate)
77
- .map(([authScheme, authParams]) => {
78
- if (authParams === undefined) return null
79
- const paramsEnc =
80
- typeof authParams === 'string'
81
- ? [authParams]
82
- : Object.entries(authParams)
83
- .filter(([_, val]) => val != null)
84
- .map(([name, val]) => `${name}=${JSON.stringify(val)}`)
85
- const authChallenge = paramsEnc?.length
86
- ? `${authScheme} ${paramsEnc.join(', ')}`
87
- : authScheme
88
- return authChallenge
89
- })
90
- .filter(Boolean)
91
- .join(', ')
79
+ const challenges: string[] = []
80
+ for (const [scheme, params] of Object.entries(wwwAuthenticate)) {
81
+ if (params == null) continue
82
+
83
+ if (typeof params === 'string') {
84
+ challenges.push(formatWWWAuthenticateChallenge(scheme, params))
85
+ } else if (Array.isArray(params)) {
86
+ for (const p of params) {
87
+ challenges.push(formatWWWAuthenticateChallenge(scheme, p))
88
+ }
89
+ } else {
90
+ challenges.push(formatWWWAuthenticateChallenge(scheme, params))
91
+ }
92
+ }
93
+ return challenges.join(', ')
94
+ }
95
+
96
+ function formatWWWAuthenticateChallenge(
97
+ scheme: string,
98
+ params: string | WWWAuthenticateParams,
99
+ ): string {
100
+ const paramsStr =
101
+ typeof params === 'string' ? params : formatWWWAuthenticateParams(params)
102
+ return paramsStr?.length ? `${scheme} ${paramsStr}` : scheme
103
+ }
104
+
105
+ function formatWWWAuthenticateParams(params: WWWAuthenticateParams): string {
106
+ const parts: string[] = []
107
+ for (const [name, val] of Object.entries(params)) {
108
+ if (val != null) parts.push(`${name}=${JSON.stringify(val)}`)
109
+ }
110
+ return parts.join(', ')
92
111
  }
package/src/nodejs.ts CHANGED
@@ -14,7 +14,7 @@ import { pipeline } from 'node:stream/promises'
14
14
  import type { ReadableStream as NodeReadableStream } from 'node:stream/web'
15
15
  import { createHttpTerminator } from 'http-terminator'
16
16
  import { WebSocket as WebSocketPonyfill, WebSocketServer } from 'ws'
17
- import { FetchHandler } from './lex-server.js'
17
+ import type { FetchHandler } from './lex-router.js'
18
18
 
19
19
  // @ts-expect-error
20
20
  Symbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose')
@@ -195,7 +195,7 @@ export async function sendResponse(
195
195
  }
196
196
 
197
197
  function toRequest(req: IncomingMessage): Request {
198
- const host = req.headers.host ?? req.socket?.localAddress ?? 'localhost'
198
+ const host = req.headers.host ?? req.socket.localAddress ?? 'localhost'
199
199
  const isEncrypted = (req.socket as any).encrypted === true
200
200
  const protocol = isEncrypted ? 'https' : 'http'
201
201
  const url = new URL(req.url ?? '/', `${protocol}://${host}`)
@@ -12,7 +12,7 @@ import {
12
12
  createDidResolver,
13
13
  } from '@atproto-labs/did-resolver'
14
14
  import { LexServerAuthError } from './errors.js'
15
- import { LexRouterAuth } from './lex-server.js'
15
+ import type { LexRouterAuth } from './lex-router.js'
16
16
 
17
17
  const BEARER_PREFIX = 'Bearer '
18
18
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"lex-server.d.ts","sourceRoot":"","sources":["../src/lex-server.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"lex-server.js","sourceRoot":"","sources":["../src/lex-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { encode } from '@atproto/lex-cbor'\nimport { LexError, LexValue, isPlainObject, ui8Concat } from '@atproto/lex-data'\nimport { lexParse, lexToJson } from '@atproto/lex-json'\nimport {\n InferMethodInput,\n InferMethodMessage,\n InferMethodOutput,\n InferMethodOutputBody,\n InferMethodOutputEncoding,\n InferMethodParams,\n Main,\n NsidString,\n Procedure,\n Query,\n Subscription,\n getMain,\n isNsidString,\n} from '@atproto/lex-schema'\nimport { drainWebsocket } from './lib/drain-websocket.js'\n\ntype Awaitable<T> = T | Promise<T>\n\n/**\n * Union type representing the supported Lexicon method types.\n *\n * - `Query`: Read-only methods invoked via HTTP GET\n * - `Procedure`: Methods that may modify state, invoked via HTTP POST\n * - `Subscription`: Real-time streaming methods over WebSocket\n */\nexport type LexMethod = Query | Procedure | Subscription\n\n/**\n * Network address for TCP or UDP connections.\n *\n * @example\n * ```typescript\n * const addr: NetAddr = {\n * hostname: '127.0.0.1',\n * port: 3000,\n * transport: 'tcp'\n * }\n * ```\n */\nexport type NetAddr = {\n /** The hostname or IP address of the connection. */\n hostname: string\n /** The port number of the connection. */\n port: number\n /** The transport protocol used. */\n transport: 'tcp' | 'udp'\n}\n\n/**\n * Unix domain socket address.\n *\n * @example\n * ```typescript\n * const addr: UnixAddr = {\n * path: '/var/run/app.sock',\n * transport: 'unix'\n * }\n * ```\n */\nexport type UnixAddr = {\n /** The filesystem path to the Unix socket. */\n path: string\n /** The transport protocol used. */\n transport: 'unix' | 'unixpacket'\n}\n\n/**\n * Union type for all supported address types.\n *\n * Can be a network address ({@link NetAddr}), Unix socket address ({@link UnixAddr}),\n * or `undefined` when the address is not available.\n */\nexport type Addr = NetAddr | UnixAddr | undefined\n\n/**\n * Metadata about the client connection for an incoming request.\n *\n * @typeParam A - The address type, defaults to {@link Addr}\n *\n * @example\n * ```typescript\n * const info: ConnectionInfo<NetAddr> = {\n * remoteAddr: { hostname: '192.168.1.1', port: 54321, transport: 'tcp' },\n * completed: new Promise((resolve) => socket.on('close', resolve))\n * }\n * ```\n */\nexport type ConnectionInfo<A extends Addr = Addr> = {\n /** The remote address of the client, if available. */\n remoteAddr: A\n /** Promise that resolves when the connection is fully closed. */\n completed: Promise<void>\n}\n\n/**\n * Function signature for handling HTTP requests in the XRPC router.\n *\n * This is the standard fetch-style handler that processes incoming requests\n * and returns responses. It is used both internally by the router and can\n * be used to integrate with other HTTP frameworks.\n *\n * @param request - The incoming HTTP request\n * @param connection - Optional connection metadata including remote address\n * @returns A promise resolving to the HTTP response\n *\n * @example\n * ```typescript\n * const handler: FetchHandler = async (request, connection) => {\n * console.log('Request from:', connection?.remoteAddr)\n * return new Response('Hello, World!')\n * }\n * ```\n */\nexport type FetchHandler = (\n request: Request,\n connection?: ConnectionInfo,\n) => Promise<Response>\n\n/**\n * Context object passed to XRPC method handlers.\n *\n * Contains all the information needed to process a request, including\n * parsed parameters, authentication credentials, and the raw request object.\n *\n * @typeParam Method - The Lexicon method type (Query, Procedure, or Subscription)\n * @typeParam Credentials - The type of authentication credentials, determined by the auth handler\n *\n * @example\n * ```typescript\n * const handler: LexRouterMethodHandler<MyMethod, UserCredentials> = async (ctx) => {\n * const { credentials, params, input, signal } = ctx\n * // credentials.userId is available if auth handler returns UserCredentials\n * // params contains validated query parameters\n * // input contains the request body (for procedures)\n * // signal can be used to abort long-running operations\n * return { body: { result: 'success' } }\n * }\n * ```\n */\nexport type LexRouterHandlerContext<Method extends LexMethod, Credentials> = {\n /** Authentication credentials returned by the auth handler. */\n credentials: Credentials\n /** Parsed and validated request input (body for procedures, undefined for queries). */\n input: InferMethodInput<Method, Body>\n /** Parsed and validated URL query parameters. */\n params: InferMethodParams<Method>\n /** The original HTTP request object. */\n request: Request\n /** Abort signal that is triggered when the request is cancelled. */\n signal: AbortSignal\n /** Connection metadata including remote address. */\n connection?: ConnectionInfo\n}\n\ntype AsOptionalPayloadOptions<T> = T extends undefined | void\n ? { encoding?: undefined; body?: undefined }\n : T\n\n/**\n * Return type for XRPC method handlers (queries and procedures).\n *\n * Handlers can return either:\n * - A raw {@link Response} object for full control over the HTTP response\n * - An object with `body`, optional `encoding`, and optional `headers`\n *\n * For JSON methods, the body is automatically serialized. For other encodings,\n * the body must be a valid {@link BodyInit} type.\n *\n * @typeParam Method - The Lexicon method type (Query or Procedure)\n *\n * @example\n * ```typescript\n * // Return JSON body (most common)\n * return { body: { users: [...] } }\n *\n * // Return with custom headers\n * return {\n * body: { data: 'value' },\n * headers: { 'Cache-Control': 'max-age=3600' }\n * }\n *\n * // Return raw Response for full control\n * return new Response(binaryData, {\n * headers: { 'Content-Type': 'application/octet-stream' }\n * })\n * ```\n */\nexport type LexRouterHandlerOutput<Method extends Query | Procedure> =\n | Response\n | ({\n headers?: HeadersInit\n } & (InferMethodOutputEncoding<Method> extends 'application/json'\n ? {\n // Allow omitting body when output is JSON\n encoding?: 'application/json'\n body: InferMethodOutputBody<Method>\n }\n : AsOptionalPayloadOptions<InferMethodOutput<Method, BodyInit>>))\n\n/**\n * Handler function for XRPC query and procedure methods.\n *\n * Receives a context object with request details and credentials,\n * and returns either a Response or a structured output object.\n *\n * @typeParam Method - The Lexicon method type (Query or Procedure)\n * @typeParam Credentials - The type of authentication credentials\n *\n * @example\n * ```typescript\n * const getProfile: LexRouterMethodHandler<GetProfileMethod, UserCredentials> = async (ctx) => {\n * const profile = await db.getProfile(ctx.params.actor)\n * return { body: profile }\n * }\n * ```\n */\nexport type LexRouterMethodHandler<\n Method extends Query | Procedure = Query | Procedure,\n Credentials = unknown,\n> = (\n ctx: LexRouterHandlerContext<Method, Credentials>,\n) => Awaitable<LexRouterHandlerOutput<Method>>\n\n/**\n * Configuration object for registering an XRPC method with authentication.\n *\n * Used when you need to specify both a handler and an auth function.\n *\n * @typeParam Method - The Lexicon method type (Query or Procedure)\n * @typeParam Credentials - The type of authentication credentials\n *\n * @example\n * ```typescript\n * const config: LexRouterMethodConfig<GetProfileMethod, UserCredentials> = {\n * handler: async (ctx) => {\n * return { body: await getProfile(ctx.params.actor) }\n * },\n * auth: async ({ request }) => {\n * return verifyToken(request.headers.get('authorization'))\n * }\n * }\n * ```\n */\nexport type LexRouterMethodConfig<\n Method extends Query | Procedure = Query | Procedure,\n Credentials = unknown,\n> = {\n /** The handler function that processes the request. */\n handler: LexRouterMethodHandler<Method, Credentials>\n /** Authentication function that validates credentials before the handler runs. */\n auth: LexRouterAuth<Credentials, Method>\n}\n\n/**\n * Handler function for XRPC subscription methods (WebSocket streams).\n *\n * Returns an async iterable that yields messages to be sent over the WebSocket.\n * The connection remains open until the iterable completes or an error occurs.\n *\n * @typeParam Method - The Lexicon subscription method type\n * @typeParam Credentials - The type of authentication credentials\n *\n * @example\n * ```typescript\n * const subscribeRepos: LexRouterSubscriptionHandler<SubscribeReposMethod> = async function* (ctx) {\n * const cursor = ctx.params.cursor ?? 0\n * for await (const event of eventStream.since(cursor)) {\n * if (ctx.signal.aborted) break\n * yield { $type: 'com.atproto.sync.subscribeRepos#commit', ...event }\n * }\n * }\n * ```\n */\nexport type LexRouterSubscriptionHandler<\n Method extends Subscription = Subscription,\n Credentials = unknown,\n> = (\n ctx: LexRouterHandlerContext<Method, Credentials>,\n) => AsyncIterable<InferMethodMessage<Method>>\n\n/**\n * Configuration object for registering an XRPC subscription with authentication.\n *\n * Used when you need to specify both a handler and an auth function for subscriptions.\n *\n * @typeParam Method - The Lexicon subscription method type\n * @typeParam Credentials - The type of authentication credentials\n *\n * @example\n * ```typescript\n * const config: LexRouterSubscriptionConfig<SubscribeReposMethod, ServiceCredentials> = {\n * handler: async function* (ctx) {\n * for await (const event of eventStream) {\n * yield event\n * }\n * },\n * auth: async ({ request }) => {\n * return verifyServiceAuth(request)\n * }\n * }\n * ```\n */\nexport type LexRouterSubscriptionConfig<\n Method extends Subscription = Subscription,\n Credentials = unknown,\n> = {\n /** The handler function that yields subscription messages. */\n handler: LexRouterSubscriptionHandler<Method, Credentials>\n /** Authentication function that validates credentials before the handler runs. */\n auth: LexRouterAuth<Credentials, Method>\n}\n\n/**\n * Context object passed to authentication handlers.\n *\n * Contains the information needed to authenticate a request before\n * the main handler is invoked.\n *\n * @typeParam Method - The Lexicon method type\n *\n * @example\n * ```typescript\n * const authHandler: LexRouterAuth<UserCredentials> = async (ctx) => {\n * const token = ctx.request.headers.get('authorization')\n * if (!token) throw new LexError('AuthenticationRequired', 'Missing token')\n * return { userId: await verifyToken(token) }\n * }\n * ```\n */\nexport type LexRouterAuthContext<Method extends LexMethod = LexMethod> = {\n /** The Lexicon method definition being called. */\n method: Method\n /** Parsed and validated URL query parameters. */\n params: InferMethodParams<Method>\n /** The original HTTP request object. */\n request: Request\n /** Connection metadata including remote address. */\n connection?: ConnectionInfo\n}\n\n/**\n * Authentication handler function for XRPC methods.\n *\n * Called before the main handler to validate authentication credentials.\n * Should return the validated credentials or throw an error if authentication fails.\n *\n * @typeParam Credentials - The type of credentials to return on success\n * @typeParam Method - The Lexicon method type\n *\n * @example\n * ```typescript\n * // Simple token-based auth\n * const tokenAuth: LexRouterAuth<{ userId: string }> = async ({ request }) => {\n * const token = request.headers.get('authorization')?.replace('Bearer ', '')\n * if (!token) throw new LexError('AuthenticationRequired', 'Token required')\n * const userId = await verifyToken(token)\n * return { userId }\n * }\n *\n * // Using with serviceAuth for AT Protocol service authentication\n * import { serviceAuth } from '@atproto/lex-server'\n * const auth = serviceAuth({ audience: 'did:web:example.com', unique: checkNonce })\n * ```\n */\nexport type LexRouterAuth<\n Credentials = unknown,\n Method extends LexMethod = LexMethod,\n> = (ctx: LexRouterAuthContext<Method>) => Credentials | Promise<Credentials>\n\n/**\n * Context object passed to error handler callbacks.\n *\n * Used for logging and monitoring errors that occur during request handling.\n */\nexport type LexErrorHandlerContext = {\n /** The error that was thrown during handling. */\n error: unknown\n /** The original HTTP request that triggered the error. */\n request: Request\n /** The Lexicon method that was being executed. */\n method: LexMethod\n}\n\n/**\n * Function that upgrades an HTTP request to a WebSocket connection.\n *\n * This is platform-specific: Deno provides this natively, while Node.js\n * requires the `upgradeWebSocket` function from this package.\n *\n * @param request - The HTTP request to upgrade\n * @returns An object containing the WebSocket and the upgrade response\n *\n * @example\n * ```typescript\n * // In Node.js, use the provided upgradeWebSocket function\n * import { upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * const router = new LexRouter({ upgradeWebSocket })\n * ```\n */\nexport type UpgradeWebSocket = (request: Request) => {\n /** The WebSocket instance for bidirectional communication. */\n socket: WebSocket\n /** The HTTP response to return (101 Switching Protocols). */\n response: Response\n}\n\n/**\n * Configuration options for the {@link LexRouter}.\n *\n * @example\n * ```typescript\n * const options: LexRouterOptions = {\n * upgradeWebSocket,\n * onHandlerError: async ({ error, request, method }) => {\n * console.error(`Error in ${method.nsid}:`, error)\n * await reportToSentry(error)\n * },\n * highWaterMark: 64 * 1024, // 64KB\n * lowWaterMark: 16 * 1024 // 16KB\n * }\n * ```\n */\nexport type LexRouterOptions = {\n /**\n * Function to upgrade HTTP requests to WebSocket connections.\n * Required for subscription methods. Defaults to Deno's built-in\n * upgradeWebSocket if available.\n */\n upgradeWebSocket?: UpgradeWebSocket\n /**\n * Callback invoked when an error occurs during request handling.\n * Useful for logging and error reporting. Not called for client-induced\n * errors (e.g., request abortion).\n */\n onHandlerError?: (ctx: LexErrorHandlerContext) => void | Promise<void>\n /**\n * High water mark for WebSocket backpressure (in bytes).\n * When buffered data exceeds this, the handler will wait before sending more.\n */\n highWaterMark?: number\n /**\n * Low water mark for WebSocket backpressure (in bytes).\n * The handler resumes sending when buffered data drops below this.\n */\n lowWaterMark?: number\n}\n\n/**\n * XRPC router for handling AT Protocol Lexicon methods.\n *\n * The router handles HTTP routing, parameter parsing, input validation,\n * authentication, and response serialization for XRPC methods. It supports\n * queries (GET), procedures (POST), and subscriptions (WebSocket).\n *\n * @example Setting up a basic XRPC server\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { serve, upgradeWebSocket } from '@atproto/lex-server/nodejs'\n * import { getProfile, createPost, subscribeRepos } from './lexicons'\n *\n * const router = new LexRouter({ upgradeWebSocket })\n *\n * // Register a query handler (GET request)\n * router.add(getProfile, async (ctx) => {\n * const profile = await db.getProfile(ctx.params.actor)\n * return { body: profile }\n * })\n *\n * // Register a procedure handler with authentication (POST request)\n * router.add(createPost, {\n * handler: async (ctx) => {\n * const post = await db.createPost(ctx.credentials.did, ctx.input.body)\n * return { body: { uri: post.uri, cid: post.cid } }\n * },\n * auth: async ({ request }) => {\n * return verifyAccessToken(request)\n * }\n * })\n *\n * // Register a subscription handler (WebSocket)\n * router.add(subscribeRepos, async function* (ctx) {\n * for await (const event of eventStream.since(ctx.params.cursor)) {\n * if (ctx.signal.aborted) break\n * yield event\n * }\n * })\n *\n * // Start the server\n * const server = await serve(router, { port: 3000 })\n * console.log('XRPC server listening on port 3000')\n * ```\n *\n * @example Using with service authentication\n * ```typescript\n * import { LexRouter, serviceAuth } from '@atproto/lex-server'\n *\n * const router = new LexRouter()\n *\n * const auth = serviceAuth({\n * audience: 'did:web:api.example.com',\n * unique: async (nonce) => {\n * // Check and record nonce uniqueness\n * return await nonceStore.checkAndAdd(nonce)\n * }\n * })\n *\n * router.add(protectedMethod, {\n * handler: async (ctx) => {\n * // ctx.credentials contains { did, didDocument, jwt }\n * return { body: { callerDid: ctx.credentials.did } }\n * },\n * auth\n * })\n * ```\n */\nexport class LexRouter {\n /** Map of NSID strings to their fetch handlers. */\n readonly handlers: Map<NsidString, FetchHandler> = new Map()\n\n /**\n * Creates a new XRPC router.\n *\n * @param options - Router configuration options\n */\n constructor(readonly options: LexRouterOptions = {}) {}\n\n /**\n * Registers a subscription handler without authentication.\n *\n * @param ns - The Lexicon namespace definition for the subscription\n * @param handler - Async generator function that yields subscription messages\n * @returns This router instance for chaining\n */\n add<M extends Subscription>(\n ns: Main<M>,\n handler: LexRouterSubscriptionHandler<M, void>,\n ): this\n /**\n * Registers a subscription handler with authentication.\n *\n * @param ns - The Lexicon namespace definition for the subscription\n * @param config - Configuration object with handler and auth function\n * @returns This router instance for chaining\n */\n add<M extends Subscription, Credentials>(\n ns: Main<M>,\n config: LexRouterSubscriptionConfig<M, Credentials>,\n ): this\n /**\n * Registers a query or procedure handler without authentication.\n *\n * @param ns - The Lexicon namespace definition for the method\n * @param handler - Handler function that processes requests\n * @returns This router instance for chaining\n */\n add<M extends Query | Procedure>(\n ns: Main<M>,\n handler: LexRouterMethodHandler<M, void>,\n ): this\n /**\n * Registers a query or procedure handler with authentication.\n *\n * @param ns - The Lexicon namespace definition for the method\n * @param config - Configuration object with handler and auth function\n * @returns This router instance for chaining\n */\n add<M extends Query | Procedure, Credentials>(\n ns: Main<M>,\n config: LexRouterMethodConfig<M, Credentials>,\n ): this\n /**\n * Registers a Lexicon method handler.\n *\n * This is the unified overload that accepts any method type with optional authentication.\n *\n * @param ns - The Lexicon namespace definition\n * @param config - Handler function or configuration object\n * @returns This router instance for chaining\n *\n * @throws {TypeError} If a method with the same NSID is already registered\n *\n * @example\n * ```typescript\n * // Register without auth (credentials will be void)\n * router.add(myQuery, async (ctx) => {\n * return { body: { data: 'value' } }\n * })\n *\n * // Register with auth\n * router.add(myProcedure, {\n * handler: async (ctx) => {\n * console.log('Caller:', ctx.credentials.userId)\n * return { body: { success: true } }\n * },\n * auth: async ({ request }) => ({ userId: await verifyToken(request) })\n * })\n * ```\n */\n add<M extends LexMethod, Credentials = unknown>(\n ns: Main<M>,\n config: M extends Subscription\n ?\n | LexRouterSubscriptionHandler<M, Credentials>\n | LexRouterSubscriptionConfig<M, Credentials>\n : M extends Query | Procedure\n ?\n | LexRouterMethodHandler<M, Credentials>\n | LexRouterMethodConfig<M, Credentials>\n : never,\n ): this\n add<M extends LexMethod>(\n ns: Main<M>,\n config:\n | LexRouterSubscriptionHandler<any, any>\n | LexRouterSubscriptionConfig<any, any>\n | LexRouterMethodHandler<any, any>\n | LexRouterMethodConfig<any, any>,\n ) {\n const method = getMain(ns)\n if (this.handlers.has(method.nsid)) {\n throw new TypeError(`Method ${method.nsid} already registered`)\n }\n const methodConfig =\n typeof config === 'function'\n ? { handler: config, auth: undefined }\n : config\n\n const fetch: FetchHandler =\n method.type === 'subscription'\n ? this.buildSubscriptionHandler(\n method,\n methodConfig.handler as LexRouterSubscriptionHandler<any, any>,\n methodConfig.auth,\n )\n : this.buildMethodHandler(\n method,\n methodConfig.handler as LexRouterMethodHandler<any, any>,\n methodConfig.auth,\n )\n\n this.handlers.set(method.nsid, fetch)\n\n return this\n }\n\n private buildMethodHandler<Method extends Query | Procedure, Credentials>(\n method: Method,\n methodHandler: LexRouterMethodHandler<Method, Credentials>,\n auth?: LexRouterAuth<Credentials, Method>,\n ): FetchHandler {\n const getInput = (\n method.type === 'procedure'\n ? getProcedureInput.bind(method)\n : getQueryInput.bind(method)\n ) as (request: Request) => Promise<InferMethodInput<Method, Body>>\n\n return async (request, connection) => {\n // @NOTE CORS requests should be handled by a middleware before reaching\n // this point.\n if (\n (method.type === 'procedure' && request.method !== 'POST') ||\n (method.type === 'query' &&\n request.method !== 'GET' &&\n request.method !== 'HEAD')\n ) {\n return Response.json(\n { error: 'InvalidRequest', message: 'Method not allowed' },\n { status: 405 },\n )\n }\n\n try {\n const url = new URL(request.url)\n const params = method.parameters.fromURLSearchParams(\n url.searchParams,\n ) as InferMethodParams<Method>\n\n const credentials = auth\n ? await auth({ method, params, request, connection })\n : (undefined as Credentials)\n\n const input = await getInput(request)\n\n const output = await methodHandler({\n credentials,\n params,\n input,\n request,\n connection,\n signal: request.signal,\n })\n\n if (output instanceof Response) {\n return output\n }\n\n // @TODO add validation of output based on method.output.schema?\n\n if (output.body === undefined && output.encoding === undefined) {\n return new Response(null, { status: 200, headers: output.headers })\n }\n\n if (method.output?.encoding === 'application/json') {\n return Response.json(lexToJson(output.body as LexValue), {\n status: 200,\n headers: output.headers,\n })\n }\n\n const headers = new Headers(output.headers)\n headers.set('content-type', output.encoding!)\n return new Response(output.body as BodyInit | null | undefined, {\n status: 200,\n headers,\n })\n } catch (error) {\n return this.handleError(request, method, error)\n }\n }\n }\n\n private buildSubscriptionHandler<Method extends Subscription, Credentials>(\n method: Method,\n methodHandler: LexRouterSubscriptionHandler<Method, Credentials>,\n auth?: LexRouterAuth<Credentials, Method>,\n ): FetchHandler {\n const {\n onHandlerError,\n upgradeWebSocket = (globalThis as any).Deno?.upgradeWebSocket as\n | UpgradeWebSocket\n | undefined,\n } = this.options\n if (!upgradeWebSocket) {\n throw new TypeError(\n 'WebSocket upgrade not supported in this environment. Please provide an upgradeWebSocket option when creating the LexRouter.',\n )\n }\n\n return async (request, connection) => {\n if (request.method !== 'GET') {\n return Response.json(\n { error: 'InvalidRequest', message: 'Method not allowed' },\n { status: 405 },\n )\n }\n\n if (\n request.headers.get('connection')?.toLowerCase() !== 'upgrade' ||\n request.headers.get('upgrade')?.toLowerCase() !== 'websocket'\n ) {\n return Response.json(\n {\n error: 'InvalidRequest',\n message: 'XRPC subscriptions are only available over WebSocket',\n },\n {\n status: 426,\n headers: {\n Connection: 'Upgrade',\n Upgrade: 'websocket',\n },\n },\n )\n }\n\n if (request.signal.aborted) {\n return Response.json(\n { error: 'RequestAborted', message: 'The request was aborted' },\n { status: 499 },\n )\n }\n\n try {\n const { response, socket } = upgradeWebSocket(request)\n\n // @NOTE We are using a distinct signal than request.signal because that\n // signal may get aborted before the WebSocket is closed (this is the\n // case with Deno).\n const abortController = new AbortController()\n const { signal } = abortController\n const abort = () => abortController.abort()\n\n const onMessage = (event: unknown) => {\n const error = new LexError(\n 'InvalidRequest',\n 'XRPC subscriptions do not accept messages',\n { cause: event },\n )\n socket.send(encodeErrorFrame(error))\n socket.close(1008, error.error)\n }\n\n const onOpen = async () => {\n try {\n const url = new URL(request.url)\n const params = method.parameters.fromURLSearchParams(\n url.searchParams,\n ) as InferMethodParams<Method>\n\n const credentials: Credentials = auth\n ? await auth({ method, params, request, connection })\n : (undefined as Credentials)\n\n signal.throwIfAborted()\n\n const iterable = methodHandler({\n credentials,\n params,\n input: undefined as InferMethodInput<Method, Body>,\n request,\n connection,\n signal,\n })\n\n const iterator = iterable[Symbol.asyncIterator]()\n\n signal.addEventListener('abort', async () => {\n // @NOTE will cause the process to crash if this throws\n await iterator.return?.()\n })\n\n while (!signal.aborted && socket.readyState === 1) {\n const result = await iterator.next()\n if (result.done) break\n\n // @TODO add validation of output based on method.output.schema?\n\n const data = encodeMessageFrame(method, result.value)\n\n socket.send(data)\n\n // Apply backpressure by waiting for the buffered data to drain\n // before generating the next message\n await drainWebsocket(socket, signal, this.options)\n }\n\n if (socket.readyState === 1) {\n socket.close(1000)\n }\n } catch (error) {\n // If the socket is still open, send an error frame before closing\n if (socket.readyState === 1) {\n const lexError =\n error instanceof LexError\n ? error\n : new LexError('InternalError', 'An internal error occurred')\n\n socket.send(encodeErrorFrame(lexError))\n\n socket.close(\n // https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1\n error instanceof LexError ? 1008 : 1011,\n lexError.error,\n )\n }\n\n // Only report unexpected processing errors\n if (onHandlerError && !isAbortReason(request.signal, error)) {\n await onHandlerError({ error, request, method })\n }\n } finally {\n abortController.abort()\n }\n }\n\n socket.addEventListener('error', abort)\n socket.addEventListener('close', abort)\n socket.addEventListener('open', onOpen)\n socket.addEventListener('message', onMessage)\n\n return response\n } catch (error) {\n return this.handleError(request, method, error)\n }\n }\n }\n\n private async handleError(\n request: Request,\n method: LexMethod,\n error: unknown,\n ) {\n // Only report unexpected processing errors\n const { onHandlerError } = this.options\n if (onHandlerError && !isAbortReason(request.signal, error)) {\n await onHandlerError({ error, request, method })\n }\n\n if (error instanceof LexError) {\n return error.toResponse()\n }\n\n return Response.json(\n { error: 'InternalError', message: 'An internal error occurred' },\n { status: 500 },\n )\n }\n\n /**\n * The main fetch handler for processing XRPC requests.\n *\n * Routes incoming requests to the appropriate method handler based on the\n * NSID in the URL path. Returns appropriate error responses for invalid\n * paths or unimplemented methods.\n *\n * This handler can be used directly with HTTP servers that support the\n * fetch API pattern, or converted to a Node.js request listener using\n * `toRequestListener()`.\n *\n * @param request - The incoming HTTP request\n * @param connection - Optional connection metadata\n * @returns A promise resolving to the HTTP response\n *\n * @example\n * ```typescript\n * // Use with Deno\n * Deno.serve(router.fetch)\n *\n * // Use with Bun\n * Bun.serve({ fetch: router.fetch })\n *\n * // Use with Node.js\n * import { toRequestListener } from '@atproto/lex-server/nodejs'\n * const listener = toRequestListener(router.fetch)\n * http.createServer(listener).listen(3000)\n * ```\n */\n fetch: FetchHandler = async (\n request: Request,\n connection?: ConnectionInfo,\n ): Promise<Response> => {\n const nsid = extractMethodNsid(request)\n\n const fetch = nsid\n ? (this.handlers as Map<unknown, FetchHandler>).get(nsid)\n : undefined\n if (fetch) return fetch(request, connection)\n\n if (!nsid || !isNsidString(nsid)) {\n return Response.json(\n {\n error: 'InvalidRequest',\n message: 'Invalid XRPC method path',\n },\n { status: 404 },\n )\n }\n\n return Response.json(\n {\n error: 'MethodNotImplemented',\n message: `XRPC method \"${nsid}\" not implemented on this server`,\n },\n { status: 501 },\n )\n }\n}\n\nfunction extractMethodNsid(request: Request): string | null {\n const { pathname } = new URL(request.url)\n if (!pathname.startsWith('/xrpc/')) return null\n if (pathname.includes('/', 6)) return null\n // We don't really need to validate the NSID here, the existence of the route\n // (which is looked up based on an NSID) is sufficient.\n return pathname.slice(6)\n}\n\nasync function getProcedureInput<M extends Procedure>(\n this: M,\n request: Request,\n): Promise<InferMethodInput<M, Body>> {\n const encodingRaw = request.headers\n .get('content-type')\n ?.split(';')[0]\n .trim()\n .toLowerCase()\n\n const encoding =\n encodingRaw ||\n // If the caller did not provide a content-type, but the method\n // expects an input, assume binary\n (request.body != null && this.input.encoding != null\n ? 'application/octet-stream'\n : undefined)\n\n if (!this.input.matchesEncoding(encoding)) {\n throw new LexError('InvalidRequest', `Invalid content-type: ${encoding}`)\n }\n\n if (this.input.encoding === 'application/json') {\n // @TODO limit size?\n const data = lexParse(await request.text())\n const body = this.input.schema ? this.input.schema.parse(data) : data\n return { encoding, body } as InferMethodInput<M, Body>\n } else if (this.input.encoding) {\n const body: Body = request\n return { encoding, body } as InferMethodInput<M, Body>\n } else {\n return undefined as InferMethodInput<M, Body>\n }\n}\n\nasync function getQueryInput<M extends Query>(\n this: M,\n request: Request,\n): Promise<InferMethodInput<M, Body>> {\n if (\n request.body ||\n request.headers.has('content-type') ||\n request.headers.has('content-length')\n ) {\n throw new LexError('InvalidRequest', 'GET requests must not have a body')\n }\n\n return undefined as InferMethodInput<M, Body>\n}\n\n// Pre-encoded frame header for error frames\nconst ERROR_FRAME_HEADER = /*#__PURE__*/ encode({ op: -1 })\n\nfunction encodeErrorFrame(error: LexError): Uint8Array {\n return ui8Concat([ERROR_FRAME_HEADER, encode(error.toJSON())])\n}\n\n// Pre-encoded frame header for message frames with unknown type\nconst UNKNOWN_MESSAGE_FRAME_HEADER = /*#__PURE__*/ encode({ op: 1 })\n\nfunction encodeMessageFrame(method: Subscription, value: LexValue): Uint8Array {\n if (isPlainObject(value) && typeof value.$type === 'string') {\n const { $type, ...rest } = value\n return ui8Concat([\n encode({\n op: 1,\n t:\n // If $type starts with `nsid#`, strip the NSID prefix\n $type.charCodeAt(0) !== 0x23 && // '#'\n $type.charCodeAt(method.nsid.length) === 0x23 && // '#'\n $type.startsWith(method.nsid)\n ? $type.slice(method.nsid.length)\n : $type,\n }),\n encode(rest),\n ])\n }\n\n return ui8Concat([UNKNOWN_MESSAGE_FRAME_HEADER, encode(value)])\n}\n\nfunction isAbortReason(signal: AbortSignal, error: unknown): boolean {\n if (!signal.aborted || signal.reason == null) return false\n return (\n error === signal.reason ||\n (error instanceof Error && error.cause === signal.reason)\n )\n}\n"]}