@atproto/lex-server 0.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 (61) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +598 -0
  4. package/dist/errors.d.ts +13 -0
  5. package/dist/errors.d.ts.map +1 -0
  6. package/dist/errors.js +39 -0
  7. package/dist/errors.js.map +1 -0
  8. package/dist/example.d.ts +2 -0
  9. package/dist/example.d.ts.map +1 -0
  10. package/dist/example.js +36 -0
  11. package/dist/example.js.map +1 -0
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +9 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/lex-auth-error.d.ts +15 -0
  17. package/dist/lex-auth-error.d.ts.map +1 -0
  18. package/dist/lex-auth-error.js +52 -0
  19. package/dist/lex-auth-error.js.map +1 -0
  20. package/dist/lex-server.d.ts +80 -0
  21. package/dist/lex-server.d.ts.map +1 -0
  22. package/dist/lex-server.js +285 -0
  23. package/dist/lex-server.js.map +1 -0
  24. package/dist/lib/drain-websocket.d.ts +6 -0
  25. package/dist/lib/drain-websocket.d.ts.map +1 -0
  26. package/dist/lib/drain-websocket.js +16 -0
  27. package/dist/lib/drain-websocket.js.map +1 -0
  28. package/dist/lib/sleep.d.ts +2 -0
  29. package/dist/lib/sleep.d.ts.map +1 -0
  30. package/dist/lib/sleep.js +22 -0
  31. package/dist/lib/sleep.js.map +1 -0
  32. package/dist/lib/www-authenticate.d.ts +7 -0
  33. package/dist/lib/www-authenticate.d.ts.map +1 -0
  34. package/dist/lib/www-authenticate.js +22 -0
  35. package/dist/lib/www-authenticate.js.map +1 -0
  36. package/dist/nodejs.d.ts +35 -0
  37. package/dist/nodejs.d.ts.map +1 -0
  38. package/dist/nodejs.js +236 -0
  39. package/dist/nodejs.js.map +1 -0
  40. package/dist/subscripotion.d.ts +2 -0
  41. package/dist/subscripotion.d.ts.map +1 -0
  42. package/dist/subscripotion.js +36 -0
  43. package/dist/subscripotion.js.map +1 -0
  44. package/dist/test.d.mts +2 -0
  45. package/dist/test.d.mts.map +1 -0
  46. package/dist/test.mjs +52 -0
  47. package/dist/test.mjs.map +1 -0
  48. package/nodejs.js +5 -0
  49. package/package.json +64 -0
  50. package/src/errors.ts +54 -0
  51. package/src/index.ts +8 -0
  52. package/src/lex-server.test.ts +1621 -0
  53. package/src/lex-server.ts +551 -0
  54. package/src/lib/drain-websocket.ts +23 -0
  55. package/src/lib/sleep.ts +25 -0
  56. package/src/lib/www-authenticate.ts +26 -0
  57. package/src/nodejs.test.ts +107 -0
  58. package/src/nodejs.ts +367 -0
  59. package/tsconfig.build.json +12 -0
  60. package/tsconfig.json +8 -0
  61. package/tsconfig.tests.json +9 -0
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.abortableSleep = abortableSleep;
4
+ async function abortableSleep(ms, signal) {
5
+ signal.throwIfAborted();
6
+ return new Promise((resolve, reject) => {
7
+ const cleanup = () => {
8
+ signal.removeEventListener('abort', onAbort);
9
+ clearTimeout(timeoutHandle);
10
+ };
11
+ const timeoutHandle = setTimeout(() => {
12
+ cleanup();
13
+ resolve();
14
+ }, ms);
15
+ const onAbort = () => {
16
+ cleanup();
17
+ reject(signal.reason);
18
+ };
19
+ signal.addEventListener('abort', onAbort);
20
+ });
21
+ }
22
+ //# sourceMappingURL=sleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/lib/sleep.ts"],"names":[],"mappings":";;AAAA,wCAwBC;AAxBM,KAAK,UAAU,cAAc,CAClC,EAAU,EACV,MAAmB;IAEnB,MAAM,CAAC,cAAc,EAAE,CAAA;IAEvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5C,YAAY,CAAC,aAAa,CAAC,CAAA;QAC7B,CAAC,CAAA;QAED,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,OAAO,EAAE,CAAA;YACT,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,EAAE,CAAC,CAAA;QAEN,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,OAAO,EAAE,CAAA;YACT,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACvB,CAAC,CAAA;QAED,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["export async function abortableSleep(\n ms: number,\n signal: AbortSignal,\n): Promise<void> {\n signal.throwIfAborted()\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n clearTimeout(timeoutHandle)\n }\n\n const timeoutHandle = setTimeout(() => {\n cleanup()\n resolve()\n }, ms)\n\n const onAbort = () => {\n cleanup()\n reject(signal.reason)\n }\n\n signal.addEventListener('abort', onAbort)\n })\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export type WWWAuthenticate = {
2
+ [authScheme in string]?: string | {
3
+ [authParam in string]?: string;
4
+ };
5
+ };
6
+ export declare function formatWWWAuthenticateHeader(wwwAuthenticate: WWWAuthenticate): string;
7
+ //# sourceMappingURL=www-authenticate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"www-authenticate.d.ts","sourceRoot":"","sources":["../../src/lib/www-authenticate.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;KAC3B,UAAU,IAAI,MAAM,CAAC,CAAC,EACnB,MAAM,GACN;SAAG,SAAS,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM;KAAE;CACvC,CAAA;AAED,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,eAAe,GAC/B,MAAM,CAiBR"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatWWWAuthenticateHeader = formatWWWAuthenticateHeader;
4
+ function formatWWWAuthenticateHeader(wwwAuthenticate) {
5
+ return Object.entries(wwwAuthenticate)
6
+ .map(([authScheme, authParams]) => {
7
+ if (authParams === undefined)
8
+ return null;
9
+ const paramsEnc = typeof authParams === 'string'
10
+ ? [authParams]
11
+ : Object.entries(authParams)
12
+ .filter(([_, val]) => val != null)
13
+ .map(([name, val]) => `${name}=${JSON.stringify(val)}`);
14
+ const authChallenge = paramsEnc?.length
15
+ ? `${authScheme} ${paramsEnc.join(', ')}`
16
+ : authScheme;
17
+ return authChallenge;
18
+ })
19
+ .filter(Boolean)
20
+ .join(', ');
21
+ }
22
+ //# sourceMappingURL=www-authenticate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"www-authenticate.js","sourceRoot":"","sources":["../../src/lib/www-authenticate.ts"],"names":[],"mappings":";;AAMA,kEAmBC;AAnBD,SAAgB,2BAA2B,CACzC,eAAgC;IAEhC,OAAO,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE;QAChC,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QACzC,MAAM,SAAS,GACb,OAAO,UAAU,KAAK,QAAQ;YAC5B,CAAC,CAAC,CAAC,UAAU,CAAC;YACd,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;iBACvB,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC;iBACjC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/D,MAAM,aAAa,GAAG,SAAS,EAAE,MAAM;YACrC,CAAC,CAAC,GAAG,UAAU,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACzC,CAAC,CAAC,UAAU,CAAA;QACd,OAAO,aAAa,CAAA;IACtB,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC","sourcesContent":["export type WWWAuthenticate = {\n [authScheme in string]?:\n | string // token68\n | { [authParam in string]?: string }\n}\n\nexport function formatWWWAuthenticateHeader(\n wwwAuthenticate: WWWAuthenticate,\n): string {\n return Object.entries(wwwAuthenticate)\n .map(([authScheme, authParams]) => {\n if (authParams === undefined) return null\n const paramsEnc =\n typeof authParams === 'string'\n ? [authParams]\n : Object.entries(authParams)\n .filter(([_, val]) => val != null)\n .map(([name, val]) => `${name}=${JSON.stringify(val)}`)\n const authChallenge = paramsEnc?.length\n ? `${authScheme} ${paramsEnc.join(', ')}`\n : authScheme\n return authChallenge\n })\n .filter(Boolean)\n .join(', ')\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import { IncomingMessage, Server as HttpServer, ServerOptions, ServerResponse } from 'node:http';
2
+ import { ListenOptions } from 'node:net';
3
+ export declare function upgradeWebSocket(request: Request): {
4
+ response: Response;
5
+ socket: WebSocket;
6
+ };
7
+ export type NetAddr = {
8
+ hostname: string;
9
+ port: number;
10
+ transport: 'tcp';
11
+ };
12
+ export type NodeConnectionInfo = {
13
+ localAddr?: NetAddr;
14
+ remoteAddr?: NetAddr;
15
+ };
16
+ export interface HandlerFunction {
17
+ (req: Request, info: NodeConnectionInfo): Response | Promise<Response>;
18
+ }
19
+ export interface HandlerObject {
20
+ handle: HandlerFunction;
21
+ }
22
+ export declare function toRequestListener<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(handlerFn: HandlerFunction): (req: InstanceType<Request>, res: InstanceType<Response> & {
23
+ req: InstanceType<Request>;
24
+ }, next?: (err?: unknown) => void) => void;
25
+ export type CreateServerOptions<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse> = ServerOptions<Request, Response> & {
26
+ gracefulTerminationTimeout?: number;
27
+ };
28
+ export interface Server<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse> extends HttpServer<Request, Response>, AsyncDisposable {
29
+ terminate(): Promise<void>;
30
+ [Symbol.asyncDispose](): Promise<void>;
31
+ }
32
+ export declare function createServer<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(handler: HandlerFunction | HandlerObject, options?: CreateServerOptions<Request, Response>): Server<Request, Response>;
33
+ export type StartServerOptions<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse> = ListenOptions & CreateServerOptions<Request, Response>;
34
+ export declare function serve<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(handler: HandlerFunction | HandlerObject, options?: StartServerOptions<Request, Response>): Promise<Server<Request, Response>>;
35
+ //# sourceMappingURL=nodejs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodejs.d.ts","sourceRoot":"","sources":["../src/nodejs.ts"],"names":[],"mappings":"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"}
package/dist/nodejs.js ADDED
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.upgradeWebSocket = upgradeWebSocket;
4
+ exports.toRequestListener = toRequestListener;
5
+ exports.createServer = createServer;
6
+ exports.serve = serve;
7
+ const node_events_1 = require("node:events");
8
+ const node_http_1 = require("node:http");
9
+ const node_stream_1 = require("node:stream");
10
+ const promises_1 = require("node:stream/promises");
11
+ const http_terminator_1 = require("http-terminator");
12
+ const ws_1 = require("ws");
13
+ // @ts-expect-error
14
+ Symbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose');
15
+ const kResponseWs = Symbol.for('@atproto/lex-server:WebSocket');
16
+ function isUpgradeRequest(request, upgrade) {
17
+ return (request.method === 'GET' &&
18
+ request.headers.get('connection')?.toLowerCase() === 'upgrade' &&
19
+ request.headers.get('upgrade')?.toLowerCase() === upgrade);
20
+ }
21
+ function upgradeWebSocket(request) {
22
+ if (!isUpgradeRequest(request, 'websocket')) {
23
+ throw new TypeError('upgradeWebSocket() expects a WebSocket upgrade');
24
+ }
25
+ // Placeholder response for WebSocket upgrade. The actual handling will happen
26
+ // through the handleWebSocketUpgrade function. Headers set on the response
27
+ // will be applied during the upgrade.
28
+ const response = new Response(null, { status: 200 });
29
+ // The Response constructor does not allow setting status 101, so we
30
+ // define it directly. The purpose of this response is just to signal
31
+ // that an upgrade is needed, and to carry any headers.
32
+ Object.defineProperty(response, 'status', {
33
+ value: 101,
34
+ enumerable: false,
35
+ configurable: false,
36
+ writable: false,
37
+ });
38
+ // @ts-expect-error
39
+ const socket = new ws_1.WebSocket(null, undefined, {
40
+ autoPong: true,
41
+ });
42
+ // Attach the WebSocket to the response for later retrieval
43
+ Object.defineProperty(response, kResponseWs, {
44
+ value: socket,
45
+ enumerable: false,
46
+ configurable: false,
47
+ writable: false,
48
+ });
49
+ return { response, socket };
50
+ }
51
+ function handleWebSocketUpgrade(req, response) {
52
+ const ws = response[kResponseWs];
53
+ if (!ws)
54
+ throw new TypeError('Response not created by upgradeWebSocket()');
55
+ // Create a one time use WebSocketServer to handle the upgrade
56
+ const wss = new ws_1.WebSocketServer({
57
+ autoPong: true,
58
+ noServer: true,
59
+ clientTracking: false,
60
+ perMessageDeflate: true,
61
+ // @ts-expect-error
62
+ WebSocket: function () {
63
+ // Return the websocket that was created earlier instead of a new instance
64
+ return ws;
65
+ },
66
+ });
67
+ // Apply headers that might have been set on the response object during
68
+ // handling. This will be called during wss.handleUpgrade().
69
+ wss.on('headers', (headers) => {
70
+ for (const [name, value] of response.headers) {
71
+ headers.push(`${name}: ${value}`);
72
+ }
73
+ });
74
+ wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (_socket) => {
75
+ // @TODO find a way to properly "close" the _socket when the server is
76
+ // shutting down (might require replacing http-terminator with a local
77
+ // implementation)
78
+ });
79
+ }
80
+ async function sendResponse(req, res, response) {
81
+ // Invalid usage
82
+ if (res.headersSent) {
83
+ throw new TypeError('Response has already been sent');
84
+ }
85
+ if (response.status === 101) {
86
+ return handleWebSocketUpgrade(req, response);
87
+ }
88
+ res.statusCode = response.status;
89
+ res.statusMessage = response.statusText;
90
+ for (const [key, value] of response.headers) {
91
+ res.appendHeader(key, value);
92
+ }
93
+ if (response.body != null && req.method !== 'HEAD') {
94
+ const stream = node_stream_1.Readable.fromWeb(response.body);
95
+ await (0, promises_1.pipeline)(stream, res);
96
+ }
97
+ else {
98
+ await response.body?.cancel();
99
+ res.end();
100
+ }
101
+ }
102
+ function toRequest(req) {
103
+ const host = req.headers.host ?? req.socket?.localAddress ?? 'localhost';
104
+ const isEncrypted = req.socket.encrypted === true;
105
+ const protocol = isEncrypted ? 'https' : 'http';
106
+ const url = new URL(req.url ?? '/', `${protocol}://${host}`);
107
+ const headers = toHeaders(req.headers);
108
+ const body = toBody(req);
109
+ const abortController = new AbortController();
110
+ const abort = (err) => abortController.abort(err);
111
+ req.on('close', abort);
112
+ req.on('error', abort);
113
+ req.on('end', abort);
114
+ abortController.signal.addEventListener('abort', () => {
115
+ req.off('close', abort);
116
+ req.off('error', abort);
117
+ req.off('end', abort);
118
+ }, { once: true });
119
+ return new Request(url, {
120
+ signal: abortController.signal,
121
+ method: req.method,
122
+ headers,
123
+ body,
124
+ referrer: headers.get('referrer') ?? headers.get('referer') ?? undefined,
125
+ redirect: 'manual',
126
+ // @ts-expect-error
127
+ duplex: body ? 'half' : undefined,
128
+ });
129
+ }
130
+ function toHeaders(headers) {
131
+ const result = new Headers();
132
+ for (const [key, value] of Object.entries(headers)) {
133
+ if (value === undefined)
134
+ continue;
135
+ if (Array.isArray(value)) {
136
+ for (const v of value)
137
+ result.append(key, v);
138
+ }
139
+ else {
140
+ result.set(key, value);
141
+ }
142
+ }
143
+ return result;
144
+ }
145
+ function toBody(req) {
146
+ if (req.method === 'GET' ||
147
+ req.method === 'HEAD' ||
148
+ req.method === 'OPTIONS') {
149
+ return null;
150
+ }
151
+ if (req.headers['content-type'] == null &&
152
+ req.headers['transfer-encoding'] == null &&
153
+ req.headers['content-length'] == null) {
154
+ return null;
155
+ }
156
+ return node_stream_1.Readable.toWeb(req);
157
+ }
158
+ async function handleRequest(req, res, handlerFn) {
159
+ const request = toRequest(req);
160
+ const info = toConnectionInfo(req);
161
+ const response = await handlerFn(request, info);
162
+ await sendResponse(req, res, response);
163
+ }
164
+ function toConnectionInfo(req) {
165
+ const { socket } = req;
166
+ return {
167
+ localAddr: socket.localAddress != null
168
+ ? {
169
+ hostname: socket.localAddress,
170
+ port: socket.localPort,
171
+ transport: 'tcp',
172
+ }
173
+ : undefined,
174
+ remoteAddr: socket.remoteAddress != null
175
+ ? {
176
+ hostname: socket.remoteAddress,
177
+ port: socket.remotePort,
178
+ transport: 'tcp',
179
+ }
180
+ : undefined,
181
+ };
182
+ }
183
+ function toRequestListener(handlerFn) {
184
+ return ((req, res, next) => {
185
+ handleRequest(req, res, handlerFn).catch((err) => {
186
+ if (next)
187
+ next(err);
188
+ else {
189
+ if (!res.headersSent) {
190
+ res.statusCode = 500;
191
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
192
+ res.end('Internal Server Error');
193
+ }
194
+ else if (!res.writableEnded) {
195
+ res.destroy();
196
+ }
197
+ }
198
+ });
199
+ });
200
+ }
201
+ function createServer(handler, options = {}) {
202
+ const handlerFn = typeof handler === 'function' ? handler : handler.handle.bind(handler);
203
+ const listener = toRequestListener(handlerFn);
204
+ const server = (0, node_http_1.createServer)(options, listener);
205
+ const terminator = (0, http_terminator_1.createHttpTerminator)({
206
+ server: server,
207
+ gracefulTerminationTimeout: options?.gracefulTerminationTimeout,
208
+ });
209
+ const terminate = async function terminate() {
210
+ if (this !== server) {
211
+ throw new TypeError('Server.terminate called with incorrect context');
212
+ }
213
+ // @TODO properly close all active WebSocket connections
214
+ return terminator.terminate();
215
+ };
216
+ Object.defineProperty(server, 'terminate', {
217
+ value: terminate,
218
+ enumerable: false,
219
+ configurable: false,
220
+ writable: false,
221
+ });
222
+ Object.defineProperty(server, Symbol.asyncDispose, {
223
+ value: terminate,
224
+ enumerable: false,
225
+ configurable: false,
226
+ writable: false,
227
+ });
228
+ return server;
229
+ }
230
+ async function serve(handler, options) {
231
+ const server = createServer(handler, options);
232
+ server.listen(options);
233
+ await (0, node_events_1.once)(server, 'listening');
234
+ return server;
235
+ }
236
+ //# sourceMappingURL=nodejs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodejs.js","sourceRoot":"","sources":["../src/nodejs.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events'\nimport {\n IncomingHttpHeaders,\n IncomingMessage,\n RequestListener,\n Server as HttpServer,\n ServerOptions,\n ServerResponse,\n createServer as createHttpServer,\n} from 'node:http'\nimport { ListenOptions } from 'node:net'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { createHttpTerminator } from 'http-terminator'\nimport { WebSocket as WebSocketPonyfill, WebSocketServer } from 'ws'\n\n// @ts-expect-error\nSymbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose')\n\nconst kResponseWs = Symbol.for('@atproto/lex-server:WebSocket')\n\nfunction isUpgradeRequest(request: Request, upgrade: string): boolean {\n return (\n request.method === 'GET' &&\n request.headers.get('connection')?.toLowerCase() === 'upgrade' &&\n request.headers.get('upgrade')?.toLowerCase() === upgrade\n )\n}\n\nexport function upgradeWebSocket(request: Request): {\n response: Response\n socket: WebSocket\n} {\n if (!isUpgradeRequest(request, 'websocket')) {\n throw new TypeError('upgradeWebSocket() expects a WebSocket upgrade')\n }\n\n // Placeholder response for WebSocket upgrade. The actual handling will happen\n // through the handleWebSocketUpgrade function. Headers set on the response\n // will be applied during the upgrade.\n const response = new Response(null, { status: 200 })\n\n // The Response constructor does not allow setting status 101, so we\n // define it directly. The purpose of this response is just to signal\n // that an upgrade is needed, and to carry any headers.\n Object.defineProperty(response, 'status', {\n value: 101,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n // @ts-expect-error\n const socket: WebSocket = new WebSocketPonyfill(null, undefined, {\n autoPong: true,\n })\n\n // Attach the WebSocket to the response for later retrieval\n Object.defineProperty(response, kResponseWs, {\n value: socket,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return { response, socket }\n}\n\nfunction handleWebSocketUpgrade(\n req: IncomingMessage,\n response: Response,\n): void {\n const ws = (response as { [kResponseWs]?: WebSocketPonyfill })[kResponseWs]\n if (!ws) throw new TypeError('Response not created by upgradeWebSocket()')\n\n // Create a one time use WebSocketServer to handle the upgrade\n const wss = new WebSocketServer({\n autoPong: true,\n noServer: true,\n clientTracking: false,\n perMessageDeflate: true,\n // @ts-expect-error\n WebSocket: function () {\n // Return the websocket that was created earlier instead of a new instance\n return ws\n },\n })\n\n // Apply headers that might have been set on the response object during\n // handling. This will be called during wss.handleUpgrade().\n wss.on('headers', (headers) => {\n for (const [name, value] of response.headers) {\n headers.push(`${name}: ${value}`)\n }\n })\n\n wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (_socket) => {\n // @TODO find a way to properly \"close\" the _socket when the server is\n // shutting down (might require replacing http-terminator with a local\n // implementation)\n })\n}\n\nasync function sendResponse(\n req: IncomingMessage,\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n // Invalid usage\n if (res.headersSent) {\n throw new TypeError('Response has already been sent')\n }\n\n if (response.status === 101) {\n return handleWebSocketUpgrade(req, response)\n }\n\n res.statusCode = response.status\n res.statusMessage = response.statusText\n\n for (const [key, value] of response.headers) {\n res.appendHeader(key, value)\n }\n\n if (response.body != null && req.method !== 'HEAD') {\n const stream = Readable.fromWeb(response.body as any)\n await pipeline(stream, res)\n } else {\n await response.body?.cancel()\n res.end()\n }\n}\n\nfunction toRequest(req: IncomingMessage): Request {\n const host = req.headers.host ?? req.socket?.localAddress ?? 'localhost'\n const isEncrypted = (req.socket as any).encrypted === true\n const protocol = isEncrypted ? 'https' : 'http'\n const url = new URL(req.url ?? '/', `${protocol}://${host}`)\n const headers = toHeaders(req.headers)\n const body = toBody(req)\n\n const abortController = new AbortController()\n const abort = (err?: Error) => abortController.abort(err)\n\n req.on('close', abort)\n req.on('error', abort)\n req.on('end', abort)\n\n abortController.signal.addEventListener(\n 'abort',\n () => {\n req.off('close', abort)\n req.off('error', abort)\n req.off('end', abort)\n },\n { once: true },\n )\n\n return new Request(url, {\n signal: abortController.signal,\n method: req.method,\n headers,\n body,\n referrer: headers.get('referrer') ?? headers.get('referer') ?? undefined,\n redirect: 'manual',\n // @ts-expect-error\n duplex: body ? 'half' : undefined,\n })\n}\n\nfunction toHeaders(headers: IncomingHttpHeaders): Headers {\n const result = new Headers()\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n for (const v of value) result.append(key, v)\n } else {\n result.set(key, value)\n }\n }\n return result\n}\n\nfunction toBody(req: IncomingMessage): null | ReadableStream<Uint8Array> {\n if (\n req.method === 'GET' ||\n req.method === 'HEAD' ||\n req.method === 'OPTIONS'\n ) {\n return null\n }\n\n if (\n req.headers['content-type'] == null &&\n req.headers['transfer-encoding'] == null &&\n req.headers['content-length'] == null\n ) {\n return null\n }\n\n return Readable.toWeb(req) as ReadableStream<Uint8Array>\n}\n\nexport type NetAddr = {\n hostname: string\n port: number\n transport: 'tcp'\n}\n\nexport type NodeConnectionInfo = {\n localAddr?: NetAddr\n remoteAddr?: NetAddr\n}\n\nexport interface HandlerFunction {\n (req: Request, info: NodeConnectionInfo): Response | Promise<Response>\n}\n\nexport interface HandlerObject {\n handle: HandlerFunction\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n handlerFn: HandlerFunction,\n) {\n const request = toRequest(req)\n const info = toConnectionInfo(req)\n const response = await handlerFn(request, info)\n await sendResponse(req, res, response)\n}\n\nfunction toConnectionInfo(req: IncomingMessage): NodeConnectionInfo {\n const { socket } = req\n return {\n localAddr:\n socket.localAddress != null\n ? {\n hostname: socket.localAddress,\n port: socket.localPort!,\n transport: 'tcp',\n }\n : undefined,\n remoteAddr:\n socket.remoteAddress != null\n ? {\n hostname: socket.remoteAddress,\n port: socket.remotePort!,\n transport: 'tcp',\n }\n : undefined,\n }\n}\n\nexport function toRequestListener<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(handlerFn: HandlerFunction) {\n return ((\n req: InstanceType<Request>,\n res: InstanceType<Response> & { req: InstanceType<Request> },\n next?: (err?: unknown) => void,\n ): void => {\n handleRequest(req, res, handlerFn).catch((err) => {\n if (next) next(err)\n else {\n if (!res.headersSent) {\n res.statusCode = 500\n res.setHeader('content-type', 'text/plain; charset=utf-8')\n res.end('Internal Server Error')\n } else if (!res.writableEnded) {\n res.destroy()\n }\n }\n })\n }) satisfies RequestListener<Request, Response>\n}\n\nexport type CreateServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ServerOptions<Request, Response> & {\n gracefulTerminationTimeout?: number\n}\n\nexport interface Server<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> extends HttpServer<Request, Response>,\n AsyncDisposable {\n terminate(): Promise<void>\n [Symbol.asyncDispose](): Promise<void>\n}\n\nexport function createServer<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: HandlerFunction | HandlerObject,\n options: CreateServerOptions<Request, Response> = {},\n): Server<Request, Response> {\n const handlerFn =\n typeof handler === 'function' ? handler : handler.handle.bind(handler)\n\n const listener = toRequestListener(handlerFn)\n const server = createHttpServer(options, listener)\n\n const terminator = createHttpTerminator({\n server: server as HttpServer,\n gracefulTerminationTimeout: options?.gracefulTerminationTimeout,\n })\n\n const terminate = async function terminate(this: Server<Request, Response>) {\n if (this !== server) {\n throw new TypeError('Server.terminate called with incorrect context')\n }\n // @TODO properly close all active WebSocket connections\n return terminator.terminate()\n }\n\n Object.defineProperty(server, 'terminate', {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n Object.defineProperty(server, Symbol.asyncDispose, {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return server as Server<Request, Response>\n}\n\nexport type StartServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ListenOptions & CreateServerOptions<Request, Response>\n\nexport async function serve<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: HandlerFunction | HandlerObject,\n options?: StartServerOptions<Request, Response>,\n): Promise<Server<Request, Response>> {\n const server = createServer(handler, options)\n server.listen(options)\n await once(server, 'listening')\n return server\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=subscripotion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscripotion.d.ts","sourceRoot":"","sources":["../src/subscripotion.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-namespace */
3
+ /* eslint-disable n/no-extraneous-import */
4
+ /* eslint-disable import/no-unresolved */
5
+ /* eslint-env node */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const lex_1 = require("@atproto/lex");
8
+ const index_js_1 = require("./index.js");
9
+ const nodejs_js_1 = require("./nodejs.js");
10
+ var com;
11
+ (function (com) {
12
+ let example;
13
+ (function (example) {
14
+ let echo;
15
+ (function (echo) {
16
+ echo.nsid = 'com.example.echo';
17
+ echo.message = lex_1.l.typedObject(echo.nsid, 'message', lex_1.l.object({
18
+ message: lex_1.l.string(),
19
+ }));
20
+ echo.main = lex_1.l.subscription(echo.nsid, lex_1.l.params({
21
+ message: lex_1.l.string({ minLength: 1 }),
22
+ interval: lex_1.l.optional(lex_1.l.integer({ minimum: 0, default: 500 })),
23
+ }), lex_1.l.typedUnion([lex_1.l.typedRef(() => echo.message)], false));
24
+ })(echo = example.echo || (example.echo = {}));
25
+ })(example = com.example || (com.example = {}));
26
+ })(com || (com = {}));
27
+ const router = new index_js_1.LexRouter({ upgradeWebSocket: nodejs_js_1.upgradeWebSocket })
28
+ //
29
+ .add(com.example.echo, async function* ({ params: { interval, message } }) {
30
+ while (true) {
31
+ yield com.example.echo.message.$build({ message });
32
+ await new Promise((resolve) => setTimeout(resolve, interval));
33
+ }
34
+ });
35
+ (0, nodejs_js_1.serve)(router, { port: 8080, host: '0.0.0.0' });
36
+ //# sourceMappingURL=subscripotion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscripotion.js","sourceRoot":"","sources":["../src/subscripotion.ts"],"names":[],"mappings":";AAAA,oDAAoD;AACpD,2CAA2C;AAC3C,yCAAyC;AACzC,qBAAqB;;AAErB,sCAAgC;AAChC,yCAAsC;AACtC,2CAAqD;AAErD,IAAU,GAAG,CAuBZ;AAvBD,WAAU,GAAG;IACX,IAAiB,OAAO,CAqBvB;IArBD,WAAiB,OAAO;QACtB,IAAiB,IAAI,CAmBpB;QAnBD,WAAiB,IAAI;YACN,SAAI,GAAG,kBAAkB,CAAA;YAEzB,YAAO,GAAG,OAAC,CAAC,WAAW,CAClC,KAAA,IAAI,EACJ,SAAS,EACT,OAAC,CAAC,MAAM,CAAC;gBACP,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;aACpB,CAAC,CACH,CAAA;YAEY,SAAI,GAAG,OAAC,CAAC,YAAY,CAChC,KAAA,IAAI,EACJ,OAAC,CAAC,MAAM,CAAC;gBACP,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;gBACnC,QAAQ,EAAE,OAAC,CAAC,QAAQ,CAAC,OAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;aAC9D,CAAC,EACF,OAAC,CAAC,UAAU,CAAC,CAAC,OAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAA,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CACjD,CAAA;QACH,CAAC,EAnBgB,IAAI,GAAJ,YAAI,KAAJ,YAAI,QAmBpB;IACH,CAAC,EArBgB,OAAO,GAAP,WAAO,KAAP,WAAO,QAqBvB;AACH,CAAC,EAvBS,GAAG,KAAH,GAAG,QAuBZ;AAED,MAAM,MAAM,GAAG,IAAI,oBAAS,CAAC,EAAE,gBAAgB,EAAhB,4BAAgB,EAAE,CAAC;IAChD,EAAE;KACD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IACvE,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAClD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,IAAA,iBAAK,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA","sourcesContent":["/* eslint-disable @typescript-eslint/no-namespace */\n/* eslint-disable n/no-extraneous-import */\n/* eslint-disable import/no-unresolved */\n/* eslint-env node */\n\nimport { l } from '@atproto/lex'\nimport { LexRouter } from './index.js'\nimport { serve, upgradeWebSocket } from './nodejs.js'\n\nnamespace com {\n export namespace example {\n export namespace echo {\n export const nsid = 'com.example.echo'\n\n export const message = l.typedObject(\n nsid,\n 'message',\n l.object({\n message: l.string(),\n }),\n )\n\n export const main = l.subscription(\n nsid,\n l.params({\n message: l.string({ minLength: 1 }),\n interval: l.optional(l.integer({ minimum: 0, default: 500 })),\n }),\n l.typedUnion([l.typedRef(() => message)], false),\n )\n }\n }\n}\n\nconst router = new LexRouter({ upgradeWebSocket })\n //\n .add(com.example.echo, async function* ({ params: { interval, message } }) {\n while (true) {\n yield com.example.echo.message.$build({ message })\n await new Promise((resolve) => setTimeout(resolve, interval))\n }\n })\n\nserve(router, { port: 8080, host: '0.0.0.0' })\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.mts","sourceRoot":"","sources":["../src/test.mjs"],"names":[],"mappings":""}
package/dist/test.mjs ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /* eslint-env node */
3
+ import { l } from '@atproto/lex';
4
+ import { LexRouter } from './lex-server.js';
5
+ import { startServer, upgradeWebSocket } from './nodejs.js';
6
+ const echoMessage = l.typedObject('com.example.echo', 'echoMessage', l.object({
7
+ message: l.string(),
8
+ }));
9
+ const sub = l.subscription('com.example.echo', l.params({
10
+ message: l.string({ minLength: 1 }),
11
+ }), l.typedUnion([
12
+ //
13
+ l.typedRef(() => echoMessage),
14
+ ], false));
15
+ const router = new LexRouter({
16
+ upgradeWebSocket,
17
+ onMethodNotFound: () => {
18
+ return new Response('<h1>404 Not Found</h1>', {
19
+ status: 404,
20
+ headers: { 'Content-Type': 'text/html' },
21
+ });
22
+ },
23
+ })
24
+ //
25
+ .add(sub, async function* ({ params: { message } }) {
26
+ try {
27
+ while (true) {
28
+ /** @type {l.TypedObject} */
29
+ const item = {
30
+ $type: 'com.example.echo#ff',
31
+ // @ts-expect-error
32
+ message,
33
+ };
34
+ yield item;
35
+ yield echoMessage.$build({ message });
36
+ await new Promise((resolve) => setTimeout(resolve, 500));
37
+ }
38
+ }
39
+ finally {
40
+ console.log('Subscription ended');
41
+ }
42
+ });
43
+ startServer(router, {
44
+ port: 8080,
45
+ onError: (err) => {
46
+ console.error('Server error:', err);
47
+ },
48
+ }).then((server) => {
49
+ const { port } = /** @type {import('net').AddressInfo} */ (server.address());
50
+ console.log(`Server is running on http://localhost:${port}`);
51
+ });
52
+ //# sourceMappingURL=test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.mjs","sourceRoot":"","sources":["../src/test.mjs"],"names":[],"mappings":";AAAA,qBAAqB;AAErB,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE3D,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAC/B,kBAAkB,EAClB,aAAa,EACb,CAAC,CAAC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;CACpB,CAAC,CACH,CAAA;AAED,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CACxB,kBAAkB,EAClB,CAAC,CAAC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;CACpC,CAAC,EACF,CAAC,CAAC,UAAU,CACV;IACE,EAAE;IACF,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC;CAC9B,EACD,KAAK,CACN,CACF,CAAA;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,gBAAgB;IAChB,gBAAgB,EAAE,GAAG,EAAE;QACrB,OAAO,IAAI,QAAQ,CAAC,wBAAwB,EAAE;YAC5C,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE;SACzC,CAAC,CAAA;IACJ,CAAC;CACF,CAAC;IACA,EAAE;KACD,GAAG,CAAC,GAAG,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,4BAA4B;YAC5B,MAAM,IAAI,GAAG;gBACX,KAAK,EAAE,qBAAqB;gBAC5B,mBAAmB;gBACnB,OAAO;aACR,CAAA;YAED,MAAM,IAAI,CAAA;YACV,MAAM,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;YACrC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;QAC1D,CAAC;IACH,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IACnC,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,WAAW,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACf,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IACrC,CAAC;CACF,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;IACjB,MAAM,EAAE,IAAI,EAAE,GAAG,wCAAwC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC5E,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAA;AAC9D,CAAC,CAAC,CAAA","sourcesContent":["/* eslint-env node */\n\nimport { l } from '@atproto/lex'\nimport { LexRouter } from './lex-server.js'\nimport { startServer, upgradeWebSocket } from './nodejs.js'\n\nconst echoMessage = l.typedObject(\n 'com.example.echo',\n 'echoMessage',\n l.object({\n message: l.string(),\n }),\n)\n\nconst sub = l.subscription(\n 'com.example.echo',\n l.params({\n message: l.string({ minLength: 1 }),\n }),\n l.typedUnion(\n [\n //\n l.typedRef(() => echoMessage),\n ],\n false,\n ),\n)\n\nconst router = new LexRouter({\n upgradeWebSocket,\n onMethodNotFound: () => {\n return new Response('<h1>404 Not Found</h1>', {\n status: 404,\n headers: { 'Content-Type': 'text/html' },\n })\n },\n})\n //\n .add(sub, async function* ({ params: { message } }) {\n try {\n while (true) {\n /** @type {l.TypedObject} */\n const item = {\n $type: 'com.example.echo#ff',\n // @ts-expect-error\n message,\n }\n\n yield item\n yield echoMessage.$build({ message })\n await new Promise((resolve) => setTimeout(resolve, 500))\n }\n } finally {\n console.log('Subscription ended')\n }\n })\n\nstartServer(router, {\n port: 8080,\n onError: (err) => {\n console.error('Server error:', err)\n },\n}).then((server) => {\n const { port } = /** @type {import('net').AddressInfo} */ (server.address())\n console.log(`Server is running on http://localhost:${port}`)\n})\n"]}
package/nodejs.js ADDED
@@ -0,0 +1,5 @@
1
+ /* eslint-env node, commonjs */
2
+
3
+ 'use strict'
4
+
5
+ module.exports = require('./dist/nodejs.js')
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@atproto/lex-server",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "description": "Request router for Atproto Lexicon protocols and schemas",
6
+ "keywords": [
7
+ "atproto",
8
+ "lexicon",
9
+ "router",
10
+ "typescript"
11
+ ],
12
+ "homepage": "https://atproto.com",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/bluesky-social/atproto",
16
+ "directory": "packages/lex/lex-server"
17
+ },
18
+ "files": [
19
+ "./src",
20
+ "./tsconfig.build.json",
21
+ "./tsconfig.tests.json",
22
+ "./tsconfig.json",
23
+ "./dist",
24
+ "./nodejs.js",
25
+ "./CHANGELOG.md"
26
+ ],
27
+ "sideEffects": false,
28
+ "type": "commonjs",
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "browser": "./dist/index.js",
35
+ "import": "./dist/index.js",
36
+ "require": "./dist/index.js"
37
+ },
38
+ "./nodejs": {
39
+ "types": "./dist/nodejs.d.ts",
40
+ "import": "./dist/nodejs.js",
41
+ "require": "./dist/nodejs.js"
42
+ }
43
+ },
44
+ "dependencies": {
45
+ "http-terminator": "^3.2.0",
46
+ "tslib": "^2.8.1",
47
+ "ws": "^8.18.3",
48
+ "@atproto/lex-cbor": "0.0.5",
49
+ "@atproto/lex-data": "0.0.5",
50
+ "@atproto/lex-schema": "0.0.6",
51
+ "@atproto/lex-json": "0.0.5"
52
+ },
53
+ "devDependencies": {
54
+ "@types/ws": "^8.18.1",
55
+ "@vitest/coverage-v8": "4.0.16",
56
+ "vitest": "^4.0.16",
57
+ "@atproto/lex": "0.0.8",
58
+ "@atproto/lex-client": "0.0.6"
59
+ },
60
+ "scripts": {
61
+ "build": "tsc --build tsconfig.build.json",
62
+ "test": "vitest run"
63
+ }
64
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { LexError, LexErrorCode } from '@atproto/lex-data'
2
+ import {
3
+ WWWAuthenticate,
4
+ formatWWWAuthenticateHeader,
5
+ } from './lib/www-authenticate.js'
6
+
7
+ export type { WWWAuthenticate }
8
+
9
+ export class LexServerAuthError<
10
+ N extends LexErrorCode = LexErrorCode,
11
+ > extends LexError<N> {
12
+ name = 'LexServerAuthError'
13
+
14
+ constructor(
15
+ error: N,
16
+ message: string,
17
+ readonly wwwAuthenticate: WWWAuthenticate = {},
18
+ options?: ErrorOptions,
19
+ ) {
20
+ super(error, message, options)
21
+ }
22
+
23
+ get wwwAuthenticateHeader(): string {
24
+ return formatWWWAuthenticateHeader(this.wwwAuthenticate)
25
+ }
26
+
27
+ toJSON() {
28
+ const { cause } = this
29
+ return cause instanceof LexError ? cause.toJSON() : super.toJSON()
30
+ }
31
+
32
+ toResponse(): Response {
33
+ const { wwwAuthenticateHeader } = this
34
+
35
+ const headers = wwwAuthenticateHeader
36
+ ? new Headers({
37
+ 'WWW-Authenticate': wwwAuthenticateHeader,
38
+ 'Access-Control-Expose-Headers': 'WWW-Authenticate', // CORS
39
+ })
40
+ : undefined
41
+
42
+ return Response.json(this.toJSON(), { status: 401, headers })
43
+ }
44
+
45
+ static from(
46
+ cause: LexError,
47
+ wwwAuthenticate?: WWWAuthenticate,
48
+ ): LexServerAuthError {
49
+ if (cause instanceof LexServerAuthError) return cause
50
+ return new LexServerAuthError(cause.error, cause.message, wwwAuthenticate, {
51
+ cause,
52
+ })
53
+ }
54
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+ LexError,
3
+ type LexErrorCode,
4
+ type LexErrorData,
5
+ } from '@atproto/lex-data'
6
+
7
+ export * from './errors.js'
8
+ export * from './lex-server.js'