@atcute/xrpc-server 1.0.0 → 2.0.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 (106) hide show
  1. package/dist/auth/index.d.ts +0 -1
  2. package/dist/auth/index.js +0 -1
  3. package/dist/auth/jwt-creator.d.ts +0 -1
  4. package/dist/auth/jwt-creator.js +0 -1
  5. package/dist/auth/jwt-verifier.d.ts +24 -33
  6. package/dist/auth/jwt-verifier.js +8 -10
  7. package/dist/auth/jwt.d.ts +0 -1
  8. package/dist/auth/jwt.js +22 -26
  9. package/dist/auth/types.d.ts +0 -1
  10. package/dist/auth/types.js +0 -1
  11. package/dist/main/index.d.ts +0 -1
  12. package/dist/main/index.js +0 -1
  13. package/dist/main/response.d.ts +0 -1
  14. package/dist/main/response.js +0 -1
  15. package/dist/main/router.d.ts +7 -14
  16. package/dist/main/router.js +3 -12
  17. package/dist/main/types/operation.d.ts +3 -5
  18. package/dist/main/types/operation.js +1 -1
  19. package/dist/main/types/websocket.d.ts +3 -5
  20. package/dist/main/types/websocket.js +0 -1
  21. package/dist/main/utils/frames.d.ts +0 -1
  22. package/dist/main/utils/frames.js +0 -1
  23. package/dist/main/utils/middlewares.d.ts +0 -1
  24. package/dist/main/utils/middlewares.js +1 -1
  25. package/dist/main/utils/namespaced.d.ts +0 -1
  26. package/dist/main/utils/namespaced.js +0 -1
  27. package/dist/main/utils/request-input.d.ts +3 -5
  28. package/dist/main/utils/request-input.js +3 -5
  29. package/dist/main/utils/request-params.d.ts +0 -1
  30. package/dist/main/utils/request-params.js +4 -3
  31. package/dist/main/utils/response.d.ts +0 -1
  32. package/dist/main/utils/response.js +0 -1
  33. package/dist/main/utils/websocket-mock.d.ts +0 -1
  34. package/dist/main/utils/websocket-mock.js +0 -1
  35. package/dist/main/xrpc-error.d.ts +16 -18
  36. package/dist/main/xrpc-error.js +9 -10
  37. package/dist/main/xrpc-handler.d.ts +1 -3
  38. package/dist/main/xrpc-handler.js +0 -1
  39. package/dist/middlewares/cors.d.ts +0 -1
  40. package/dist/middlewares/cors.js +0 -1
  41. package/dist/types/misc.d.ts +0 -1
  42. package/dist/types/misc.js +0 -1
  43. package/package.json +17 -20
  44. package/dist/auth/index.d.ts.map +0 -1
  45. package/dist/auth/index.js.map +0 -1
  46. package/dist/auth/jwt-creator.d.ts.map +0 -1
  47. package/dist/auth/jwt-creator.js.map +0 -1
  48. package/dist/auth/jwt-verifier.d.ts.map +0 -1
  49. package/dist/auth/jwt-verifier.js.map +0 -1
  50. package/dist/auth/jwt.d.ts.map +0 -1
  51. package/dist/auth/jwt.js.map +0 -1
  52. package/dist/auth/types.d.ts.map +0 -1
  53. package/dist/auth/types.js.map +0 -1
  54. package/dist/main/index.d.ts.map +0 -1
  55. package/dist/main/index.js.map +0 -1
  56. package/dist/main/response.d.ts.map +0 -1
  57. package/dist/main/response.js.map +0 -1
  58. package/dist/main/router.d.ts.map +0 -1
  59. package/dist/main/router.js.map +0 -1
  60. package/dist/main/types/operation.d.ts.map +0 -1
  61. package/dist/main/types/operation.js.map +0 -1
  62. package/dist/main/types/websocket.d.ts.map +0 -1
  63. package/dist/main/types/websocket.js.map +0 -1
  64. package/dist/main/utils/frames.d.ts.map +0 -1
  65. package/dist/main/utils/frames.js.map +0 -1
  66. package/dist/main/utils/middlewares.d.ts.map +0 -1
  67. package/dist/main/utils/middlewares.js.map +0 -1
  68. package/dist/main/utils/namespaced.d.ts.map +0 -1
  69. package/dist/main/utils/namespaced.js.map +0 -1
  70. package/dist/main/utils/request-input.d.ts.map +0 -1
  71. package/dist/main/utils/request-input.js.map +0 -1
  72. package/dist/main/utils/request-params.d.ts.map +0 -1
  73. package/dist/main/utils/request-params.js.map +0 -1
  74. package/dist/main/utils/response.d.ts.map +0 -1
  75. package/dist/main/utils/response.js.map +0 -1
  76. package/dist/main/utils/websocket-mock.d.ts.map +0 -1
  77. package/dist/main/utils/websocket-mock.js.map +0 -1
  78. package/dist/main/xrpc-error.d.ts.map +0 -1
  79. package/dist/main/xrpc-error.js.map +0 -1
  80. package/dist/main/xrpc-handler.d.ts.map +0 -1
  81. package/dist/main/xrpc-handler.js.map +0 -1
  82. package/dist/middlewares/cors.d.ts.map +0 -1
  83. package/dist/middlewares/cors.js.map +0 -1
  84. package/dist/types/misc.d.ts.map +0 -1
  85. package/dist/types/misc.js.map +0 -1
  86. package/lib/auth/index.ts +0 -2
  87. package/lib/auth/jwt-creator.ts +0 -53
  88. package/lib/auth/jwt-verifier.ts +0 -395
  89. package/lib/auth/jwt.ts +0 -146
  90. package/lib/auth/types.ts +0 -4
  91. package/lib/main/index.ts +0 -17
  92. package/lib/main/response.ts +0 -9
  93. package/lib/main/router.ts +0 -464
  94. package/lib/main/types/operation.ts +0 -130
  95. package/lib/main/types/websocket.ts +0 -22
  96. package/lib/main/utils/frames.ts +0 -71
  97. package/lib/main/utils/middlewares.ts +0 -13
  98. package/lib/main/utils/namespaced.ts +0 -5
  99. package/lib/main/utils/request-input.ts +0 -89
  100. package/lib/main/utils/request-params.ts +0 -108
  101. package/lib/main/utils/response.ts +0 -14
  102. package/lib/main/utils/websocket-mock.ts +0 -118
  103. package/lib/main/xrpc-error.ts +0 -201
  104. package/lib/main/xrpc-handler.ts +0 -53
  105. package/lib/middlewares/cors.ts +0 -104
  106. package/lib/types/misc.ts +0 -4
