@atproto/lex-server 0.1.4 → 0.1.5

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.
@@ -1,34 +0,0 @@
1
- import { abortableSleep } from './sleep.js'
2
-
3
- /**
4
- * Performs polling based backpressure management for a WebSocket connection. If
5
- * the amount of buffered data exceeds the specified high water mark, this
6
- * function will wait until the buffered amount drops below the low water mark
7
- * before resolving. This is useful for preventing memory issues when sending
8
- * large amounts of data over a WebSocket connection.
9
- */
10
- export async function drainWebsocket(
11
- socket: WebSocket,
12
- signal: AbortSignal,
13
- {
14
- highWaterMark = 250_000, // 250 KB
15
- lowWaterMark = 50_000, // 50 KB
16
- }: {
17
- highWaterMark?: number
18
- lowWaterMark?: number
19
- } = {},
20
- ): Promise<void> {
21
- if (socket.bufferedAmount > highWaterMark) {
22
- // Once we exceed the high water mark, we wait until the buffered amount
23
- // drops below the low water mark before allowing more data to be sent. This
24
- // creates a hysteresis effect that prevents rapid toggling around the
25
- // threshold.
26
- while (
27
- socket.readyState === 1 &&
28
- socket.bufferedAmount !== 0 &&
29
- socket.bufferedAmount > lowWaterMark
30
- ) {
31
- await abortableSleep(10, signal)
32
- }
33
- }
34
- }
package/src/lib/sleep.ts DELETED
@@ -1,25 +0,0 @@
1
- export async function abortableSleep(
2
- ms: number,
3
- signal: AbortSignal,
4
- ): Promise<void> {
5
- signal.throwIfAborted()
6
-
7
- return new Promise((resolve, reject) => {
8
- const cleanup = () => {
9
- signal.removeEventListener('abort', onAbort)
10
- clearTimeout(timeoutHandle)
11
- }
12
-
13
- const timeoutHandle = setTimeout(() => {
14
- cleanup()
15
- resolve()
16
- }, ms)
17
-
18
- const onAbort = () => {
19
- cleanup()
20
- reject(signal.reason)
21
- }
22
-
23
- signal.addEventListener('abort', onAbort)
24
- })
25
- }
@@ -1,134 +0,0 @@
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
- })
@@ -1,111 +0,0 @@
1
- /**
2
- * Type representing the value of a WWW-Authenticate HTTP header.
3
- *
4
- * Supports multiple authentication schemes, each with optional parameters.
5
- * Parameters can be provided as a token68 string (for schemes like Bearer)
6
- * or as key-value pairs.
7
- *
8
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7235#section-4.1 | RFC 7235 Section 4.1}
9
- *
10
- * @example Bearer scheme with parameters
11
- * ```typescript
12
- * const auth: WWWAuthenticate = {
13
- * Bearer: {
14
- * realm: 'api.example.com',
15
- * error: 'InvalidToken',
16
- * error_description: 'The token has expired'
17
- * }
18
- * }
19
- * // Formats to: Bearer realm="api.example.com", error="InvalidToken", error_description="The token has expired"
20
- * ```
21
- *
22
- * @example Multiple schemes
23
- * ```typescript
24
- * const auth: WWWAuthenticate = {
25
- * Bearer: { realm: 'api' },
26
- * Basic: { realm: 'api' }
27
- * }
28
- * // Formats to: Bearer realm="api", Basic realm="api"
29
- * ```
30
- *
31
- * @example Token68 value (no parameters)
32
- * ```typescript
33
- * const auth: WWWAuthenticate = {
34
- * Bearer: 'base64encodedvalue=='
35
- * }
36
- * // Formats to: Bearer base64encodedvalue==
37
- * ```
38
- */
39
- export type WWWAuthenticate = {
40
- [authScheme in string]?:
41
- | string // token68
42
- | WWWAuthenticateParams
43
- | (string | WWWAuthenticateParams)[]
44
- }
45
-
46
- export type WWWAuthenticateParams = { [authParam in string]?: string }
47
-
48
- /**
49
- * Formats a WWWAuthenticate object into an HTTP header string.
50
- *
51
- * Converts the structured authentication scheme and parameter data into
52
- * the proper WWW-Authenticate header format per RFC 7235.
53
- *
54
- * @param wwwAuthenticate - The authentication schemes and parameters
55
- * @returns Formatted header string ready for use in HTTP responses
56
- *
57
- * @example
58
- * ```typescript
59
- * const header = formatWWWAuthenticateHeader({
60
- * Bearer: {
61
- * realm: 'api.example.com',
62
- * error: 'MissingToken'
63
- * }
64
- * })
65
- * // Returns: 'Bearer realm="api.example.com", error="MissingToken"'
66
- * ```
67
- *
68
- * @example Empty or undefined values
69
- * ```typescript
70
- * const header = formatWWWAuthenticateHeader({
71
- * Bearer: { realm: 'api', error: undefined }
72
- * })
73
- * // Returns: 'Bearer realm="api"' (undefined values are omitted)
74
- * ```
75
- */
76
- export function formatWWWAuthenticateHeader(
77
- wwwAuthenticate: WWWAuthenticate,
78
- ): string {
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(', ')
111
- }
@@ -1,107 +0,0 @@
1
- import { AddressInfo } from 'node:net'
2
- import { scheduler } from 'node:timers/promises'
3
- import { afterAll, beforeAll, describe, expect, it } from 'vitest'
4
- import { Server, serve } from './nodejs.js'
5
-
6
- describe('Node.js RequestListener', () => {
7
- let server: Server
8
- let address: string
9
-
10
- beforeAll(async () => {
11
- server = await serve(async (request) => {
12
- const { pathname } = new URL(request.url)
13
- if (pathname === '/hello') {
14
- return new Response('Hello, world!', {
15
- status: 200,
16
- headers: { 'Content-Type': 'text/plain' },
17
- })
18
- } else if (pathname === '/throw') {
19
- throw new Error('Test error')
20
- } else if (pathname === '/echo') {
21
- return new Response(request.body, {
22
- status: 200,
23
- headers: { 'Content-Type': 'application/octet-stream' },
24
- })
25
- }
26
- return new Response('Not Found', { status: 404 })
27
- })
28
- const { port } = server.address() as AddressInfo
29
- address = `http://localhost:${port}`
30
- })
31
-
32
- afterAll(async () => {
33
- await server.terminate()
34
- })
35
-
36
- it('should respond with Hello, world! on /hello', async () => {
37
- const res = await fetch(new URL(`/hello`, address))
38
- const text = await res.text()
39
- expect(res.status).toBe(200)
40
- expect(text).toBe('Hello, world!')
41
- })
42
-
43
- it('should respond with Not Found on unknown path', async () => {
44
- const res = await fetch(new URL(`/unknown`, address))
45
- const text = await res.text()
46
- expect(res.status).toBe(404)
47
- expect(text).toBe('Not Found')
48
- })
49
-
50
- it('should handle thrown errors and respond with 500', async () => {
51
- const res = await fetch(new URL(`/throw`, address))
52
- const text = await res.text()
53
- expect(res.status).toBe(500)
54
- expect(text).toBe('Internal Server Error')
55
- })
56
-
57
- it('should handle streaming bodies', async () => {
58
- const totalSize = 1024 * 1024
59
- const consumerSize = 42 * 1024
60
-
61
- let sentBytes = 0
62
- let receivedBytes = 0
63
-
64
- const res = await fetch(new URL(`/echo`, address), {
65
- method: 'POST',
66
- // @ts-expect-error
67
- duplex: 'half',
68
- body: new ReadableStream({
69
- async pull(controller) {
70
- const chunkSize = Math.min(1024, totalSize - sentBytes)
71
- controller.enqueue('A'.repeat(chunkSize))
72
- sentBytes += chunkSize
73
- await scheduler.wait(0) // Yield to event loop
74
- if (sentBytes === totalSize) controller.close()
75
- },
76
- }),
77
- })
78
-
79
- const reader = res.body!.getReader()
80
-
81
- // eslint-disable-next-line no-constant-condition
82
- while (true) {
83
- const result = await reader.read()
84
- if (result.done) break
85
- receivedBytes += Buffer.byteLength(result.value)
86
- if (receivedBytes >= consumerSize) {
87
- await reader.cancel()
88
- break
89
- }
90
- }
91
-
92
- expect(receivedBytes).toBeGreaterThanOrEqual(consumerSize)
93
- expect(sentBytes).toBeGreaterThanOrEqual(consumerSize)
94
- expect(sentBytes).toBeLessThan(totalSize)
95
- })
96
-
97
- it('should echo back request body on /echo', async () => {
98
- const body = `Echo this back`
99
- const res = await fetch(new URL(`/echo`, address), {
100
- method: 'POST',
101
- body,
102
- })
103
- const text = await res.text()
104
- expect(res.status).toBe(200)
105
- expect(text).toBe(body)
106
- })
107
- })