@atcute/xrpc-server 0.1.12 → 1.0.0
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/README.md +81 -20
- package/dist/auth/jwt-creator.d.ts +4 -2
- package/dist/auth/jwt-creator.d.ts.map +1 -1
- package/dist/auth/jwt-creator.js +1 -1
- package/dist/auth/jwt-creator.js.map +1 -1
- package/dist/auth/jwt-verifier.d.ts +69 -8
- package/dist/auth/jwt-verifier.d.ts.map +1 -1
- package/dist/auth/jwt-verifier.js +130 -19
- package/dist/auth/jwt-verifier.js.map +1 -1
- package/dist/auth/jwt.d.ts +7 -2
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js +14 -7
- package/dist/auth/jwt.js.map +1 -1
- package/dist/main/router.d.ts +32 -4
- package/dist/main/router.d.ts.map +1 -1
- package/dist/main/router.js +63 -10
- package/dist/main/router.js.map +1 -1
- package/dist/main/types/operation.d.ts +8 -0
- package/dist/main/types/operation.d.ts.map +1 -1
- package/dist/main/types/websocket.d.ts +8 -0
- package/dist/main/types/websocket.d.ts.map +1 -1
- package/dist/main/utils/websocket-mock.d.ts.map +1 -1
- package/dist/main/utils/websocket-mock.js +3 -0
- package/dist/main/utils/websocket-mock.js.map +1 -1
- package/dist/main/xrpc-error.d.ts +55 -15
- package/dist/main/xrpc-error.d.ts.map +1 -1
- package/dist/main/xrpc-error.js +66 -26
- package/dist/main/xrpc-error.js.map +1 -1
- package/lib/auth/jwt-creator.ts +5 -3
- package/lib/auth/jwt-verifier.ts +205 -25
- package/lib/auth/jwt.ts +21 -10
- package/lib/main/router.ts +95 -14
- package/lib/main/types/operation.ts +8 -0
- package/lib/main/types/websocket.ts +8 -0
- package/lib/main/utils/websocket-mock.ts +3 -0
- package/lib/main/xrpc-error.ts +107 -44
- package/package.json +19 -15
package/lib/main/router.ts
CHANGED
|
@@ -38,8 +38,13 @@ type InternalRouteData = {
|
|
|
38
38
|
export type FetchMiddleware = Middleware<[request: Request], Promise<Response>>;
|
|
39
39
|
|
|
40
40
|
export type NotFoundHandler = (request: Request) => Promisable<Response>;
|
|
41
|
+
export type HealthCheckHandler = (request: Request) => Promisable<Response>;
|
|
41
42
|
export type ExceptionHandler = (error: unknown, request: Request) => Promisable<Response>;
|
|
42
|
-
|
|
43
|
+
|
|
44
|
+
/** telemetry hook invoked for unexpected HTTP handler errors; fire-and-forget. */
|
|
45
|
+
export type ErrorObserver = (ctx: { error: unknown; request: Request }) => void;
|
|
46
|
+
/** telemetry hook invoked for unexpected subscription errors; fire-and-forget. */
|
|
47
|
+
export type SocketErrorObserver = (ctx: { error: unknown; request: Request }) => void;
|
|
43
48
|
|
|
44
49
|
export const defaultExceptionHandler: ExceptionHandler = (error: unknown) => {
|
|
45
50
|
if (error instanceof XRPCError) {
|
|
@@ -60,23 +65,40 @@ export const defaultNotFoundHandler: NotFoundHandler = () => {
|
|
|
60
65
|
return new Response('Not Found', { status: 404 });
|
|
61
66
|
};
|
|
62
67
|
|
|
63
|
-
export const defaultSubscriptionExceptionHandler: SubscriptionExceptionHandler = (error: unknown) => {
|
|
64
|
-
throw error;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
68
|
export interface XRPCRouterOptions {
|
|
68
69
|
middlewares?: FetchMiddleware[];
|
|
69
70
|
handleNotFound?: NotFoundHandler;
|
|
71
|
+
/**
|
|
72
|
+
* optional handler for `/xrpc/_health`. when provided, the router answers
|
|
73
|
+
* health-check requests by invoking this handler; when absent, the path
|
|
74
|
+
* falls through to `handleNotFound`. `_health` is not part of the atproto
|
|
75
|
+
* XRPC spec, so callers opt in explicitly.
|
|
76
|
+
*/
|
|
77
|
+
handleHealthCheck?: HealthCheckHandler;
|
|
78
|
+
/** translates a thrown error into an HTTP response. */
|
|
70
79
|
handleException?: ExceptionHandler;
|
|
71
|
-
|
|
80
|
+
/**
|
|
81
|
+
* fire-and-forget telemetry hook for unexpected HTTP errors. not invoked for
|
|
82
|
+
* client-induced errors (aborted requests, `XRPCError` subclasses, thrown
|
|
83
|
+
* `Response` objects).
|
|
84
|
+
*/
|
|
85
|
+
onError?: ErrorObserver;
|
|
86
|
+
/**
|
|
87
|
+
* fire-and-forget telemetry hook for unexpected subscription errors. not
|
|
88
|
+
* invoked for aborted signals or `XRPCSubscriptionError` (which is
|
|
89
|
+
* translated to an error frame).
|
|
90
|
+
*/
|
|
91
|
+
onSocketError?: SocketErrorObserver;
|
|
72
92
|
websocket?: WebSocketAdapter;
|
|
73
93
|
}
|
|
74
94
|
|
|
75
95
|
export class XRPCRouter {
|
|
76
96
|
#handlers: Record<string, InternalRouteData> = {};
|
|
77
97
|
#handleNotFound: NotFoundHandler;
|
|
98
|
+
#handleHealthCheck?: HealthCheckHandler;
|
|
78
99
|
#handleException: ExceptionHandler;
|
|
79
|
-
#
|
|
100
|
+
#onError?: ErrorObserver;
|
|
101
|
+
#onSocketError?: SocketErrorObserver;
|
|
80
102
|
#websocket?: WebSocketAdapter;
|
|
81
103
|
|
|
82
104
|
fetch: (request: Request) => Promise<Response>;
|
|
@@ -85,7 +107,9 @@ export class XRPCRouter {
|
|
|
85
107
|
middlewares = [],
|
|
86
108
|
handleException = defaultExceptionHandler,
|
|
87
109
|
handleNotFound = defaultNotFoundHandler,
|
|
88
|
-
|
|
110
|
+
handleHealthCheck,
|
|
111
|
+
onError,
|
|
112
|
+
onSocketError,
|
|
89
113
|
websocket,
|
|
90
114
|
}: XRPCRouterOptions = {}) {
|
|
91
115
|
const runner = createAsyncMiddlewareRunner([...middlewares, (request) => this.#dispatch(request)]);
|
|
@@ -93,10 +117,46 @@ export class XRPCRouter {
|
|
|
93
117
|
this.fetch = (request) => runner(request);
|
|
94
118
|
this.#handleException = handleException;
|
|
95
119
|
this.#handleNotFound = handleNotFound;
|
|
96
|
-
this.#
|
|
120
|
+
this.#handleHealthCheck = handleHealthCheck;
|
|
121
|
+
this.#onError = onError;
|
|
122
|
+
this.#onSocketError = onSocketError;
|
|
97
123
|
this.#websocket = websocket;
|
|
98
124
|
}
|
|
99
125
|
|
|
126
|
+
#observeError(error: unknown, request: Request): void {
|
|
127
|
+
// client-induced errors are not bugs; skip telemetry
|
|
128
|
+
if (request.signal.aborted) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (error instanceof XRPCError) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (error instanceof Response) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
this.#onError?.({ error, request });
|
|
140
|
+
} catch {
|
|
141
|
+
// observer threw; swallow to keep response path deterministic
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#observeSocketError(error: unknown, request: Request): void {
|
|
146
|
+
if (request.signal.aborted) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (error instanceof XRPCSubscriptionError) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
this.#onSocketError?.({ error, request });
|
|
155
|
+
} catch {
|
|
156
|
+
// observer threw; swallow to keep socket close path deterministic
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
100
160
|
async #dispatch(request: Request): Promise<Response> {
|
|
101
161
|
const url = new URL(request.url);
|
|
102
162
|
const pathname = url.pathname;
|
|
@@ -107,15 +167,31 @@ export class XRPCRouter {
|
|
|
107
167
|
|
|
108
168
|
const nsid = pathname.slice('/xrpc/'.length);
|
|
109
169
|
|
|
170
|
+
if (nsid === '_health' && this.#handleHealthCheck !== undefined) {
|
|
171
|
+
try {
|
|
172
|
+
return await this.#handleHealthCheck(request);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (request.signal.aborted) {
|
|
175
|
+
return new Response(null, { status: 499 });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.#observeError(err, request);
|
|
179
|
+
return this.#handleException(err, request);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
110
183
|
const route = this.#handlers[nsid];
|
|
111
184
|
if (route === undefined) {
|
|
112
185
|
return this.#handleNotFound(request);
|
|
113
186
|
}
|
|
114
187
|
|
|
115
|
-
|
|
188
|
+
// allow HEAD alongside GET; the runtime is responsible for stripping the
|
|
189
|
+
// response body per the Fetch API.
|
|
190
|
+
const allowed = request.method === route.method || (route.method === 'GET' && request.method === 'HEAD');
|
|
191
|
+
if (!allowed) {
|
|
116
192
|
return Response.json(
|
|
117
|
-
{ error: '
|
|
118
|
-
{ status: 405, headers: { allow:
|
|
193
|
+
{ error: 'InvalidRequest', message: `invalid http method (expected ${route.method})` },
|
|
194
|
+
{ status: 405, headers: { allow: route.method === 'GET' ? 'GET, HEAD' : route.method } },
|
|
119
195
|
);
|
|
120
196
|
}
|
|
121
197
|
|
|
@@ -131,6 +207,7 @@ export class XRPCRouter {
|
|
|
131
207
|
return new Response(null, { status: 499 });
|
|
132
208
|
}
|
|
133
209
|
|
|
210
|
+
this.#observeError(err, request);
|
|
134
211
|
return this.#handleException(err, request);
|
|
135
212
|
}
|
|
136
213
|
}
|
|
@@ -350,12 +427,16 @@ export class XRPCRouter {
|
|
|
350
427
|
|
|
351
428
|
const frame = encodeMessageFrame(body, type);
|
|
352
429
|
await ws.send(frame);
|
|
430
|
+
const drained = ws.drain();
|
|
431
|
+
if (drained) {
|
|
432
|
+
await drained;
|
|
433
|
+
}
|
|
353
434
|
}
|
|
354
435
|
|
|
355
436
|
ws.close(1000);
|
|
356
437
|
} catch (err) {
|
|
357
438
|
if (err instanceof XRPCSubscriptionError) {
|
|
358
|
-
const frame = encodeErrorFrame(err.error, err.
|
|
439
|
+
const frame = encodeErrorFrame(err.error, err.message || undefined);
|
|
359
440
|
|
|
360
441
|
try {
|
|
361
442
|
await ws.send(frame);
|
|
@@ -368,7 +449,7 @@ export class XRPCRouter {
|
|
|
368
449
|
}
|
|
369
450
|
|
|
370
451
|
ws.close(1011, `internal server error`);
|
|
371
|
-
this.#
|
|
452
|
+
this.#observeSocketError(err, request);
|
|
372
453
|
}
|
|
373
454
|
});
|
|
374
455
|
|
|
@@ -12,6 +12,14 @@ import type {
|
|
|
12
12
|
import type { Literal, Promisable } from '../../types/misc.ts';
|
|
13
13
|
import type { JSONResponse } from '../response.ts';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* untyped variant of {@link QueryContext} / {@link ProcedureContext}.
|
|
17
|
+
*
|
|
18
|
+
* `input` is set only when the lexicon declares a `lex` input body and the
|
|
19
|
+
* request JSON parsed successfully. for blob inputs (and for procedures that
|
|
20
|
+
* declare no input at all) it is `undefined`; handlers that expect a blob
|
|
21
|
+
* should stream from `request.body` directly.
|
|
22
|
+
*/
|
|
15
23
|
export type UnknownOperationContext = {
|
|
16
24
|
request: Request;
|
|
17
25
|
signal: AbortSignal;
|
|
@@ -3,6 +3,14 @@ import type { Promisable } from '../../types/misc.ts';
|
|
|
3
3
|
export interface WebSocketConnection {
|
|
4
4
|
signal: AbortSignal;
|
|
5
5
|
send(data: Uint8Array): void | Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* backpressure hook invoked by the router after every frame it sends.
|
|
8
|
+
* adapters that can observe the outgoing send buffer (Node `ws`, Bun, Deno)
|
|
9
|
+
* should resolve only once the buffer has drained below a healthy threshold.
|
|
10
|
+
* adapters without that visibility (e.g. Cloudflare Workers) should return
|
|
11
|
+
* synchronously.
|
|
12
|
+
*/
|
|
13
|
+
drain(): void | Promise<void>;
|
|
6
14
|
close(code?: number, reason?: string): void;
|
|
7
15
|
}
|
|
8
16
|
|
|
@@ -79,6 +79,9 @@ export class MockWebSocketAdapter implements WebSocketAdapter {
|
|
|
79
79
|
send(data) {
|
|
80
80
|
onMessage.emit(data);
|
|
81
81
|
},
|
|
82
|
+
drain() {
|
|
83
|
+
// tests have no outgoing buffer to observe
|
|
84
|
+
},
|
|
82
85
|
close(code = 1000, reason = '') {
|
|
83
86
|
if (!signal.aborted) {
|
|
84
87
|
onClose.emit({ code, reason, wasClean: true });
|
package/lib/main/xrpc-error.ts
CHANGED
|
@@ -1,7 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* a single WWW-Authenticate challenge. exactly one of `params` or `token68` may
|
|
3
|
+
* be provided. a bare scheme (no params, no token) is valid and renders as just
|
|
4
|
+
* the scheme name.
|
|
5
|
+
*
|
|
6
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7235#section-4.1 | RFC 7235 §4.1}
|
|
7
|
+
*/
|
|
8
|
+
export interface WWWAuthenticateChallenge {
|
|
9
|
+
/** authentication scheme, e.g. `Bearer`, `DPoP`, `Basic`. */
|
|
10
|
+
scheme: string;
|
|
11
|
+
/** auth-param pairs. entries whose value is `undefined` are omitted. */
|
|
12
|
+
params?: Record<string, string | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* token68 value for schemes that carry one instead of auth-params (e.g.
|
|
15
|
+
* `Basic`). mutually exclusive with `params`.
|
|
16
|
+
*/
|
|
17
|
+
token68?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* formats one or more WWW-Authenticate challenges into a single header value.
|
|
22
|
+
*
|
|
23
|
+
* each challenge is emitted as `<scheme>` followed by its params or token68.
|
|
24
|
+
* multiple challenges are joined with `, `. auth-param values are quoted using
|
|
25
|
+
* `JSON.stringify` (RFC 7230 quoted-string semantics for ASCII content).
|
|
26
|
+
*
|
|
27
|
+
* @param challenges one challenge, or an ordered array of challenges
|
|
28
|
+
* @returns the formatted header value
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* formatWWWAuthenticate({ scheme: 'Bearer', params: { error: 'BadJwtSignature' } })
|
|
33
|
+
* // => `Bearer error="BadJwtSignature"`
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const formatWWWAuthenticate = (
|
|
37
|
+
challenges: WWWAuthenticateChallenge | WWWAuthenticateChallenge[],
|
|
38
|
+
): string => {
|
|
39
|
+
const list = Array.isArray(challenges) ? challenges : [challenges];
|
|
40
|
+
return list.map(formatChallenge).join(', ');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const formatChallenge = (challenge: WWWAuthenticateChallenge): string => {
|
|
44
|
+
if (challenge.token68 !== undefined) {
|
|
45
|
+
return `${challenge.scheme} ${challenge.token68}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (challenge.params !== undefined) {
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
for (const name in challenge.params) {
|
|
51
|
+
const value = challenge.params[name];
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
parts.push(`${name}=${JSON.stringify(value)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (parts.length > 0) {
|
|
58
|
+
return `${challenge.scheme} ${parts.join(', ')}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return challenge.scheme;
|
|
63
|
+
};
|
|
64
|
+
|
|
1
65
|
export interface XRPCErrorOptions {
|
|
2
66
|
status: number;
|
|
3
67
|
error: string;
|
|
4
|
-
|
|
68
|
+
message?: string;
|
|
5
69
|
headers?: HeadersInit;
|
|
6
70
|
}
|
|
7
71
|
|
|
@@ -11,54 +75,65 @@ export class XRPCError extends Error {
|
|
|
11
75
|
|
|
12
76
|
/** error name */
|
|
13
77
|
readonly error: string;
|
|
14
|
-
/** error message */
|
|
15
|
-
readonly description?: string;
|
|
16
78
|
/** response headers */
|
|
17
79
|
readonly headers?: HeadersInit;
|
|
18
80
|
|
|
19
|
-
constructor({ status, error,
|
|
20
|
-
super(
|
|
81
|
+
constructor({ status, error, message, headers }: XRPCErrorOptions) {
|
|
82
|
+
super(message);
|
|
21
83
|
|
|
22
84
|
this.status = status;
|
|
23
85
|
|
|
24
86
|
this.error = error;
|
|
25
|
-
this.description = description;
|
|
26
87
|
this.headers = headers;
|
|
27
88
|
}
|
|
28
89
|
|
|
29
90
|
toResponse(): Response {
|
|
30
91
|
return Response.json(
|
|
31
|
-
{ error: this.error, message: this.
|
|
92
|
+
{ error: this.error, message: this.message || undefined },
|
|
32
93
|
{ status: this.status, headers: this.headers },
|
|
33
94
|
);
|
|
34
95
|
}
|
|
35
96
|
}
|
|
36
97
|
|
|
37
98
|
export class InvalidRequestError extends XRPCError {
|
|
38
|
-
constructor({
|
|
39
|
-
status
|
|
40
|
-
error = 'InvalidRequest',
|
|
41
|
-
description,
|
|
42
|
-
headers,
|
|
43
|
-
}: Partial<XRPCErrorOptions> = {}) {
|
|
44
|
-
super({ status, error, description, headers });
|
|
99
|
+
constructor({ status = 400, error = 'InvalidRequest', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
100
|
+
super({ status, error, message, headers });
|
|
45
101
|
}
|
|
46
102
|
}
|
|
47
103
|
|
|
104
|
+
export interface AuthRequiredErrorOptions extends Partial<XRPCErrorOptions> {
|
|
105
|
+
/**
|
|
106
|
+
* WWW-Authenticate challenge(s) to attach to the response. the formatted
|
|
107
|
+
* header is set on `headers` automatically, and `access-control-expose-headers`
|
|
108
|
+
* is appended so browsers can read it from CORS responses.
|
|
109
|
+
*/
|
|
110
|
+
wwwAuthenticate?: WWWAuthenticateChallenge | WWWAuthenticateChallenge[];
|
|
111
|
+
}
|
|
112
|
+
|
|
48
113
|
export class AuthRequiredError extends XRPCError {
|
|
49
114
|
constructor({
|
|
50
115
|
status = 401,
|
|
51
116
|
error = 'AuthenticationRequired',
|
|
52
|
-
|
|
117
|
+
message,
|
|
53
118
|
headers,
|
|
54
|
-
|
|
55
|
-
|
|
119
|
+
wwwAuthenticate,
|
|
120
|
+
}: AuthRequiredErrorOptions = {}) {
|
|
121
|
+
let mergedHeaders = headers;
|
|
122
|
+
|
|
123
|
+
if (wwwAuthenticate !== undefined) {
|
|
124
|
+
const target = new Headers(headers);
|
|
125
|
+
target.set('www-authenticate', formatWWWAuthenticate(wwwAuthenticate));
|
|
126
|
+
target.append('access-control-expose-headers', 'www-authenticate');
|
|
127
|
+
mergedHeaders = target;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
super({ status, error, message, headers: mergedHeaders });
|
|
56
131
|
}
|
|
57
132
|
}
|
|
58
133
|
|
|
59
134
|
export class ForbiddenError extends XRPCError {
|
|
60
|
-
constructor({ status = 403, error = 'Forbidden',
|
|
61
|
-
super({ status, error,
|
|
135
|
+
constructor({ status = 403, error = 'Forbidden', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
136
|
+
super({ status, error, message, headers });
|
|
62
137
|
}
|
|
63
138
|
}
|
|
64
139
|
|
|
@@ -66,10 +141,10 @@ export class RateLimitExceededError extends XRPCError {
|
|
|
66
141
|
constructor({
|
|
67
142
|
status = 429,
|
|
68
143
|
error = 'RateLimitExceeded',
|
|
69
|
-
|
|
144
|
+
message,
|
|
70
145
|
headers,
|
|
71
146
|
}: Partial<XRPCErrorOptions> = {}) {
|
|
72
|
-
super({ status, error,
|
|
147
|
+
super({ status, error, message, headers });
|
|
73
148
|
}
|
|
74
149
|
}
|
|
75
150
|
|
|
@@ -77,21 +152,16 @@ export class InternalServerError extends XRPCError {
|
|
|
77
152
|
constructor({
|
|
78
153
|
status = 500,
|
|
79
154
|
error = 'InternalServerError',
|
|
80
|
-
|
|
155
|
+
message,
|
|
81
156
|
headers,
|
|
82
157
|
}: Partial<XRPCErrorOptions> = {}) {
|
|
83
|
-
super({ status, error,
|
|
158
|
+
super({ status, error, message, headers });
|
|
84
159
|
}
|
|
85
160
|
}
|
|
86
161
|
|
|
87
162
|
export class UpstreamFailureError extends XRPCError {
|
|
88
|
-
constructor({
|
|
89
|
-
status
|
|
90
|
-
error = 'UpstreamFailure',
|
|
91
|
-
description,
|
|
92
|
-
headers,
|
|
93
|
-
}: Partial<XRPCErrorOptions> = {}) {
|
|
94
|
-
super({ status, error, description, headers });
|
|
163
|
+
constructor({ status = 502, error = 'UpstreamFailure', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
164
|
+
super({ status, error, message, headers });
|
|
95
165
|
}
|
|
96
166
|
}
|
|
97
167
|
|
|
@@ -99,40 +169,33 @@ export class NotEnoughResourcesError extends XRPCError {
|
|
|
99
169
|
constructor({
|
|
100
170
|
status = 503,
|
|
101
171
|
error = 'NotEnoughResources',
|
|
102
|
-
|
|
172
|
+
message,
|
|
103
173
|
headers,
|
|
104
174
|
}: Partial<XRPCErrorOptions> = {}) {
|
|
105
|
-
super({ status, error,
|
|
175
|
+
super({ status, error, message, headers });
|
|
106
176
|
}
|
|
107
177
|
}
|
|
108
178
|
|
|
109
179
|
export class UpstreamTimeoutError extends XRPCError {
|
|
110
|
-
constructor({
|
|
111
|
-
status
|
|
112
|
-
error = 'UpstreamTimeout',
|
|
113
|
-
description,
|
|
114
|
-
headers,
|
|
115
|
-
}: Partial<XRPCErrorOptions> = {}) {
|
|
116
|
-
super({ status, error, description, headers });
|
|
180
|
+
constructor({ status = 504, error = 'UpstreamTimeout', message, headers }: Partial<XRPCErrorOptions> = {}) {
|
|
181
|
+
super({ status, error, message, headers });
|
|
117
182
|
}
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
export interface XRPCSubscriptionErrorOptions {
|
|
121
186
|
closeCode?: number;
|
|
122
187
|
error: string;
|
|
123
|
-
|
|
188
|
+
message?: string;
|
|
124
189
|
}
|
|
125
190
|
|
|
126
191
|
export class XRPCSubscriptionError extends Error {
|
|
127
192
|
readonly closeCode: number;
|
|
128
193
|
readonly error: string;
|
|
129
|
-
readonly description?: string;
|
|
130
194
|
|
|
131
|
-
constructor({ closeCode = 1008, error,
|
|
132
|
-
super(
|
|
195
|
+
constructor({ closeCode = 1008, error, message }: XRPCSubscriptionErrorOptions) {
|
|
196
|
+
super(message);
|
|
133
197
|
|
|
134
198
|
this.closeCode = closeCode;
|
|
135
199
|
this.error = error;
|
|
136
|
-
this.description = description;
|
|
137
200
|
}
|
|
138
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atcute/xrpc-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "a small web framework for handling XRPC operations",
|
|
5
5
|
"license": "0BSD",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dist/",
|
|
12
12
|
"lib/",
|
|
13
13
|
"!lib/**/*.bench.ts",
|
|
14
|
-
"!lib/**/*.test.ts"
|
|
14
|
+
"!lib/**/*.test.ts",
|
|
15
|
+
"!dist/**/*.{test,bench}.*"
|
|
15
16
|
],
|
|
16
17
|
"type": "module",
|
|
17
18
|
"exports": {
|
|
@@ -24,26 +25,29 @@
|
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@badrap/valita": "^0.4.6",
|
|
27
|
-
"nanoid": "^5.1.
|
|
28
|
+
"nanoid": "^5.1.11",
|
|
29
|
+
"@atcute/cbor": "^2.3.3",
|
|
30
|
+
"@atcute/identity-resolver": "^1.2.3",
|
|
28
31
|
"@atcute/crypto": "^2.4.1",
|
|
29
|
-
"@atcute/
|
|
30
|
-
"@atcute/
|
|
31
|
-
"@atcute/
|
|
32
|
-
"@atcute/uint8array": "^1.1.1"
|
|
33
|
-
"@atcute/identity": "^1.1.4",
|
|
34
|
-
"@atcute/multibase": "^1.2.0"
|
|
32
|
+
"@atcute/lexicons": "^1.3.1",
|
|
33
|
+
"@atcute/identity": "^1.1.5",
|
|
34
|
+
"@atcute/multibase": "^1.2.0",
|
|
35
|
+
"@atcute/uint8array": "^1.1.1"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@atcute/xrpc-server": "file:",
|
|
38
39
|
"@mary-ext/simple-event-emitter": "^1.0.1",
|
|
39
|
-
"@types/node": "^25.
|
|
40
|
-
"@vitest/coverage-v8": "^4.1.
|
|
41
|
-
"vitest": "^4.1.
|
|
42
|
-
"@atcute/
|
|
43
|
-
"@atcute/
|
|
40
|
+
"@types/node": "^25.6.0",
|
|
41
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
42
|
+
"vitest": "^4.1.5",
|
|
43
|
+
"@atcute/atproto": "^3.1.12",
|
|
44
|
+
"@atcute/bluesky": "^3.3.4"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@atcute/lexicons": "^1.0.0"
|
|
44
48
|
},
|
|
45
49
|
"scripts": {
|
|
46
|
-
"build": "tsgo
|
|
50
|
+
"build": "tsgo",
|
|
47
51
|
"test": "vitest --coverage",
|
|
48
52
|
"prepublish": "rm -rf dist; pnpm run build"
|
|
49
53
|
}
|