Skip to content

Commit 599a189

Browse files
committed
Enable synchronous API connections to existing servers
Allow the synchronous JavaScript API to attach to standalone and LSP-hosted API sessions without spawning another compiler process. Use FIFO pairs on Unix and named pipes on Windows, with cancellable setup and cleanup.
1 parent bea2e84 commit 599a189

19 files changed

Lines changed: 770 additions & 169 deletions

File tree

‎packages/typescript/src/api/async/api.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
422422

423423
/**
424424
* Create an API instance from an existing LSP connection's API session.
425-
* Use this when connecting to an API pipe provided by an LSP server via custom/initializeAPISession.
425+
* Use this with the pipe returned by custom/initializeAPISession. The
426+
* requested session transport must match the async or sync API variant.
426427
*/
427428
static async fromLSPConnection(options: LSPConnectionOptions): Promise<API<true>> {
428429
const api = new API<true>(options);

‎packages/typescript/src/api/options.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import { fileURLToPath } from "node:url";
88
import type { FileSystemCallbacks } from "./fs.ts";
99

1010
export interface ClientSocketOptions {
11-
/** Path to the Unix domain socket or Windows named pipe for API communication */
11+
/**
12+
* Path returned by custom/initializeAPISession.
13+
*
14+
* The async API connects to a Unix domain socket or Windows named pipe.
15+
* The sync API connects to a FIFO pair on Unix or a named pipe on Windows.
16+
*/
1217
pipe: string;
1318
/** Maximum encoded byte size of each batch response page. Defaults to 300 million bytes. Individual responses can be larger than this size, but this controls where batch pages are cutoff. */
1419
maxResponseBytesPerPage?: number | undefined;

‎packages/typescript/src/api/sync/api.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
476476

477477
/**
478478
* Create an API instance from an existing LSP connection's API session.
479-
* Use this when connecting to an API pipe provided by an LSP server via custom/initializeAPISession.
479+
* Use this with the pipe returned by custom/initializeAPISession. The
480+
* requested session transport must match the async or sync API variant.
480481
*/
481482
static get fromLSPConnection(): {
482483
(options: LSPConnectionOptions): API<true>;

‎packages/typescript/src/api/sync/client.ts‎

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import type { FileSystemCallbacks } from "../fs.ts";
12
import {
23
configureFileSystemCallbacks,
34
encodeFileSystemCallbackResult,
5+
type FileSystemCallbackConfiguration,
46
} from "../fsCallbacks.ts";
57
import {
68
type ClientOptions,
@@ -35,30 +37,40 @@ export class Client {
3537
private maxResponseBytesPerPage: number | undefined;
3638

3739
constructor(options: ClientOptions) {
38-
if (!isSpawnOptions(options)) {
39-
throw new Error("Socket connections are not yet supported in the sync client");
40-
}
40+
let fsConfiguration: FileSystemCallbackConfiguration | undefined;
41+
let channel: SyncRpcChannel;
42+
let fs: FileSystemCallbacks | undefined;
43+
let collectTiming = false;
4144

42-
const args = getAPIProcessArgs(options, false);
4345
this.maxResponseBytesPerPage = options.maxResponseBytesPerPage;
46+
if (isSpawnOptions(options)) {
47+
const args = getAPIProcessArgs(options, false);
4448

45-
const fsConfiguration = configureFileSystemCallbacks(options.fs);
46-
if (fsConfiguration.arguments.length > 0) {
47-
args.push(`--callbacks=${fsConfiguration.arguments.join(",")}`);
48-
}
49+
fsConfiguration = configureFileSystemCallbacks(options.fs);
50+
if (fsConfiguration.arguments.length > 0) {
51+
args.push(`--callbacks=${fsConfiguration.arguments.join(",")}`);
52+
}
4953

50-
const collectTiming = options.collectTiming ?? false;
54+
collectTiming = options.collectTiming ?? false;
55+
fs = options.fs;
56+
channel = new SyncRpcChannel({
57+
exe: resolveExePath(options),
58+
args,
59+
}, collectTiming);
60+
}
61+
else {
62+
channel = new SyncRpcChannel({ pipe: options.pipe }, collectTiming);
63+
}
5164
if (collectTiming) {
5265
this.timing = new TimingCollector();
5366
}
5467

55-
const channel = new SyncRpcChannel(resolveExePath(options), args, collectTiming);
5668
this.channel = channel;
5769

58-
if (options.fs) {
70+
if (fs && fsConfiguration) {
5971
for (const name of fsConfiguration.callbackNames) {
6072
if (name === "writeFile") {
61-
const callback = options.fs.writeFile;
73+
const callback = fs.writeFile;
6274
if (typeof callback !== "function") throw new Error("Invalid writeFile callback configuration");
6375

6476
channel.registerCallback(name, (_, arg) => {
@@ -69,7 +81,7 @@ export class Client {
6981
continue;
7082
}
7183

72-
const callback = options.fs[name];
84+
const callback = fs[name];
7385
if (typeof callback !== "function") throw new Error(`Invalid ${name} callback configuration`);
7486
channel.registerCallback(name, (_, arg) => {
7587
return JSON.stringify(encodeFileSystemCallbackResult(name, callback(JSON.parse(arg))));

0 commit comments

Comments
 (0)