@grackle-ai/ahp-transport 0.132.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.
Files changed (46) hide show
  1. package/dist/ahp-client-socket.d.ts +109 -0
  2. package/dist/ahp-client-socket.d.ts.map +1 -0
  3. package/dist/ahp-client-socket.js +364 -0
  4. package/dist/ahp-client-socket.js.map +1 -0
  5. package/dist/ahp-server-socket.d.ts +111 -0
  6. package/dist/ahp-server-socket.d.ts.map +1 -0
  7. package/dist/ahp-server-socket.js +260 -0
  8. package/dist/ahp-server-socket.js.map +1 -0
  9. package/dist/backoff.d.ts +28 -0
  10. package/dist/backoff.d.ts.map +1 -0
  11. package/dist/backoff.js +31 -0
  12. package/dist/backoff.js.map +1 -0
  13. package/dist/client-id-store.d.ts +41 -0
  14. package/dist/client-id-store.d.ts.map +1 -0
  15. package/dist/client-id-store.js +66 -0
  16. package/dist/client-id-store.js.map +1 -0
  17. package/dist/error-codes.d.ts +45 -0
  18. package/dist/error-codes.d.ts.map +1 -0
  19. package/dist/error-codes.js +32 -0
  20. package/dist/error-codes.js.map +1 -0
  21. package/dist/examples/echo-subscriber.d.ts +29 -0
  22. package/dist/examples/echo-subscriber.d.ts.map +1 -0
  23. package/dist/examples/echo-subscriber.js +102 -0
  24. package/dist/examples/echo-subscriber.js.map +1 -0
  25. package/dist/heartbeat.d.ts +67 -0
  26. package/dist/heartbeat.d.ts.map +1 -0
  27. package/dist/heartbeat.js +79 -0
  28. package/dist/heartbeat.js.map +1 -0
  29. package/dist/index.d.ts +22 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +16 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/json-rpc-session.d.ts +106 -0
  34. package/dist/json-rpc-session.d.ts.map +1 -0
  35. package/dist/json-rpc-session.js +294 -0
  36. package/dist/json-rpc-session.js.map +1 -0
  37. package/dist/mocks/fake-websocket.d.ts +53 -0
  38. package/dist/mocks/fake-websocket.d.ts.map +1 -0
  39. package/dist/mocks/fake-websocket.js +86 -0
  40. package/dist/mocks/fake-websocket.js.map +1 -0
  41. package/dist/mocks/test-driver.d.ts +47 -0
  42. package/dist/mocks/test-driver.d.ts.map +1 -0
  43. package/dist/mocks/test-driver.js +122 -0
  44. package/dist/mocks/test-driver.js.map +1 -0
  45. package/dist/tsdoc-metadata.json +11 -0
  46. package/package.json +51 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Client-side AHP transport. Connects to an {@link AhpServerSocket} over a
