@typerighter/rpc-client 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/client.d.ts +54 -0
- package/dist/core/client.js +195 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.js +67 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +114 -0
- package/package.json +7 -7
- package/src/core/client.ts +260 -0
- package/src/core/index.ts +10 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +252 -0
- package/tsconfig.json +15 -0
- package/src/typedown_server.d.ts +0 -272
- package/src/typedown_server.js +0 -9
- package/src/typedown_server_bg.js +0 -782
- package/src/typedown_server_bg.wasm +0 -0
- package/src/typedown_server_bg.wasm.d.ts +0 -42
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 client with Content-Length framing
|
|
3
|
+
* Simplified from vscode-jsonrpc (MIT, Microsoft Corporation)
|
|
4
|
+
* Ref: https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L506-L591
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createConnection } from 'node:net';
|
|
8
|
+
import type { Readable, Writable } from 'node:stream';
|
|
9
|
+
import type { Message, RequestMessage, NotificationMessage, ResponseMessage } from './types.js';
|
|
10
|
+
import { Message as MessageGuards, ErrorCodes, ResponseError } from './types.js';
|
|
11
|
+
|
|
12
|
+
type PendingRequest = {
|
|
13
|
+
resolve: (value: unknown) => void;
|
|
14
|
+
reject: (error: ResponseError) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// A Disposable interface to undo a previous side-effectful step
|
|
18
|
+
interface Disposable {
|
|
19
|
+
dispose (): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const HEADER_SEPARATOR = Buffer.from('\r\n\r\n', 'ascii');
|
|
23
|
+
const HEADER_SEPARATOR_LENGTH = 4; // \r\n\r\n
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Simplified MessageConnection interface
|
|
27
|
+
* https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L506-L591
|
|
28
|
+
*/
|
|
29
|
+
export interface MessageConnection extends Disposable {
|
|
30
|
+
/** Send a JSON-RPC request and wait for the response */
|
|
31
|
+
sendRequest<R> (method: string, params?: any[] | object): Promise<R>;
|
|
32
|
+
/** Send a one-way JSON-RPC notification (no response expected) */
|
|
33
|
+
sendNotification (method: string, params?: any[] | object): void;
|
|
34
|
+
/** Register a handler for server-sent notifications, returns an unsubscribe handle */
|
|
35
|
+
onNotification (method: string, handler: (params: any) => void): Disposable;
|
|
36
|
+
/** Register a listener for connection close, returns an unsubscribe handle */
|
|
37
|
+
onClose (listener: () => void): Disposable;
|
|
38
|
+
/** Reject all pending requests and stop listening */
|
|
39
|
+
dispose (): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class JsonRpcClient implements MessageConnection {
|
|
43
|
+
// Transport
|
|
44
|
+
private reader: Readable;
|
|
45
|
+
private writer: Writable;
|
|
46
|
+
|
|
47
|
+
// Request/response matching
|
|
48
|
+
private sequenceNumber = 1;
|
|
49
|
+
private responsePromises = new Map<number, PendingRequest>();
|
|
50
|
+
private notificationHandlers = new Map<string, (params: unknown) => void>();
|
|
51
|
+
|
|
52
|
+
// Inbound buffer, raw chunks to avoid corrupting multi-byte UTF-8
|
|
53
|
+
private chunks: Buffer[] = [];
|
|
54
|
+
private chunksLength = 0;
|
|
55
|
+
private contentLength = -1; // -1 = waiting for header
|
|
56
|
+
|
|
57
|
+
// Lifecycle
|
|
58
|
+
private disposed = false;
|
|
59
|
+
private closeHandlers: Array<() => void> = [];
|
|
60
|
+
|
|
61
|
+
constructor (reader: Readable, writer: Writable) {
|
|
62
|
+
this.reader = reader;
|
|
63
|
+
this.writer = writer;
|
|
64
|
+
|
|
65
|
+
this.reader.on('data', (chunk: Buffer) => this.onData(chunk));
|
|
66
|
+
this.reader.on('end', () => this.handleStreamClose());
|
|
67
|
+
this.reader.on('close', () => this.handleStreamClose());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static connectTcp (host: string, port: number): Promise<JsonRpcClient> {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const socket = createConnection({ host, port }, () => {
|
|
73
|
+
resolve(new JsonRpcClient(socket, socket));
|
|
74
|
+
});
|
|
75
|
+
socket.on('error', reject);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static connectStdio (stdin: Readable, stdout: Writable): JsonRpcClient {
|
|
80
|
+
return new JsonRpcClient(stdin, stdout);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L1563-L1590
|
|
84
|
+
dispose (): void {
|
|
85
|
+
if (this.disposed) return;
|
|
86
|
+
this.disposed = true;
|
|
87
|
+
|
|
88
|
+
this.rejectAllPending('Connection disposed');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L565
|
|
92
|
+
onClose (listener: () => void): Disposable {
|
|
93
|
+
this.closeHandlers.push(listener);
|
|
94
|
+
return {
|
|
95
|
+
dispose: () => {
|
|
96
|
+
const index = this.closeHandlers.indexOf(listener);
|
|
97
|
+
if (index >= 0) this.closeHandlers.splice(index, 1);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L537
|
|
103
|
+
sendRequest<R> (method: string, params?: any[] | object): Promise<R> {
|
|
104
|
+
const id = this.sequenceNumber++;
|
|
105
|
+
const message: RequestMessage = { jsonrpc: '2.0', id, method, params };
|
|
106
|
+
|
|
107
|
+
return new Promise<R>((resolve, reject) => {
|
|
108
|
+
this.responsePromises.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
109
|
+
this.writeMessage(message);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L549
|
|
114
|
+
sendNotification (method: string, params?: any[] | object): void {
|
|
115
|
+
const message: NotificationMessage = { jsonrpc: '2.0', method, params };
|
|
116
|
+
this.writeMessage(message);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/connection.ts#L557
|
|
120
|
+
onNotification (method: string, handler: (params: any) => void): Disposable {
|
|
121
|
+
this.notificationHandlers.set(method, handler);
|
|
122
|
+
return {
|
|
123
|
+
dispose: () => this.notificationHandlers.delete(method),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Accumulate chunk and parse as many complete messages as possible
|
|
128
|
+
// A single chunk can contain multiple messages
|
|
129
|
+
// Ref: vscode-jsonrpc ReadableStreamMessageReader.nextMessage
|
|
130
|
+
private onData (chunk: Buffer): void {
|
|
131
|
+
this.chunks.push(chunk);
|
|
132
|
+
this.chunksLength += chunk.length;
|
|
133
|
+
|
|
134
|
+
while (true) {
|
|
135
|
+
if (this.contentLength < 0) {
|
|
136
|
+
const buffer = this.compact();
|
|
137
|
+
const separatorIndex = buffer.indexOf(HEADER_SEPARATOR);
|
|
138
|
+
|
|
139
|
+
if (separatorIndex < 0) return;
|
|
140
|
+
|
|
141
|
+
const header = buffer.subarray(0, separatorIndex).toString('ascii');
|
|
142
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
143
|
+
|
|
144
|
+
if (!match) {
|
|
145
|
+
this.consume(separatorIndex + HEADER_SEPARATOR_LENGTH);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.contentLength = Number(match[1]);
|
|
150
|
+
this.consume(separatorIndex + HEADER_SEPARATOR_LENGTH);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (this.chunksLength < this.contentLength) return;
|
|
154
|
+
|
|
155
|
+
const buffer = this.compact();
|
|
156
|
+
const body = buffer.subarray(0, this.contentLength).toString('utf-8');
|
|
157
|
+
|
|
158
|
+
this.consume(this.contentLength);
|
|
159
|
+
this.contentLength = -1;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const message = JSON.parse(body);
|
|
163
|
+
|
|
164
|
+
if (MessageGuards.isResponse(message)) {
|
|
165
|
+
this.handleResponse(message);
|
|
166
|
+
} else if (MessageGuards.isNotification(message)) {
|
|
167
|
+
this.handleNotification(message);
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[jsonrpc] Failed to parse message:', error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Ref: vscode-jsonrpc connection.ts
|
|
176
|
+
|
|
177
|
+
private handleResponse (response: ResponseMessage): void {
|
|
178
|
+
if (typeof response.id !== 'number') return;
|
|
179
|
+
|
|
180
|
+
const pending = this.responsePromises.get(response.id);
|
|
181
|
+
|
|
182
|
+
if (!pending) return;
|
|
183
|
+
this.responsePromises.delete(response.id);
|
|
184
|
+
|
|
185
|
+
if (response.error) {
|
|
186
|
+
pending.reject(new ResponseError(response.error.code, response.error.message, response.error.data));
|
|
187
|
+
} else if (response.result !== undefined) {
|
|
188
|
+
pending.resolve(response.result);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private handleNotification (notification: NotificationMessage): void {
|
|
193
|
+
const handler = this.notificationHandlers.get(notification.method);
|
|
194
|
+
|
|
195
|
+
if (!handler) return;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
handler(notification.params);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(`[jsonrpc] Notification handler for '${notification.method}' threw:`, error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Cleanup
|
|
205
|
+
|
|
206
|
+
private rejectAllPending (reason: string): void {
|
|
207
|
+
for (const [, pending] of this.responsePromises) {
|
|
208
|
+
pending.reject(new ResponseError(ErrorCodes.PendingResponseRejected, reason));
|
|
209
|
+
}
|
|
210
|
+
this.responsePromises.clear();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Fires once on stream end/close
|
|
214
|
+
private handleStreamClose (): void {
|
|
215
|
+
if (this.disposed) return;
|
|
216
|
+
this.disposed = true;
|
|
217
|
+
|
|
218
|
+
this.rejectAllPending('Server disconnected');
|
|
219
|
+
|
|
220
|
+
for (const handler of this.closeHandlers) {
|
|
221
|
+
handler();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Helpers */
|
|
226
|
+
|
|
227
|
+
// Single buffer write to prevent interleaving under concurrent requests
|
|
228
|
+
private writeMessage (message: Message): void {
|
|
229
|
+
const body = Buffer.from(JSON.stringify(message), 'utf-8');
|
|
230
|
+
const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, 'ascii');
|
|
231
|
+
|
|
232
|
+
this.writer.write(Buffer.concat([header, body]));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Concat all chunks into 1 buffer
|
|
236
|
+
private compact (): Buffer {
|
|
237
|
+
if (this.chunks.length === 1) return this.chunks[0];
|
|
238
|
+
|
|
239
|
+
const buffer = Buffer.concat(this.chunks);
|
|
240
|
+
|
|
241
|
+
this.chunks = [buffer];
|
|
242
|
+
this.chunksLength = buffer.length;
|
|
243
|
+
|
|
244
|
+
return buffer;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Consume n bytes from buffer
|
|
248
|
+
private consume (bytes: number): void {
|
|
249
|
+
const buffer = this.compact();
|
|
250
|
+
const remaining = buffer.subarray(bytes);
|
|
251
|
+
|
|
252
|
+
if (remaining.length > 0) {
|
|
253
|
+
this.chunks = [remaining];
|
|
254
|
+
this.chunksLength = remaining.length;
|
|
255
|
+
} else {
|
|
256
|
+
this.chunks = [];
|
|
257
|
+
this.chunksLength = 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 message types and error codes
|
|
3
|
+
* Copied from vscode-jsonrpc (MIT, Microsoft Corporation)
|
|
4
|
+
* Ref: https://github.com/microsoft/vscode-languageserver-node/tree/main/jsonrpc/src/common/messages
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L11
|
|
8
|
+
export interface Message {
|
|
9
|
+
jsonrpc: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L18
|
|
13
|
+
export interface RequestMessage extends Message {
|
|
14
|
+
id: number | string | null;
|
|
15
|
+
method: string;
|
|
16
|
+
params?: any[] | object;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L150-L165
|
|
20
|
+
export interface ResponseMessage extends Message {
|
|
21
|
+
id: number | string | null;
|
|
22
|
+
result?: string | number | boolean | object | any[] | null;
|
|
23
|
+
error?: ResponseErrorLiteral<any>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L283-L292
|
|
27
|
+
export interface NotificationMessage extends Message {
|
|
28
|
+
method: string;
|
|
29
|
+
params?: any[] | object;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L105-L122
|
|
33
|
+
export interface ResponseErrorLiteral<D = void> {
|
|
34
|
+
code: number;
|
|
35
|
+
message: string;
|
|
36
|
+
data?: D;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L298-L323
|
|
40
|
+
export namespace Message {
|
|
41
|
+
export function isRequest (message: Message | undefined): message is RequestMessage {
|
|
42
|
+
const candidate = message as RequestMessage;
|
|
43
|
+
return candidate !== undefined && typeof candidate.method === 'string'
|
|
44
|
+
&& (typeof candidate.id === 'string' || typeof candidate.id === 'number');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isNotification (message: Message | undefined): message is NotificationMessage {
|
|
48
|
+
const candidate = message as NotificationMessage;
|
|
49
|
+
return candidate !== undefined && typeof candidate.method === 'string'
|
|
50
|
+
&& (candidate as any).id === undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isResponse (message: Message | undefined): message is ResponseMessage {
|
|
54
|
+
const candidate = message as ResponseMessage;
|
|
55
|
+
return candidate !== undefined
|
|
56
|
+
&& (candidate.result !== undefined || !!candidate.error)
|
|
57
|
+
&& (typeof candidate.id === 'string' || typeof candidate.id === 'number' || candidate.id === null);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L32-L83
|
|
62
|
+
export namespace ErrorCodes {
|
|
63
|
+
export const ParseError: -32700 = -32700;
|
|
64
|
+
export const InvalidRequest: -32600 = -32600;
|
|
65
|
+
export const MethodNotFound: -32601 = -32601;
|
|
66
|
+
export const InvalidParams: -32602 = -32602;
|
|
67
|
+
export const InternalError: -32603 = -32603;
|
|
68
|
+
|
|
69
|
+
// JSON-RPC reserved error range (-32099 to -32000)
|
|
70
|
+
export const jsonrpcReservedErrorRangeStart: -32099 = -32099;
|
|
71
|
+
export const MessageWriteError: -32099 = -32099;
|
|
72
|
+
export const MessageReadError: -32098 = -32098;
|
|
73
|
+
export const PendingResponseRejected: -32097 = -32097;
|
|
74
|
+
export const ConnectionInactive: -32096 = -32096;
|
|
75
|
+
export const ServerNotInitialized: -32002 = -32002;
|
|
76
|
+
export const UnknownErrorCode: -32001 = -32001;
|
|
77
|
+
export const jsonrpcReservedErrorRangeEnd: -32000 = -32000;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// https://github.com/microsoft/vscode-languageserver-node/blob/5010cdf9822e1038a30ee7eb6ee5d7aaa79acc4a/jsonrpc/src/common/messages.ts#L128-L148
|
|
81
|
+
export class ResponseError<D = void> extends Error {
|
|
82
|
+
readonly code: number;
|
|
83
|
+
readonly data: D | undefined;
|
|
84
|
+
|
|
85
|
+
constructor (code: number, message: string, data?: D) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.code = typeof code === 'number' ? code : ErrorCodes.UnknownErrorCode;
|
|
88
|
+
this.data = data;
|
|
89
|
+
Object.setPrototypeOf(this, ResponseError.prototype);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toJson (): ResponseErrorLiteral<D> {
|
|
93
|
+
const result: ResponseErrorLiteral<D> = {
|
|
94
|
+
code: this.code,
|
|
95
|
+
message: this.message,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (this.data !== undefined) {
|
|
99
|
+
result.data = this.data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typedown RPC client
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper over JsonRpcClient that provides typed methods
|
|
5
|
+
* matching the Rust RPC server's contract (rpc/contract.rs)
|
|
6
|
+
*
|
|
7
|
+
* The server (typedown-rpc binary) sends notifications for FS changes;
|
|
8
|
+
* register handlers via onContentChanged/onSchemaChanged/onConfigChanged
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Readable, Writable } from 'node:stream';
|
|
12
|
+
import { JsonRpcClient } from './core/index.js';
|
|
13
|
+
|
|
14
|
+
export { JsonRpcClient, Message, ResponseError, ErrorCodes } from './core/index.js';
|
|
15
|
+
export type { RequestMessage, ResponseMessage, NotificationMessage, ResponseErrorLiteral } from './core/index.js';
|
|
16
|
+
|
|
17
|
+
/* Types contract
|
|
18
|
+
* WARNING: must match the Rust contract in rpc/contract.rs */
|
|
19
|
+
|
|
20
|
+
export interface TdDiagnosticItem {
|
|
21
|
+
filepath: string;
|
|
22
|
+
line: number;
|
|
23
|
+
column: number;
|
|
24
|
+
severity: string;
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TdContentNotification {
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TdFileMetadata {
|
|
34
|
+
mtime: number;
|
|
35
|
+
ctime: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TdHeading {
|
|
39
|
+
level: number;
|
|
40
|
+
title: string;
|
|
41
|
+
slug: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TdSidebarItem {
|
|
45
|
+
filepath: string;
|
|
46
|
+
schema?: string;
|
|
47
|
+
schemaLabel?: string;
|
|
48
|
+
label?: string;
|
|
49
|
+
icon?: TdIcon;
|
|
50
|
+
metadata: TdFileMetadata;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TdContentSummary {
|
|
54
|
+
filepath: string;
|
|
55
|
+
schema?: string;
|
|
56
|
+
schemaLabel?: string;
|
|
57
|
+
label?: string;
|
|
58
|
+
icon?: TdIcon;
|
|
59
|
+
header: Record<string, any>;
|
|
60
|
+
excerpt?: string;
|
|
61
|
+
metadata: TdFileMetadata;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TdNavItem {
|
|
65
|
+
title: string;
|
|
66
|
+
link: string;
|
|
67
|
+
icon?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface TdIcon {
|
|
71
|
+
name: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TdDiagnosticReport {
|
|
75
|
+
diagnostics: TdDiagnosticItem[];
|
|
76
|
+
fileCount: number;
|
|
77
|
+
errorCount: number;
|
|
78
|
+
warningCount: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TdFormatResult {
|
|
82
|
+
content: string;
|
|
83
|
+
changed: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface TdSchemaNotification {
|
|
87
|
+
schema: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface TdSchemaInfo {
|
|
91
|
+
schema: string;
|
|
92
|
+
label: string;
|
|
93
|
+
properties: Record<string, any>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TdSiteConfig {
|
|
97
|
+
version: string;
|
|
98
|
+
basePath: string;
|
|
99
|
+
rootDir: string;
|
|
100
|
+
siteTitle: string;
|
|
101
|
+
siteDescription: string;
|
|
102
|
+
repo: string | undefined;
|
|
103
|
+
author: string | undefined;
|
|
104
|
+
license: string | undefined;
|
|
105
|
+
publicDir: string;
|
|
106
|
+
nav: TdNavItem[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface TdBuiltResource {
|
|
110
|
+
schema?: string;
|
|
111
|
+
schemaLabel?: string;
|
|
112
|
+
label?: string;
|
|
113
|
+
icon?: TdIcon;
|
|
114
|
+
header: Record<string, any>;
|
|
115
|
+
content: string;
|
|
116
|
+
headings: TdHeading[];
|
|
117
|
+
title?: string;
|
|
118
|
+
metadata: TdFileMetadata;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Server-reserved JSON-RPC error code for cancelled queries (-32000 to -32099)
|
|
122
|
+
export const RPC_CANCELLED_CODE = -32002;
|
|
123
|
+
|
|
124
|
+
/* Method names
|
|
125
|
+
* WARNING: must match Rust contract constants in rpc/contract.rs */
|
|
126
|
+
|
|
127
|
+
const METHOD_REQUEST_FILE = 'typedown_build.request_file';
|
|
128
|
+
const METHOD_REQUEST_FILES = 'typedown_build.request_files';
|
|
129
|
+
const METHOD_LIST_VAULT = 'typedown_build.list_vault';
|
|
130
|
+
const METHOD_LIST_FILES_GROUPED_BY_SCHEMA = 'typedown_build.list_files_grouped_by_schema';
|
|
131
|
+
const METHOD_LIST_SIDEBAR = 'typedown_build.list_sidebar';
|
|
132
|
+
const METHOD_LIST_SCHEMAS = 'typedown_build.list_schemas';
|
|
133
|
+
const METHOD_GET_SCHEMA = 'typedown_build.get_schema';
|
|
134
|
+
const METHOD_GET_VERSION = 'typedown_build.get_version';
|
|
135
|
+
const METHOD_GET_CONFIG = 'typedown_build.get_config';
|
|
136
|
+
const METHOD_CHECK_VAULT = 'typedown_build.check_vault';
|
|
137
|
+
const METHOD_FORMAT_FILE = 'typedown_build.format_file';
|
|
138
|
+
|
|
139
|
+
const NOTIF_CONTENT_CHANGED = 'typedown_build.content_changed';
|
|
140
|
+
const NOTIF_CONTENT_CREATED = 'typedown_build.content_created';
|
|
141
|
+
const NOTIF_CONTENT_DELETED = 'typedown_build.content_deleted';
|
|
142
|
+
const NOTIF_SCHEMA_CHANGED = 'typedown_build.schema_changed';
|
|
143
|
+
const NOTIF_SCHEMA_CREATED = 'typedown_build.schema_created';
|
|
144
|
+
const NOTIF_SCHEMA_DELETED = 'typedown_build.schema_deleted';
|
|
145
|
+
const NOTIF_CONFIG_CHANGED = 'typedown_build.config_changed';
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Typed RPC client for the Typedown build server
|
|
149
|
+
*
|
|
150
|
+
* Wraps a generic JsonRpcClient with typed request methods
|
|
151
|
+
* and notification handlers matching the Rust RPC contract
|
|
152
|
+
*/
|
|
153
|
+
export class RpcClient {
|
|
154
|
+
private rpc: JsonRpcClient;
|
|
155
|
+
|
|
156
|
+
private constructor (rpc: JsonRpcClient) {
|
|
157
|
+
this.rpc = rpc;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static async connectTcp (host: string, port: number): Promise<RpcClient> {
|
|
161
|
+
const rpc = await JsonRpcClient.connectTcp(host, port);
|
|
162
|
+
return new RpcClient(rpc);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static connectStdio (stdin: Readable, stdout: Writable): RpcClient {
|
|
166
|
+
return new RpcClient(JsonRpcClient.connectStdio(stdin, stdout));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
dispose (): void {
|
|
170
|
+
this.rpc.dispose();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
onClose (callback: () => void): { dispose: () => void } {
|
|
174
|
+
return this.rpc.onClose(callback);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Request methods */
|
|
178
|
+
|
|
179
|
+
requestFile (path: string): Promise<TdBuiltResource> {
|
|
180
|
+
return this.rpc.sendRequest(METHOD_REQUEST_FILE, { filePath: path });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
requestFiles (paths: string[]): Promise<TdBuiltResource[]> {
|
|
184
|
+
return this.rpc.sendRequest(METHOD_REQUEST_FILES, { filePaths: paths });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
listVault (): Promise<string[]> {
|
|
188
|
+
return this.rpc.sendRequest(METHOD_LIST_VAULT, {});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
listFilesGroupedBySchema (): Promise<Record<string, TdContentSummary[]>> {
|
|
192
|
+
return this.rpc.sendRequest(METHOD_LIST_FILES_GROUPED_BY_SCHEMA, {});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
listSidebar (): Promise<TdSidebarItem[]> {
|
|
196
|
+
return this.rpc.sendRequest(METHOD_LIST_SIDEBAR, {});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
listSchemas (): Promise<string[]> {
|
|
200
|
+
return this.rpc.sendRequest(METHOD_LIST_SCHEMAS, {});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
getSchema (schema: string): Promise<TdSchemaInfo> {
|
|
204
|
+
return this.rpc.sendRequest(METHOD_GET_SCHEMA, { schema });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getVersion (): Promise<string> {
|
|
208
|
+
return this.rpc.sendRequest(METHOD_GET_VERSION, {});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getConfig (): Promise<TdSiteConfig> {
|
|
212
|
+
return this.rpc.sendRequest(METHOD_GET_CONFIG, {});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
checkVault (): Promise<TdDiagnosticReport> {
|
|
216
|
+
return this.rpc.sendRequest(METHOD_CHECK_VAULT, {});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
formatFile (path: string): Promise<TdFormatResult> {
|
|
220
|
+
return this.rpc.sendRequest(METHOD_FORMAT_FILE, { filePath: path });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* Notification handlers, server pushes these when FS changes are detected */
|
|
224
|
+
|
|
225
|
+
onContentChanged (callback: (notification: TdContentNotification) => void): void {
|
|
226
|
+
this.rpc.onNotification(NOTIF_CONTENT_CHANGED, callback);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
onContentCreated (callback: (notification: TdContentNotification) => void): void {
|
|
230
|
+
this.rpc.onNotification(NOTIF_CONTENT_CREATED, callback);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
onContentDeleted (callback: (notification: TdContentNotification) => void): void {
|
|
234
|
+
this.rpc.onNotification(NOTIF_CONTENT_DELETED, callback);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
onSchemaChanged (callback: (notification: TdSchemaNotification) => void): void {
|
|
238
|
+
this.rpc.onNotification(NOTIF_SCHEMA_CHANGED, callback);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
onSchemaCreated (callback: (notification: TdSchemaNotification) => void): void {
|
|
242
|
+
this.rpc.onNotification(NOTIF_SCHEMA_CREATED, callback);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onSchemaDeleted (callback: (notification: TdSchemaNotification) => void): void {
|
|
246
|
+
this.rpc.onNotification(NOTIF_SCHEMA_DELETED, callback);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
onConfigChanged (callback: (config: TdSiteConfig) => void): void {
|
|
250
|
+
this.rpc.onNotification(NOTIF_CONFIG_CHANGED, callback);
|
|
251
|
+
}
|
|
252
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "NodeNext",
|
|
4
|
+
"moduleResolution": "NodeNext",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"rootDir": "./src"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|