@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,89 +0,0 @@
1
- import type { XRPCBlobBodyParam, XRPCLexBodyParam } from '@atcute/lexicons/validations';
2
-
3
- import type { Result } from '../../types/misc.ts';
4
-
5
- /**
6
- * checks whether a request has a meaningful body.
7
- *
8
- * Node.js HTTP-to-fetch adapters always provide a `ReadableStream` for
9
- * `request.body`, even when no body content was sent. this function
10
- * uses `content-length` to handle that case while still respecting the
11
- * web `Request` API where `body === null` signals no body.
12
- *
13
- * @param request incoming request to check
14
- * @returns whether the request has body content
15
- */
16
- export const hasRequestBody = (request: Request): boolean => {
17
- if (request.body === null) {
18
- return false;
19
- }
20
-
21
- // Node.js adapters set content-length: 0 for bodiless requests while
22
- // still providing a ReadableStream body; treat this as no body.
23
- if (request.headers.get('content-length') === '0') {
24
- return false;
25
- }
26
-
27
- return true;
28
- };
29
-
30
- const jsonMimeValidator = (() => {
31
- const JSON_RE = /^\s*application\/json\s*(?:$|;)/;
32
-
33
- return (request: Request): Result<void, string> => {
34
- const type = request.headers.get('content-type');
35
- if (type === null) {
36
- return { ok: false, error: `missing input content type (expected application/json)` };
37
- }
38
-
39
- if (!JSON_RE.test(type)) {
40
- return { ok: false, error: `invalid input content type (expected application/json)` };
41
- }
42
-
43
- return { ok: true, value: undefined };
44
- };
45
- })();
46
-
47
- export const constructMimeValidator = (param: XRPCLexBodyParam | XRPCBlobBodyParam) => {
48
- if (param.type === 'lex') {
49
- return jsonMimeValidator;
50
- }
51
-
52
- const mimes = param.encoding;
53
- if (mimes === undefined || mimes.length === 0) {
54
- return null;
55
- }
56
-
57
- const pattern = new RegExp(`^\\s*(?:${mimes.map(escapeRegexp).join('|')})\\s*(?:$|;)`);
58
-
59
- return (request: Request): Result<void, string> => {
60
- const type = request.headers.get('content-type');
61
- if (type === null) {
62
- return { ok: false, error: `missing input content type (expected ${separatedList(mimes, 'or')})` };
63
- }
64
-
65
- if (!pattern.test(type)) {
66
- return { ok: false, error: `invalid input content type (expected ${separatedList(mimes, 'or')})` };
67
- }
68
-
69
- return { ok: true, value: undefined };
70
- };
71
- };
72
-
73
- const separatedList = (list: string[], sep: 'or' | 'and'): string => {
74
- switch (list.length) {
75
- case 0: {
76
- return `nothing`;
77
- }
78
- case 1: {
79
- return list[0];
80
- }
81
- default: {
82
- return `${list.slice(0, -1).join(', ')} ${sep} ${list[list.length - 1]}`;
83
- }
84
- }
85
- };
86
-
87
- const escapeRegexp = (input: string): string => {
88
- return input.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
89
- };
@@ -1,108 +0,0 @@
1
- import {
2
- safeParse,
3
- type ArraySchema,
4
- type BaseSchema,
5
- type ObjectSchema,
6
- type OptionalSchema,
7
- type ValidationResult,
8
- } from '@atcute/lexicons/validations';
9
-
10
- import type { Literal } from '../../types/misc.ts';
11
-
12
- type MaybeArray<T> = T | T[];
13
-
14
- const isArraySchema = (schema: BaseSchema): schema is ArraySchema => {
15
- return schema.type === 'array';
16
- };
17
-
18
- const isOptionalSchema = (schema: BaseSchema): schema is OptionalSchema => {
19
- return schema.type === 'optional';
20
- };
21
-
22
- const unwrapOptional = (schema: BaseSchema): BaseSchema => {
23
- return isOptionalSchema(schema) ? schema.wrapped : schema;
24
- };
25
-
26
- const unwrapArray = (schema: BaseSchema): BaseSchema => {
27
- return isArraySchema(schema) ? schema.item : schema;
28
- };
29
-
30
- const coerceBoolean = (str: string): boolean | null => {
31
- switch (str) {
32
- case 'true':
33
- return true;
34
- case 'false':
35
- return false;
36
- }
37
-
38
- return null;
39
- };
40
-
41
- const coerceInteger = (str: string): number => {
42
- return Number(str);
43
- };
44
-
45
- export const constructParamsHandler = <TSchema extends ObjectSchema>(schema: TSchema) => {
46
- const entries = Object.entries(schema.shape).map(([key, schema]) => {
47
- const nonnullable = unwrapOptional(schema);
48
- const singular = unwrapArray(nonnullable);
49
-
50
- let coerce: ((x: string) => Literal | null) | undefined;
51
- switch (singular.type) {
52
- case 'boolean': {
53
- coerce = coerceBoolean;
54
- break;
55
- }
56
- case 'integer': {
57
- coerce = coerceInteger;
58
- break;
59
- }
60
- }
61
-
62
- return {
63
- key: key,
64
- coerce: coerce,
65
- multiple: isArraySchema(nonnullable),
66
- optional: isOptionalSchema(schema) && schema.default === undefined,
67
- };
68
- });
69
-
70
- const len = entries.length;
71
-
72
- return (searchParams: URLSearchParams): ValidationResult<Record<string, MaybeArray<Literal>>> => {
73
- const input: Record<string, MaybeArray<Literal | null>> = {};
74
-
75
- for (let idx = 0; idx < len; idx++) {
76
- const entry = entries[idx];
77
- const key = entry.key;
78
- const coerce = entry.coerce;
79
-
80
- const raw = searchParams.getAll(key);
81
- const count = raw.length;
82
-
83
- let value: MaybeArray<Literal | null>;
84
-
85
- if (entry.multiple || count > 1) {
86
- value = coerce !== undefined ? raw.map(coerce) : raw;
87
- } else {
88
- if (count === 0) {
89
- continue;
90
- }
91
-
92
- value = coerce !== undefined ? coerce(raw[0]) : raw[0];
93
- }
94
-
95
- /*#__INLINE__*/ set(input, key, value);
96
- }
97
-
98
- return safeParse(schema, input);
99
- };
100
- };
101
-
102
- const set = <K extends PropertyKey, V>(obj: Record<K, V>, key: NoInfer<K>, value: NoInfer<V>): void => {
103
- if (key === '__proto__') {
104
- Object.defineProperty(obj, key, { value });
105
- } else {
106
- obj[key] = value;
107
- }
108
- };
@@ -1,14 +0,0 @@
1
- import type { Err } from '@atcute/lexicons/validations';
2
-
3
- export const invalidRequest = (message: string) => {
4
- return Response.json({ error: 'InvalidRequest', message }, { status: 400 });
5
- };
6
-
7
- export const validationError = (kind: 'params' | 'input', err: Err): Response => {
8
- const message = `invalid ${kind}: ${err.message}`;
9
-
10
- return Response.json(
11
- { error: 'InvalidRequest', message: message, 'net.kelinci.atcute.issues': err.issues },
12
- { status: 400 },
13
- );
14
- };
@@ -1,118 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
-
3
- import { SimpleEventEmitter } from '@mary-ext/simple-event-emitter';
4
-
5
- import type { Promisable } from '../../types/misc.ts';
6
- import type { XRPCRouter } from '../router.ts';
7
- import type { WebSocketAdapter, WebSocketConnection } from '../types/websocket.ts';
8
-
9
- interface WebSocketHandlerContext {
10
- handler: ((ws: WebSocketConnection) => Promisable<void>) | null;
11
- }
12
-
13
- export interface CloseEvent {
14
- code: number;
15
- reason: string;
16
- wasClean: boolean;
17
- }
18
-
19
- export interface SubscriptionClient extends Disposable {
20
- onMessage: SimpleEventEmitter<[data: Uint8Array]>;
21
- onClose: SimpleEventEmitter<[event: CloseEvent]>;
22
- dispose(): void;
23
- }
24
-
25
- export interface SubscriptionMock {
26
- subscribe(url: string): Promise<SubscriptionClient>;
27
- }
28
-
29
- export class MockWebSocketAdapter implements WebSocketAdapter {
30
- #context = new AsyncLocalStorage<WebSocketHandlerContext>();
31
-
32
- upgrade(
33
- _request: Request,
34
- handler: (ws: WebSocketConnection) => Promisable<void>,
35
- ): Promisable<Response | undefined> {
36
- const ctx = this.#context.getStore();
37
- if (!ctx) {
38
- return undefined;
39
- }
40
-
41
- ctx.handler = handler;
42
-
43
- return new Response(null);
44
- }
45
-
46
- attach(router: XRPCRouter): SubscriptionMock {
47
- return {
48
- subscribe: async (url) => {
49
- const ctx: WebSocketHandlerContext = {
50
- handler: null,
51
- };
52
-
53
- await this.#context.run(ctx, async () => {
54
- const urlp = new URL(url, 'http://localhost');
55
- const request = new Request(urlp, {
56
- headers: {
57
- upgrade: 'websocket',
58
- connection: 'upgrade',
59
- },
60
- });
61
-
62
- const response = await router.fetch(request);
63
-
64
- return response;
65
- });
66
-
67
- if (!ctx.handler) {
68
- throw new Error(`WebSocket upgrade succeeded but no handler was set`);
69
- }
70
-
71
- const onMessage = new SimpleEventEmitter<[data: Uint8Array]>();
72
- const onClose = new SimpleEventEmitter<[event: CloseEvent]>();
73
-
74
- const controller = new AbortController();
75
- const signal = controller.signal;
76
-
77
- const connection: WebSocketConnection = {
78
- signal: signal,
79
- send(data) {
80
- onMessage.emit(data);
81
- },
82
- drain() {
83
- // tests have no outgoing buffer to observe
84
- },
85
- close(code = 1000, reason = '') {
86
- if (!signal.aborted) {
87
- onClose.emit({ code, reason, wasClean: true });
88
- controller.abort();
89
- }
90
- },
91
- };
92
-
93
- {
94
- const handler = ctx.handler;
95
- setTimeout(() => {
96
- handler(connection);
97
- }, 1);
98
- }
99
-
100
- const client: SubscriptionClient = {
101
- onMessage,
102
- onClose,
103
- dispose() {
104
- if (!signal.aborted) {
105
- onClose.emit({ code: 1000, reason: '', wasClean: true });
106
- controller.abort();
107
- }
108
- },
109
- [Symbol.dispose]() {
110
- this.dispose();
111
- },
112
- };
113
-
114
- return client;
115
- },
116
- };
117
- }
118
- }
@@ -1,201 +0,0 @@
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
-
65
- export interface XRPCErrorOptions {
66
- status: number;
67
- error: string;
68
- message?: string;
69
- headers?: HeadersInit;
70
- }
71
-
72
- export class XRPCError extends Error {
73
- /** response status */
74
- readonly status: number;
75
-
76
- /** error name */
77
- readonly error: string;
78
- /** response headers */
79
- readonly headers?: HeadersInit;
80
-
81
- constructor({ status, error, message, headers }: XRPCErrorOptions) {
82
- super(message);
83
-
84
- this.status = status;
85
-
86
- this.error = error;
87
- this.headers = headers;
88
- }
89
-
90
- toResponse(): Response {
91
- return Response.json(
92
- { error: this.error, message: this.message || undefined },
93
- { status: this.status, headers: this.headers },
94
- );
95
- }
96
- }
97
-
98
- export class InvalidRequestError extends XRPCError {
99
- constructor({ status = 400, error = 'InvalidRequest', message, headers }: Partial<XRPCErrorOptions> = {}) {
100
- super({ status, error, message, headers });
101
- }
102
- }
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
-
113
- export class AuthRequiredError extends XRPCError {
114
- constructor({
115
- status = 401,
116
- error = 'AuthenticationRequired',
117
- message,
118
- headers,
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 });
131
- }
132
- }
133
-
134
- export class ForbiddenError extends XRPCError {
135
- constructor({ status = 403, error = 'Forbidden', message, headers }: Partial<XRPCErrorOptions> = {}) {
136
- super({ status, error, message, headers });
137
- }
138
- }
139
-
140
- export class RateLimitExceededError extends XRPCError {
141
- constructor({
142
- status = 429,
143
- error = 'RateLimitExceeded',
144
- message,
145
- headers,
146
- }: Partial<XRPCErrorOptions> = {}) {
147
- super({ status, error, message, headers });
148
- }
149
- }
150
-
151
- export class InternalServerError extends XRPCError {
152
- constructor({
153
- status = 500,
154
- error = 'InternalServerError',
155
- message,
156
- headers,
157
- }: Partial<XRPCErrorOptions> = {}) {
158
- super({ status, error, message, headers });
159
- }
160
- }
161
-
162
- export class UpstreamFailureError extends XRPCError {
163
- constructor({ status = 502, error = 'UpstreamFailure', message, headers }: Partial<XRPCErrorOptions> = {}) {
164
- super({ status, error, message, headers });
165
- }
166
- }
167
-
168
- export class NotEnoughResourcesError extends XRPCError {
169
- constructor({
170
- status = 503,
171
- error = 'NotEnoughResources',
172
- message,
173
- headers,
174
- }: Partial<XRPCErrorOptions> = {}) {
175
- super({ status, error, message, headers });
176
- }
177
- }
178
-
179
- export class UpstreamTimeoutError extends XRPCError {
180
- constructor({ status = 504, error = 'UpstreamTimeout', message, headers }: Partial<XRPCErrorOptions> = {}) {
181
- super({ status, error, message, headers });
182
- }
183
- }
184
-
185
- export interface XRPCSubscriptionErrorOptions {
186
- closeCode?: number;
187
- error: string;
188
- message?: string;
189
- }
190
-
191
- export class XRPCSubscriptionError extends Error {
192
- readonly closeCode: number;
193
- readonly error: string;
194
-
195
- constructor({ closeCode = 1008, error, message }: XRPCSubscriptionErrorOptions) {
196
- super(message);
197
-
198
- this.closeCode = closeCode;
199
- this.error = error;
200
- }
201
- }
@@ -1,53 +0,0 @@
1
- import type { XRPCProcedureMetadata, XRPCQueryMetadata } from '@atcute/lexicons/validations';
2
-
3
- import { XRPCRouter, type XRPCRouterOptions } from './router.ts';
4
- import type { ProcedureConfig, QueryConfig } from './types/operation.ts';
5
- import { unwrapLxm, type Namespaced } from './utils/namespaced.ts';
6
-
7
- type XrpcHandlerRouterOptions = Pick<XRPCRouterOptions, 'middlewares' | 'handleNotFound' | 'handleException'>;
8
-
9
- export type XrpcQueryHandlerOptions<TQuery extends XRPCQueryMetadata> = {
10
- lxm: TQuery | Namespaced<TQuery>;
11
- routerOptions?: XrpcHandlerRouterOptions;
12
- } & QueryConfig<TQuery>;
13
-
14
- export type XrpcProcedureHandlerOptions<TProcedure extends XRPCProcedureMetadata> = {
15
- lxm: TProcedure | Namespaced<TProcedure>;
16
- routerOptions?: XrpcHandlerRouterOptions;
17
- } & ProcedureConfig<TProcedure>;
18
-
19
- export type XrpcHandlerOptions =
20
- | XrpcQueryHandlerOptions<XRPCQueryMetadata>
21
- | XrpcProcedureHandlerOptions<XRPCProcedureMetadata>;
22
-
23
- /**
24
- * create a fetch handler for a single xrpc query or procedure.
25
- * requests are expected at `/xrpc/<nsid>`.
26
- * subscriptions are not supported.
27
- */
28
- export function createXrpcHandler<TQuery extends XRPCQueryMetadata>(
29
- options: XrpcQueryHandlerOptions<TQuery>,
30
- ): (request: Request) => Promise<Response>;
31
- export function createXrpcHandler<TProcedure extends XRPCProcedureMetadata>(
32
- options: XrpcProcedureHandlerOptions<TProcedure>,
33
- ): (request: Request) => Promise<Response>;
34
- export function createXrpcHandler(options: XrpcHandlerOptions): (request: Request) => Promise<Response> {
35
- const { lxm, handler, routerOptions } = options;
36
-
37
- const router = new XRPCRouter(routerOptions);
38
-
39
- const schema = unwrapLxm(lxm);
40
-
41
- switch (schema.type) {
42
- case 'xrpc_query': {
43
- router.addQuery(schema, { handler: handler as QueryConfig<XRPCQueryMetadata>['handler'] });
44
- break;
45
- }
46
- case 'xrpc_procedure': {
47
- router.addProcedure(schema, { handler: handler as ProcedureConfig<XRPCProcedureMetadata>['handler'] });
48
- break;
49
- }
50
- }
51
-
52
- return router.fetch;
53
- }
@@ -1,104 +0,0 @@
1
- import type { FetchMiddleware } from '../main/router.ts';
2
-
3
- export interface CORSOptions {
4
- /** Additional headers to expose to the client */
5
- exposedHeaders?: string[];
6
- /** Additional headers to allow */
7
- allowedHeaders?: string[];
8
- /** NSID prefixes to exclude from CORS handling */
9
- exclude?: string[];
10
- /** allow requests from private networks (e.g., localhost to a local server) */
11
- allowPrivateNetwork?: boolean;
12
- }
13
-
14
- const DEFAULT_EXPOSED_HEADERS = [
15
- 'dpop-nonce',
16
- 'www-authenticate',
17
-
18
- 'ratelimit-limit',
19
- 'ratelimit-policy',
20
- 'ratelimit-remaining',
21
- 'ratelimit-reset',
22
- ];
23
-
24
- const DEFAULT_ALLOWED_HEADERS = [
25
- 'content-type',
26
-
27
- 'authorization',
28
- 'dpop',
29
-
30
- 'atproto-accept-labelers',
31
- 'atproto-proxy',
32
- ];
33
-
34
- const RE_XRPC_NSID = /^\/xrpc\/([^?]*)/;
35
-
36
- export const cors = (options: CORSOptions = {}): FetchMiddleware => {
37
- const exposedHeaders = Array.from(
38
- new Set([...DEFAULT_EXPOSED_HEADERS, ...(options.exposedHeaders?.map((h) => h.toLowerCase()) || [])]),
39
- )
40
- // oxlint-disable-next-line unicorn/no-array-sort -- Array.from already clones
41
- .sort();
42
-
43
- const allowedHeaders = Array.from(
44
- new Set([...DEFAULT_ALLOWED_HEADERS, ...(options.allowedHeaders?.map((h) => h.toLowerCase()) || [])]),
45
- )
46
- // oxlint-disable-next-line unicorn/no-array-sort -- Array.from already clones
47
- .sort()
48
- .join(',');
49
-
50
- const exclude = options.exclude;
51
- const allowPrivateNetwork = options.allowPrivateNetwork;
52
-
53
- return async (request, next) => {
54
- // check if this NSID should be excluded from CORS handling
55
- if (exclude) {
56
- const url = new URL(request.url);
57
- const match = RE_XRPC_NSID.exec(url.pathname);
58
-
59
- if (match) {
60
- const nsid = match[1];
61
- const excluded = exclude.some((pattern) => {
62
- if (pattern.endsWith('.*')) {
63
- return nsid.startsWith(pattern.slice(0, -1));
64
- }
65
- return nsid === pattern;
66
- });
67
-
68
- if (excluded) {
69
- return next(request);
70
- }
71
- }
72
- }
73
-
74
- const origin = request.headers.get('origin') || '*';
75
-
76
- // Handle preflight requests
77
- if (request.method === 'OPTIONS') {
78
- const headers = new Headers();
79
- headers.set('access-control-max-age', '86400');
80
- headers.set('access-control-allow-origin', origin);
81
-
82
- if (allowedHeaders) {
83
- headers.set('access-control-allow-headers', allowedHeaders);
84
- }
85
-
86
- if (allowPrivateNetwork && request.headers.get('access-control-request-private-network') === 'true') {
87
- headers.set('access-control-allow-private-network', 'true');
88
- }
89
-
90
- return new Response(null, { status: 204, headers: headers });
91
- }
92
-
93
- const response = await next(request);
94
-
95
- const expose = exposedHeaders.filter((h) => response.headers.has(h)).join(',');
96
-
97
- response.headers.set('access-control-allow-origin', origin);
98
- if (expose.length > 0) {
99
- response.headers.append('access-control-expose-headers', expose);
100
- }
101
-
102
- return response;
103
- };
104
- };
package/lib/types/misc.ts DELETED
@@ -1,4 +0,0 @@
1
- export type Promisable<T> = T | Promise<T>;
2
- export type Literal = string | number | boolean;
3
-
4
- export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };