@atproto/xrpc-server 0.8.0 → 0.9.1

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 (45) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/auth.js +11 -11
  3. package/dist/auth.js.map +1 -1
  4. package/dist/errors.d.ts +67 -0
  5. package/dist/errors.d.ts.map +1 -0
  6. package/dist/errors.js +202 -0
  7. package/dist/errors.js.map +1 -0
  8. package/dist/index.d.ts +4 -3
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +5 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/rate-limiter.d.ts +69 -32
  13. package/dist/rate-limiter.d.ts.map +1 -1
  14. package/dist/rate-limiter.js +58 -41
  15. package/dist/rate-limiter.js.map +1 -1
  16. package/dist/server.d.ts +19 -14
  17. package/dist/server.d.ts.map +1 -1
  18. package/dist/server.js +151 -137
  19. package/dist/server.js.map +1 -1
  20. package/dist/types.d.ts +80 -178
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/types.js +9 -226
  23. package/dist/types.js.map +1 -1
  24. package/dist/util.d.ts +9 -8
  25. package/dist/util.d.ts.map +1 -1
  26. package/dist/util.js +148 -108
  27. package/dist/util.js.map +1 -1
  28. package/package.json +4 -3
  29. package/src/auth.ts +1 -1
  30. package/src/errors.ts +293 -0
  31. package/src/index.ts +9 -3
  32. package/src/rate-limiter.ts +188 -96
  33. package/src/server.ts +198 -154
  34. package/src/types.ts +144 -439
  35. package/src/util.ts +176 -125
  36. package/tests/auth.test.ts +2 -2
  37. package/tests/bodies.test.ts +18 -27
  38. package/tests/errors.test.ts +1 -1
  39. package/tests/ipld.test.ts +15 -14
  40. package/tests/parameters.test.ts +4 -7
  41. package/tests/procedures.test.ts +22 -34
  42. package/tests/queries.test.ts +9 -12
  43. package/tests/rate-limiter.test.ts +7 -7
  44. package/tests/responses.test.ts +12 -15
  45. package/tsconfig.build.tsbuildinfo +1 -1
package/src/types.ts CHANGED
@@ -1,35 +1,27 @@
1
1
  import { IncomingMessage } from 'node:http'
2
2
  import { Readable } from 'node:stream'
3
- import express from 'express'
4
- import { isHttpError } from 'http-errors'
3
+ import { NextFunction, Request, Response } from 'express'
5
4
  import { z } from 'zod'
6
- import {
7
- ResponseType,
8
- ResponseTypeStrings,
9
- XRPCError as XRPCClientError,
10
- httpResponseCodeToName,
11
- httpResponseCodeToString,
12
- } from '@atproto/xrpc'
5
+ import { ErrorResult, XRPCError } from './errors'
6
+ import { CalcKeyFn, CalcPointsFn, RateLimiterI } from './rate-limiter'
7
+
8
+ export type Awaitable<T> = T | Promise<T>
13
9
 
14
10
  export type CatchallHandler = (
15
- req: express.Request,
16
- _res: express.Response,
17
- next: express.NextFunction,
11
+ req: Request,
12
+ res: Response,
13
+ next: NextFunction,
18
14
  ) => unknown
19
15
 
20
16
  export type Options = {
21
17
  validateResponse?: boolean
22
18
  catchall?: CatchallHandler
23
- payload?: {
24
- jsonLimit?: number
25
- blobLimit?: number
26
- textLimit?: number
27
- }
19
+ payload?: RouteOptions
28
20
  rateLimits?: {
29
- creator: RateLimiterCreator
30
- global?: ServerRateLimitDescription[]
31
- shared?: ServerRateLimitDescription[]
32
- bypass?: (ctx: XRPCReqContext) => boolean
21
+ creator: RateLimiterCreator<HandlerContext>
22
+ global?: ServerRateLimitDescription<HandlerContext>[]
23
+ shared?: ServerRateLimitDescription<HandlerContext>[]
24
+ bypass?: (ctx: HandlerContext) => boolean
33
25
  }
34
26
  /**
35
27
  * By default, errors are converted to {@link XRPCError} using
@@ -44,30 +36,31 @@ export type Options = {
44
36
  errorParser?: (err: unknown) => XRPCError
45
37
  }
46
38
 
47
- export type UndecodedParams = (typeof express.request)['query']
39
+ export type UndecodedParams = Request['query']
48
40
 
49
41
  export type Primitive = string | number | boolean
50
- export type Params = Record<string, Primitive | Primitive[] | undefined>
42
+ export type Params = { [P in string]?: undefined | Primitive | Primitive[] }
51
43
 
52
- export const handlerInput = z.object({
53
- encoding: z.string(),
54
- body: z.any(),
55
- })
56
- export type HandlerInput = z.infer<typeof handlerInput>
44
+ export type HandlerInput = {
45
+ encoding: string
46
+ body: unknown
47
+ }
57
48
 
58
- export const handlerAuth = z.object({
59
- credentials: z.any(),
60
- artifacts: z.any(),
61
- })
62
- export type HandlerAuth = z.infer<typeof handlerAuth>
49
+ export type AuthResult = {
50
+ credentials: unknown
51
+ artifacts?: unknown
52
+ }
63
53
 
64
54
  export const headersSchema = z.record(z.string())
65
55
 
56
+ export type Headers = z.infer<typeof headersSchema>
57
+
66
58
  export const handlerSuccess = z.object({
67
59
  encoding: z.string(),
68
60
  body: z.any(),
69
61
  headers: headersSchema.optional(),
70
62
  })
63
+
71
64
  export type HandlerSuccess = z.infer<typeof handlerSuccess>
72
65
 
73
66
  export const handlerPipeThroughBuffer = z.object({
@@ -93,451 +86,163 @@ export const handlerPipeThrough = z.union([
93
86
 
94
87
  export type HandlerPipeThrough = z.infer<typeof handlerPipeThrough>
95
88
 
96
- export const handlerError = z.object({
97
- status: z.number(),
98
- error: z.string().optional(),
99
- message: z.string().optional(),
100
- })
101
- export type HandlerError = z.infer<typeof handlerError>
102
-
103
- export type HandlerOutput = HandlerSuccess | HandlerPipeThrough | HandlerError
104
-
105
- export type XRPCReqContext = {
106
- auth: HandlerAuth | undefined
107
- params: Params
108
- input: HandlerInput | undefined
109
- req: express.Request
110
- res: express.Response
111
- resetRouteRateLimits: () => Promise<void>
112
- }
113
-
114
- export type XRPCHandler = (
115
- ctx: XRPCReqContext,
116
- ) => Promise<HandlerOutput> | HandlerOutput | undefined
117
-
118
- export type XRPCStreamHandler = (ctx: {
119
- auth: HandlerAuth | undefined
120
- params: Params
121
- req: IncomingMessage
122
- signal: AbortSignal
123
- }) => AsyncIterable<unknown>
124
-
125
- export type AuthOutput = HandlerAuth | HandlerError
126
-
127
- export interface AuthVerifierContext {
128
- req: express.Request
129
- res: express.Response
130
- }
131
-
132
- export type AuthVerifier = (
133
- ctx: AuthVerifierContext,
134
- ) => Promise<AuthOutput> | AuthOutput
135
-
136
- export interface StreamAuthVerifierContext {
137
- req: IncomingMessage
138
- }
139
-
140
- export type StreamAuthVerifier = (
141
- ctx: StreamAuthVerifierContext,
142
- ) => Promise<AuthOutput> | AuthOutput
89
+ export type Auth = void | AuthResult
90
+ export type Input = void | HandlerInput
91
+ export type Output = void | HandlerSuccess | ErrorResult
143
92
 
144
- export type CalcKeyFn = (ctx: XRPCReqContext) => string | null
145
- export type CalcPointsFn = (ctx: XRPCReqContext) => number
146
-
147
- export interface RateLimiterI {
148
- consume: RateLimiterConsume
149
- reset: RateLimiterReset
150
- }
93
+ export type AuthVerifier<C, A extends AuthResult = AuthResult> =
94
+ | ((ctx: C) => Awaitable<A | ErrorResult>)
95
+ | ((ctx: C) => Awaitable<A>)
151
96
 
152
- export type RateLimiterConsumeOptions = {
153
- calcKey?: CalcKeyFn
154
- calcPoints?: CalcPointsFn
97
+ export type MethodAuthContext<P extends Params = Params> = {
98
+ params: P
99
+ req: Request
100
+ res: Response
155
101
  }
156
102
 
157
- export type RateLimiterConsume = (
158
- ctx: XRPCReqContext,
159
- opts?: RateLimiterConsumeOptions,
160
- ) => Promise<RateLimiterStatus | RateLimitExceededError | null>
103
+ export type MethodAuthVerifier<
104
+ A extends AuthResult = AuthResult,
105
+ P extends Params = Params,
106
+ > = AuthVerifier<MethodAuthContext<P>, A>
161
107
 
162
- export type RateLimiterResetOptions = {
163
- calcKey?: CalcKeyFn
108
+ export type HandlerContext<
109
+ A extends Auth = Auth,
110
+ P extends Params = Params,
111
+ I extends Input = Input,
112
+ > = MethodAuthContext<P> & {
113
+ auth: A
114
+ input: I
115
+ resetRouteRateLimits: () => Promise<void>
164
116
  }
165
117
 
166
- export type RateLimiterReset = (
167
- ctx: XRPCReqContext,
168
- opts?: RateLimiterResetOptions,
169
- ) => Promise<void>
118
+ export type MethodHandler<
119
+ A extends Auth = Auth,
120
+ P extends Params = Params,
121
+ I extends Input = Input,
122
+ O extends Output = Output,
123
+ > = (ctx: HandlerContext<A, P, I>) => Awaitable<O | HandlerPipeThrough>
170
124
 
171
- export type RateLimiterCreator = (opts: {
125
+ export type RateLimiterCreator<T extends HandlerContext = HandlerContext> = <
126
+ C extends T = T,
127
+ >(opts: {
172
128
  keyPrefix: string
173
129
  durationMs: number
174
130
  points: number
175
- calcKey?: CalcKeyFn
176
- calcPoints?: CalcPointsFn
177
- }) => RateLimiterI
178
-
179
- export type ServerRateLimitDescription = {
131
+ calcKey: CalcKeyFn<C>
132
+ calcPoints: CalcPointsFn<C>
133
+ failClosed?: boolean
134
+ }) => RateLimiterI<C>
135
+
136
+ export type ServerRateLimitDescription<
137
+ C extends HandlerContext = HandlerContext,
138
+ > = {
180
139
  name: string
181
140
  durationMs: number
182
141
  points: number
183
- calcKey?: CalcKeyFn
184
- calcPoints?: CalcPointsFn
142
+ calcKey?: CalcKeyFn<C>
143
+ calcPoints?: CalcPointsFn<C>
144
+ failClosed?: boolean
185
145
  }
186
146
 
187
- export type SharedRateLimitOpts = {
147
+ export type SharedRateLimitOpts<C extends HandlerContext = HandlerContext> = {
188
148
  name: string
189
- calcKey?: CalcKeyFn
190
- calcPoints?: CalcPointsFn
149
+ calcKey?: CalcKeyFn<C>
150
+ calcPoints?: CalcPointsFn<C>
191
151
  }
192
152
 
193
- export type RouteRateLimitOpts = {
153
+ export type RouteRateLimitOpts<C extends HandlerContext = HandlerContext> = {
194
154
  durationMs: number
195
155
  points: number
196
- calcKey?: CalcKeyFn
197
- calcPoints?: CalcPointsFn
156
+ calcKey?: CalcKeyFn<C>
157
+ calcPoints?: CalcPointsFn<C>
198
158
  }
199
159
 
200
- export type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts
160
+ export type RateLimitOpts<C extends HandlerContext = HandlerContext> =
161
+ | SharedRateLimitOpts<C>
162
+ | RouteRateLimitOpts<C>
201
163
 
202
- export const isShared = (
203
- opts: HandlerRateLimitOpts,
204
- ): opts is SharedRateLimitOpts => {
164
+ export function isSharedRateLimitOpts<
165
+ C extends HandlerContext = HandlerContext,
166
+ >(opts: RateLimitOpts<C>): opts is SharedRateLimitOpts<C> {
205
167
  return typeof opts['name'] === 'string'
206
168
  }
207
169
 
208
- export type RateLimiterStatus = {
209
- limit: number
210
- duration: number
211
- remainingPoints: number
212
- msBeforeNext: number
213
- consumedPoints: number
214
- isFirstInDuration: boolean
215
- }
216
-
217
- export type RouteOpts = {
170
+ export type RouteOptions = {
218
171
  blobLimit?: number
172
+ jsonLimit?: number
173
+ textLimit?: number
174
+ }
175
+
176
+ export type MethodConfig<
177
+ A extends Auth = Auth,
178
+ P extends Params = Params,
179
+ I extends Input = Input,
180
+ O extends Output = Output,
181
+ > = {
182
+ handler: MethodHandler<A, P, I, O>
183
+ auth?: MethodAuthVerifier<Extract<A, AuthResult>, P>
184
+ opts?: RouteOptions
185
+ rateLimit?:
186
+ | RateLimitOpts<HandlerContext<A, P, I>>
187
+ | RateLimitOpts<HandlerContext<A, P, I>>[]
188
+ }
189
+
190
+ export type MethodConfigOrHandler<
191
+ A extends Auth = Auth,
192
+ P extends Params = Params,
193
+ I extends Input = Input,
194
+ O extends Output = Output,
195
+ > = MethodHandler<A, P, I, O> | MethodConfig<A, P, I, O>
196
+
197
+ export type StreamAuthContext<P extends Params = Params> = {
198
+ params: P
199
+ req: IncomingMessage
219
200
  }
220
201
 
221
- export type XRPCHandlerConfig = {
222
- opts?: RouteOpts
223
- rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[]
224
- auth?: AuthVerifier
225
- handler: XRPCHandler
226
- }
227
-
228
- export type XRPCStreamHandlerConfig = {
229
- auth?: StreamAuthVerifier
230
- handler: XRPCStreamHandler
231
- }
202
+ export type StreamAuthVerifier<
203
+ A extends AuthResult = AuthResult,
204
+ P extends Params = Params,
205
+ > = AuthVerifier<StreamAuthContext<P>, A>
232
206
 
233
- export { ResponseType }
234
-
235
- /**
236
- * Converts an upstream XRPC {@link ResponseType} into a downstream {@link ResponseType}.
237
- */
238
- function mapFromClientError(error: XRPCClientError): {
239
- error: string
240
- message: string
241
- type: ResponseType
242
- } {
243
- switch (error.status) {
244
- case ResponseType.InvalidResponse:
245
- // Upstream server returned an XRPC response that is not compatible with our internal lexicon definitions for that XRPC method.
246
- // @NOTE This could be reflected as both a 500 ("we" are at fault) and 502 ("they" are at fault). Let's be gents about it.
247
- return {
248
- error: httpResponseCodeToName(ResponseType.InternalServerError),
249
- message: httpResponseCodeToString(ResponseType.InternalServerError),
250
- type: ResponseType.InternalServerError,
251
- }
252
- case ResponseType.Unknown:
253
- // Typically a network error / unknown host
254
- return {
255
- error: httpResponseCodeToName(ResponseType.InternalServerError),
256
- message: httpResponseCodeToString(ResponseType.InternalServerError),
257
- type: ResponseType.InternalServerError,
258
- }
259
- default:
260
- return {
261
- error: error.error,
262
- message: error.message,
263
- type: error.status,
264
- }
265
- }
207
+ export type StreamContext<
208
+ A extends Auth = Auth,
209
+ P extends Params = Params,
210
+ > = StreamAuthContext<P> & {
211
+ auth: A
212
+ signal: AbortSignal
266
213
  }
267
214
 
268
- export class XRPCError extends Error {
269
- constructor(
270
- public type: ResponseType,
271
- public errorMessage?: string,
272
- public customErrorName?: string,
273
- options?: ErrorOptions,
274
- ) {
275
- super(errorMessage, options)
276
- }
277
-
278
- get statusCode(): number {
279
- const { type } = this
280
-
281
- // Fool-proofing. `new XRPCError(123.5 as number, '')` does not generate a TypeScript error.
282
- // Because of this, we can end-up with any numeric value instead of an actual `ResponseType`.
283
- // For legacy reasons, the `type` argument is not checked in the constructor, so we check it here.
284
- if (type < 400 || type >= 600 || !Number.isFinite(type)) {
285
- return 500
286
- }
287
-
288
- return type
289
- }
290
-
291
- get payload() {
292
- return {
293
- error: this.customErrorName ?? this.typeName,
294
- message:
295
- this.type === ResponseType.InternalServerError
296
- ? this.typeStr // Do not respond with error details for 500s
297
- : this.errorMessage || this.typeStr,
298
- }
299
- }
300
-
301
- get typeName(): string | undefined {
302
- return ResponseType[this.type]
303
- }
304
-
305
- get typeStr(): string | undefined {
306
- return ResponseTypeStrings[this.type]
307
- }
215
+ export type StreamHandler<
216
+ A extends Auth = Auth,
217
+ P extends Params = Params,
218
+ O = unknown,
219
+ > = (ctx: StreamContext<A, P>) => AsyncIterable<O>
308
220
 
309
- static fromError(cause: unknown): XRPCError {
310
- if (cause instanceof XRPCError) {
311
- return cause
312
- }
313
-
314
- if (cause instanceof XRPCClientError) {
315
- const { error, message, type } = mapFromClientError(cause)
316
- return new XRPCError(type, message, error, { cause })
317
- }
318
-
319
- if (isHttpError(cause)) {
320
- return new XRPCError(cause.status, cause.message, cause.name, { cause })
321
- }
322
-
323
- if (isHandlerError(cause)) {
324
- return this.fromHandlerError(cause)
325
- }
326
-
327
- if (cause instanceof Error) {
328
- return new InternalServerError(cause.message, undefined, { cause })
329
- }
330
-
331
- return new InternalServerError(
332
- 'Unexpected internal server error',
333
- undefined,
334
- { cause },
335
- )
336
- }
337
-
338
- static fromHandlerError(err: HandlerError): XRPCError {
339
- return new XRPCError(err.status, err.message, err.error, { cause: err })
340
- }
221
+ export type StreamConfig<
222
+ A extends Auth = Auth,
223
+ P extends Params = Params,
224
+ O = unknown,
225
+ > = {
226
+ auth?: StreamAuthVerifier<Extract<A, AuthResult>, P>
227
+ handler: StreamHandler<A, P, O>
341
228
  }
342
229
 
343
- export function isHandlerError(v: unknown): v is HandlerError {
344
- return (
345
- !!v &&
346
- typeof v === 'object' &&
347
- typeof v['status'] === 'number' &&
348
- (v['error'] === undefined || typeof v['error'] === 'string') &&
349
- (v['message'] === undefined || typeof v['message'] === 'string')
350
- )
351
- }
230
+ export type StreamConfigOrHandler<
231
+ A extends Auth = Auth,
232
+ P extends Params = Params,
233
+ O = unknown,
234
+ > = StreamHandler<A, P, O> | StreamConfig<A, P, O>
352
235
 
353
236
  export function isHandlerPipeThroughBuffer(
354
- v: HandlerOutput,
355
- ): v is HandlerPipeThroughBuffer {
356
- // We only need to discriminate between possible HandlerOutput values
357
- return v['buffer'] !== undefined
237
+ output: Output,
238
+ ): output is HandlerPipeThroughBuffer {
239
+ // We only need to discriminate between possible Output values
240
+ return output != null && 'buffer' in output && output['buffer'] !== undefined
358
241
  }
359
242
 
360
243
  export function isHandlerPipeThroughStream(
361
- v: HandlerOutput,
362
- ): v is HandlerPipeThroughStream {
363
- // We only need to discriminate between possible HandlerOutput values
364
- return v['stream'] !== undefined
365
- }
366
-
367
- export class InvalidRequestError extends XRPCError {
368
- constructor(
369
- errorMessage?: string,
370
- customErrorName?: string,
371
- options?: ErrorOptions,
372
- ) {
373
- super(ResponseType.InvalidRequest, errorMessage, customErrorName, options)
374
- }
375
-
376
- [Symbol.hasInstance](instance: unknown): boolean {
377
- return (
378
- instance instanceof XRPCError &&
379
- instance.type === ResponseType.InvalidRequest
380
- )
381
- }
382
- }
383
-
384
- export class AuthRequiredError extends XRPCError {
385
- constructor(
386
- errorMessage?: string,
387
- customErrorName?: string,
388
- options?: ErrorOptions,
389
- ) {
390
- super(
391
- ResponseType.AuthenticationRequired,
392
- errorMessage,
393
- customErrorName,
394
- options,
395
- )
396
- }
397
-
398
- [Symbol.hasInstance](instance: unknown): boolean {
399
- return (
400
- instance instanceof XRPCError &&
401
- instance.type === ResponseType.AuthenticationRequired
402
- )
403
- }
404
- }
405
-
406
- export class ForbiddenError extends XRPCError {
407
- constructor(
408
- errorMessage?: string,
409
- customErrorName?: string,
410
- options?: ErrorOptions,
411
- ) {
412
- super(ResponseType.Forbidden, errorMessage, customErrorName, options)
413
- }
414
-
415
- [Symbol.hasInstance](instance: unknown): boolean {
416
- return (
417
- instance instanceof XRPCError && instance.type === ResponseType.Forbidden
418
- )
419
- }
420
- }
421
-
422
- export class RateLimitExceededError extends XRPCError {
423
- constructor(
424
- public status: RateLimiterStatus,
425
- errorMessage?: string,
426
- customErrorName?: string,
427
- options?: ErrorOptions,
428
- ) {
429
- super(
430
- ResponseType.RateLimitExceeded,
431
- errorMessage,
432
- customErrorName,
433
- options,
434
- )
435
- }
436
-
437
- [Symbol.hasInstance](instance: unknown): boolean {
438
- return (
439
- instance instanceof XRPCError &&
440
- instance.type === ResponseType.RateLimitExceeded
441
- )
442
- }
443
- }
444
-
445
- export class InternalServerError extends XRPCError {
446
- constructor(
447
- errorMessage?: string,
448
- customErrorName?: string,
449
- options?: ErrorOptions,
450
- ) {
451
- super(
452
- ResponseType.InternalServerError,
453
- errorMessage,
454
- customErrorName,
455
- options,
456
- )
457
- }
458
-
459
- [Symbol.hasInstance](instance: unknown): boolean {
460
- return (
461
- instance instanceof XRPCError &&
462
- instance.type === ResponseType.InternalServerError
463
- )
464
- }
465
- }
466
-
467
- export class UpstreamFailureError extends XRPCError {
468
- constructor(
469
- errorMessage?: string,
470
- customErrorName?: string,
471
- options?: ErrorOptions,
472
- ) {
473
- super(ResponseType.UpstreamFailure, errorMessage, customErrorName, options)
474
- }
475
-
476
- [Symbol.hasInstance](instance: unknown): boolean {
477
- return (
478
- instance instanceof XRPCError &&
479
- instance.type === ResponseType.UpstreamFailure
480
- )
481
- }
482
- }
483
-
484
- export class NotEnoughResourcesError extends XRPCError {
485
- constructor(
486
- errorMessage?: string,
487
- customErrorName?: string,
488
- options?: ErrorOptions,
489
- ) {
490
- super(
491
- ResponseType.NotEnoughResources,
492
- errorMessage,
493
- customErrorName,
494
- options,
495
- )
496
- }
497
-
498
- [Symbol.hasInstance](instance: unknown): boolean {
499
- return (
500
- instance instanceof XRPCError &&
501
- instance.type === ResponseType.NotEnoughResources
502
- )
503
- }
504
- }
505
-
506
- export class UpstreamTimeoutError extends XRPCError {
507
- constructor(
508
- errorMessage?: string,
509
- customErrorName?: string,
510
- options?: ErrorOptions,
511
- ) {
512
- super(ResponseType.UpstreamTimeout, errorMessage, customErrorName, options)
513
- }
514
-
515
- [Symbol.hasInstance](instance: unknown): boolean {
516
- return (
517
- instance instanceof XRPCError &&
518
- instance.type === ResponseType.UpstreamTimeout
519
- )
520
- }
521
- }
522
-
523
- export class MethodNotImplementedError extends XRPCError {
524
- constructor(
525
- errorMessage?: string,
526
- customErrorName?: string,
527
- options?: ErrorOptions,
528
- ) {
529
- super(
530
- ResponseType.MethodNotImplemented,
531
- errorMessage,
532
- customErrorName,
533
- options,
534
- )
535
- }
536
-
537
- [Symbol.hasInstance](instance: unknown): boolean {
538
- return (
539
- instance instanceof XRPCError &&
540
- instance.type === ResponseType.MethodNotImplemented
541
- )
542
- }
244
+ output: Output,
245
+ ): output is HandlerPipeThroughStream {
246
+ // We only need to discriminate between possible Output values
247
+ return output != null && 'stream' in output && output['stream'] !== undefined
543
248
  }