@atproto/xrpc-server 0.4.2 → 0.4.3

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,6 +1,6 @@
1
1
  import { RateLimiterAbstract, RateLimiterRes } from 'rate-limiter-flexible';
2
2
  import { CalcKeyFn, CalcPointsFn, RateLimitExceededError, RateLimiterConsume, RateLimiterI, RateLimiterStatus, XRPCReqContext } from './types';
3
- export declare type RateLimiterOpts = {
3
+ export type RateLimiterOpts = {
4
4
  keyPrefix: string;
5
5
  durationMs: number;
6
6
  points: number;
package/dist/server.d.ts CHANGED
@@ -22,7 +22,7 @@ export declare class Server {
22
22
  addLexicons(docs: LexiconDoc[]): void;
23
23
  protected addRoute(nsid: string, def: LexXrpcQuery | LexXrpcProcedure, config: XRPCHandlerConfig): Promise<void>;
24
24
  catchall(req: express.Request, _res: express.Response, next: NextFunction): Promise<void>;
25
- createHandler(nsid: string, def: LexXrpcQuery | LexXrpcProcedure, handler: XRPCHandler): RequestHandler;
25
+ createHandler(nsid: string, def: LexXrpcQuery | LexXrpcProcedure, routeCfg: XRPCHandlerConfig): RequestHandler;
26
26
  protected addSubscription(nsid: string, def: LexXrpcSubscription, config: XRPCStreamHandlerConfig): Promise<void>;
27
27
  private enableStreamingOnListen;
28
28
  private setupRouteRateLimits;
@@ -8,4 +8,4 @@ export declare class XrpcStreamServer {
8
8
  handler: Handler;
9
9
  });
10
10
  }
11
- export declare type Handler = (req: IncomingMessage, signal: AbortSignal, socket: WebSocket, server: XrpcStreamServer) => AsyncIterable<Frame>;
11
+ export type Handler = (req: IncomingMessage, signal: AbortSignal, socket: WebSocket, server: XrpcStreamServer) => AsyncIterable<Frame>;
@@ -13,7 +13,7 @@ export declare const messageFrameHeader: z.ZodObject<{
13
13
  op: FrameType.Message;
14
14
  t?: string | undefined;
15
15
  }>;
16
- export declare type MessageFrameHeader = z.infer<typeof messageFrameHeader>;
16
+ export type MessageFrameHeader = z.infer<typeof messageFrameHeader>;
17
17
  export declare const errorFrameHeader: z.ZodObject<{
18
18
  op: z.ZodLiteral<FrameType.Error>;
19
19
  }, "strip", z.ZodTypeAny, {
@@ -31,8 +31,8 @@ export declare const errorFrameBody: z.ZodObject<{
31
31
  error: string;
32
32
  message?: string | undefined;
33
33
  }>;
34
- export declare type ErrorFrameHeader = z.infer<typeof errorFrameHeader>;
35
- export declare type ErrorFrameBody<T extends string = string> = {
34
+ export type ErrorFrameHeader = z.infer<typeof errorFrameHeader>;
35
+ export type ErrorFrameBody<T extends string = string> = {
36
36
  error: T;
37
37
  } & z.infer<typeof errorFrameBody>;
38
38
  export declare const frameHeader: z.ZodUnion<[z.ZodObject<{
@@ -51,7 +51,7 @@ export declare const frameHeader: z.ZodUnion<[z.ZodObject<{
51
51
  }, {
52
52
  op: FrameType.Error;
53
53
  }>]>;
54
- export declare type FrameHeader = z.infer<typeof frameHeader>;
54
+ export type FrameHeader = z.infer<typeof frameHeader>;
55
55
  export declare class DisconnectError extends Error {
56
56
  wsCode: CloseCode;
57
57
  xrpcCode?: string | undefined;
package/dist/types.d.ts CHANGED
@@ -3,7 +3,7 @@ import { IncomingMessage } from 'http';
3
3
  import express from 'express';
4
4
  import zod from 'zod';
5
5
  import { ResponseType } from '@atproto/xrpc';
6
- export declare type Options = {
6
+ export type Options = {
7
7
  validateResponse?: boolean;
8
8
  payload?: {
9
9
  jsonLimit?: number;
@@ -16,9 +16,9 @@ export declare type Options = {
16
16
  shared?: ServerRateLimitDescription[];
17
17
  };
18
18
  };
19
- export declare type UndecodedParams = typeof express.request['query'];
20
- export declare type Primitive = string | number | boolean;
21
- export declare type Params = Record<string, Primitive | Primitive[] | undefined>;
19
+ export type UndecodedParams = typeof express.request['query'];
20
+ export type Primitive = string | number | boolean;
21
+ export type Params = Record<string, Primitive | Primitive[] | undefined>;
22
22
  export declare const handlerInput: zod.ZodObject<{
23
23
  encoding: zod.ZodString;
24
24
  body: zod.ZodAny;
@@ -29,7 +29,7 @@ export declare const handlerInput: zod.ZodObject<{
29
29
  encoding: string;
30
30
  body?: any;
31
31
  }>;
32
- export declare type HandlerInput = zod.infer<typeof handlerInput>;
32
+ export type HandlerInput = zod.infer<typeof handlerInput>;
33
33
  export declare const handlerAuth: zod.ZodObject<{
34
34
  credentials: zod.ZodAny;
35
35
  artifacts: zod.ZodAny;
@@ -40,7 +40,7 @@ export declare const handlerAuth: zod.ZodObject<{
40
40
  credentials?: any;
41
41
  artifacts?: any;
42
42
  }>;
43
- export declare type HandlerAuth = zod.infer<typeof handlerAuth>;
43
+ export type HandlerAuth = zod.infer<typeof handlerAuth>;
44
44
  export declare const handlerSuccess: zod.ZodObject<{
45
45
  encoding: zod.ZodString;
46
46
  body: zod.ZodAny;
@@ -54,7 +54,21 @@ export declare const handlerSuccess: zod.ZodObject<{
54
54
  body?: any;
55
55
  headers?: Record<string, string> | undefined;
56
56
  }>;
57
- export declare type HandlerSuccess = zod.infer<typeof handlerSuccess>;
57
+ export type HandlerSuccess = zod.infer<typeof handlerSuccess>;
58
+ export declare const handlerPipeThrough: zod.ZodObject<{
59
+ encoding: zod.ZodString;
60
+ buffer: zod.ZodType<ArrayBuffer, zod.ZodTypeDef, ArrayBuffer>;
61
+ headers: zod.ZodOptional<zod.ZodRecord<zod.ZodString, zod.ZodString>>;
62
+ }, "strip", zod.ZodTypeAny, {
63
+ encoding: string;
64
+ buffer: ArrayBuffer;
65
+ headers?: Record<string, string> | undefined;
66
+ }, {
67
+ encoding: string;
68
+ buffer: ArrayBuffer;
69
+ headers?: Record<string, string> | undefined;
70
+ }>;
71
+ export type HandlerPipeThrough = zod.infer<typeof handlerPipeThrough>;
58
72
  export declare const handlerError: zod.ZodObject<{
59
73
  status: zod.ZodNumber;
60
74
  error: zod.ZodOptional<zod.ZodString>;
@@ -68,67 +82,67 @@ export declare const handlerError: zod.ZodObject<{
68
82
  error?: string | undefined;
69
83
  message?: string | undefined;
70
84
  }>;
71
- export declare type HandlerError = zod.infer<typeof handlerError>;
72
- export declare type HandlerOutput = HandlerSuccess | HandlerError;
73
- export declare type XRPCReqContext = {
85
+ export type HandlerError = zod.infer<typeof handlerError>;
86
+ export type HandlerOutput = HandlerSuccess | HandlerPipeThrough | HandlerError;
87
+ export type XRPCReqContext = {
74
88
  auth: HandlerAuth | undefined;
75
89
  params: Params;
76
90
  input: HandlerInput | undefined;
77
91
  req: express.Request;
78
92
  res: express.Response;
79
93
  };
80
- export declare type XRPCHandler = (ctx: XRPCReqContext) => Promise<HandlerOutput> | HandlerOutput | undefined;
81
- export declare type XRPCStreamHandler = (ctx: {
94
+ export type XRPCHandler = (ctx: XRPCReqContext) => Promise<HandlerOutput> | HandlerOutput | undefined;
95
+ export type XRPCStreamHandler = (ctx: {
82
96
  auth: HandlerAuth | undefined;
83
97
  params: Params;
84
98
  req: IncomingMessage;
85
99
  signal: AbortSignal;
86
100
  }) => AsyncIterable<unknown>;
87
- export declare type AuthOutput = HandlerAuth | HandlerError;
88
- export declare type AuthVerifier = (ctx: {
101
+ export type AuthOutput = HandlerAuth | HandlerError;
102
+ export type AuthVerifier = (ctx: {
89
103
  req: express.Request;
90
104
  res: express.Response;
91
105
  }) => Promise<AuthOutput> | AuthOutput;
92
- export declare type StreamAuthVerifier = (ctx: {
106
+ export type StreamAuthVerifier = (ctx: {
93
107
  req: IncomingMessage;
94
108
  }) => Promise<AuthOutput> | AuthOutput;
95
- export declare type CalcKeyFn = (ctx: XRPCReqContext) => string;
96
- export declare type CalcPointsFn = (ctx: XRPCReqContext) => number;
109
+ export type CalcKeyFn = (ctx: XRPCReqContext) => string | null;
110
+ export type CalcPointsFn = (ctx: XRPCReqContext) => number;
97
111
  export interface RateLimiterI {
98
112
  consume: RateLimiterConsume;
99
113
  }
100
- export declare type RateLimiterConsume = (ctx: XRPCReqContext, opts?: {
114
+ export type RateLimiterConsume = (ctx: XRPCReqContext, opts?: {
101
115
  calcKey?: CalcKeyFn;
102
116
  calcPoints?: CalcPointsFn;
103
117
  }) => Promise<RateLimiterStatus | RateLimitExceededError | null>;
104
- export declare type RateLimiterCreator = (opts: {
118
+ export type RateLimiterCreator = (opts: {
105
119
  keyPrefix: string;
106
120
  durationMs: number;
107
121
  points: number;
108
- calcKey?: (ctx: XRPCReqContext) => string;
109
- calcPoints?: (ctx: XRPCReqContext) => number;
122
+ calcKey?: CalcKeyFn;
123
+ calcPoints?: CalcPointsFn;
110
124
  }) => RateLimiterI;
111
- export declare type ServerRateLimitDescription = {
125
+ export type ServerRateLimitDescription = {
112
126
  name: string;
113
127
  durationMs: number;
114
128
  points: number;
115
- calcKey?: (ctx: XRPCReqContext) => string;
116
- calcPoints?: (ctx: XRPCReqContext) => number;
129
+ calcKey?: CalcKeyFn;
130
+ calcPoints?: CalcPointsFn;
117
131
  };
118
- export declare type SharedRateLimitOpts = {
132
+ export type SharedRateLimitOpts = {
119
133
  name: string;
120
- calcKey?: (ctx: XRPCReqContext) => string;
121
- calcPoints?: (ctx: XRPCReqContext) => number;
134
+ calcKey?: CalcKeyFn;
135
+ calcPoints?: CalcPointsFn;
122
136
  };
123
- export declare type RouteRateLimitOpts = {
137
+ export type RouteRateLimitOpts = {
124
138
  durationMs: number;
125
139
  points: number;
126
- calcKey?: (ctx: XRPCReqContext) => string;
127
- calcPoints?: (ctx: XRPCReqContext) => number;
140
+ calcKey?: CalcKeyFn;
141
+ calcPoints?: CalcPointsFn;
128
142
  };
129
- export declare type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts;
143
+ export type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts;
130
144
  export declare const isShared: (opts: HandlerRateLimitOpts) => opts is SharedRateLimitOpts;
131
- export declare type RateLimiterStatus = {
145
+ export type RateLimiterStatus = {
132
146
  limit: number;
133
147
  duration: number;
134
148
  remainingPoints: number;
@@ -136,12 +150,16 @@ export declare type RateLimiterStatus = {
136
150
  consumedPoints: number;
137
151
  isFirstInDuration: boolean;
138
152
  };
139
- export declare type XRPCHandlerConfig = {
153
+ export type RouteOpts = {
154
+ blobLimit?: number;
155
+ };
156
+ export type XRPCHandlerConfig = {
157
+ opts?: RouteOpts;
140
158
  rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[];
141
159
  auth?: AuthVerifier;
142
160
  handler: XRPCHandler;
143
161
  };
144
- export declare type XRPCStreamHandlerConfig = {
162
+ export type XRPCStreamHandlerConfig = {
145
163
  auth?: StreamAuthVerifier;
146
164
  handler: XRPCStreamHandler;
147
165
  };
package/dist/util.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import express from 'express';
2
2
  import { Lexicons, LexXrpcProcedure, LexXrpcQuery, LexXrpcSubscription } from '@atproto/lexicon';
3
- import { UndecodedParams, Params, HandlerInput, HandlerSuccess, Options } from './types';
3
+ import { UndecodedParams, Params, HandlerInput, HandlerSuccess, RouteOpts } from './types';
4
4
  export declare function decodeQueryParams(def: LexXrpcProcedure | LexXrpcQuery | LexXrpcSubscription, params: UndecodedParams): Params;
5
5
  export declare function decodeQueryParam(type: string, value: unknown): string | number | boolean | undefined;
6
6
  export declare function getQueryParams(url?: string): Record<string, string | string[]>;
7
- export declare function validateInput(nsid: string, def: LexXrpcProcedure | LexXrpcQuery, req: express.Request, opts: Options, lexicons: Lexicons): HandlerInput | undefined;
7
+ export declare function validateInput(nsid: string, def: LexXrpcProcedure | LexXrpcQuery, req: express.Request, opts: RouteOpts, lexicons: Lexicons): HandlerInput | undefined;
8
8
  export declare function validateOutput(nsid: string, def: LexXrpcProcedure | LexXrpcQuery, output: HandlerSuccess | undefined, lexicons: Lexicons): HandlerSuccess | undefined;
9
9
  export declare function normalizeMime(v: string): any;
10
10
  export declare function hasBody(req: express.Request): string | true | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/xrpc-server",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "license": "MIT",
5
5
  "description": "atproto HTTP API (XRPC) server library",
6
6
  "keywords": [
@@ -25,7 +25,7 @@
25
25
  "zod": "^3.21.4",
26
26
  "@atproto/common": "^0.3.3",
27
27
  "@atproto/crypto": "^0.3.0",
28
- "@atproto/lexicon": "^0.3.1"
28
+ "@atproto/lexicon": "^0.3.2"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/express": "^4.17.13",
@@ -36,8 +36,8 @@
36
36
  "jose": "^4.15.4",
37
37
  "key-encoder": "^2.0.3",
38
38
  "multiformats": "^9.9.0",
39
- "@atproto/crypto": "^0.3.0",
40
- "@atproto/xrpc": "^0.4.1"
39
+ "@atproto/xrpc": "^0.4.2",
40
+ "@atproto/crypto": "^0.3.0"
41
41
  },
42
42
  "scripts": {
43
43
  "test": "jest",
@@ -75,6 +75,9 @@ export class RateLimiter implements RateLimiterI {
75
75
  return null
76
76
  }
77
77
  const key = opts?.calcKey ? opts.calcKey(ctx) : this.calcKey(ctx)
78
+ if (key === null) {
79
+ return null
80
+ }
78
81
  const points = opts?.calcPoints
79
82
  ? opts.calcPoints(ctx)
80
83
  : this.calcPoints(ctx)
package/src/server.ts CHANGED
@@ -36,6 +36,8 @@ import {
36
36
  RateLimiterConsume,
37
37
  isShared,
38
38
  RateLimitExceededError,
39
+ HandlerPipeThrough,
40
+ handlerPipeThrough,
39
41
  } from './types'
40
42
  import {
41
43
  decodeQueryParams,
@@ -173,7 +175,7 @@ export class Server {
173
175
  this.routes[verb](
174
176
  `/xrpc/${nsid}`,
175
177
  ...middleware,
176
- this.createHandler(nsid, def, config.handler),
178
+ this.createHandler(nsid, def, config),
177
179
  )
178
180
  }
179
181
 
@@ -206,10 +208,13 @@ export class Server {
206
208
  createHandler(
207
209
  nsid: string,
208
210
  def: LexXrpcQuery | LexXrpcProcedure,
209
- handler: XRPCHandler,
211
+ routeCfg: XRPCHandlerConfig,
210
212
  ): RequestHandler {
213
+ const routeOpts = {
214
+ blobLimit: routeCfg.opts?.blobLimit ?? this.options.payload?.blobLimit,
215
+ }
211
216
  const validateReqInput = (req: express.Request) =>
212
- validateInput(nsid, def, req, this.options, this.lex)
217
+ validateInput(nsid, def, req, routeOpts, this.lex)
213
218
  const validateResOutput =
214
219
  this.options.validateResponse === false
215
220
  ? (output?: HandlerSuccess) => output
@@ -254,12 +259,26 @@ export class Server {
254
259
  }
255
260
 
256
261
  // run the handler
257
- const outputUnvalidated = await handler(reqCtx)
262
+ const outputUnvalidated = await routeCfg.handler(reqCtx)
258
263
 
259
264
  if (isHandlerError(outputUnvalidated)) {
260
265
  throw XRPCError.fromError(outputUnvalidated)
261
266
  }
262
267
 
268
+ if (outputUnvalidated && isHandlerPipeThrough(outputUnvalidated)) {
269
+ // set headers
270
+ if (outputUnvalidated?.headers) {
271
+ Object.entries(outputUnvalidated.headers).forEach(([name, val]) => {
272
+ res.header(name, val)
273
+ })
274
+ }
275
+ res
276
+ .header('Content-Type', outputUnvalidated.encoding)
277
+ .status(200)
278
+ .send(Buffer.from(outputUnvalidated.buffer))
279
+ return
280
+ }
281
+
263
282
  if (!outputUnvalidated || isHandlerSuccess(outputUnvalidated)) {
264
283
  // validate response
265
284
  const output = validateResOutput(outputUnvalidated)
@@ -445,6 +464,26 @@ function isHandlerSuccess(v: HandlerOutput): v is HandlerSuccess {
445
464
  return handlerSuccess.safeParse(v).success
446
465
  }
447
466
 
467
+ function isHandlerPipeThrough(v: HandlerOutput): v is HandlerPipeThrough {
468
+ if (v === null || typeof v !== 'object') {
469
+ return false
470
+ }
471
+ if (!isString(v['encoding']) || !(v['buffer'] instanceof ArrayBuffer)) {
472
+ return false
473
+ }
474
+ if (v['headers'] !== undefined) {
475
+ if (v['headers'] === null || typeof v['headers'] !== 'object') {
476
+ return false
477
+ }
478
+ if (!Object.values(v['headers']).every(isString)) {
479
+ return false
480
+ }
481
+ }
482
+ return true
483
+ }
484
+
485
+ const isString = (val: unknown): val is string => typeof val === 'string'
486
+
448
487
  const kRequestLocals = Symbol('requestLocals')
449
488
 
450
489
  function createLocalsMiddleware(nsid: string): RequestHandler {
package/src/types.ts CHANGED
@@ -46,6 +46,13 @@ export const handlerSuccess = zod.object({
46
46
  })
47
47
  export type HandlerSuccess = zod.infer<typeof handlerSuccess>
48
48
 
49
+ export const handlerPipeThrough = zod.object({
50
+ encoding: zod.string(),
51
+ buffer: zod.instanceof(ArrayBuffer),
52
+ headers: zod.record(zod.string()).optional(),
53
+ })
54
+ export type HandlerPipeThrough = zod.infer<typeof handlerPipeThrough>
55
+
49
56
  export const handlerError = zod.object({
50
57
  status: zod.number(),
51
58
  error: zod.string().optional(),
@@ -53,7 +60,7 @@ export const handlerError = zod.object({
53
60
  })
54
61
  export type HandlerError = zod.infer<typeof handlerError>
55
62
 
56
- export type HandlerOutput = HandlerSuccess | HandlerError
63
+ export type HandlerOutput = HandlerSuccess | HandlerPipeThrough | HandlerError
57
64
 
58
65
  export type XRPCReqContext = {
59
66
  auth: HandlerAuth | undefined
@@ -85,7 +92,7 @@ export type StreamAuthVerifier = (ctx: {
85
92
  req: IncomingMessage
86
93
  }) => Promise<AuthOutput> | AuthOutput
87
94
 
88
- export type CalcKeyFn = (ctx: XRPCReqContext) => string
95
+ export type CalcKeyFn = (ctx: XRPCReqContext) => string | null
89
96
  export type CalcPointsFn = (ctx: XRPCReqContext) => number
90
97
 
91
98
  export interface RateLimiterI {
@@ -101,29 +108,29 @@ export type RateLimiterCreator = (opts: {
101
108
  keyPrefix: string
102
109
  durationMs: number
103
110
  points: number
104
- calcKey?: (ctx: XRPCReqContext) => string
105
- calcPoints?: (ctx: XRPCReqContext) => number
111
+ calcKey?: CalcKeyFn
112
+ calcPoints?: CalcPointsFn
106
113
  }) => RateLimiterI
107
114
 
108
115
  export type ServerRateLimitDescription = {
109
116
  name: string
110
117
  durationMs: number
111
118
  points: number
112
- calcKey?: (ctx: XRPCReqContext) => string
113
- calcPoints?: (ctx: XRPCReqContext) => number
119
+ calcKey?: CalcKeyFn
120
+ calcPoints?: CalcPointsFn
114
121
  }
115
122
 
116
123
  export type SharedRateLimitOpts = {
117
124
  name: string
118
- calcKey?: (ctx: XRPCReqContext) => string
119
- calcPoints?: (ctx: XRPCReqContext) => number
125
+ calcKey?: CalcKeyFn
126
+ calcPoints?: CalcPointsFn
120
127
  }
121
128
 
122
129
  export type RouteRateLimitOpts = {
123
130
  durationMs: number
124
131
  points: number
125
- calcKey?: (ctx: XRPCReqContext) => string
126
- calcPoints?: (ctx: XRPCReqContext) => number
132
+ calcKey?: CalcKeyFn
133
+ calcPoints?: CalcPointsFn
127
134
  }
128
135
 
129
136
  export type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts
@@ -143,7 +150,12 @@ export type RateLimiterStatus = {
143
150
  isFirstInDuration: boolean
144
151
  }
145
152
 
153
+ export type RouteOpts = {
154
+ blobLimit?: number
155
+ }
156
+
146
157
  export type XRPCHandlerConfig = {
158
+ opts?: RouteOpts
147
159
  rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[]
148
160
  auth?: AuthVerifier
149
161
  handler: XRPCHandler
package/src/util.ts CHANGED
@@ -19,8 +19,8 @@ import {
19
19
  handlerSuccess,
20
20
  InvalidRequestError,
21
21
  InternalServerError,
22
- Options,
23
22
  XRPCError,
23
+ RouteOpts,
24
24
  } from './types'
25
25
 
26
26
  export function decodeQueryParams(
@@ -82,7 +82,7 @@ export function validateInput(
82
82
  nsid: string,
83
83
  def: LexXrpcProcedure | LexXrpcQuery,
84
84
  req: express.Request,
85
- opts: Options,
85
+ opts: RouteOpts,
86
86
  lexicons: Lexicons,
87
87
  ): HandlerInput | undefined {
88
88
  // request expectation
@@ -139,7 +139,7 @@ export function validateInput(
139
139
  if (req.readableEnded) {
140
140
  body = req.body
141
141
  } else {
142
- body = decodeBodyStream(req, opts.payload?.blobLimit)
142
+ body = decodeBodyStream(req, opts.blobLimit)
143
143
  }
144
144
 
145
145
  return {