@query-farm/vgi-rpc 0.7.5 → 0.8.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/README.md +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/tcp.d.ts +19 -0
- package/dist/client/tcp.d.ts.map +1 -0
- package/dist/client/types.d.ts +8 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1519 -1260
- package/dist/index.js.map +12 -10
- package/dist/launcher/index.d.ts +1 -0
- package/dist/launcher/index.d.ts.map +1 -1
- package/dist/launcher/serve-tcp.d.ts +66 -0
- package/dist/launcher/serve-tcp.d.ts.map +1 -0
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +1 -0
- package/src/client/tcp.ts +68 -0
- package/src/client/types.ts +8 -0
- package/src/index.ts +6 -0
- package/src/launcher/index.ts +1 -0
- package/src/launcher/serve-tcp.ts +382 -0
- package/src/types.ts +5 -0
package/dist/launcher/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
export { computeHash } from "./hash.js";
|
|
19
19
|
export { type LaunchConfig, launch } from "./launch.js";
|
|
20
20
|
export { acquireLock, type FileLockHandle, tryAcquireLock } from "./lock.js";
|
|
21
|
+
export { type ServeTcpHandle, type ServeTcpOptions, serveTcp } from "./serve-tcp.js";
|
|
21
22
|
export { type ServeUnixHandle, type ServeUnixOptions, serveUnix } from "./serve-unix.js";
|
|
22
23
|
export { defaultStateDir, type GcResult, gcStateDir, probeSocket, type SocketPaths, type StatusRow, socketPaths, statusRows, } from "./state.js";
|
|
23
24
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/launcher/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,eAAe,EACf,KAAK,QAAQ,EACb,UAAU,EACV,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/launcher/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,eAAe,EACf,KAAK,QAAQ,EACb,UAAU,EACV,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ExternalLocationConfig } from "../external.js";
|
|
2
|
+
import type { Protocol } from "../protocol.js";
|
|
3
|
+
import { type DispatchHook, type ServeStartHook } from "../types.js";
|
|
4
|
+
/** Configuration for {@link serveTcp}. */
|
|
5
|
+
export interface ServeTcpOptions {
|
|
6
|
+
/** Interface to bind. Defaults to `127.0.0.1` (loopback only). Binding a
|
|
7
|
+
* routable address exposes the unauthenticated framing on the network. */
|
|
8
|
+
host?: string;
|
|
9
|
+
/** TCP port to bind. Defaults to `0`, which lets the OS pick a free port
|
|
10
|
+
* (reported via the `TCP:<host>:<port>` line and {@link onBound}). */
|
|
11
|
+
port?: number;
|
|
12
|
+
/** Self-terminate after this many seconds with zero connected clients.
|
|
13
|
+
* Default: 300. `0` disables the timer (server runs until killed). */
|
|
14
|
+
idleTimeout?: number;
|
|
15
|
+
/** Grace period after `listen()` succeeds before the idle timer starts
|
|
16
|
+
* ticking. Default: 5 — gives the first launcher caller a chance to
|
|
17
|
+
* connect after the `TCP:<host>:<port>` announcement. */
|
|
18
|
+
startupGraceSeconds?: number;
|
|
19
|
+
/** Optional logical-service / protocol-contract version label. */
|
|
20
|
+
protocolVersion?: string;
|
|
21
|
+
/** Custom server identifier. */
|
|
22
|
+
serverId?: string;
|
|
23
|
+
/** Enable __describe__ method. Default: true. */
|
|
24
|
+
enableDescribe?: boolean;
|
|
25
|
+
/** Optional dispatch hook for observability. */
|
|
26
|
+
dispatchHook?: DispatchHook;
|
|
27
|
+
/** Optional external-storage config for large-batch externalisation. */
|
|
28
|
+
externalLocation?: ExternalLocationConfig;
|
|
29
|
+
/** Lifecycle hook fired once before the first dispatched request. */
|
|
30
|
+
onServeStart?: ServeStartHook;
|
|
31
|
+
/** Maximum listen backlog. Default: 128. */
|
|
32
|
+
backlog?: number;
|
|
33
|
+
/** Called *after* `listen()` returns successfully but *before*
|
|
34
|
+
* `TCP:<host>:<port>` is printed. Invoked with the bound host and the
|
|
35
|
+
* *actual* bound port (resolved when `port=0`). */
|
|
36
|
+
onBound?: (host: string, port: number) => void;
|
|
37
|
+
/** Override the stream used for the `TCP:<host>:<port>` line. Defaults to
|
|
38
|
+
* `process.stdout`. */
|
|
39
|
+
announcementSink?: NodeJS.WritableStream;
|
|
40
|
+
}
|
|
41
|
+
/** Handle returned by {@link serveTcp} for callers that want to stop the server. */
|
|
42
|
+
export interface ServeTcpHandle {
|
|
43
|
+
/** Host the server is listening on. */
|
|
44
|
+
readonly host: string;
|
|
45
|
+
/** Actual bound TCP port (resolved from `0` when the OS auto-selects). */
|
|
46
|
+
readonly port: number;
|
|
47
|
+
/** Shut down the listener. */
|
|
48
|
+
stop(): Promise<void>;
|
|
49
|
+
/** Promise that resolves when the server has stopped (idle timeout, stop(),
|
|
50
|
+
* or a fatal error). Mirrors Python's blocking `serve()` return. */
|
|
51
|
+
readonly done: Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Bind a TCP socket and serve `protocol` over per-connection IPC streams.
|
|
55
|
+
*
|
|
56
|
+
* The network analog of {@link serveUnix}: same raw Arrow-IPC framing, only
|
|
57
|
+
* the listening socket differs. Nagle's algorithm is disabled
|
|
58
|
+
* (`setNoDelay(true)`) on each connection so the lockstep request/response
|
|
59
|
+
* framing is not delayed waiting to coalesce writes.
|
|
60
|
+
*
|
|
61
|
+
* SECURITY: no authentication or TLS — trusted networks only; the default
|
|
62
|
+
* host is loopback (`127.0.0.1`). Use the HTTP transport for untrusted
|
|
63
|
+
* networks.
|
|
64
|
+
*/
|
|
65
|
+
export declare function serveTcp(protocol: Protocol, options?: ServeTcpOptions): Promise<ServeTcpHandle>;
|
|
66
|
+
//# sourceMappingURL=serve-tcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-tcp.d.ts","sourceRoot":"","sources":["../../src/launcher/serve-tcp.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAEL,KAAK,YAAY,EAGjB,KAAK,cAAc,EAEpB,MAAM,aAAa,CAAC;AAQrB,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B;+EAC2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;2EACuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;4EACwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;8DAE0D;IAC1D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kEAAkE;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gDAAgD;IAChD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,qEAAqE;IACrE,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;wDAEoD;IACpD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C;4BACwB;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAC1C;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB;0EACsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA2QzG"}
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export declare enum MethodType {
|
|
|
23
23
|
* - `PIPE` — Stdio worker (the standalone {@link VgiRpcServer} loop).
|
|
24
24
|
* - `HTTP` — Fetch-style HTTP handler (`createHttpHandler`).
|
|
25
25
|
* - `UNIX` — AF_UNIX socket handler (the launcher path).
|
|
26
|
+
* - `TCP` — AF_INET socket handler. Raw Arrow-IPC framing over a bare TCP
|
|
27
|
+
* socket — no auth/TLS; use `HTTP` for untrusted networks.
|
|
26
28
|
*/
|
|
27
29
|
export declare enum TransportKind {
|
|
28
30
|
/** Stdio worker — the standalone {@link VgiRpcServer} loop. */
|
|
@@ -30,7 +32,10 @@ export declare enum TransportKind {
|
|
|
30
32
|
/** Fetch-style HTTP handler (`createHttpHandler`). */
|
|
31
33
|
HTTP = "http",
|
|
32
34
|
/** AF_UNIX socket handler (the launcher path). */
|
|
33
|
-
UNIX = "unix"
|
|
35
|
+
UNIX = "unix",
|
|
36
|
+
/** AF_INET (TCP) socket handler. Raw Arrow-IPC framing over a bare TCP
|
|
37
|
+
* socket — no authentication or TLS; trusted networks only. */
|
|
38
|
+
TCP = "tcp"
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Optional lifecycle hook fired once per process before the first
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,OAAO,EAA6B,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC;;;GAGG;AACH,oBAAY,UAAU;IACpB,wDAAwD;IACxD,KAAK,UAAU;IACf,kEAAkE;IAClE,MAAM,WAAW;CAClB;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Raw-TCP RPC client.
|
|
6
|
+
*
|
|
7
|
+
* Node/Bun only — statically imports `node:net`, so this module is exported
|
|
8
|
+
* exclusively from the node barrel (`src/index.ts`), never from the
|
|
9
|
+
* runtime-agnostic core (which is re-exported into browser/workerd bundles).
|
|
10
|
+
*
|
|
11
|
+
* SECURITY: raw TCP carries no authentication or TLS — connect only to
|
|
12
|
+
* trusted endpoints. Use `httpConnect` for untrusted networks.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { connect } from "node:net";
|
|
16
|
+
import type { RpcClient } from "./connect.js";
|
|
17
|
+
import { pipeConnect } from "./pipe.js";
|
|
18
|
+
import type { TcpConnectOptions } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Connect to a vgi-rpc server over a raw TCP socket and wrap it with
|
|
22
|
+
* {@link pipeConnect}. The network analog of `subprocessConnect`: identical
|
|
23
|
+
* lockstep raw Arrow-IPC framing, only the transport differs. Nagle's
|
|
24
|
+
* algorithm is disabled (`setNoDelay(true)`) so lockstep requests are not
|
|
25
|
+
* delayed coalescing writes. The returned client's {@link RpcClient.close}
|
|
26
|
+
* also destroys the socket.
|
|
27
|
+
*
|
|
28
|
+
* SECURITY: raw TCP carries **no authentication or TLS** — only connect to
|
|
29
|
+
* trusted endpoints. Use `httpConnect` for untrusted networks.
|
|
30
|
+
*
|
|
31
|
+
* @param host Hostname or IP address of the TCP server.
|
|
32
|
+
* @param port TCP port of the server.
|
|
33
|
+
* @param options Optional log/external-location configuration.
|
|
34
|
+
*/
|
|
35
|
+
export function tcpConnect(host: string, port: number, options?: TcpConnectOptions): RpcClient {
|
|
36
|
+
const socket = connect({ host, port });
|
|
37
|
+
socket.setNoDelay(true);
|
|
38
|
+
// Swallow socket errors so an EPIPE/ECONNRESET doesn't crash the process;
|
|
39
|
+
// the in-flight call surfaces the failure as an EOF on the reader instead.
|
|
40
|
+
socket.on("error", () => {});
|
|
41
|
+
|
|
42
|
+
const readable = socket as unknown as ReadableStream<Uint8Array>;
|
|
43
|
+
const writable = {
|
|
44
|
+
write(data: Uint8Array): void {
|
|
45
|
+
socket.write(data);
|
|
46
|
+
},
|
|
47
|
+
end(): void {
|
|
48
|
+
socket.end();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const client = pipeConnect(readable, writable, {
|
|
53
|
+
onLog: options?.onLog,
|
|
54
|
+
externalLocation: options?.externalLocation,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const originalClose = client.close;
|
|
58
|
+
client.close = () => {
|
|
59
|
+
originalClose.call(client);
|
|
60
|
+
try {
|
|
61
|
+
socket.destroy();
|
|
62
|
+
} catch {
|
|
63
|
+
// Socket may already be closed
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return client;
|
|
68
|
+
}
|
package/src/client/types.ts
CHANGED
|
@@ -49,6 +49,14 @@ export interface PipeConnectOptions {
|
|
|
49
49
|
externalLocation?: import("../external.js").ExternalLocationConfig;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Options for {@link tcpConnect}, the raw-TCP-socket RPC client.
|
|
54
|
+
*
|
|
55
|
+
* SECURITY: raw TCP carries no authentication or TLS — connect only to
|
|
56
|
+
* trusted endpoints (use {@link httpConnect} for untrusted networks).
|
|
57
|
+
*/
|
|
58
|
+
export interface TcpConnectOptions extends PipeConnectOptions {}
|
|
59
|
+
|
|
52
60
|
/** Options for {@link subprocessConnect}, which spawns a server process and pipes to it. */
|
|
53
61
|
export interface SubprocessConnectOptions extends PipeConnectOptions {
|
|
54
62
|
/** Working directory for the spawned process. Defaults to the current directory. */
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
// Node-only raw-TCP client (statically imports `node:net`, so it lives outside
|
|
5
|
+
// the runtime-agnostic core that browser/workerd bundles re-export).
|
|
6
|
+
export { tcpConnect } from "./client/tcp.js";
|
|
4
7
|
// Full node/bun barrel: the runtime-agnostic core plus the node-only AF_UNIX
|
|
5
8
|
// launcher. Cloudflare Worker / browser builds resolve `./index.workerd.ts`
|
|
6
9
|
// (the `workerd`/`worker`/`browser` export conditions) instead, which omits
|
|
@@ -16,10 +19,13 @@ export {
|
|
|
16
19
|
type LaunchConfig,
|
|
17
20
|
launch,
|
|
18
21
|
probeSocket,
|
|
22
|
+
type ServeTcpHandle,
|
|
23
|
+
type ServeTcpOptions,
|
|
19
24
|
type ServeUnixHandle,
|
|
20
25
|
type ServeUnixOptions,
|
|
21
26
|
type SocketPaths,
|
|
22
27
|
type StatusRow,
|
|
28
|
+
serveTcp,
|
|
23
29
|
serveUnix,
|
|
24
30
|
socketPaths,
|
|
25
31
|
statusRows,
|
package/src/launcher/index.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
export { computeHash } from "./hash.js";
|
|
23
23
|
export { type LaunchConfig, launch } from "./launch.js";
|
|
24
24
|
export { acquireLock, type FileLockHandle, tryAcquireLock } from "./lock.js";
|
|
25
|
+
export { type ServeTcpHandle, type ServeTcpOptions, serveTcp } from "./serve-tcp.js";
|
|
25
26
|
export { type ServeUnixHandle, type ServeUnixOptions, serveUnix } from "./serve-unix.js";
|
|
26
27
|
export {
|
|
27
28
|
defaultStateDir,
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AF_INET (raw TCP) worker runner for vgi-rpc TypeScript.
|
|
6
|
+
*
|
|
7
|
+
* Bind a TCP socket on `(host, port)`, accept connections, and dispatch
|
|
8
|
+
* each via the same per-connection IPC-stream loop as {@link serveUnix}.
|
|
9
|
+
* This is the network analog of the AF_UNIX runner — identical raw
|
|
10
|
+
* Arrow-IPC framing, only the listening socket differs. Implements the
|
|
11
|
+
* cross-language launcher contract:
|
|
12
|
+
*
|
|
13
|
+
* - Accept `[HOST:]PORT` (host defaults to `127.0.0.1`, port `0` lets the
|
|
14
|
+
* OS pick a free port) and `--idle-timeout SEC`.
|
|
15
|
+
* - Emit `TCP:<host>:<port>\n` to stdout once bind+listen succeed, where
|
|
16
|
+
* `<port>` is the *actual* bound port (resolved when `port=0`).
|
|
17
|
+
* - Self-terminate after `idleTimeout` seconds with zero connected
|
|
18
|
+
* clients; the timer starts ticking only after a `startupGrace` window
|
|
19
|
+
* so a slow first caller doesn't see the server vanish.
|
|
20
|
+
*
|
|
21
|
+
* SECURITY: raw TCP carries **no authentication or TLS** — it is the bare
|
|
22
|
+
* framing protocol on a socket. The default host is loopback-only
|
|
23
|
+
* (`127.0.0.1`). Binding a routable address (e.g. `0.0.0.0`) exposes the
|
|
24
|
+
* unauthenticated protocol on the network and must only be done on a
|
|
25
|
+
* trusted network; use the HTTP transport otherwise.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { createServer, type Server, type Socket } from "node:net";
|
|
29
|
+
import { schema as makeSchema, serializeBatch } from "../arrow/index.js";
|
|
30
|
+
import { DESCRIBE_METHOD_NAME } from "../constants.js";
|
|
31
|
+
import { buildDescribeBatch } from "../dispatch/describe.js";
|
|
32
|
+
import { dispatchStream } from "../dispatch/stream.js";
|
|
33
|
+
import { dispatchUnary } from "../dispatch/unary.js";
|
|
34
|
+
import { RpcError, VersionError } from "../errors.js";
|
|
35
|
+
import type { ExternalLocationConfig } from "../external.js";
|
|
36
|
+
import type { Protocol } from "../protocol.js";
|
|
37
|
+
import {
|
|
38
|
+
type CallStatistics,
|
|
39
|
+
type DispatchHook,
|
|
40
|
+
type DispatchInfo,
|
|
41
|
+
MethodType,
|
|
42
|
+
type ServeStartHook,
|
|
43
|
+
TransportKind,
|
|
44
|
+
} from "../types.js";
|
|
45
|
+
import { IpcStreamReader } from "../wire/reader.js";
|
|
46
|
+
import { applyDefaults, parseRequest } from "../wire/request.js";
|
|
47
|
+
import { buildErrorBatch } from "../wire/response.js";
|
|
48
|
+
import { IpcStreamWriter } from "../wire/writer.js";
|
|
49
|
+
|
|
50
|
+
const EMPTY_SCHEMA = makeSchema([]);
|
|
51
|
+
|
|
52
|
+
/** Configuration for {@link serveTcp}. */
|
|
53
|
+
export interface ServeTcpOptions {
|
|
54
|
+
/** Interface to bind. Defaults to `127.0.0.1` (loopback only). Binding a
|
|
55
|
+
* routable address exposes the unauthenticated framing on the network. */
|
|
56
|
+
host?: string;
|
|
57
|
+
/** TCP port to bind. Defaults to `0`, which lets the OS pick a free port
|
|
58
|
+
* (reported via the `TCP:<host>:<port>` line and {@link onBound}). */
|
|
59
|
+
port?: number;
|
|
60
|
+
/** Self-terminate after this many seconds with zero connected clients.
|
|
61
|
+
* Default: 300. `0` disables the timer (server runs until killed). */
|
|
62
|
+
idleTimeout?: number;
|
|
63
|
+
/** Grace period after `listen()` succeeds before the idle timer starts
|
|
64
|
+
* ticking. Default: 5 — gives the first launcher caller a chance to
|
|
65
|
+
* connect after the `TCP:<host>:<port>` announcement. */
|
|
66
|
+
startupGraceSeconds?: number;
|
|
67
|
+
/** Optional logical-service / protocol-contract version label. */
|
|
68
|
+
protocolVersion?: string;
|
|
69
|
+
/** Custom server identifier. */
|
|
70
|
+
serverId?: string;
|
|
71
|
+
/** Enable __describe__ method. Default: true. */
|
|
72
|
+
enableDescribe?: boolean;
|
|
73
|
+
/** Optional dispatch hook for observability. */
|
|
74
|
+
dispatchHook?: DispatchHook;
|
|
75
|
+
/** Optional external-storage config for large-batch externalisation. */
|
|
76
|
+
externalLocation?: ExternalLocationConfig;
|
|
77
|
+
/** Lifecycle hook fired once before the first dispatched request. */
|
|
78
|
+
onServeStart?: ServeStartHook;
|
|
79
|
+
/** Maximum listen backlog. Default: 128. */
|
|
80
|
+
backlog?: number;
|
|
81
|
+
/** Called *after* `listen()` returns successfully but *before*
|
|
82
|
+
* `TCP:<host>:<port>` is printed. Invoked with the bound host and the
|
|
83
|
+
* *actual* bound port (resolved when `port=0`). */
|
|
84
|
+
onBound?: (host: string, port: number) => void;
|
|
85
|
+
/** Override the stream used for the `TCP:<host>:<port>` line. Defaults to
|
|
86
|
+
* `process.stdout`. */
|
|
87
|
+
announcementSink?: NodeJS.WritableStream;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Handle returned by {@link serveTcp} for callers that want to stop the server. */
|
|
91
|
+
export interface ServeTcpHandle {
|
|
92
|
+
/** Host the server is listening on. */
|
|
93
|
+
readonly host: string;
|
|
94
|
+
/** Actual bound TCP port (resolved from `0` when the OS auto-selects). */
|
|
95
|
+
readonly port: number;
|
|
96
|
+
/** Shut down the listener. */
|
|
97
|
+
stop(): Promise<void>;
|
|
98
|
+
/** Promise that resolves when the server has stopped (idle timeout, stop(),
|
|
99
|
+
* or a fatal error). Mirrors Python's blocking `serve()` return. */
|
|
100
|
+
readonly done: Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Bind a TCP socket and serve `protocol` over per-connection IPC streams.
|
|
105
|
+
*
|
|
106
|
+
* The network analog of {@link serveUnix}: same raw Arrow-IPC framing, only
|
|
107
|
+
* the listening socket differs. Nagle's algorithm is disabled
|
|
108
|
+
* (`setNoDelay(true)`) on each connection so the lockstep request/response
|
|
109
|
+
* framing is not delayed waiting to coalesce writes.
|
|
110
|
+
*
|
|
111
|
+
* SECURITY: no authentication or TLS — trusted networks only; the default
|
|
112
|
+
* host is loopback (`127.0.0.1`). Use the HTTP transport for untrusted
|
|
113
|
+
* networks.
|
|
114
|
+
*/
|
|
115
|
+
export async function serveTcp(protocol: Protocol, options: ServeTcpOptions = {}): Promise<ServeTcpHandle> {
|
|
116
|
+
const host = options.host ?? "127.0.0.1";
|
|
117
|
+
const requestedPort = options.port ?? 0;
|
|
118
|
+
const idleTimeoutS = options.idleTimeout ?? 300;
|
|
119
|
+
const startupGraceS = options.startupGraceSeconds ?? 5;
|
|
120
|
+
const protocolVersion = options.protocolVersion ?? "";
|
|
121
|
+
const serverId = options.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
122
|
+
const enableDescribe = options.enableDescribe ?? true;
|
|
123
|
+
const dispatchHook = options.dispatchHook ?? null;
|
|
124
|
+
const externalConfig = options.externalLocation;
|
|
125
|
+
const onServeStart = options.onServeStart ?? null;
|
|
126
|
+
const backlog = options.backlog ?? 128;
|
|
127
|
+
const announcementSink = options.announcementSink ?? process.stdout;
|
|
128
|
+
|
|
129
|
+
// Cache for __describe__ — Web-Crypto digest is async, so memoise.
|
|
130
|
+
let describePromise: Promise<{ batch: import("../arrow/index.js").VgiBatch; protocolHash: string }> | null = null;
|
|
131
|
+
function describeInfo(): Promise<{ batch: import("../arrow/index.js").VgiBatch; protocolHash: string }> {
|
|
132
|
+
if (!describePromise) {
|
|
133
|
+
describePromise = buildDescribeBatch(protocol.name, protocol.getMethods(), serverId).then(
|
|
134
|
+
({ batch, metadata }) => ({
|
|
135
|
+
batch,
|
|
136
|
+
protocolHash: metadata.get("vgi_rpc.protocol_hash") ?? "",
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return describePromise;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Lifecycle: only commit `serveStartFired` after the hook returns successfully.
|
|
144
|
+
let serveStartFired = false;
|
|
145
|
+
async function notifyTransport(): Promise<void> {
|
|
146
|
+
if (serveStartFired) return;
|
|
147
|
+
if (onServeStart) {
|
|
148
|
+
await onServeStart(TransportKind.TCP);
|
|
149
|
+
}
|
|
150
|
+
serveStartFired = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const server: Server = createServer({ allowHalfOpen: false });
|
|
154
|
+
|
|
155
|
+
let activeConnections = 0;
|
|
156
|
+
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
157
|
+
let resolveDone: () => void = () => {};
|
|
158
|
+
let rejectDone: (err: unknown) => void = () => {};
|
|
159
|
+
const done = new Promise<void>((resolve, reject) => {
|
|
160
|
+
resolveDone = resolve;
|
|
161
|
+
rejectDone = reject;
|
|
162
|
+
});
|
|
163
|
+
let stopped = false;
|
|
164
|
+
|
|
165
|
+
function armIdleTimer(): void {
|
|
166
|
+
if (idleTimeoutS <= 0) return;
|
|
167
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
168
|
+
idleTimer = setTimeout(() => {
|
|
169
|
+
if (activeConnections === 0 && !stopped) {
|
|
170
|
+
void shutdown();
|
|
171
|
+
}
|
|
172
|
+
}, idleTimeoutS * 1000);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function disarmIdleTimer(): void {
|
|
176
|
+
if (idleTimer) {
|
|
177
|
+
clearTimeout(idleTimer);
|
|
178
|
+
idleTimer = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function shutdown(): Promise<void> {
|
|
183
|
+
if (stopped) return;
|
|
184
|
+
stopped = true;
|
|
185
|
+
disarmIdleTimer();
|
|
186
|
+
await new Promise<void>((resolve) => {
|
|
187
|
+
server.close(() => resolve());
|
|
188
|
+
});
|
|
189
|
+
resolveDone();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
server.on("connection", (socket) => {
|
|
193
|
+
// Disable Nagle so the lockstep framing is not delayed coalescing writes.
|
|
194
|
+
try {
|
|
195
|
+
socket.setNoDelay(true);
|
|
196
|
+
} catch {
|
|
197
|
+
// best-effort
|
|
198
|
+
}
|
|
199
|
+
activeConnections += 1;
|
|
200
|
+
disarmIdleTimer();
|
|
201
|
+
handleConnection(socket)
|
|
202
|
+
.catch((err) => {
|
|
203
|
+
// Per-connection errors must not take down the server — log to stderr
|
|
204
|
+
// and let the next connection proceed.
|
|
205
|
+
process.stderr.write(`vgi-rpc/tcp: connection failed: ${(err as Error)?.message ?? err}\n`);
|
|
206
|
+
})
|
|
207
|
+
.finally(() => {
|
|
208
|
+
activeConnections -= 1;
|
|
209
|
+
socket.destroy();
|
|
210
|
+
if (activeConnections === 0 && !stopped) {
|
|
211
|
+
armIdleTimer();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
server.on("error", (err) => {
|
|
217
|
+
if (stopped) return;
|
|
218
|
+
rejectDone(err);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
async function handleConnection(socket: Socket): Promise<void> {
|
|
222
|
+
// The reader takes any Node Readable; sockets are duplex Readables.
|
|
223
|
+
const reader = await IpcStreamReader.create(socket);
|
|
224
|
+
// Build the writer over the Socket itself, not its raw fd, so we go
|
|
225
|
+
// through `socket.write` + `'drain'` and yield the event loop while the
|
|
226
|
+
// kernel send buffer drains (see serve-unix.ts for the rationale).
|
|
227
|
+
const writer = new IpcStreamWriter(socket);
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Fire on_serve_start lazily — first request retries on hook failure.
|
|
231
|
+
await notifyTransport();
|
|
232
|
+
|
|
233
|
+
while (true) {
|
|
234
|
+
try {
|
|
235
|
+
await serveOnce(reader, writer);
|
|
236
|
+
} catch (e: unknown) {
|
|
237
|
+
const err = e as { code?: string; message?: string };
|
|
238
|
+
// EOF/closed client → end this connection cleanly.
|
|
239
|
+
if (
|
|
240
|
+
err?.message?.includes("closed") ||
|
|
241
|
+
err?.message?.includes("Expected Schema Message") ||
|
|
242
|
+
err?.message?.includes("null or length 0") ||
|
|
243
|
+
err?.message?.includes("EOF") ||
|
|
244
|
+
err?.code === "EPIPE" ||
|
|
245
|
+
err?.code === "ERR_STREAM_PREMATURE_CLOSE" ||
|
|
246
|
+
err?.code === "ERR_STREAM_DESTROYED"
|
|
247
|
+
) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
throw e;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
try {
|
|
255
|
+
await reader.cancel();
|
|
256
|
+
} catch {
|
|
257
|
+
// already closed
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function serveOnce(reader: IpcStreamReader, writer: IpcStreamWriter): Promise<void> {
|
|
263
|
+
const stream = await reader.readStream();
|
|
264
|
+
if (!stream) {
|
|
265
|
+
throw new Error("EOF");
|
|
266
|
+
}
|
|
267
|
+
const { schema, batches } = stream;
|
|
268
|
+
if (batches.length === 0) {
|
|
269
|
+
const err = new RpcError("ProtocolError", "Request stream contains no batches", "");
|
|
270
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA, err, serverId, null);
|
|
271
|
+
await writer.writeStream(EMPTY_SCHEMA, [errBatch]);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const batch = batches[0];
|
|
275
|
+
let methodName: string;
|
|
276
|
+
let params: Record<string, unknown>;
|
|
277
|
+
let requestId: string | null;
|
|
278
|
+
try {
|
|
279
|
+
const parsed = parseRequest(schema, batch);
|
|
280
|
+
methodName = parsed.methodName;
|
|
281
|
+
params = parsed.params;
|
|
282
|
+
requestId = parsed.requestId;
|
|
283
|
+
} catch (e: unknown) {
|
|
284
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA, e as Error, serverId, null);
|
|
285
|
+
await writer.writeStream(EMPTY_SCHEMA, [errBatch]);
|
|
286
|
+
if (e instanceof VersionError || e instanceof RpcError) return;
|
|
287
|
+
throw e;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (methodName === DESCRIBE_METHOD_NAME && enableDescribe) {
|
|
291
|
+
const { batch: descBatch } = await describeInfo();
|
|
292
|
+
await writer.writeStream(descBatch.schema, [descBatch]);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const methods = protocol.getMethods();
|
|
297
|
+
const method = methods.get(methodName);
|
|
298
|
+
if (!method) {
|
|
299
|
+
const available = [...methods.keys()].sort();
|
|
300
|
+
const err = new Error(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
|
|
301
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA, err, serverId, requestId);
|
|
302
|
+
await writer.writeStream(EMPTY_SCHEMA, [errBatch]);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const methodType = method.type === MethodType.UNARY ? "unary" : "stream";
|
|
307
|
+
let requestData: Uint8Array | undefined;
|
|
308
|
+
try {
|
|
309
|
+
requestData = serializeBatch(batch);
|
|
310
|
+
} catch {
|
|
311
|
+
// best-effort
|
|
312
|
+
}
|
|
313
|
+
const { protocolHash } = await describeInfo();
|
|
314
|
+
const info: DispatchInfo = {
|
|
315
|
+
method: methodName,
|
|
316
|
+
methodType,
|
|
317
|
+
serverId,
|
|
318
|
+
requestId,
|
|
319
|
+
protocol: protocol.name,
|
|
320
|
+
protocolHash,
|
|
321
|
+
protocolVersion,
|
|
322
|
+
kind: TransportKind.TCP,
|
|
323
|
+
principal: "",
|
|
324
|
+
authDomain: "",
|
|
325
|
+
authenticated: false,
|
|
326
|
+
remoteAddr: "",
|
|
327
|
+
requestData,
|
|
328
|
+
};
|
|
329
|
+
const stats: CallStatistics = {
|
|
330
|
+
inputBatches: 0,
|
|
331
|
+
outputBatches: 0,
|
|
332
|
+
inputRows: 0,
|
|
333
|
+
outputRows: 0,
|
|
334
|
+
inputBytes: 0,
|
|
335
|
+
outputBytes: 0,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const token = dispatchHook?.onDispatchStart(info);
|
|
339
|
+
let dispatchError: Error | undefined;
|
|
340
|
+
applyDefaults(params, method.defaults);
|
|
341
|
+
try {
|
|
342
|
+
if (method.type === MethodType.UNARY) {
|
|
343
|
+
await dispatchUnary(method, params, writer, serverId, requestId, externalConfig, TransportKind.TCP);
|
|
344
|
+
} else {
|
|
345
|
+
await dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, TransportKind.TCP);
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {
|
|
348
|
+
dispatchError = e instanceof Error ? e : new Error(String(e));
|
|
349
|
+
throw e;
|
|
350
|
+
} finally {
|
|
351
|
+
dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// bind + listen
|
|
356
|
+
await new Promise<void>((resolve, reject) => {
|
|
357
|
+
server.listen({ host, port: requestedPort, backlog }, () => resolve());
|
|
358
|
+
server.once("error", (err) => reject(err));
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const address = server.address();
|
|
362
|
+
const boundPort = typeof address === "object" && address ? address.port : requestedPort;
|
|
363
|
+
|
|
364
|
+
options.onBound?.(host, boundPort);
|
|
365
|
+
// Cross-language launcher contract: announce on stdout, then write nothing
|
|
366
|
+
// more to stdout for the rest of the process lifetime.
|
|
367
|
+
announcementSink.write(`TCP:${host}:${boundPort}\n`);
|
|
368
|
+
|
|
369
|
+
// Start the idle timer with a startup grace window.
|
|
370
|
+
if (idleTimeoutS > 0) {
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
if (activeConnections === 0 && !stopped) armIdleTimer();
|
|
373
|
+
}, startupGraceS * 1000).unref?.();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
host,
|
|
378
|
+
port: boundPort,
|
|
379
|
+
stop: shutdown,
|
|
380
|
+
done,
|
|
381
|
+
};
|
|
382
|
+
}
|