@@ -1,464 +0,0 @@
1
- import {
2
- safeParse,
3
- type XRPCProcedureMetadata,
4
- type XRPCQueryMetadata,
5
- type XRPCSubscriptionMetadata,
6
- } from '@atcute/lexicons/validations';
7
-
8
- import type { Literal, Promisable } from '../types/misc.ts';
9
-
10
- import type {
11
- ProcedureConfig,
12
- QueryConfig,
13
- SubscriptionConfig,
14
- UnknownOperationContext,
15
- UnknownSubscriptionContext,
16
- } from './types/operation.ts';
17
- import type { WebSocketAdapter } from './types/websocket.ts';
18
- import { encodeErrorFrame, encodeMessageFrame, extractMessageType, omitMessageType } from './utils/frames.ts';
19
- import { createAsyncMiddlewareRunner, type Middleware } from './utils/middlewares.ts';
20
- import { unwrapLxm, type Namespaced } from './utils/namespaced.ts';
21
- import { constructMimeValidator, hasRequestBody } from './utils/request-input.ts';
22
- import { constructParamsHandler } from './utils/request-params.ts';
23
- import { invalidRequest, validationError } from './utils/response.ts';
24
- import { XRPCError, XRPCSubscriptionError } from './xrpc-error.ts';
25
-
26
- type InternalRequestContext = {
27
- url: URL;
28
- request: Request;
29
- };
30
-
31
- type InternalRequestHandler = (context: InternalRequestContext) => Promise<Response>;
32
-
33
- type InternalRouteData = {
34
- method: 'GET' | 'POST';
35
- handler: InternalRequestHandler;
36
- };
37
-
38
- export type FetchMiddleware = Middleware<[request: Request], Promise<Response>>;
39
-
40
- export type NotFoundHandler = (request: Request) => Promisable<Response>;
41
- export type HealthCheckHandler = (request: Request) => Promisable<Response>;
42
- export type ExceptionHandler = (error: unknown, request: Request) => Promisable<Response>;
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;
48
-
49
- export const defaultExceptionHandler: ExceptionHandler = (error: unknown) => {
50
- if (error instanceof XRPCError) {
51
- return error.toResponse();
52
- }
53
-
54
- if (error instanceof Response) {
55
- return error;
56
- }
57
-
58
- return Response.json(
59
- { error: 'InternalServerError', message: `an exception happened whilst processing this request` },
60
- { status: 500 },
61
- );
62
- };
63
-
64
- export const defaultNotFoundHandler: NotFoundHandler = () => {
65
- return new Response('Not Found', { status: 404 });
66
- };
67
-
68
- export interface XRPCRouterOptions {
69
- middlewares?: FetchMiddleware[];
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. */
79
- handleException?: ExceptionHandler;
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;
92
- websocket?: WebSocketAdapter;
93
- }
94
-
95
- export class XRPCRouter {
96
- #handlers: Record<string, InternalRouteData> = {};
97
- #handleNotFound: NotFoundHandler;
98
- #handleHealthCheck?: HealthCheckHandler;
99
- #handleException: ExceptionHandler;
100
- #onError?: ErrorObserver;
101
- #onSocketError?: SocketErrorObserver;
102
- #websocket?: WebSocketAdapter;
103
-
104
- fetch: (request: Request) => Promise<Response>;
105
-
106
- constructor({
107
- middlewares = [],
108
- handleException = defaultExceptionHandler,
109
- handleNotFound = defaultNotFoundHandler,
110
- handleHealthCheck,
111
- onError,
112
- onSocketError,
113
- websocket,
114
- }: XRPCRouterOptions = {}) {
115
- const runner = createAsyncMiddlewareRunner([...middlewares, (request) => this.#dispatch(request)]);
116
-
117
- this.fetch = (request) => runner(request);
118
- this.#handleException = handleException;
119
- this.#handleNotFound = handleNotFound;
120
- this.#handleHealthCheck = handleHealthCheck;
121
- this.#onError = onError;
122
- this.#onSocketError = onSocketError;
123
- this.#websocket = websocket;
124
- }
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
-
160
- async #dispatch(request: Request): Promise<Response> {
161
- const url = new URL(request.url);
162
- const pathname = url.pathname;
163
-
164
- if (!pathname.startsWith('/xrpc/')) {
165
- return this.#handleNotFound(request);
166
- }
167
-
168
- const nsid = pathname.slice('/xrpc/'.length);
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
-
183
- const route = this.#handlers[nsid];
184
- if (route === undefined) {
185
- return this.#handleNotFound(request);
186
- }
187
-
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) {
192
- return Response.json(
193
- { error: 'InvalidRequest', message: `invalid http method (expected ${route.method})` },
194
- { status: 405, headers: { allow: route.method === 'GET' ? 'GET, HEAD' : route.method } },
195
- );
196
- }
197
-
198
- try {
199
- const response = await route.handler({
200
- request: request,
201
- url: url,
202
- });
203
-
204
- return response;
205
- } catch (err) {
206
- if (request.signal.aborted) {
207
- return new Response(null, { status: 499 });
208
- }
209
-
210
- this.#observeError(err, request);
211
- return this.#handleException(err, request);
212
- }
213
- }
214
-
215
- /** @deprecated use `addQuery` and `addProcedure` instead */
216
- add<TQuery extends XRPCQueryMetadata>(
217
- query: TQuery | Namespaced<TQuery>,
218
- config: QueryConfig<TQuery>,
219
- ): void;
220
- add<TProcedure extends XRPCProcedureMetadata>(
221
- procedure: TProcedure | Namespaced<TProcedure>,
222
- config: ProcedureConfig<TProcedure>,
223
- ): void;
224
- add(
225
- operation:
226
- | XRPCQueryMetadata
227
- | XRPCProcedureMetadata
228
- | Namespaced<XRPCQueryMetadata | XRPCProcedureMetadata>,
229
- config: any,
230
- ): void {
231
- const schema = unwrapLxm(operation);
232
-
233
- switch (schema.type) {
234
- case 'xrpc_query': {
235
- return this.addQuery(schema, config);
236
- }
237
- case 'xrpc_procedure': {
238
- return this.addProcedure(schema, config);
239
- }
240
- }
241
- }
242
-
243
- addQuery<TQuery extends XRPCQueryMetadata, TConfig extends QueryConfig<TQuery>>(
244
- query: TQuery | Namespaced<TQuery>,
245
- config: TConfig,
246
- ): void {
247
- const querySchema = unwrapLxm(query);
248
- const handleParams = querySchema.params ? constructParamsHandler(querySchema.params) : null;
249
-
250
- const handler = config.handler;
251
-
252
- this.#handlers[querySchema.nsid] = {
253
- method: 'GET',
254
- handler: async ({ request, url }) => {
255
- let params: Record<string, Literal | Literal[]>;
256
-
257
- if (handleParams !== null) {
258
- const result = handleParams(url.searchParams);
259
- if (!result.ok) {
260
- return validationError('params', result);
261
- }
262
-
263
- params = result.value;
264
- } else {
265
- params = {};
266
- }
267
-
268
- const context: UnknownOperationContext = {
269
- request: request,
270
- signal: request.signal,
271
- params: params,
272
- };
273
-
274
- const output = await handler(context as any);
275
-
276
- if (output instanceof Response) {
277
- return output;
278
- }
279
-
280
- return new Response(null);
281
- },
282
- };
283
- }
284
-
285
- addProcedure<TProcedure extends XRPCProcedureMetadata, TConfig extends ProcedureConfig<TProcedure>>(
286
- procedure: TProcedure | Namespaced<TProcedure>,
287
- config: TConfig,
288
- ): void {
289
- const procedureSchema = unwrapLxm(procedure);
290
- const handleParams = procedureSchema.params ? constructParamsHandler(procedureSchema.params) : null;
291
- const validateInputType = procedureSchema.input ? constructMimeValidator(procedureSchema.input) : null;
292
-
293
- const requiresInput = procedureSchema.input !== null;
294
- const inputSchema = procedureSchema.input?.type === 'lex' ? procedureSchema.input.schema : null;
295
-
296
- const handler = config.handler;
297
-
298
- this.#handlers[procedureSchema.nsid] = {
299
- method: 'POST',
300
- handler: async ({ request, url }) => {
301
- let params: Record<string, Literal | Literal[]>;
302
- let input: Record<string, unknown> | undefined;
303
-
304
- if (handleParams !== null) {
305
- const result = handleParams(url.searchParams);
306
- if (!result.ok) {
307
- return validationError('params', result);
308
- }
309
-
310
- params = result.value;
311
- } else {
312
- params = {};
313
- }
314
-
315
- if (requiresInput) {
316
- if (!hasRequestBody(request)) {
317
- return invalidRequest(`request body is expected but none was provided`);
318
- }
319
-
320
- if (validateInputType !== null) {
321
- const result = validateInputType(request);
322
- if (!result.ok) {
323
- return invalidRequest(result.error);
324
- }
325
- }
326
-
327
- if (inputSchema !== null) {
328
- let raw: any;
329
- try {
330
- raw = await request.json();
331
- } catch {
332
- return invalidRequest(`invalid request body (failed to parse json)`);
333
- }
334
-
335
- const result = safeParse(inputSchema, raw);
336
- if (!result.ok) {
337
- return validationError('input', result);
338
- }
339
-
340
- input = result.value;
341
- }
342
- } else {
343
- if (hasRequestBody(request)) {
344
- return invalidRequest(`request body is provided when none was expected`);
345
- }
346
- }
347
-
348
- const context: UnknownOperationContext = {
349
- request: request,
350
- signal: request.signal,
351
- params: params,
352
- input: input,
353
- };
354
-
355
- const output = await handler(context as any);
356
-
357
- if (output instanceof Response) {
358
- return output;
359
- }
360
-
361
- return new Response(null);
362
- },
363
- };
364
- }
365
-
366
- addSubscription<
367
- TSubscription extends XRPCSubscriptionMetadata,
368
- TConfig extends SubscriptionConfig<TSubscription>,
369
- >(subscription: TSubscription | Namespaced<TSubscription>, config: TConfig): void {
370
- const websocket = this.#websocket;
371
- if (websocket === undefined) {
372
- throw new Error(`WebSocket adapter not configured`);
373
- }
374
-
375
- const subscriptionSchema = unwrapLxm(subscription);
376
- const nsid = subscriptionSchema.nsid;
377
-
378
- const handleParams = subscriptionSchema.params ? constructParamsHandler(subscriptionSchema.params) : null;
379
- const handler = config.handler;
380
-
381
- this.#handlers[nsid] = {
382
- method: 'GET',
383
- handler: async ({ request, url }) => {
384
- {
385
- const con = request.headers.get('connection');
386
- const req = request.headers.get('upgrade');
387
- if (
388
- con === null ||
389
- req === null ||
390
- con.toLowerCase() !== 'upgrade' ||
391
- req.toLowerCase() !== 'websocket'
392
- ) {
393
- return invalidRequest(`invalid WebSocket upgrade`);
394
- }
395
- }
396
-
397
- let params: Record<string, Literal | Literal[]>;
398
-
399
- if (handleParams !== null) {
400
- const result = handleParams(url.searchParams);
401
- if (!result.ok) {
402
- return validationError('params', result);
403
- }
404
-
405
- params = result.value;
406
- } else {
407
- params = {};
408
- }
409
-
410
- const upgrade = await websocket.upgrade(request, async (ws) => {
411
- const signal = ws.signal;
412
-
413
- const context: UnknownSubscriptionContext = {
414
- request: request,
415
- params: params,
416
- signal: signal,
417
- };
418
-
419
- try {
420
- for await (const message of handler(context)) {
421
- if (signal.aborted) {
422
- break;
423
- }
424
-
425
- const type = extractMessageType(message, nsid);
426
- const body = omitMessageType(message);
427
-
428
- const frame = encodeMessageFrame(body, type);
429
- await ws.send(frame);
430
- const drained = ws.drain();
431
- if (drained) {
432
- await drained;
433
- }
434
- }
435
-
436
- ws.close(1000);
437
- } catch (err) {
438
- if (err instanceof XRPCSubscriptionError) {
439
- const frame = encodeErrorFrame(err.error, err.message || undefined);
440
-
441
- try {
442
- await ws.send(frame);
443
- } catch {
444
- // best-effort, socket may already be closed
445
- }
446
-
447
- ws.close(err.closeCode, err.error);
448
- return;
449
- }
450
-
451
- ws.close(1011, `internal server error`);
452
- this.#observeSocketError(err, request);
453
- }
454
- });
455
-
456
- if (upgrade !== undefined) {
457
- return upgrade;
458
- }
459
-
460
- return invalidRequest(`WebSocket upgrade failed`);
461
- },
462
- };
463
- }
464
- }
@@ -1,130 +0,0 @@
1
- import type {
2
- InferOutput,
3
- ObjectSchema,
4
- VariantSchema,
5
- XRPCBlobBodyParam,
6
- XRPCLexBodyParam,
7
- XRPCProcedureMetadata,
8
- XRPCQueryMetadata,
9
- XRPCSubscriptionMetadata,
10
- } from '@atcute/lexicons/validations';
11
-
12
- import type { Literal, Promisable } from '../../types/misc.ts';
13
- import type { JSONResponse } from '../response.ts';
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
- */
23
- export type UnknownOperationContext = {
24
- request: Request;
25
- signal: AbortSignal;
26
- params: Record<string, Literal | Literal[]>;
27
- input?: Record<string, unknown>;
28
- };
29
-
30
- // #region Query
31
-
32
- export type QueryContext<TQuery extends XRPCQueryMetadata> = {
33
- request: Request;
34
- signal: AbortSignal;
35
- } & (TQuery['params'] extends ObjectSchema
36
- ? {
37
- params: InferOutput<TQuery['params']>;
38
- }
39
- : {
40
- // params
41
- });
42
-
43
- export type QueryHandler<TQuery extends XRPCQueryMetadata> = (
44
- context: QueryContext<TQuery>,
45
- ) => Promisable<
46
- TQuery['output'] extends null
47
- ? Response | void
48
- : TQuery['output'] extends XRPCLexBodyParam
49
- ? Response | JSONResponse<InferOutput<TQuery['output']['schema']>>
50
- : Response
51
- >;
52
-
53
- export type QueryConfig<TQuery extends XRPCQueryMetadata = XRPCQueryMetadata> = {
54
- handler: QueryHandler<TQuery>;
55
- };
56
-
57
- // #region Procedure
58
-
59
- export type ProcedureContext<TProcedure extends XRPCProcedureMetadata> = {
60
- signal: AbortSignal;
61
- } & (TProcedure['input'] extends XRPCBlobBodyParam
62
- ? {
63
- request: Request & { readonly body: ReadableStream<Uint8Array> };
64
- }
65
- : TProcedure['input'] extends XRPCLexBodyParam
66
- ? {
67
- request: Request & { readonly body: null };
68
- }
69
- : {
70
- request: Request;
71
- }) &
72
- (TProcedure['params'] extends ObjectSchema
73
- ? {
74
- params: InferOutput<TProcedure['params']>;
75
- }
76
- : {
77
- // params
78
- }) &
79
- (TProcedure['input'] extends XRPCLexBodyParam
80
- ? {
81
- input: InferOutput<TProcedure['input']['schema']>;
82
- }
83
- : {
84
- // input
85
- });
86
-
87
- export type ProcedureHandler<TProcedure extends XRPCProcedureMetadata> = (
88
- context: ProcedureContext<TProcedure>,
89
- ) => Promisable<
90
- TProcedure['output'] extends null
91
- ? Response | void
92
- : TProcedure['output'] extends XRPCLexBodyParam
93
- ? Response | JSONResponse<InferOutput<TProcedure['output']['schema']>>
94
- : Response
95
- >;
96
-
97
- export type ProcedureConfig<TProcedure extends XRPCProcedureMetadata = XRPCProcedureMetadata> = {
98
- handler: ProcedureHandler<TProcedure>;
99
- };
100
-
101
- // #region Subscription
102
-
103
- export interface UnknownSubscriptionContext {
104
- request: Request;
105
- signal: AbortSignal;
106
- params: Record<string, Literal | Literal[]>;
107
- }
108
-
109
- export type SubscriptionContext<TSubscription extends XRPCSubscriptionMetadata> = {
110
- request: Request;
111
- signal: AbortSignal;
112
- } & (TSubscription['params'] extends ObjectSchema
113
- ? {
114
- params: InferOutput<TSubscription['params']>;
115
- }
116
- : {
117
- // params
118
- });
119
-
120
- export type SubscriptionHandler<TSubscription extends XRPCSubscriptionMetadata> = (
121
- context: SubscriptionContext<TSubscription>,
122
- ) => AsyncIterable<
123
- TSubscription['message'] extends ObjectSchema | VariantSchema<any>
124
- ? InferOutput<TSubscription['message']>
125
- : never
126
- >;
127
-
128
- export type SubscriptionConfig<TSubscription extends XRPCSubscriptionMetadata = XRPCSubscriptionMetadata> = {
129
- handler: SubscriptionHandler<TSubscription>;
130
- };
@@ -1,22 +0,0 @@
1
- import type { Promisable } from '../../types/misc.ts';
2
-
3
- export interface WebSocketConnection {
4
- signal: AbortSignal;
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>;
14
- close(code?: number, reason?: string): void;
15
- }
16
-
17
- export interface WebSocketAdapter {
18
- upgrade(
19
- request: Request,
20
- handler: (ws: WebSocketConnection) => Promisable<void>,
21
- ): Promisable<Response | undefined>;
22
- }
@@ -1,71 +0,0 @@
1
- import { encode } from '@atcute/cbor';
2
- import { concat } from '@atcute/uint8array';
3
-
4
- interface MessageFrameHeader {
5
- op: 1;
6
- t?: string; // Type discriminator for union messages (relative to NSID)
7
- }
8
-
9
- interface ErrorFrameHeader {
10
- op: -1;
11
- }
12
-
13
- interface ErrorFrameBody {
14
- error: string;
15
- message?: string;
16
- }
17
-
18
- export const encodeMessageFrame = (body: unknown, type?: string): Uint8Array => {
19
- const header: MessageFrameHeader = {
20
- op: 1,
21
- t: type,
22
- };
23
-
24
- return concat([encode(header), encode(body)]);
25
- };
26
-
27
- export const encodeErrorFrame = (error: string, message?: string): Uint8Array => {
28
- const header: ErrorFrameHeader = {
29
- op: -1,
30
- };
31
-
32
- const body: ErrorFrameBody = {
33
- error: error,
34
- message: message,
35
- };
36
-
37
- return concat([encode(header), encode(body)]);
38
- };
39
-
40
- export const extractMessageType = (message: unknown, nsid: string): string | undefined => {
41
- if (typeof message !== 'object' || message === null) {
42
- return undefined;
43
- }
44
-
45
- const obj = message as Record<string, unknown>;
46
- const type = obj.$type;
47
-
48
- if (typeof type !== 'string') {
49
- return undefined;
50
- }
51
-
52
- // If type starts with the subscription NSID, make it relative
53
- // e.g., "com.atproto.sync.subscribeRepos#commit" → "#commit"
54
- if (type.startsWith(nsid + '#')) {
55
- return type.slice(nsid.length);
56
- }
57
-
58
- // Otherwise return the full type
59
- return type;
60
- };
61
-
62
- export const omitMessageType = (message: unknown): unknown => {
63
- if (typeof message !== 'object' || message === null) {
64
- return message;
65
- }
66
-
67
- const obj = message as Record<string, unknown>;
68
- const { $type: _type, ...rest } = obj;
69
-
70
- return rest;
71
- };
@@ -1,13 +0,0 @@
1
- export type Middleware<TParams extends any[], TReturn> = (
2
- ...params: [...TParams, next: (...params: TParams) => TReturn]
3
- ) => TReturn;
4
-
5
- export const createAsyncMiddlewareRunner = <TParams extends any[], TReturn>(
6
- middlewares: [...Middleware<TParams, Promise<TReturn>>[], Middleware<TParams, Promise<TReturn>>],
7
- ) => {
8
- // prettier-ignore
9
- return middlewares.reduceRight<(...params: TParams) => Promise<TReturn>>(
10
- (next, run) => (...args) => run(...args, next),
11
- () => Promise.reject(new Error(`middleware chain exhausted`)),
12
- );
13
- };
@@ -1,5 +0,0 @@
1
- export type Namespaced<T> = { mainSchema: T };
2
-
3
- export const unwrapLxm = <T extends object>(schema: T | Namespaced<T>): T => {
4
- return 'mainSchema' in schema ? (schema as Namespaced<T>).mainSchema : schema;
5
- };