3
+ * WebSocket, performs the AHP `initialize` handshake, persists the client
4
+ * identifier via a {@link ClientIdStore}, and reconnects with exponential
5
+ * backoff on transport failures.
6
+ *
7
+ * Channel-scoped concerns (per-`(host, channel)` `serverSeq` tracking,
8
+ * subscription replay, generation counter) live in `MultiHostClient`
9
+ * (HR8b). This class only owns the per-host connection lifecycle.
10
+ */
11
+ import type { CommandMap, InitializeResult } from "@grackle-ai/ahp";
12
+ import { WebSocket } from "ws";
13
+ import { type BackoffPolicy } from "./backoff.js";
14
+ import type { ClientIdStore } from "./client-id-store.js";
15
+ import { type NotificationHandler, type RequestHandler } from "./json-rpc-session.js";
16
+ /** Connection-lifecycle state for an {@link AhpClientSocket}. */
17
+ export type AhpConnectionState = "connecting" | "open" | "reconnecting" | "closed";
18
+ /** Construction options for {@link AhpClientSocket}. */
19
+ export interface AhpClientSocketOptions {
20
+ /** Full WebSocket URL, e.g. `ws://host:7433/ahp`. */
21
+ readonly url: string;
22
+ /** Bearer token sent on the HTTP upgrade. Empty string disables auth. */
23
+ readonly powerlineToken: string;
24
+ /** Persistent store for the AHP clientId across reconnects/restarts. */
25
+ readonly clientIdStore: ClientIdStore;
26
+ /** Namespacing key for the clientId in the store (e.g., the host identifier). */
27
+ readonly clientIdKey: string;
28
+ /** Called when the host sends a JSON-RPC notification. */
29
+ readonly onNotification?: NotificationHandler;
30
+ /** Called when the host sends a JSON-RPC request (e.g., resourceRequest). */
31
+ readonly onRequest?: RequestHandler;
32
+ /** Called on every connection-state transition. */
33
+ readonly onStateChange?: (state: AhpConnectionState) => void;
34
+ /** Override the reconnect backoff policy (testing). */
35
+ readonly backoff?: BackoffPolicy;
36
+ /** Override the WebSocket constructor (testing). */
37
+ readonly webSocketCtor?: typeof WebSocket;
38
+ /** Locale forwarded to the host in InitializeParams.locale. */
39
+ readonly locale?: string;
40
+ }
41
+ /**
42
+ * Bidirectional AHP transport over a single WebSocket with automatic
43
+ * reconnect, persistent client identity, and queued operations during
44
+ * reconnect.
45
+ *
46
+ * @example Connect, issue a typed request, react to server notifications:
47
+ * ```ts
48
+ * const client = new AhpClientSocket({
49
+ * url: "ws://127.0.0.1:7433/ahp",
50
+ * powerlineToken: process.env.GRACKLE_POWERLINE_TOKEN ?? "",
51
+ * clientIdStore: new FileClientIdStore(join(homedir(), ".grackle", "ahp")),
52
+ * clientIdKey: "powerline-1",
53
+ * onNotification: (n) => {
54
+ * if (n.method === "action") applyAction(n.params);
55
+ * },
56
+ * onStateChange: (state) => console.log("transport state:", state),
57
+ * });
58
+ * const result = await client.open();
59
+ * await client.request("createSession", { ... });
60
+ * ```
61
+ *
62
+ * @example Clean shutdown:
63
+ * ```ts
64
+ * await client.close();
65
+ * // client.state === "closed"; further request() calls reject immediately.
66
+ * ```
67
+ */
68
+ export declare class AhpClientSocket {
69
+ private readonly url;
70
+ private readonly powerlineToken;
71
+ private readonly clientIdStore;
72
+ private readonly clientIdKey;
73
+ private readonly onNotification;
74
+ private readonly onRequest;
75
+ private readonly onStateChange;
76
+ private readonly backoff;
77
+ private readonly WebSocketCtor;
78
+ private readonly locale;
79
+ private currentState;
80
+ private currentClientId;
81
+ private session;
82
+ private socket;
83
+ private userClosed;
84
+ private openInProgress;
85
+ private reconnectTimer;
86
+ private readonly pendingOps;
87
+ constructor(opts: AhpClientSocketOptions);
88
+ /** Current lifecycle state. */
89
+ get state(): AhpConnectionState;
90
+ /** Current AHP clientId, available once `open()` has resolved at least once. */
91
+ get clientId(): string | undefined;
92
+ /** Opens the connection and resolves after the initialize handshake. */
93
+ open(): Promise<InitializeResult>;
94
+ /** Sends a request; queues during reconnect, rejects after `.close()`. */
95
+ request<M extends keyof CommandMap>(method: M, params: CommandMap[M]["params"]): Promise<CommandMap[M]["result"]>;
96
+ /** Fires a notification; silently drops if not currently open. */
97
+ notify(method: string, params: unknown): void;
98
+ /** Closes the connection permanently. Idempotent. */
99
+ close(): Promise<void>;
100
+ private loadOrMintClientId;
101
+ private connectOnce;
102
+ private headersForUpgrade;
103
+ private handleSessionClose;
104
+ private scheduleReconnect;
105
+ private flushPendingOps;
106
+ private failPendingOps;
107
+ private setState;
108
+ }
109
+ //# sourceMappingURL=ahp-client-socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ahp-client-socket.d.ts","sourceRoot":"","sources":["../src/ahp-client-socket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Client-side AHP transport. Connects to an {@link AhpServerSocket} over a
3
+ * WebSocket, performs the AHP `initialize` handshake, persists the client
4
+ * identifier via a {@link ClientIdStore}, and reconnects with exponential
5
+ * backoff on transport failures.
6
+ *
7
+ * Channel-scoped concerns (per-`(host, channel)` `serverSeq` tracking,
8
+ * subscription replay, generation counter) live in `MultiHostClient`
9
+ * (HR8b). This class only owns the per-host connection lifecycle.
10
+ */
11
+ import { randomUUID } from "node:crypto";
12
+ import { WebSocket } from "ws";
13
+ import { exponentialBackoff } from "./backoff.js";
14
+ import { TransportError, WsCloseCode } from "./error-codes.js";
15
+ import { JsonRpcSession, } from "./json-rpc-session.js";
16
+ /** Protocol versions the client offers in `InitializeParams.protocolVersions`. */
17
+ const SUPPORTED_PROTOCOL_VERSIONS = ["0.1.0"];
18
+ /**
19
+ * Bidirectional AHP transport over a single WebSocket with automatic
20
+ * reconnect, persistent client identity, and queued operations during
21
+ * reconnect.
22
+ *
23
+ * @example Connect, issue a typed request, react to server notifications:
24
+ * ```ts
25
+ * const client = new AhpClientSocket({
26
+ * url: "ws://127.0.0.1:7433/ahp",
27
+ * powerlineToken: process.env.GRACKLE_POWERLINE_TOKEN ?? "",
28
+ * clientIdStore: new FileClientIdStore(join(homedir(), ".grackle", "ahp")),
29
+ * clientIdKey: "powerline-1",
30
+ * onNotification: (n) => {
31
+ * if (n.method === "action") applyAction(n.params);
32
+ * },
33
+ * onStateChange: (state) => console.log("transport state:", state),
34
+ * });
35
+ * const result = await client.open();
36
+ * await client.request("createSession", { ... });
37
+ * ```
38
+ *
39
+ * @example Clean shutdown:
40
+ * ```ts
41
+ * await client.close();
42
+ * // client.state === "closed"; further request() calls reject immediately.
43
+ * ```
44
+ */
45
+ export class AhpClientSocket {
46
+ url;
47
+ powerlineToken;
48
+ clientIdStore;
49
+ clientIdKey;
50
+ onNotification;
51
+ onRequest;
52
+ onStateChange;
53
+ backoff;
54
+ WebSocketCtor;
55
+ locale;
56
+ currentState = "closed";
57
+ currentClientId;
58
+ session;
59
+ socket;
60
+ userClosed = false;
61
+ openInProgress = false;
62
+ reconnectTimer;
63
+ pendingOps = [];
64
+ constructor(opts) {
65
+ this.url = opts.url;
66
+ this.powerlineToken = opts.powerlineToken;
67
+ this.clientIdStore = opts.clientIdStore;
68
+ this.clientIdKey = opts.clientIdKey;
69
+ this.onNotification = opts.onNotification;
70
+ this.onRequest = opts.onRequest;
71
+ this.onStateChange = opts.onStateChange;
72
+ this.backoff = opts.backoff ?? exponentialBackoff();
73
+ this.WebSocketCtor = opts.webSocketCtor ?? WebSocket;
74
+ this.locale = opts.locale;
75
+ }
76
+ /** Current lifecycle state. */
77
+ get state() {
78
+ return this.currentState;
79
+ }
80
+ /** Current AHP clientId, available once `open()` has resolved at least once. */
81
+ get clientId() {
82
+ return this.currentClientId;
83
+ }
84
+ /** Opens the connection and resolves after the initialize handshake. */
85
+ async open() {
86
+ if (this.userClosed) {
87
+ throw new TransportError("user-closed", "open() called after close()");
88
+ }
89
+ if (this.openInProgress || this.currentState !== "closed") {
90
+ // "connecting", "open", "reconnecting", or a concurrent open() in
91
+ // flight before the first await reached `connectOnce()`.
92
+ throw new TransportError("connection-lost", `open() called in state '${this.currentState}'`);
93
+ }
94
+ // Synchronously claim the lifecycle so a concurrent open() call (which
95
+ // also runs to its first await without yielding) sees the flag and bails.
96
+ this.openInProgress = true;
97
+ try {
98
+ this.currentClientId = this.currentClientId ?? (await this.loadOrMintClientId());
99
+ // close() may have been called while we were awaiting storage I/O. If
100
+ // so, don't establish a WebSocket — surface a user-closed error.
101
+ if (this.userClosed) {
102
+ throw new TransportError("user-closed", "close() called during open()");
103
+ }
104
+ return await this.connectOnce(/* isReconnect */ false);
105
+ }
106
+ finally {
107
+ this.openInProgress = false;
108
+ }
109
+ }
110
+ /** Sends a request; queues during reconnect, rejects after `.close()`. */
111
+ request(method, params) {
112
+ if (this.currentState === "closed" || this.userClosed) {
113
+ return Promise.reject(new TransportError("user-closed", `request ${String(method)} after close`));
114
+ }
115
+ if (this.currentState === "open" && this.session !== undefined) {
116
+ return this.session.request(method, params);
117
+ }
118
+ return new Promise((resolve, reject) => {
119
+ this.pendingOps.push({
120
+ run: (s) => {
121
+ s.request(method, params).then(resolve, reject);
122
+ },
123
+ reject,
124
+ });
125
+ });
126
+ }
127
+ /** Fires a notification; silently drops if not currently open. */
128
+ notify(method, params) {
129
+ if (this.currentState === "open" && this.session !== undefined) {
130
+ this.session.notify(method, params);
131
+ }
132
+ }
133
+ /** Closes the connection permanently. Idempotent. */
134
+ async close() {
135
+ if (this.userClosed) {
136
+ return;
137
+ }
138
+ this.userClosed = true;
139
+ if (this.reconnectTimer !== undefined) {
140
+ clearTimeout(this.reconnectTimer);
141
+ this.reconnectTimer = undefined;
142
+ }
143
+ const wasInTerminal = this.currentState === "closed";
144
+ if (this.session !== undefined) {
145
+ this.session.close(WsCloseCode.Normal, "client closed");
146
+ }
147
+ else if (this.socket !== undefined) {
148
+ try {
149
+ this.socket.close(WsCloseCode.Normal, "client closed");
150
+ }
151
+ catch {
152
+ // ignore
153
+ }
154
+ }
155
+ this.failPendingOps(new TransportError("user-closed", "close() called"));
156
+ if (!wasInTerminal) {
157
+ this.setState("closed");
158
+ }
159
+ }
160
+ // ─── Internals ─────────────────────────────────────────────────────
161
+ async loadOrMintClientId() {
162
+ const stored = await this.clientIdStore.load(this.clientIdKey);
163
+ if (stored !== undefined) {
164
+ return stored;
165
+ }
166
+ const minted = randomUUID();
167
+ await this.clientIdStore.save(this.clientIdKey, minted);
168
+ return minted;
169
+ }
170
+ connectOnce(isReconnect) {
171
+ this.setState(isReconnect ? "reconnecting" : "connecting");
172
+ return new Promise((resolveOuter, rejectOuter) => {
173
+ // Distinguishes "expected" closes (handshake failed, terminal teardown
174
+ // during connectOnce) from real transport drops that should trigger
175
+ // reconnect. Set BEFORE calling session.close() in the failure paths.
176
+ let suppressReconnect = false;
177
+ const failTransport = (err) => {
178
+ // Transport-layer failure (TCP error, WS upgrade rejected, etc.).
179
+ // On reconnect attempts, chain the next retry; on initial open,
180
+ // terminate.
181
+ rejectOuter(err);
182
+ if (isReconnect && !this.userClosed) {
183
+ this.scheduleReconnect();
184
+ }
185
+ else {
186
+ this.socket = undefined;
187
+ this.session = undefined;
188
+ this.failPendingOps(err);
189
+ this.setState("closed");
190
+ }
191
+ };
192
+ const failHandshake = (err) => {
193
+ // Handshake-layer failure (initialize rejected). Always terminal —
194
+ // a stale clientId or unsupported protocol won't fix itself on retry.
195
+ rejectOuter(err);
196
+ this.socket = undefined;
197
+ this.session = undefined;
198
+ this.failPendingOps(err);
199
+ this.setState("closed");
200
+ };
201
+ let ws;
202
+ try {
203
+ ws = new this.WebSocketCtor(this.url, {
204
+ headers: this.headersForUpgrade(),
205
+ });
206
+ }
207
+ catch (err) {
208
+ failTransport(err);
209
+ return;
210
+ }
211
+ this.socket = ws;
212
+ // `ws` emits `unexpected-response` for every non-101 HTTP response to
213
+ // the upgrade. ws delegates response handling to us once this event
214
+ // fires, so we MUST drain (or destroy) the response — otherwise the
215
+ // socket leaks. 401 is terminal (auth-failed, no point retrying with
216
+ // the same creds); other statuses (404, 426, 500, ...) are treated as
217
+ // transport failures so reconnect attempts chain via the backoff.
218
+ let upgradeRejected = false;
219
+ ws.once("unexpected-response", (_req, res) => {
220
+ upgradeRejected = true;
221
+ const status = res.statusCode ?? 0;
222
+ // Drain the response body so the socket can close cleanly.
223
+ res.resume();
224
+ // IncomingMessage can emit BOTH 'end' and 'close' during an upgrade
225
+ // rejection. Guard so fail-* fires exactly once — otherwise a
226
+ // reconnect-time non-401 would schedule duplicate retries.
227
+ let finished = false;
228
+ const done = () => {
229
+ if (finished) {
230
+ return;
231
+ }
232
+ finished = true;
233
+ if (status === 401) {
234
+ failHandshake(new TransportError("auth-failed", "host rejected upgrade with HTTP 401"));
235
+ }
236
+ else {
237
+ failTransport(new TransportError("connection-lost", `host rejected upgrade with HTTP ${status}`));
238
+ }
239
+ };
240
+ // Use both 'end' and 'close' so we don't hang if the server destroys
241
+ // the response without an explicit end. The `finished` guard above
242
+ // ensures we still complete exactly once.
243
+ res.once("end", done);
244
+ res.once("close", done);
245
+ });
246
+ const onError = (err) => {
247
+ // ws emits "error" before "close" on transport failures. If the
248
+ // session is already constructed (we're past initialize), let
249
+ // handleSessionClose drive recovery; otherwise this is a transport
250
+ // failure during the upgrade. `unexpected-response` handles non-101
251
+ // statuses; suppress here so we don't double-fail.
252
+ if (this.session !== undefined) {
253
+ return;
254
+ }
255
+ if (upgradeRejected) {
256
+ return;
257
+ }
258
+ failTransport(err);
259
+ };
260
+ ws.once("open", () => {
261
+ ws.off("error", onError);
262
+ const session = new JsonRpcSession({
263
+ socket: ws,
264
+ onRequest: this.onRequest,
265
+ onNotification: this.onNotification,
266
+ onClose: (code, reason) => {
267
+ if (suppressReconnect) {
268
+ // Expected close after a handshake failure — failHandshake
269
+ // already cleaned up the session/socket and set state.
270
+ void code;
271
+ void reason;
272
+ return;
273
+ }
274
+ this.handleSessionClose(code, reason);
275
+ },
276
+ });
277
+ this.session = session;
278
+ // Send initialize.
279
+ const params = {
280
+ channel: "ahp-root://",
281
+ protocolVersions: SUPPORTED_PROTOCOL_VERSIONS,
282
+ clientId: this.currentClientId,
283
+ ...(this.locale !== undefined ? { locale: this.locale } : {}),
284
+ };
285
+ session.request("initialize", params).then((result) => {
286
+ this.setState("open");
287
+ this.backoff.reset();
288
+ this.flushPendingOps();
289
+ resolveOuter(result);
290
+ }, (err) => {
291
+ // Handshake failure is terminal on both initial and reconnect
292
+ // attempts. Suppress the session-close handler so it doesn't
293
+ // schedule a reconnect, then run the handshake-failure cleanup.
294
+ suppressReconnect = true;
295
+ session.close(WsCloseCode.Normal, "initialize failed");
296
+ failHandshake(err);
297
+ });
298
+ });
299
+ ws.once("error", onError);
300
+ });
301
+ }
302
+ headersForUpgrade() {
303
+ if (this.powerlineToken === "") {
304
+ return {};
305
+ }
306
+ return { Authorization: `Bearer ${this.powerlineToken}` };
307
+ }
308
+ handleSessionClose(code, _reason) {
309
+ this.session = undefined;
310
+ this.socket = undefined;
311
+ if (this.userClosed) {
312
+ this.setState("closed");
313
+ return;
314
+ }
315
+ if (code === WsCloseCode.AuthRejected) {
316
+ this.failPendingOps(new TransportError("auth-failed", `host closed with code ${code}`));
317
+ this.setState("closed");
318
+ return;
319
+ }
320
+ this.scheduleReconnect();
321
+ }
322
+ scheduleReconnect() {
323
+ if (this.userClosed) {
324
+ return;
325
+ }
326
+ this.setState("reconnecting");
327
+ const delay = this.backoff.next();
328
+ this.reconnectTimer = setTimeout(() => {
329
+ this.reconnectTimer = undefined;
330
+ if (this.userClosed) {
331
+ return;
332
+ }
333
+ // Fire-and-forget; on success/failure we update state internally.
334
+ this.connectOnce(/* isReconnect */ true).catch(() => {
335
+ // connectOnce schedules its own reconnect on failure.
336
+ });
337
+ }, delay);
338
+ }
339
+ flushPendingOps() {
340
+ if (this.session === undefined) {
341
+ return;
342
+ }
343
+ const ops = this.pendingOps.splice(0, this.pendingOps.length);
344
+ for (const op of ops) {
345
+ op.run(this.session);
346
+ }
347
+ }
348
+ failPendingOps(err) {
349
+ const ops = this.pendingOps.splice(0, this.pendingOps.length);
350
+ for (const op of ops) {
351
+ op.reject(err);
352
+ }
353
+ }
354
+ setState(next) {
355
+ if (next === this.currentState) {
356
+ return;
357
+ }
358
+ this.currentState = next;
359
+ this.onStateChange?.(next);
360
+ }
361
+ }
362
+ // AhpRequest / AhpResponse are not re-exported from this package — consumers
363
+ // import them directly from `@grackle-ai/ahp`.
364
+ //# sourceMappingURL=ahp-client-socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ahp-client-socket.js","sourceRoot":"","sources":["../src/ahp-client-socket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Server-side AHP transport. Mounts on an existing HTTP/HTTP2 server,
3
+ * authenticates the upgrade request via `Authorization: Bearer <token>`,
4
+ * runs the AHP `initialize` handshake, then surfaces an
5
+ * {@link AhpServerConnection} (with a ready-to-use {@link JsonRpcSession})
6
+ * to the application.
7
+ *
8
+ * Heartbeat: WebSocket-level pings every 30s; 2 consecutive missed pongs
9
+ * close with code 4001.
10
+ */
11
+ import type { InitializeParams, InitializeResult, AhpNotification, AhpRequest, AhpResponse } from "@grackle-ai/ahp";
12
+ import type { Server as HttpServer } from "node:http";
13
+ import type { Http2SecureServer } from "node:http2";
14
+ import { JsonRpcSession } from "./json-rpc-session.js";
15
+ /** A connected, fully-initialized AHP client. */
16
+ export interface AhpServerConnection {
17
+ /** Client-supplied stable identifier from `InitializeParams.clientId`. */
18
+ readonly clientId: string;
19
+ /** The initialize params the client sent. */
20
+ readonly initializeParams: InitializeParams;
21
+ /** Bidirectional JSON-RPC session for this connection. */
22
+ readonly session: JsonRpcSession;
23
+ /** Remote socket address, if known. */
24
+ readonly remoteAddress: string | undefined;
25
+ }
26
+ /** Construction options for {@link AhpServerSocket}. */
27
+ export interface AhpServerSocketOptions {
28
+ /** Host HTTP/HTTP2 server. The socket attaches an "upgrade" listener. */
29
+ readonly server: HttpServer | Http2SecureServer;
30
+ /** Bearer token validated on the HTTP upgrade. Pass "" to disable auth (dev only). */
31
+ readonly powerlineToken: string;
32
+ /** Path component of the WS URL. Defaults to "/ahp". */
33
+ readonly path?: string;
34
+ /**
35
+ * Builds the `InitializeResult` (from `@grackle-ai/ahp`) given the client's
36
+ * `InitializeParams`. May return a Promise. Throw or reject to fail the
37
+ * handshake — the session will receive a JSON-RPC error response and close.
38
+ */
39
+ readonly onInitialize: (params: InitializeParams) => Promise<InitializeResult> | InitializeResult;
40
+ /** Handles requests other than `initialize`. */
41
+ readonly onRequest?: (req: AhpRequest, conn: AhpServerConnection) => Promise<AhpResponse>;
42
+ /**
43
+ * Handles client→server notifications (e.g., `dispatchAction`, `unsubscribe`).
44
+ * Fires only after the handshake completes; pre-initialize notifications are dropped.
45
+ */
46
+ readonly onNotification?: (notif: AhpNotification, conn: AhpServerConnection) => void;
47
+ /** Called once the handshake succeeds and the connection is usable. */
48
+ readonly onConnection?: (conn: AhpServerConnection) => void;
49
+ /** Called when a connection closes. */
50
+ readonly onDisconnect?: (clientId: string, code: number, reason: string) => void;
51
+ /** Override the WS-level ping interval (testing). Default 30_000ms. */
52
+ readonly heartbeatIntervalMs?: number;
53
+ /** Override the missed-pong threshold (testing). Default 2. */
54
+ readonly heartbeatMissedLimit?: number;
55
+ }
56
+ /**
57
+ * AHP-spec JSON-RPC/WebSocket server. Mounts on an existing
58
+ * `http.Server`/`http2.SecureServer` and surfaces fully-initialized
59
+ * `AhpServerConnection`s to the application.
60
+ *
61
+ * @example Mount on an HTTP server and accept one client:
62
+ * ```ts
63
+ * import { createServer } from "node:http";
64
+ * const server = createServer();
65
+ * server.listen(7433, "127.0.0.1");
66
+ *
67
+ * const ahp = new AhpServerSocket({
68
+ * server,
69
+ * powerlineToken: process.env.GRACKLE_POWERLINE_TOKEN ?? "",
70
+ * onInitialize: (params) => ({
71
+ * protocolVersion: "0.1.0",
72
+ * serverSeq: 0,
73
+ * snapshots: [],
74
+ * }),
75
+ * onConnection: (conn) => {
76
+ * console.log("client connected:", conn.clientId);
77
+ * conn.session.notify("action", { channel: "ahp-session:/x", serverSeq: 1, action: {...} });
78
+ * },
79
+ * onRequest: async (req, conn) => {
80
+ * // typed dispatch on req.method
81
+ * return { jsonrpc: "2.0", id: req.id, result: null };
82
+ * },
83
+ * });
84
+ * ```
85
+ */
86
+ export declare class AhpServerSocket {
87
+ private readonly server;
88
+ private readonly powerlineToken;
89
+ private readonly path;
90
+ private readonly onInitialize;
91
+ private readonly onRequest;
92
+ private readonly onNotification;
93
+ private readonly onConnection;
94
+ private readonly onDisconnect;
95
+ private readonly heartbeatIntervalMs;
96
+ private readonly heartbeatMissedLimit;
97
+ private readonly wss;
98
+ private readonly connections;
99
+ private readonly upgradeListener;
100
+ private closed;
101
+ constructor(opts: AhpServerSocketOptions);
102
+ /** Stops accepting new connections and closes all existing sessions. */
103
+ close(): Promise<void>;
104
+ private handleUpgrade;
105
+ private authorizeUpgrade;
106
+ private attachSocket;
107
+ private handleRequest;
108
+ private handleNotification;
109
+ private handleSocketClose;
110
+ }
111
+ //# sourceMappingURL=ahp-server-socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ahp-server-socket.d.ts","sourceRoot":"","sources":["../src/ahp-server-socket.ts"],"names":[],"mappings":"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"}