@atproto/xrpc-server 0.11.5 → 0.11.6
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/CHANGELOG.md +20 -0
- package/package.json +26 -21
- package/jest.config.cjs +0 -21
- package/src/auth.ts +0 -235
- package/src/errors.ts +0 -312
- package/src/index.ts +0 -14
- package/src/logger.ts +0 -8
- package/src/rate-limiter-http.ts +0 -82
- package/src/rate-limiter.ts +0 -279
- package/src/server.ts +0 -858
- package/src/stream/frames.ts +0 -125
- package/src/stream/index.ts +0 -5
- package/src/stream/logger.ts +0 -6
- package/src/stream/server.ts +0 -66
- package/src/stream/stream.ts +0 -39
- package/src/stream/subscription.ts +0 -96
- package/src/stream/types.ts +0 -27
- package/src/types.ts +0 -330
- package/src/util.ts +0 -708
- package/tests/_util.ts +0 -124
- package/tests/auth.test.ts +0 -333
- package/tests/bodies.test.ts +0 -608
- package/tests/errors.test.ts +0 -299
- package/tests/frames.test.ts +0 -135
- package/tests/ipld.test.ts +0 -97
- package/tests/parameters.test.ts +0 -331
- package/tests/parsing.test.ts +0 -89
- package/tests/procedures.test.ts +0 -176
- package/tests/queries.test.ts +0 -140
- package/tests/rate-limiter.test.ts +0 -312
- package/tests/responses.test.ts +0 -72
- package/tests/stream.test.ts +0 -169
- package/tests/subscriptions.test.ts +0 -398
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/src/stream/frames.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { decodeAll, encode } from '@atproto/lex-cbor'
|
|
2
|
-
import { LexValue, isPlainObject } from '@atproto/lex-data'
|
|
3
|
-
import { XRPCError } from '../errors.js'
|
|
4
|
-
import {
|
|
5
|
-
ErrorFrameBody,
|
|
6
|
-
ErrorFrameHeader,
|
|
7
|
-
FrameHeader,
|
|
8
|
-
FrameType,
|
|
9
|
-
MessageFrameHeader,
|
|
10
|
-
errorFrameBody,
|
|
11
|
-
frameHeader,
|
|
12
|
-
} from './types.js'
|
|
13
|
-
|
|
14
|
-
export abstract class Frame<T extends LexValue = LexValue> {
|
|
15
|
-
abstract header: FrameHeader
|
|
16
|
-
abstract body: T
|
|
17
|
-
|
|
18
|
-
get op(): FrameType {
|
|
19
|
-
return this.header.op
|
|
20
|
-
}
|
|
21
|
-
toBytes(): Uint8Array {
|
|
22
|
-
return Buffer.concat([encode(this.header), encode(this.body)])
|
|
23
|
-
}
|
|
24
|
-
isMessage(): this is MessageFrame {
|
|
25
|
-
return this.op === FrameType.Message
|
|
26
|
-
}
|
|
27
|
-
isError(): this is ErrorFrame {
|
|
28
|
-
return this.op === FrameType.Error
|
|
29
|
-
}
|
|
30
|
-
static fromBytes(bytes: Uint8Array) {
|
|
31
|
-
const [header, body, ...rest] = decodeAll(bytes)
|
|
32
|
-
if (rest.length) {
|
|
33
|
-
throw new Error('Too many CBOR data items in frame')
|
|
34
|
-
} else if (body === undefined) {
|
|
35
|
-
throw new Error('Missing frame body')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const parsedHeader = frameHeader.safeParse(header)
|
|
39
|
-
if (!parsedHeader.success) {
|
|
40
|
-
throw new Error(`Invalid frame header: ${parsedHeader.reason.message}`)
|
|
41
|
-
}
|
|
42
|
-
const frameOp = parsedHeader.value.op
|
|
43
|
-
if (frameOp === FrameType.Message) {
|
|
44
|
-
return new MessageFrame(body, {
|
|
45
|
-
type: parsedHeader.value.t,
|
|
46
|
-
})
|
|
47
|
-
} else if (frameOp === FrameType.Error) {
|
|
48
|
-
const parsedBody = errorFrameBody.safeParse(body)
|
|
49
|
-
if (!parsedBody.success) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`Invalid error frame body: ${parsedBody.reason.message}`,
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
return new ErrorFrame(parsedBody.value)
|
|
55
|
-
} else {
|
|
56
|
-
const exhaustiveCheck: never = frameOp
|
|
57
|
-
throw new Error(`Unknown frame op: ${exhaustiveCheck}`)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export class MessageFrame<T extends LexValue = LexValue> extends Frame<T> {
|
|
63
|
-
header: MessageFrameHeader
|
|
64
|
-
body: T
|
|
65
|
-
|
|
66
|
-
constructor(body: T, opts?: { type?: string }) {
|
|
67
|
-
super()
|
|
68
|
-
this.header =
|
|
69
|
-
opts?.type !== undefined
|
|
70
|
-
? { op: FrameType.Message, t: opts?.type }
|
|
71
|
-
: { op: FrameType.Message }
|
|
72
|
-
this.body = body
|
|
73
|
-
}
|
|
74
|
-
get type() {
|
|
75
|
-
return this.header.t
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
static fromLexValue(data: LexValue, nsid: string) {
|
|
79
|
-
if (!isPlainObject(data)) {
|
|
80
|
-
return new MessageFrame(data)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const $type = data?.['$type']
|
|
84
|
-
if (typeof $type !== 'string') {
|
|
85
|
-
return new MessageFrame(data)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let type: string
|
|
89
|
-
|
|
90
|
-
const split = $type.split('#')
|
|
91
|
-
if (split.length === 2 && (split[0] === '' || split[0] === nsid)) {
|
|
92
|
-
type = `#${split[1]}`
|
|
93
|
-
} else {
|
|
94
|
-
type = $type
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const { $type: _, ...clone } = data
|
|
98
|
-
return new MessageFrame(clone, { type })
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export class ErrorFrame<T extends string = string> extends Frame<
|
|
103
|
-
ErrorFrameBody<T>
|
|
104
|
-
> {
|
|
105
|
-
header: ErrorFrameHeader
|
|
106
|
-
body: ErrorFrameBody<T>
|
|
107
|
-
|
|
108
|
-
constructor(body: ErrorFrameBody<T>) {
|
|
109
|
-
super()
|
|
110
|
-
this.header = { op: FrameType.Error }
|
|
111
|
-
this.body = body
|
|
112
|
-
}
|
|
113
|
-
get code() {
|
|
114
|
-
return this.body.error
|
|
115
|
-
}
|
|
116
|
-
get message() {
|
|
117
|
-
return this.body.message
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
static fromError(err: unknown): ErrorFrame {
|
|
121
|
-
if (err instanceof ErrorFrame) return err
|
|
122
|
-
const { error = 'Unknown', message } = XRPCError.fromError(err).payload
|
|
123
|
-
return new ErrorFrame({ error, message })
|
|
124
|
-
}
|
|
125
|
-
}
|
package/src/stream/index.ts
DELETED
package/src/stream/logger.ts
DELETED
package/src/stream/server.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { IncomingMessage } from 'node:http'
|
|
2
|
-
import type { ServerOptions } from 'ws'
|
|
3
|
-
import { WebSocket, WebSocketServer } from 'ws'
|
|
4
|
-
import { CloseCode, DisconnectError } from '@atproto/ws-client'
|
|
5
|
-
import { ErrorFrame, Frame } from './frames.js'
|
|
6
|
-
import { logger } from './logger.js'
|
|
7
|
-
|
|
8
|
-
export class XrpcStreamServer {
|
|
9
|
-
wss: WebSocketServer
|
|
10
|
-
constructor(opts: ServerOptions & { handler: Handler }) {
|
|
11
|
-
const { handler, ...serverOpts } = opts
|
|
12
|
-
this.wss = new WebSocketServer(serverOpts)
|
|
13
|
-
this.wss.on('connection', async (socket, req) => {
|
|
14
|
-
socket.on('error', (err) => logger.error(err, 'websocket error'))
|
|
15
|
-
try {
|
|
16
|
-
const ac = new AbortController()
|
|
17
|
-
const iterator = unwrapIterator(handler(req, ac.signal, socket, this))
|
|
18
|
-
socket.once('close', () => {
|
|
19
|
-
iterator.return?.()
|
|
20
|
-
ac.abort()
|
|
21
|
-
})
|
|
22
|
-
const safeFrames = wrapIterator(iterator)
|
|
23
|
-
for await (const frame of safeFrames) {
|
|
24
|
-
await new Promise((res, rej) => {
|
|
25
|
-
socket.send(frame.toBytes(), { binary: true }, (err) => {
|
|
26
|
-
// @TODO this callback may give more aggressive on backpressure than
|
|
27
|
-
// we ultimately want, but trying it out for the time being.
|
|
28
|
-
if (err) return rej(err)
|
|
29
|
-
res(undefined)
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
if (frame instanceof ErrorFrame) {
|
|
33
|
-
throw new DisconnectError(CloseCode.Policy, frame.body.error)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} catch (err) {
|
|
37
|
-
if (err instanceof DisconnectError) {
|
|
38
|
-
return socket.close(err.wsCode, err.xrpcCode)
|
|
39
|
-
} else {
|
|
40
|
-
logger.error({ err }, 'websocket server error')
|
|
41
|
-
return socket.terminate()
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
socket.close(CloseCode.Normal)
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type Handler = (
|
|
50
|
-
req: IncomingMessage,
|
|
51
|
-
signal: AbortSignal,
|
|
52
|
-
socket: WebSocket,
|
|
53
|
-
server: XrpcStreamServer,
|
|
54
|
-
) => AsyncIterable<Frame>
|
|
55
|
-
|
|
56
|
-
function unwrapIterator<T>(iterable: AsyncIterable<T>): AsyncIterator<T> {
|
|
57
|
-
return iterable[Symbol.asyncIterator]()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function wrapIterator<T>(iterator: AsyncIterator<T>): AsyncIterable<T> {
|
|
61
|
-
return {
|
|
62
|
-
[Symbol.asyncIterator]() {
|
|
63
|
-
return iterator
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/stream/stream.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { DuplexOptions } from 'node:stream'
|
|
2
|
-
import { WebSocket, createWebSocketStream } from 'ws'
|
|
3
|
-
import { ResponseType, XRPCError } from '@atproto/xrpc'
|
|
4
|
-
import { Frame, MessageFrame } from './frames.js'
|
|
5
|
-
|
|
6
|
-
export function streamByteChunks(ws: WebSocket, options?: DuplexOptions) {
|
|
7
|
-
return createWebSocketStream(ws, {
|
|
8
|
-
...options,
|
|
9
|
-
readableObjectMode: true, // Ensures frame bytes don't get buffered/combined together
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function* byFrame(ws: WebSocket, options?: DuplexOptions) {
|
|
14
|
-
const wsStream = streamByteChunks(ws, options)
|
|
15
|
-
for await (const chunk of wsStream) {
|
|
16
|
-
yield Frame.fromBytes(chunk)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function* byMessage(ws: WebSocket, options?: DuplexOptions) {
|
|
21
|
-
const wsStream = streamByteChunks(ws, options)
|
|
22
|
-
for await (const chunk of wsStream) {
|
|
23
|
-
const msg = ensureChunkIsMessage(chunk)
|
|
24
|
-
yield msg
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function ensureChunkIsMessage(chunk: Uint8Array): MessageFrame {
|
|
29
|
-
const frame = Frame.fromBytes(chunk)
|
|
30
|
-
if (frame.isMessage()) {
|
|
31
|
-
return frame
|
|
32
|
-
} else if (frame.isError()) {
|
|
33
|
-
// @TODO work -1 error code into XRPCError
|
|
34
|
-
// @ts-ignore
|
|
35
|
-
throw new XRPCError(-1, frame.code, frame.message)
|
|
36
|
-
} else {
|
|
37
|
-
throw new XRPCError(ResponseType.Unknown, undefined, 'Unknown frame type')
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type { ClientOptions } from 'ws'
|
|
2
|
-
import { isPlainObject } from '@atproto/lex-data'
|
|
3
|
-
import { WebSocketKeepAlive } from '@atproto/ws-client'
|
|
4
|
-
import { ensureChunkIsMessage } from './stream.js'
|
|
5
|
-
|
|
6
|
-
export class Subscription<T = unknown> {
|
|
7
|
-
constructor(
|
|
8
|
-
public opts: ClientOptions & {
|
|
9
|
-
service: string
|
|
10
|
-
method: string
|
|
11
|
-
maxReconnectSeconds?: number
|
|
12
|
-
heartbeatIntervalMs?: number
|
|
13
|
-
signal?: AbortSignal
|
|
14
|
-
validate: (obj: unknown) => T | undefined
|
|
15
|
-
onReconnectError?: (
|
|
16
|
-
error: unknown,
|
|
17
|
-
n: number,
|
|
18
|
-
initialSetup: boolean,
|
|
19
|
-
) => void
|
|
20
|
-
getParams?: () =>
|
|
21
|
-
| Record<string, unknown>
|
|
22
|
-
| Promise<Record<string, unknown> | undefined>
|
|
23
|
-
| undefined
|
|
24
|
-
},
|
|
25
|
-
) {}
|
|
26
|
-
|
|
27
|
-
async *[Symbol.asyncIterator](): AsyncGenerator<T> {
|
|
28
|
-
const ws = new WebSocketKeepAlive({
|
|
29
|
-
...this.opts,
|
|
30
|
-
getUrl: async () => {
|
|
31
|
-
const params = (await this.opts.getParams?.()) ?? {}
|
|
32
|
-
const query = encodeQueryParams(params)
|
|
33
|
-
return `${this.opts.service}/xrpc/${this.opts.method}?${query}`
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
for await (const chunk of ws) {
|
|
37
|
-
const message = ensureChunkIsMessage(chunk)
|
|
38
|
-
const t = message.header.t
|
|
39
|
-
|
|
40
|
-
const typedBody = isPlainObject(message.body)
|
|
41
|
-
? t !== undefined
|
|
42
|
-
? {
|
|
43
|
-
...message.body,
|
|
44
|
-
$type: t.startsWith('#') ? this.opts.method + t : t,
|
|
45
|
-
}
|
|
46
|
-
: message.body
|
|
47
|
-
: undefined
|
|
48
|
-
|
|
49
|
-
const result = this.opts.validate(typedBody)
|
|
50
|
-
if (result !== undefined) {
|
|
51
|
-
yield result
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default Subscription
|
|
58
|
-
|
|
59
|
-
function encodeQueryParams(obj: Record<string, unknown>): string {
|
|
60
|
-
const params = new URLSearchParams()
|
|
61
|
-
Object.entries(obj).forEach(([key, value]) => {
|
|
62
|
-
const encoded = encodeQueryParam(value)
|
|
63
|
-
if (Array.isArray(encoded)) {
|
|
64
|
-
encoded.forEach((enc) => params.append(key, enc))
|
|
65
|
-
} else {
|
|
66
|
-
params.set(key, encoded)
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
return params.toString()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Adapted from xrpc, but without any lex-specific knowledge
|
|
73
|
-
function encodeQueryParam(value: unknown): string | string[] {
|
|
74
|
-
if (typeof value === 'string') {
|
|
75
|
-
return value
|
|
76
|
-
}
|
|
77
|
-
if (typeof value === 'number') {
|
|
78
|
-
return value.toString()
|
|
79
|
-
}
|
|
80
|
-
if (typeof value === 'boolean') {
|
|
81
|
-
return value ? 'true' : 'false'
|
|
82
|
-
}
|
|
83
|
-
if (typeof value === 'undefined') {
|
|
84
|
-
return ''
|
|
85
|
-
}
|
|
86
|
-
if (typeof value === 'object') {
|
|
87
|
-
if (value instanceof Date) {
|
|
88
|
-
return value.toISOString()
|
|
89
|
-
} else if (Array.isArray(value)) {
|
|
90
|
-
return value.flatMap(encodeQueryParam)
|
|
91
|
-
} else if (!value) {
|
|
92
|
-
return ''
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
throw new Error(`Cannot encode ${typeof value}s into query params`)
|
|
96
|
-
}
|
package/src/stream/types.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { l } from '@atproto/lex-schema'
|
|
2
|
-
|
|
3
|
-
export enum FrameType {
|
|
4
|
-
Message = 1,
|
|
5
|
-
Error = -1,
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const messageFrameHeader = l.object({
|
|
9
|
-
op: l.literal(FrameType.Message), // Frame op
|
|
10
|
-
t: l.optional(l.string()), // Message body type discriminator
|
|
11
|
-
})
|
|
12
|
-
export type MessageFrameHeader = l.Infer<typeof messageFrameHeader>
|
|
13
|
-
|
|
14
|
-
export const errorFrameHeader = l.object({
|
|
15
|
-
op: l.literal(FrameType.Error),
|
|
16
|
-
})
|
|
17
|
-
export const errorFrameBody = l.object({
|
|
18
|
-
error: l.string(), // Error code
|
|
19
|
-
message: l.optional(l.string()), // Error message
|
|
20
|
-
})
|
|
21
|
-
export type ErrorFrameHeader = l.Infer<typeof errorFrameHeader>
|
|
22
|
-
export type ErrorFrameBody<T extends string = string> = { error: T } & l.Infer<
|
|
23
|
-
typeof errorFrameBody
|
|
24
|
-
>
|
|
25
|
-
|
|
26
|
-
export const frameHeader = l.union([messageFrameHeader, errorFrameHeader])
|
|
27
|
-
export type FrameHeader = l.Infer<typeof frameHeader>
|
package/src/types.ts
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
import { IncomingMessage } from 'node:http'
|
|
2
|
-
import { Readable } from 'node:stream'
|
|
3
|
-
import { NextFunction, Request, Response } from 'express'
|
|
4
|
-
import { l } from '@atproto/lex-schema'
|
|
5
|
-
import { ErrorResult, XRPCError } from './errors.js'
|
|
6
|
-
import { CalcKeyFn, CalcPointsFn, RateLimiterI } from './rate-limiter.js'
|
|
7
|
-
|
|
8
|
-
export type Awaitable<T> = T | Promise<T>
|
|
9
|
-
|
|
10
|
-
export type CatchallHandler = (
|
|
11
|
-
req: Request,
|
|
12
|
-
res: Response,
|
|
13
|
-
next: NextFunction,
|
|
14
|
-
) => unknown
|
|
15
|
-
|
|
16
|
-
export type Options = {
|
|
17
|
-
validateResponse?: boolean
|
|
18
|
-
catchall?: CatchallHandler
|
|
19
|
-
payload?: RouteOptions
|
|
20
|
-
rateLimits?: {
|
|
21
|
-
creator: RateLimiterCreator<HandlerContext>
|
|
22
|
-
global?: ServerRateLimitDescription<HandlerContext>[]
|
|
23
|
-
shared?: ServerRateLimitDescription<HandlerContext>[]
|
|
24
|
-
bypass?: (ctx: HandlerContext) => boolean
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* By default, errors are converted to {@link XRPCError} using
|
|
28
|
-
* {@link XRPCError.fromError} before being rendered. If method handlers throw
|
|
29
|
-
* error objects that are not properly rendered in the HTTP response, this
|
|
30
|
-
* function can be used to properly convert them to {@link XRPCError}. The
|
|
31
|
-
* provided function will typically fallback to the default error conversion
|
|
32
|
-
* (`return XRPCError.fromError(err)`) if the error is not recognized.
|
|
33
|
-
*
|
|
34
|
-
* @note This function should not throw errors.
|
|
35
|
-
*/
|
|
36
|
-
errorParser?: (err: unknown) => XRPCError
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type UndecodedParams = Request['query']
|
|
40
|
-
|
|
41
|
-
export type Primitive = string | number | boolean
|
|
42
|
-
export type Params = { [P in string]?: undefined | Primitive | Primitive[] }
|
|
43
|
-
|
|
44
|
-
export type HandlerInput = {
|
|
45
|
-
encoding: string
|
|
46
|
-
body: unknown
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type AuthResult = {
|
|
50
|
-
credentials: unknown
|
|
51
|
-
artifacts?: unknown
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const headersSchema = l.dict(l.string(), l.string())
|
|
55
|
-
|
|
56
|
-
export type Headers = l.Infer<typeof headersSchema>
|
|
57
|
-
|
|
58
|
-
export const handlerSuccess = l.object({
|
|
59
|
-
encoding: l.string(),
|
|
60
|
-
body: l.unknown(),
|
|
61
|
-
headers: l.optional(headersSchema),
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
export type HandlerSuccess = l.Infer<typeof handlerSuccess>
|
|
65
|
-
|
|
66
|
-
export const handlerPipeThroughBuffer = l.object({
|
|
67
|
-
encoding: l.string(),
|
|
68
|
-
buffer: l.custom(
|
|
69
|
-
(v): v is Buffer => v instanceof Buffer,
|
|
70
|
-
'Expected a Buffer',
|
|
71
|
-
),
|
|
72
|
-
headers: l.optional(headersSchema),
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
export type HandlerPipeThroughBuffer = l.Infer<typeof handlerPipeThroughBuffer>
|
|
76
|
-
|
|
77
|
-
export const handlerPipeThroughStream = l.object({
|
|
78
|
-
encoding: l.string(),
|
|
79
|
-
stream: l.custom(
|
|
80
|
-
(v): v is Readable => v instanceof Readable,
|
|
81
|
-
'Expected a Readable stream',
|
|
82
|
-
),
|
|
83
|
-
headers: l.optional(headersSchema),
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
export type HandlerPipeThroughStream = l.Infer<typeof handlerPipeThroughStream>
|
|
87
|
-
|
|
88
|
-
export const handlerPipeThrough = l.union([
|
|
89
|
-
handlerPipeThroughBuffer,
|
|
90
|
-
handlerPipeThroughStream,
|
|
91
|
-
])
|
|
92
|
-
|
|
93
|
-
export type HandlerPipeThrough = l.Infer<typeof handlerPipeThrough>
|
|
94
|
-
|
|
95
|
-
export type Auth = void | AuthResult
|
|
96
|
-
export type Input = void | HandlerInput
|
|
97
|
-
export type Output = void | HandlerSuccess | HandlerPipeThrough | ErrorResult
|
|
98
|
-
|
|
99
|
-
export type AuthVerifier<C, A extends AuthResult = AuthResult> =
|
|
100
|
-
| ((ctx: C) => Awaitable<A | ErrorResult>)
|
|
101
|
-
| ((ctx: C) => Awaitable<A>)
|
|
102
|
-
|
|
103
|
-
export type MethodAuthContext<P extends Params = Params> = {
|
|
104
|
-
params: P
|
|
105
|
-
req: Request
|
|
106
|
-
res: Response
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export type MethodAuthVerifier<
|
|
110
|
-
A extends AuthResult = AuthResult,
|
|
111
|
-
P extends Params = Params,
|
|
112
|
-
> = AuthVerifier<MethodAuthContext<P>, A>
|
|
113
|
-
|
|
114
|
-
export type HandlerContext<
|
|
115
|
-
A extends Auth = Auth,
|
|
116
|
-
P extends Params = Params,
|
|
117
|
-
I extends Input = Input,
|
|
118
|
-
> = MethodAuthContext<P> & {
|
|
119
|
-
auth: A
|
|
120
|
-
input: I
|
|
121
|
-
resetRouteRateLimits: () => Promise<void>
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export type MethodHandler<
|
|
125
|
-
A extends Auth = Auth,
|
|
126
|
-
P extends Params = Params,
|
|
127
|
-
I extends Input = Input,
|
|
128
|
-
O extends Output = Output,
|
|
129
|
-
> = (ctx: HandlerContext<A, P, I>) => Awaitable<O | HandlerPipeThrough>
|
|
130
|
-
|
|
131
|
-
export type RateLimiterCreator<T extends HandlerContext = HandlerContext> = <
|
|
132
|
-
C extends T = T,
|
|
133
|
-
>(opts: {
|
|
134
|
-
keyPrefix: string
|
|
135
|
-
durationMs: number
|
|
136
|
-
points: number
|
|
137
|
-
calcKey: CalcKeyFn<C>
|
|
138
|
-
calcPoints: CalcPointsFn<C>
|
|
139
|
-
failClosed?: boolean
|
|
140
|
-
}) => RateLimiterI<C>
|
|
141
|
-
|
|
142
|
-
export type ServerRateLimitDescription<
|
|
143
|
-
C extends HandlerContext = HandlerContext,
|
|
144
|
-
> = {
|
|
145
|
-
name: string
|
|
146
|
-
durationMs: number
|
|
147
|
-
points: number
|
|
148
|
-
calcKey?: CalcKeyFn<C>
|
|
149
|
-
calcPoints?: CalcPointsFn<C>
|
|
150
|
-
failClosed?: boolean
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export type SharedRateLimitOpts<C extends HandlerContext = HandlerContext> = {
|
|
154
|
-
name: string
|
|
155
|
-
calcKey?: CalcKeyFn<C>
|
|
156
|
-
calcPoints?: CalcPointsFn<C>
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export type RouteRateLimitOpts<C extends HandlerContext = HandlerContext> = {
|
|
160
|
-
durationMs: number
|
|
161
|
-
points: number
|
|
162
|
-
calcKey?: CalcKeyFn<C>
|
|
163
|
-
calcPoints?: CalcPointsFn<C>
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export type RateLimitOpts<C extends HandlerContext = HandlerContext> =
|
|
167
|
-
| SharedRateLimitOpts<C>
|
|
168
|
-
| RouteRateLimitOpts<C>
|
|
169
|
-
|
|
170
|
-
export function isSharedRateLimitOpts<
|
|
171
|
-
C extends HandlerContext = HandlerContext,
|
|
172
|
-
>(opts: RateLimitOpts<C>): opts is SharedRateLimitOpts<C> {
|
|
173
|
-
return typeof opts['name'] === 'string'
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export type RouteOptions = {
|
|
177
|
-
blobLimit?: number
|
|
178
|
-
jsonLimit?: number
|
|
179
|
-
textLimit?: number
|
|
180
|
-
paramsParseLoose?: boolean
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export type MethodAuth<
|
|
184
|
-
A extends Auth = Auth,
|
|
185
|
-
P extends Params = Params,
|
|
186
|
-
> = MethodAuthVerifier<Extract<A, AuthResult>, P>
|
|
187
|
-
|
|
188
|
-
export type MethodRateLimit<
|
|
189
|
-
A extends Auth = Auth,
|
|
190
|
-
P extends Params = Params,
|
|
191
|
-
I extends Input = Input,
|
|
192
|
-
> =
|
|
193
|
-
| RateLimitOpts<HandlerContext<A, P, I>>
|
|
194
|
-
| RateLimitOpts<HandlerContext<A, P, I>>[]
|
|
195
|
-
|
|
196
|
-
export type MethodConfig<
|
|
197
|
-
A extends Auth = Auth,
|
|
198
|
-
P extends Params = Params,
|
|
199
|
-
I extends Input = Input,
|
|
200
|
-
O extends Output = Output,
|
|
201
|
-
> = {
|
|
202
|
-
handler: MethodHandler<A, P, I, O>
|
|
203
|
-
auth?: MethodAuth<A, P>
|
|
204
|
-
opts?: RouteOptions
|
|
205
|
-
rateLimit?: MethodRateLimit<A, P, I>
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export type MethodConfigWithAuth<
|
|
209
|
-
A extends Auth = Auth,
|
|
210
|
-
P extends Params = Params,
|
|
211
|
-
I extends Input = Input,
|
|
212
|
-
O extends Output = Output,
|
|
213
|
-
> = {
|
|
214
|
-
handler: MethodHandler<A, P, I, O>
|
|
215
|
-
auth: MethodAuth<A, P>
|
|
216
|
-
opts?: RouteOptions
|
|
217
|
-
rateLimit?: MethodRateLimit<A, P, I>
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export type MethodConfigOrHandler<
|
|
221
|
-
A extends Auth = Auth,
|
|
222
|
-
P extends Params = Params,
|
|
223
|
-
I extends Input = Input,
|
|
224
|
-
O extends Output = Output,
|
|
225
|
-
> = MethodHandler<A, P, I, O> | MethodConfig<A, P, I, O>
|
|
226
|
-
|
|
227
|
-
export type StreamAuthContext<P extends Params = Params> = {
|
|
228
|
-
params: P
|
|
229
|
-
req: IncomingMessage
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export type LexMethodParams<M extends l.Procedure | l.Query | l.Subscription> =
|
|
233
|
-
l.InferMethodParams<M>
|
|
234
|
-
|
|
235
|
-
export type LexMethodInput<M extends l.Procedure | l.Query> =
|
|
236
|
-
l.InferMethodInput<M, Readable>
|
|
237
|
-
|
|
238
|
-
export type LexMethodOutput<M extends l.Procedure | l.Query> =
|
|
239
|
-
l.InferMethodOutput<M, Readable> extends undefined
|
|
240
|
-
? l.InferMethodOutput<M, Uint8Array | Readable> | void
|
|
241
|
-
: l.InferMethodOutput<M, Uint8Array | Readable>
|
|
242
|
-
|
|
243
|
-
export type LexMethodMessage<M extends l.Subscription> = l.InferMethodMessage<M>
|
|
244
|
-
|
|
245
|
-
export type LexMethodHandler<
|
|
246
|
-
M extends l.Procedure | l.Query,
|
|
247
|
-
A extends Auth = Auth,
|
|
248
|
-
> = MethodHandler<A, LexMethodParams<M>, LexMethodInput<M>, LexMethodOutput<M>>
|
|
249
|
-
|
|
250
|
-
export type LexMethodConfig<
|
|
251
|
-
M extends l.Procedure | l.Query,
|
|
252
|
-
A extends Auth = Auth,
|
|
253
|
-
> = MethodConfig<A, LexMethodParams<M>, LexMethodInput<M>, LexMethodOutput<M>>
|
|
254
|
-
|
|
255
|
-
export type LexSubscriptionHandler<
|
|
256
|
-
M extends l.Subscription,
|
|
257
|
-
A extends Auth = Auth,
|
|
258
|
-
> = StreamHandler<
|
|
259
|
-
Extract<A, AuthResult>,
|
|
260
|
-
LexMethodParams<M>,
|
|
261
|
-
LexMethodMessage<M>
|
|
262
|
-
>
|
|
263
|
-
|
|
264
|
-
export type LexSubscriptionConfig<
|
|
265
|
-
M extends l.Subscription,
|
|
266
|
-
A extends Auth = Auth,
|
|
267
|
-
> = StreamConfig<A, LexMethodParams<M>, LexMethodMessage<M>>
|
|
268
|
-
|
|
269
|
-
export type StreamAuthVerifier<
|
|
270
|
-
A extends AuthResult = AuthResult,
|
|
271
|
-
P extends Params = Params,
|
|
272
|
-
> = AuthVerifier<StreamAuthContext<P>, A>
|
|
273
|
-
|
|
274
|
-
export type StreamContext<
|
|
275
|
-
A extends Auth = Auth,
|
|
276
|
-
P extends Params = Params,
|
|
277
|
-
> = StreamAuthContext<P> & {
|
|
278
|
-
auth: A
|
|
279
|
-
signal: AbortSignal
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export type StreamHandler<
|
|
283
|
-
A extends Auth = Auth,
|
|
284
|
-
P extends Params = Params,
|
|
285
|
-
O = unknown,
|
|
286
|
-
> = (ctx: StreamContext<A, P>) => AsyncIterable<O>
|
|
287
|
-
|
|
288
|
-
export type StreamConfig<
|
|
289
|
-
A extends Auth = Auth,
|
|
290
|
-
P extends Params = Params,
|
|
291
|
-
O = unknown,
|
|
292
|
-
> = {
|
|
293
|
-
auth?: StreamAuthVerifier<Extract<A, AuthResult>, P>
|
|
294
|
-
handler: StreamHandler<A, P, O>
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export type StreamConfigOrHandler<
|
|
298
|
-
A extends Auth = Auth,
|
|
299
|
-
P extends Params = Params,
|
|
300
|
-
O = unknown,
|
|
301
|
-
> = StreamHandler<A, P, O> | StreamConfig<A, P, O>
|
|
302
|
-
|
|
303
|
-
export function isHandlerSuccess(output: Output): output is HandlerSuccess {
|
|
304
|
-
// We only need to discriminate between possible Output values
|
|
305
|
-
return (
|
|
306
|
-
output != null &&
|
|
307
|
-
'body' in output && // body is non optional (contrary to what type inference may suggest)
|
|
308
|
-
'encoding' in output &&
|
|
309
|
-
// Allows using objects that extends HandlerSuccess with a "status" field as
|
|
310
|
-
// output, as long as the status is < 400, in order to avoid being confused
|
|
311
|
-
// with ErrorResult objects.
|
|
312
|
-
(!('status' in output) ||
|
|
313
|
-
output.status == null ||
|
|
314
|
-
Number(output.status) < 400)
|
|
315
|
-
)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export function isHandlerPipeThroughBuffer(
|
|
319
|
-
output: Output,
|
|
320
|
-
): output is HandlerPipeThroughBuffer {
|
|
321
|
-
// We only need to discriminate between possible Output values
|
|
322
|
-
return output != null && 'buffer' in output && output['buffer'] !== undefined
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function isHandlerPipeThroughStream(
|
|
326
|
-
output: Output,
|
|
327
|
-
): output is HandlerPipeThroughStream {
|
|
328
|
-
// We only need to discriminate between possible Output values
|
|
329
|
-
return output != null && 'stream' in output && output['stream'] !== undefined
|
|
330
|
-
}
|