@atproto/lex-server 0.0.7 → 0.0.9

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.
package/src/errors.ts CHANGED
@@ -6,11 +6,54 @@ import {
6
6
 
7
7
  export type { WWWAuthenticate }
8
8
 
9
+ /**
10
+ * Error class for authentication failures in XRPC server handlers.
11
+ *
12
+ * Extends {@link LexError} to include WWW-Authenticate header support,
13
+ * which is required by HTTP authentication standards (RFC 7235).
14
+ * The error automatically generates the appropriate 401 response with
15
+ * the WWW-Authenticate header when converted to a Response.
16
+ *
17
+ * @typeParam N - The Lexicon error code type
18
+ *
19
+ * @example Throwing an auth error
20
+ * ```typescript
21
+ * import { LexServerAuthError } from '@atproto/lex-server'
22
+ *
23
+ * throw new LexServerAuthError(
24
+ * 'AuthenticationRequired',
25
+ * 'Invalid or expired token',
26
+ * { Bearer: { error: 'InvalidToken', realm: 'api.example.com' } }
27
+ * )
28
+ * ```
29
+ *
30
+ * @example Converting from a LexError
31
+ * ```typescript
32
+ * try {
33
+ * await validateToken(token)
34
+ * } catch (error) {
35
+ * if (error instanceof LexError) {
36
+ * throw LexServerAuthError.from(error, {
37
+ * Bearer: { error: 'InvalidToken' }
38
+ * })
39
+ * }
40
+ * throw error
41
+ * }
42
+ * ```
43
+ */
9
44
  export class LexServerAuthError<
10
45
  N extends LexErrorCode = LexErrorCode,
11
46
  > extends LexError<N> {
12
47
  name = 'LexServerAuthError'
13
48
 
49
+ /**
50
+ * Creates a new authentication error.
51
+ *
52
+ * @param error - The Lexicon error code (e.g., 'AuthenticationRequired')
53
+ * @param message - Human-readable error message
54
+ * @param wwwAuthenticate - WWW-Authenticate header parameters
55
+ * @param options - Standard Error options including `cause`
56
+ */
14
57
  constructor(
15
58
  error: N,
16
59
  message: string,
@@ -20,15 +63,48 @@ export class LexServerAuthError<
20
63
  super(error, message, options)
21
64
  }
22
65
 
66
+ /**
67
+ * Gets the formatted WWW-Authenticate header value.
68
+ *
69
+ * @returns The formatted header string for the 401 response
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const error = new LexServerAuthError('AuthenticationRequired', 'Token required', {
74
+ * Bearer: { realm: 'api.example.com', error: 'MissingToken' }
75
+ * })
76
+ * console.log(error.wwwAuthenticateHeader)
77
+ * // Output: 'Bearer realm="api.example.com", error="MissingToken"'
78
+ * ```
79
+ */
23
80
  get wwwAuthenticateHeader(): string {
24
81
  return formatWWWAuthenticateHeader(this.wwwAuthenticate)
25
82
  }
26
83
 
84
+ /**
85
+ * Converts the error to a JSON representation suitable for response bodies.
86
+ *
87
+ * If the error was created from another LexError (via `from()`), returns
88
+ * the original error's JSON representation.
89
+ *
90
+ * @returns JSON object with error code and message
91
+ */
27
92
  toJSON() {
28
93
  const { cause } = this
29
94
  return cause instanceof LexError ? cause.toJSON() : super.toJSON()
30
95
  }
31
96
 
97
+ /**
98
+ * Converts the error to an HTTP 401 Response with WWW-Authenticate header.
99
+ *
100
+ * The response includes:
101
+ * - Status code 401 (Unauthorized)
102
+ * - WWW-Authenticate header (if parameters were provided)
103
+ * - Access-Control-Expose-Headers for CORS compatibility
104
+ * - JSON body with error details
105
+ *
106
+ * @returns HTTP Response object ready to be sent to the client
107
+ */
32
108
  toResponse(): Response {
33
109
  const { wwwAuthenticateHeader } = this
34
110
 
@@ -42,6 +118,24 @@ export class LexServerAuthError<
42
118
  return Response.json(this.toJSON(), { status: 401, headers })
43
119
  }
44
120
 
121
+ /**
122
+ * Creates a LexServerAuthError from an existing LexError.
123
+ *
124
+ * If the input is already a LexServerAuthError, returns it unchanged.
125
+ * Otherwise, wraps the error with the provided WWW-Authenticate parameters.
126
+ *
127
+ * @param cause - The original LexError to wrap
128
+ * @param wwwAuthenticate - WWW-Authenticate header parameters
129
+ * @returns A LexServerAuthError instance
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * const lexError = new LexError('AuthenticationRequired', 'Token expired')
134
+ * const authError = LexServerAuthError.from(lexError, {
135
+ * Bearer: { error: 'ExpiredToken' }
136
+ * })
137
+ * ```
138
+ */
45
139
  static from(
46
140
  cause: LexError,
47
141
  wwwAuthenticate?: WWWAuthenticate,
@@ -1509,6 +1509,7 @@ describe('Subscription', () => {
1509
1509
 
1510
1510
  it('handles subscriptions with cleanup', async () => {
1511
1511
  let sentCount = 0
1512
+ const maxMessages = 10
1512
1513
 
1513
1514
  const { resolve, promise: finallyPromise } = timeoutDeferred(5000)
1514
1515
 
@@ -1516,7 +1517,7 @@ describe('Subscription', () => {
1516
1517
  io.example.subscribe,
1517
1518
  async function* ({ params: { message }, signal }) {
1518
1519
  try {
1519
- for (; sentCount < 10; ) {
1520
+ for (; sentCount < maxMessages; ) {
1520
1521
  await scheduler.wait(5, { signal })
1521
1522
  yield { message, count: ++sentCount }
1522
1523
  }
@@ -1558,7 +1559,7 @@ describe('Subscription', () => {
1558
1559
  ])
1559
1560
 
1560
1561
  expect(sentCount).toBeGreaterThanOrEqual(3)
1561
- expect(sentCount).toBeLessThan(5)
1562
+ expect(sentCount).toBeLessThan(maxMessages)
1562
1563
  })
1563
1564
 
1564
1565
  it('returns 405 for non-GET request', async () => {