@atcute/xrpc-server 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -28
- package/dist/auth/jwt-creator.d.ts.map +1 -1
- package/dist/auth/jwt-creator.js.map +1 -1
- package/dist/auth/jwt-verifier.d.ts.map +1 -1
- package/dist/auth/jwt-verifier.js.map +1 -1
- package/dist/auth/jwt.d.ts +10 -15
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js.map +1 -1
- package/dist/main/index.d.ts +2 -0
- package/dist/main/index.d.ts.map +1 -1
- package/dist/main/index.js +2 -0
- package/dist/main/index.js.map +1 -1
- package/dist/main/response.js.map +1 -1
- package/dist/main/router.d.ts +15 -5
- package/dist/main/router.d.ts.map +1 -1
- package/dist/main/router.js +103 -16
- package/dist/main/router.js.map +1 -1
- package/dist/main/types/operation.d.ts +16 -1
- package/dist/main/types/operation.d.ts.map +1 -1
- package/dist/main/types/websocket.d.ts +10 -0
- package/dist/main/types/websocket.d.ts.map +1 -0
- package/dist/main/types/websocket.js +2 -0
- package/dist/main/types/websocket.js.map +1 -0
- package/dist/main/utils/event-emitter.d.ts +37 -0
- package/dist/main/utils/event-emitter.d.ts.map +1 -0
- package/dist/main/utils/event-emitter.js +96 -0
- package/dist/main/utils/event-emitter.js.map +1 -0
- package/dist/main/utils/frames.d.ts +5 -0
- package/dist/main/utils/frames.d.ts.map +1 -0
- package/dist/main/utils/frames.js +45 -0
- package/dist/main/utils/frames.js.map +1 -0
- package/dist/main/utils/middlewares.d.ts.map +1 -1
- package/dist/main/utils/middlewares.js.map +1 -1
- package/dist/main/utils/namespaced.d.ts +5 -0
- package/dist/main/utils/namespaced.d.ts.map +1 -0
- package/dist/main/utils/namespaced.js +4 -0
- package/dist/main/utils/namespaced.js.map +1 -0
- package/dist/main/utils/request-input.d.ts +1 -1
- package/dist/main/utils/request-input.d.ts.map +1 -1
- package/dist/main/utils/request-input.js.map +1 -1
- package/dist/main/utils/request-params.d.ts +1 -1
- package/dist/main/utils/request-params.d.ts.map +1 -1
- package/dist/main/utils/request-params.js.map +1 -1
- package/dist/main/utils/response.d.ts +1 -1
- package/dist/main/utils/response.d.ts.map +1 -1
- package/dist/main/utils/response.js.map +1 -1
- package/dist/main/utils/websocket-mock.d.ts +24 -0
- package/dist/main/utils/websocket-mock.d.ts.map +1 -0
- package/dist/main/utils/websocket-mock.js +71 -0
- package/dist/main/utils/websocket-mock.js.map +1 -0
- package/dist/main/xrpc-error.d.ts +12 -1
- package/dist/main/xrpc-error.d.ts.map +1 -1
- package/dist/main/xrpc-error.js +11 -0
- package/dist/main/xrpc-error.js.map +1 -1
- package/dist/main/xrpc-handler.d.ts +23 -0
- package/dist/main/xrpc-handler.d.ts.map +1 -0
- package/dist/main/xrpc-handler.js +19 -0
- package/dist/main/xrpc-handler.js.map +1 -0
- package/dist/middlewares/cors.d.ts.map +1 -1
- package/dist/middlewares/cors.js.map +1 -1
- package/lib/auth/jwt.ts +16 -5
- package/lib/main/index.ts +3 -0
- package/lib/main/router.ts +158 -23
- package/lib/main/types/operation.ts +33 -0
- package/lib/main/types/websocket.ts +14 -0
- package/lib/main/utils/event-emitter.ts +116 -0
- package/lib/main/utils/frames.ts +71 -0
- package/lib/main/utils/namespaced.ts +5 -0
- package/lib/main/utils/websocket-mock.ts +111 -0
- package/lib/main/xrpc-error.ts +20 -0
- package/lib/main/xrpc-handler.ts +54 -0
- package/package.json +17 -12
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** Converts a tuple into a listener function */
|
|
2
|
+
export type ListenerFor<T extends any[]> = (...args: T) => void;
|
|
3
|
+
|
|
4
|
+
/** Generic record of the event name and its argument tuple */
|
|
5
|
+
export type EventMap = {
|
|
6
|
+
[key: string | symbol]: any[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type MaybeArray<T> = T | T[];
|
|
10
|
+
|
|
11
|
+
/** Event emitter */
|
|
12
|
+
export class EventEmitter<Events extends EventMap> {
|
|
13
|
+
#events?: { [E in keyof Events]?: MaybeArray<ListenerFor<Events[E]>> };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Appends a listener for the specified event name
|
|
17
|
+
* @param name Name of the event
|
|
18
|
+
* @param listener Callback that should be invoked when an event is dispatched
|
|
19
|
+
* @returns Cleanup function that can be called to remove it
|
|
20
|
+
*/
|
|
21
|
+
on<E extends keyof Events>(name: E, listener: ListenerFor<Events[E]>): () => void {
|
|
22
|
+
let events = this.#events;
|
|
23
|
+
let existing: MaybeArray<ListenerFor<Events[E]>> | undefined;
|
|
24
|
+
|
|
25
|
+
if (events === undefined) {
|
|
26
|
+
events = this.#events = Object.create(null);
|
|
27
|
+
} else {
|
|
28
|
+
existing = events[name];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (existing === undefined) {
|
|
32
|
+
events![name] = listener;
|
|
33
|
+
} else if (typeof existing === 'function') {
|
|
34
|
+
events![name] = [existing, listener];
|
|
35
|
+
} else {
|
|
36
|
+
events![name] = existing.concat(listener);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// @ts-expect-error: complains about `listener`
|
|
40
|
+
return this.off.bind(this, name, listener);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove listener from the specified event name
|
|
45
|
+
* @param name Name of the event
|
|
46
|
+
* @param listener Callback to remove
|
|
47
|
+
*/
|
|
48
|
+
off<E extends keyof Events>(name: E, listener: ListenerFor<Events[E]>): void {
|
|
49
|
+
const events = this.#events;
|
|
50
|
+
|
|
51
|
+
if (events === undefined) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const list = events[name];
|
|
56
|
+
|
|
57
|
+
if (list == undefined) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (list === listener) {
|
|
62
|
+
delete events[name];
|
|
63
|
+
} else if (typeof list !== 'function') {
|
|
64
|
+
const index = list.indexOf(listener);
|
|
65
|
+
|
|
66
|
+
if (index !== -1) {
|
|
67
|
+
if (list.length === 2) {
|
|
68
|
+
// ^ flips the bit, it's either 0 or 1 here.
|
|
69
|
+
events[name] = list[index ^ 1];
|
|
70
|
+
} else {
|
|
71
|
+
events[name] = list.toSpliced(index, 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Emit an event with the specified name and its payload
|
|
79
|
+
* @param name Name of the event
|
|
80
|
+
* @param args Payload for the event
|
|
81
|
+
* @returns Whether a listener has been called
|
|
82
|
+
*/
|
|
83
|
+
emit<E extends keyof Events>(name: E, ...args: Events[E]): boolean {
|
|
84
|
+
const events = this.#events;
|
|
85
|
+
|
|
86
|
+
if (events === undefined) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const handler = events[name];
|
|
91
|
+
|
|
92
|
+
if (handler === undefined) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof handler === 'function') {
|
|
97
|
+
handler.apply(this, args);
|
|
98
|
+
} else {
|
|
99
|
+
for (let idx = 0, len = handler.length; idx < len; idx++) {
|
|
100
|
+
handler[idx].apply(this, args);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Determines if there is a listener on a specified event name
|
|
109
|
+
* @param name Name of the event
|
|
110
|
+
* @returns Whether there is a listener registered
|
|
111
|
+
*/
|
|
112
|
+
has(name: keyof Events): boolean {
|
|
113
|
+
const events = this.#events;
|
|
114
|
+
return events !== undefined && name in events;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
3
|
+
import type { Promisable } from '../../types/misc.js';
|
|
4
|
+
import type { XRPCRouter } from '../router.js';
|
|
5
|
+
import type { WebSocketAdapter, WebSocketConnection } from '../types/websocket.js';
|
|
6
|
+
import { EventEmitter } from './event-emitter.js';
|
|
7
|
+
|
|
8
|
+
interface WebSocketHandlerContext {
|
|
9
|
+
handler: ((ws: WebSocketConnection) => Promisable<void>) | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SubscriptionClient extends Disposable {
|
|
13
|
+
events: EventEmitter<{
|
|
14
|
+
message: [data: Uint8Array];
|
|
15
|
+
close: [event: { code: number; reason: string; wasClean: boolean }];
|
|
16
|
+
}>;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SubscriptionMock {
|
|
21
|
+
subscribe(url: string): Promise<SubscriptionClient>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class MockWebSocketAdapter implements WebSocketAdapter {
|
|
25
|
+
#context = new AsyncLocalStorage<WebSocketHandlerContext>();
|
|
26
|
+
|
|
27
|
+
upgrade(
|
|
28
|
+
_request: Request,
|
|
29
|
+
handler: (ws: WebSocketConnection) => Promisable<void>,
|
|
30
|
+
): Promisable<Response | undefined> {
|
|
31
|
+
const ctx = this.#context.getStore();
|
|
32
|
+
if (!ctx) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ctx.handler = handler;
|
|
37
|
+
|
|
38
|
+
return new Response(null);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
attach(router: XRPCRouter): SubscriptionMock {
|
|
42
|
+
return {
|
|
43
|
+
subscribe: async (url) => {
|
|
44
|
+
const ctx: WebSocketHandlerContext = {
|
|
45
|
+
handler: null,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await this.#context.run(ctx, async () => {
|
|
49
|
+
const urlp = new URL(url, 'http://localhost');
|
|
50
|
+
const request = new Request(urlp, {
|
|
51
|
+
headers: {
|
|
52
|
+
upgrade: 'websocket',
|
|
53
|
+
connection: 'upgrade',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const response = await router.fetch(request);
|
|
58
|
+
|
|
59
|
+
return response;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!ctx.handler) {
|
|
63
|
+
throw new Error(`WebSocket upgrade succeeded but no handler was set`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const events = new EventEmitter<{
|
|
67
|
+
message: [data: Uint8Array];
|
|
68
|
+
close: [event: { code: number; reason: string; wasClean: boolean }];
|
|
69
|
+
}>();
|
|
70
|
+
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const signal = controller.signal;
|
|
73
|
+
|
|
74
|
+
const connection: WebSocketConnection = {
|
|
75
|
+
signal: signal,
|
|
76
|
+
send(data) {
|
|
77
|
+
events.emit('message', data);
|
|
78
|
+
},
|
|
79
|
+
close(code = 1000, reason = '') {
|
|
80
|
+
if (!signal.aborted) {
|
|
81
|
+
events.emit('close', { code, reason, wasClean: true });
|
|
82
|
+
controller.abort();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
{
|
|
88
|
+
const handler = ctx.handler;
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
handler(connection);
|
|
91
|
+
}, 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const client: SubscriptionClient = {
|
|
95
|
+
events,
|
|
96
|
+
dispose() {
|
|
97
|
+
if (!signal.aborted) {
|
|
98
|
+
events.emit('close', { code: 1000, reason: '', wasClean: true });
|
|
99
|
+
controller.abort();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[Symbol.dispose]() {
|
|
103
|
+
this.dispose();
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return client;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
package/lib/main/xrpc-error.ts
CHANGED
|
@@ -78,3 +78,23 @@ export class UpstreamTimeoutError extends XRPCError {
|
|
|
78
78
|
super({ status, error, description });
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
export interface XRPCSubscriptionErrorOptions {
|
|
83
|
+
closeCode?: number;
|
|
84
|
+
error: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class XRPCSubscriptionError extends Error {
|
|
89
|
+
readonly closeCode: number;
|
|
90
|
+
readonly error: string;
|
|
91
|
+
readonly description?: string;
|
|
92
|
+
|
|
93
|
+
constructor({ closeCode = 1008, error, description }: XRPCSubscriptionErrorOptions) {
|
|
94
|
+
super(`Subscription error: ${error}${description ? ` - ${description}` : ''}`);
|
|
95
|
+
|
|
96
|
+
this.closeCode = closeCode;
|
|
97
|
+
this.error = error;
|
|
98
|
+
this.description = description;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { XRPCProcedureMetadata, XRPCQueryMetadata } from '@atcute/lexicons/validations';
|
|
2
|
+
|
|
3
|
+
import { XRPCRouter, type XRPCRouterOptions } from './router.js';
|
|
4
|
+
import type { ProcedureConfig, QueryConfig } from './types/operation.js';
|
|
5
|
+
import { unwrapLxm, type Namespaced } from './utils/namespaced.js';
|
|
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
|
+
{
|
|
16
|
+
lxm: TProcedure | Namespaced<TProcedure>;
|
|
17
|
+
routerOptions?: XrpcHandlerRouterOptions;
|
|
18
|
+
} & ProcedureConfig<TProcedure>;
|
|
19
|
+
|
|
20
|
+
export type XrpcHandlerOptions =
|
|
21
|
+
| XrpcQueryHandlerOptions<XRPCQueryMetadata>
|
|
22
|
+
| XrpcProcedureHandlerOptions<XRPCProcedureMetadata>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* create a fetch handler for a single xrpc query or procedure.
|
|
26
|
+
* requests are expected at `/xrpc/<nsid>`.
|
|
27
|
+
* subscriptions are not supported.
|
|
28
|
+
*/
|
|
29
|
+
export function createXrpcHandler<TQuery extends XRPCQueryMetadata>(
|
|
30
|
+
options: XrpcQueryHandlerOptions<TQuery>,
|
|
31
|
+
): (request: Request) => Promise<Response>;
|
|
32
|
+
export function createXrpcHandler<TProcedure extends XRPCProcedureMetadata>(
|
|
33
|
+
options: XrpcProcedureHandlerOptions<TProcedure>,
|
|
34
|
+
): (request: Request) => Promise<Response>;
|
|
35
|
+
export function createXrpcHandler(options: XrpcHandlerOptions): (request: Request) => Promise<Response> {
|
|
36
|
+
const { lxm, handler, routerOptions } = options;
|
|
37
|
+
|
|
38
|
+
const router = new XRPCRouter(routerOptions);
|
|
39
|
+
|
|
40
|
+
const schema = unwrapLxm(lxm);
|
|
41
|
+
|
|
42
|
+
switch (schema.type) {
|
|
43
|
+
case 'xrpc_query': {
|
|
44
|
+
router.addQuery(schema, { handler: handler as QueryConfig<XRPCQueryMetadata>['handler'] });
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'xrpc_procedure': {
|
|
48
|
+
router.addProcedure(schema, { handler: handler as ProcedureConfig<XRPCProcedureMetadata>['handler'] });
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return router.fetch;
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@atcute/xrpc-server",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.4",
|
|
5
5
|
"description": "a small web framework for handling XRPC operations",
|
|
6
6
|
"license": "0BSD",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/mary-ext/atcute",
|
|
9
9
|
"directory": "packages/servers/xrpc-server"
|
|
10
10
|
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
11
14
|
"files": [
|
|
12
15
|
"dist/",
|
|
13
16
|
"lib/",
|
|
@@ -21,23 +24,25 @@
|
|
|
21
24
|
},
|
|
22
25
|
"dependencies": {
|
|
23
26
|
"@badrap/valita": "^0.4.6",
|
|
24
|
-
"nanoid": "^5.1.
|
|
25
|
-
"@atcute/
|
|
26
|
-
"@atcute/
|
|
27
|
-
"@atcute/
|
|
28
|
-
"@atcute/identity-resolver": "^1.1
|
|
27
|
+
"nanoid": "^5.1.6",
|
|
28
|
+
"@atcute/cbor": "^2.2.8",
|
|
29
|
+
"@atcute/crypto": "^2.3.0",
|
|
30
|
+
"@atcute/identity": "^1.1.3",
|
|
31
|
+
"@atcute/identity-resolver": "^1.2.1",
|
|
29
32
|
"@atcute/multibase": "^1.1.6",
|
|
30
|
-
"@atcute/
|
|
33
|
+
"@atcute/lexicons": "^1.2.5",
|
|
34
|
+
"@atcute/uint8array": "^1.0.6"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@atcute/xrpc-server": "file:",
|
|
34
|
-
"@
|
|
35
|
-
"vitest": "^
|
|
36
|
-
"
|
|
37
|
-
"@atcute/
|
|
38
|
+
"@types/node": "^22.19.3",
|
|
39
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
40
|
+
"vitest": "^4.0.16",
|
|
41
|
+
"@atcute/atproto": "^3.1.9",
|
|
42
|
+
"@atcute/bluesky": "^3.2.14"
|
|
38
43
|
},
|
|
39
44
|
"scripts": {
|
|
40
|
-
"build": "
|
|
45
|
+
"build": "tsgo --project tsconfig.build.json",
|
|
41
46
|
"test": "vitest --coverage",
|
|
42
47
|
"prepublish": "rm -rf dist; pnpm run build"
|
|
43
48
|
}
|