@hediet/linkrpc-infra 0.0.1

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.
@@ -0,0 +1,439 @@
1
+ import { discriminatedUnion, enum as enum$1, literal, object, optional, unknown } from "zod/mini";
2
+ import { defineInterface, requestType } from "@hediet/linkrpc";
3
+ import * as readline from "node:readline";
4
+ //#region src/json-rpc/client.ts
5
+ const MAX_RAW_FRAME_BACKLOG = 1024;
6
+ async function connectRawJsonRpcTransport(client, params) {
7
+ const messageListeners = /* @__PURE__ */ new Set();
8
+ const closeListeners = /* @__PURE__ */ new Set();
9
+ const backlog = [];
10
+ let buffering = true;
11
+ let closed = false;
12
+ let closeReason;
13
+ const finish = (reason) => {
14
+ if (closed) return;
15
+ closed = true;
16
+ closeReason = reason;
17
+ rejectReady(/* @__PURE__ */ new Error(`Raw JSON-RPC transport closed before ready: ${reason ?? "closed"}`));
18
+ for (const listener of closeListeners) listener(reason);
19
+ };
20
+ let resolveReady;
21
+ let rejectReady;
22
+ const ready = new Promise((resolve, reject) => {
23
+ resolveReady = resolve;
24
+ rejectReady = reject;
25
+ });
26
+ const call = client.connectRaw(params === void 0 ? {} : { params }, { onMessage: (event) => {
27
+ if (event.type === "ready") {
28
+ resolveReady();
29
+ return;
30
+ }
31
+ const frame = event.frame;
32
+ if (buffering) {
33
+ if (backlog.length >= MAX_RAW_FRAME_BACKLOG) {
34
+ const reason = "Raw JSON-RPC frame backlog exceeded";
35
+ finish(reason);
36
+ call.cancel(reason);
37
+ return;
38
+ }
39
+ backlog.push(frame);
40
+ return;
41
+ }
42
+ for (const listener of messageListeners) listener(frame);
43
+ } });
44
+ call.then(({ reason }) => finish(reason), (error) => {
45
+ rejectReady(error);
46
+ finish(error instanceof Error ? error.message : String(error));
47
+ });
48
+ await ready;
49
+ return {
50
+ get closed() {
51
+ return closed;
52
+ },
53
+ async send(frame) {
54
+ if (closed) throw new Error(`JSON-RPC transport is closed: ${closeReason ?? "closed"}`);
55
+ await call.send({ frame });
56
+ },
57
+ onMessage(listener) {
58
+ const registration = addListener(messageListeners, listener);
59
+ if (buffering) {
60
+ buffering = false;
61
+ for (const frame of backlog.splice(0)) listener(frame);
62
+ }
63
+ return registration;
64
+ },
65
+ onClose(listener) {
66
+ if (closed) {
67
+ let disposed = false;
68
+ queueMicrotask(() => {
69
+ if (!disposed) listener(closeReason);
70
+ });
71
+ return { dispose: () => {
72
+ disposed = true;
73
+ } };
74
+ }
75
+ return addListener(closeListeners, listener);
76
+ },
77
+ close(reason) {
78
+ finish(reason ?? "cancelled");
79
+ call.cancel(reason);
80
+ }
81
+ };
82
+ }
83
+ function addListener(listeners, listener) {
84
+ listeners.add(listener);
85
+ return { dispose: () => listeners.delete(listener) };
86
+ }
87
+ //#endregion
88
+ //#region src/json-rpc/interface.ts
89
+ const jsonRpcConnectionCloseReasonSchema = enum$1([
90
+ "cancelled",
91
+ "closed",
92
+ "disposed",
93
+ "remoteClosed"
94
+ ]);
95
+ const rawFrameSchema = object({ frame: unknown() });
96
+ /**
97
+ * Opens a transparent JSON-RPC transport over one duplex LinkRPC request.
98
+ * Frames are intentionally opaque; the endpoint owns JSON-RPC semantics.
99
+ */
100
+ const jsonRpcConnectionInterface = defineInterface({
101
+ id: "jsonRpcConnection",
102
+ description: "Opens transparent JSON-RPC transports over LinkRPC streams."
103
+ }, { connectRaw: requestType(object({ params: optional(unknown()) }), object({ reason: jsonRpcConnectionCloseReasonSchema }), { description: "Open a transparent JSON-RPC transport tied to this request. Cancellation closes the transport." }).withStream({
104
+ client: rawFrameSchema,
105
+ server: discriminatedUnion("type", [object({ type: literal("ready") }), object({
106
+ type: literal("frame"),
107
+ frame: unknown()
108
+ })])
109
+ }) });
110
+ //#endregion
111
+ //#region src/json-rpc/messageTransport.ts
112
+ /** Adapt a parsed JSON-RPC transport to the transport consumed by LinkRpcConnection. */
113
+ function adaptJsonRpcTransport(transport, options = {}) {
114
+ const maxPendingMessages = options.maxPendingMessages ?? 1024;
115
+ if (!Number.isSafeInteger(maxPendingMessages) || maxPendingMessages < 1) throw new Error("maxPendingMessages must be a positive safe integer");
116
+ let listener;
117
+ const backlog = [];
118
+ const closeListeners = /* @__PURE__ */ new Set();
119
+ let closeReason;
120
+ let disposed = false;
121
+ const messages = transport.onMessage((frame) => {
122
+ if (transport.closed || disposed) return;
123
+ try {
124
+ const message = assertJsonRpcMessage(frame);
125
+ if (listener) listener(message);
126
+ else if (backlog.length < maxPendingMessages) backlog.push(message);
127
+ else transport.close(`JSON-RPC message backlog exceeded ${maxPendingMessages}`);
128
+ } catch (error) {
129
+ transport.close(error instanceof Error ? error.message : String(error));
130
+ }
131
+ });
132
+ const closeSubscription = transport.onClose((reason) => {
133
+ closeReason = reason;
134
+ listener = void 0;
135
+ backlog.length = 0;
136
+ for (const closeListener of closeListeners) closeListener(reason);
137
+ closeListeners.clear();
138
+ });
139
+ return {
140
+ get closed() {
141
+ return transport.closed;
142
+ },
143
+ get closeReason() {
144
+ return closeReason;
145
+ },
146
+ send(message) {
147
+ assertJsonRpcMessage(message);
148
+ return transport.send(message);
149
+ },
150
+ setListener(value) {
151
+ if (disposed || transport.closed) return;
152
+ listener = value;
153
+ while (listener && backlog.length > 0) listener(backlog.shift());
154
+ },
155
+ onClose(closeListener) {
156
+ if (transport.closed) {
157
+ let disposed = false;
158
+ queueMicrotask(() => {
159
+ if (!disposed) closeListener(closeReason);
160
+ });
161
+ return { dispose: () => {
162
+ disposed = true;
163
+ } };
164
+ }
165
+ closeListeners.add(closeListener);
166
+ return { dispose: () => closeListeners.delete(closeListener) };
167
+ },
168
+ dispose() {
169
+ if (disposed) return;
170
+ disposed = true;
171
+ messages.dispose();
172
+ listener = void 0;
173
+ backlog.length = 0;
174
+ transport.close("disposed");
175
+ closeSubscription.dispose();
176
+ closeListeners.clear();
177
+ }
178
+ };
179
+ }
180
+ function assertJsonRpcMessage(value) {
181
+ if (!isObject(value) || value.jsonrpc !== "2.0") throw new Error("Invalid external JSON-RPC message: expected jsonrpc \"2.0\"");
182
+ const hasMethod = typeof value.method === "string";
183
+ const hasId = Object.prototype.hasOwnProperty.call(value, "id");
184
+ if (hasMethod) {
185
+ if (hasId && !isRequestId(value.id)) throw new Error("Invalid external JSON-RPC request id");
186
+ if ("params" in value && value.params === void 0) throw new Error("Invalid external JSON-RPC params");
187
+ return value;
188
+ }
189
+ if (!hasId || !(value.id === null || isRequestId(value.id))) throw new Error("Invalid external JSON-RPC response id");
190
+ const hasResult = Object.prototype.hasOwnProperty.call(value, "result");
191
+ const hasError = Object.prototype.hasOwnProperty.call(value, "error");
192
+ if (hasResult === hasError) throw new Error("Invalid external JSON-RPC response: expected exactly one of result or error");
193
+ if (hasError) {
194
+ const error = value.error;
195
+ if (!isObject(error) || typeof error.code !== "number" || typeof error.message !== "string") throw new Error("Invalid external JSON-RPC error object");
196
+ }
197
+ return value;
198
+ }
199
+ function isObject(value) {
200
+ return typeof value === "object" && value !== null && !Array.isArray(value);
201
+ }
202
+ function isRequestId(value) {
203
+ return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
204
+ }
205
+ //#endregion
206
+ //#region src/json-rpc/server.ts
207
+ /** Register a raw JSON-RPC transport factory on a LinkRPC connection. */
208
+ function registerJsonRpcConnectionService(options) {
209
+ return options.connection.register(jsonRpcConnectionInterface, { connectRaw: async ({ params }, _ctx, stream) => {
210
+ const transport = await options.openTransport(params, stream.signal);
211
+ if (stream.signal.aborted) {
212
+ transport.close("cancelled");
213
+ return { reason: "cancelled" };
214
+ }
215
+ if (transport.closed) throw new Error("JSON-RPC transport closed before it was ready");
216
+ let settle;
217
+ const closed = new Promise((resolve) => {
218
+ let settled = false;
219
+ settle = (reason) => {
220
+ if (settled) return;
221
+ settled = true;
222
+ resolve(reason);
223
+ };
224
+ });
225
+ let outbound = Promise.resolve();
226
+ const messages = transport.onMessage((frame) => {
227
+ outbound = outbound.then(() => stream.send({
228
+ type: "frame",
229
+ frame
230
+ })).catch(() => settle("remoteClosed"));
231
+ });
232
+ const closure = transport.onClose(() => settle("remoteClosed"));
233
+ let inbound = Promise.resolve();
234
+ stream.onMessage(({ frame }) => {
235
+ inbound = inbound.then(() => transport.send(frame)).catch(() => settle("remoteClosed"));
236
+ });
237
+ await stream.send({ type: "ready" });
238
+ const abort = waitForAbort(stream.signal);
239
+ const reason = await Promise.race([closed, abort.promise.then(() => "cancelled")]);
240
+ abort.dispose();
241
+ messages.dispose();
242
+ closure.dispose();
243
+ transport.close(reason);
244
+ return { reason };
245
+ } }, options.serviceId === void 0 ? {} : { serviceId: options.serviceId });
246
+ }
247
+ function waitForAbort(signal) {
248
+ if (signal.aborted) return {
249
+ promise: Promise.resolve(),
250
+ dispose() {}
251
+ };
252
+ let resolve;
253
+ const promise = new Promise((r) => {
254
+ resolve = r;
255
+ });
256
+ const onAbort = () => resolve();
257
+ signal.addEventListener("abort", onAbort, { once: true });
258
+ return {
259
+ promise,
260
+ dispose() {
261
+ signal.removeEventListener("abort", onAbort);
262
+ }
263
+ };
264
+ }
265
+ //#endregion
266
+ //#region src/json-rpc/stdio.ts
267
+ const MAX_FRAME_BACKLOG = 1024;
268
+ /** Adapt newline-delimited JSON-RPC stdio streams to a message transport. */
269
+ function createNdjsonJsonRpcTransport(options) {
270
+ const messageListeners = /* @__PURE__ */ new Set();
271
+ const closeListeners = /* @__PURE__ */ new Set();
272
+ const backlog = [];
273
+ const lines = readline.createInterface({ input: options.input });
274
+ let closed = false;
275
+ let closeReason;
276
+ const finish = (reason) => {
277
+ if (closed) return;
278
+ closed = true;
279
+ closeReason = reason;
280
+ lines.close();
281
+ for (const listener of closeListeners) listener(reason);
282
+ };
283
+ lines.on("line", (line) => {
284
+ const trimmed = line.trim();
285
+ if (trimmed.length === 0) return;
286
+ let frame;
287
+ try {
288
+ frame = JSON.parse(trimmed);
289
+ } catch (error) {
290
+ finish(`Invalid JSON-RPC NDJSON frame: ${error instanceof Error ? error.message : String(error)}`);
291
+ return;
292
+ }
293
+ if (messageListeners.size === 0) {
294
+ if (backlog.length >= MAX_FRAME_BACKLOG) {
295
+ finish("JSON-RPC stdio frame backlog exceeded");
296
+ return;
297
+ }
298
+ backlog.push(frame);
299
+ return;
300
+ }
301
+ for (const listener of messageListeners) listener(frame);
302
+ });
303
+ lines.on("close", () => finish("closed"));
304
+ options.input.on("error", (error) => finish(error.message));
305
+ options.output.on("error", (error) => finish(error.message));
306
+ return {
307
+ get closed() {
308
+ return closed;
309
+ },
310
+ send(frame) {
311
+ if (closed) return Promise.reject(/* @__PURE__ */ new Error("JSON-RPC stdio transport is closed"));
312
+ return new Promise((resolve, reject) => {
313
+ options.output.write(`${JSON.stringify(frame)}\n`, (error) => {
314
+ if (error) reject(error);
315
+ else resolve();
316
+ });
317
+ });
318
+ },
319
+ onMessage(listener) {
320
+ messageListeners.add(listener);
321
+ for (const frame of backlog.splice(0)) listener(frame);
322
+ return { dispose: () => messageListeners.delete(listener) };
323
+ },
324
+ onClose(listener) {
325
+ if (closed) {
326
+ let disposed = false;
327
+ queueMicrotask(() => {
328
+ if (!disposed) listener(closeReason);
329
+ });
330
+ return { dispose: () => {
331
+ disposed = true;
332
+ } };
333
+ }
334
+ closeListeners.add(listener);
335
+ return { dispose: () => closeListeners.delete(listener) };
336
+ },
337
+ close(reason) {
338
+ finish(reason);
339
+ }
340
+ };
341
+ }
342
+ //#endregion
343
+ //#region src/json-rpc/transport.ts
344
+ function connectJsonRpcTransports(left, right) {
345
+ let disposed = false;
346
+ const subscriptions = [
347
+ left.onMessage((frame) => {
348
+ right.send(frame).catch((error) => closeBoth(String(error)));
349
+ }),
350
+ right.onMessage((frame) => {
351
+ left.send(frame).catch((error) => closeBoth(String(error)));
352
+ }),
353
+ left.onClose((reason) => closeBoth(reason)),
354
+ right.onClose((reason) => closeBoth(reason))
355
+ ];
356
+ const closeBoth = (reason) => {
357
+ if (disposed) return;
358
+ disposed = true;
359
+ for (const subscription of subscriptions) subscription.dispose();
360
+ left.close(reason);
361
+ right.close(reason);
362
+ };
363
+ return { dispose() {
364
+ if (disposed) return;
365
+ disposed = true;
366
+ for (const subscription of subscriptions) subscription.dispose();
367
+ } };
368
+ }
369
+ var JsonRpcTransportPair = class {
370
+ a;
371
+ b;
372
+ constructor() {
373
+ const a = new InMemoryJsonRpcTransport();
374
+ const b = new InMemoryJsonRpcTransport();
375
+ a.peer = b;
376
+ b.peer = a;
377
+ this.a = a;
378
+ this.b = b;
379
+ }
380
+ };
381
+ var InMemoryJsonRpcTransport = class {
382
+ peer;
383
+ _messageListeners = /* @__PURE__ */ new Set();
384
+ _closeListeners = /* @__PURE__ */ new Set();
385
+ _backlog = [];
386
+ _closed = false;
387
+ _closeReason;
388
+ send(frame) {
389
+ if (this._closed || this.peer._closed) return Promise.reject(/* @__PURE__ */ new Error("JSON-RPC transport is closed"));
390
+ queueMicrotask(() => this.peer._deliver(structuredClone(frame)));
391
+ return Promise.resolve();
392
+ }
393
+ get closed() {
394
+ return this._closed;
395
+ }
396
+ onMessage(listener) {
397
+ this._messageListeners.add(listener);
398
+ for (const frame of this._backlog.splice(0)) listener(frame);
399
+ return { dispose: () => this._messageListeners.delete(listener) };
400
+ }
401
+ onClose(listener) {
402
+ if (this._closed) {
403
+ let disposed = false;
404
+ queueMicrotask(() => {
405
+ if (!disposed) listener(this._closeReason);
406
+ });
407
+ return { dispose: () => {
408
+ disposed = true;
409
+ } };
410
+ }
411
+ this._closeListeners.add(listener);
412
+ return { dispose: () => this._closeListeners.delete(listener) };
413
+ }
414
+ close(reason) {
415
+ if (this._closed) return;
416
+ this._closed = true;
417
+ this._closeReason = reason;
418
+ for (const listener of this._closeListeners) listener(reason);
419
+ this.peer._remoteClosed(reason);
420
+ }
421
+ _deliver(frame) {
422
+ if (this._closed) return;
423
+ if (this._messageListeners.size === 0) {
424
+ this._backlog.push(frame);
425
+ return;
426
+ }
427
+ for (const listener of this._messageListeners) listener(frame);
428
+ }
429
+ _remoteClosed(reason) {
430
+ if (this._closed) return;
431
+ this._closed = true;
432
+ this._closeReason = reason;
433
+ for (const listener of this._closeListeners) listener(reason);
434
+ }
435
+ };
436
+ //#endregion
437
+ export { JsonRpcTransportPair, adaptJsonRpcTransport, assertJsonRpcMessage, connectJsonRpcTransports, connectRawJsonRpcTransport, createNdjsonJsonRpcTransport, jsonRpcConnectionCloseReasonSchema, jsonRpcConnectionInterface, registerJsonRpcConnectionService };
438
+
439
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["zEnum"],"sources":["../../src/json-rpc/client.ts","../../src/json-rpc/interface.ts","../../src/json-rpc/messageTransport.ts","../../src/json-rpc/server.ts","../../src/json-rpc/stdio.ts","../../src/json-rpc/transport.ts"],"sourcesContent":["import type { InterfaceClient, JsonValue } from '@hediet/linkrpc';\nimport { jsonRpcConnectionInterface } from './interface';\nimport type { Disposable, JsonRpcTransport } from './transport';\n\ntype JsonRpcConnectionClient = InterfaceClient<typeof jsonRpcConnectionInterface>;\nconst MAX_RAW_FRAME_BACKLOG = 1_024;\n\nexport async function connectRawJsonRpcTransport(\n client: JsonRpcConnectionClient,\n params?: JsonValue,\n): Promise<JsonRpcTransport> {\n const messageListeners = new Set<(frame: JsonValue) => void>();\n const closeListeners = new Set<(reason?: string) => void>();\n const backlog: JsonValue[] = [];\n let buffering = true;\n let closed = false;\n let closeReason: string | undefined;\n\n const finish = (reason?: string): void => {\n if (closed) return;\n closed = true;\n closeReason = reason;\n rejectReady(new Error(`Raw JSON-RPC transport closed before ready: ${reason ?? 'closed'}`));\n for (const listener of closeListeners) listener(reason);\n };\n\n let resolveReady!: () => void;\n let rejectReady!: (error: unknown) => void;\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve;\n rejectReady = reject;\n });\n const call = client.connectRaw(\n params === undefined ? {} : { params },\n {\n onMessage: (event) => {\n if (event.type === 'ready') {\n resolveReady();\n return;\n }\n const frame = event.frame as JsonValue;\n if (buffering) {\n if (backlog.length >= MAX_RAW_FRAME_BACKLOG) {\n const reason = 'Raw JSON-RPC frame backlog exceeded';\n finish(reason);\n void call.cancel(reason);\n return;\n }\n backlog.push(frame);\n return;\n }\n for (const listener of messageListeners) listener(frame);\n },\n },\n );\n void call.then(\n ({ reason }) => finish(reason),\n (error) => {\n rejectReady(error);\n finish(error instanceof Error ? error.message : String(error));\n },\n );\n await ready;\n\n return {\n get closed(): boolean {\n return closed;\n },\n async send(frame): Promise<void> {\n if (closed) {\n throw new Error(`JSON-RPC transport is closed: ${closeReason ?? 'closed'}`);\n }\n await call.send({ frame });\n },\n onMessage(listener): Disposable {\n const registration = addListener(messageListeners, listener);\n if (buffering) {\n buffering = false;\n for (const frame of backlog.splice(0)) listener(frame);\n }\n return registration;\n },\n onClose(listener): Disposable {\n if (closed) {\n let disposed = false;\n queueMicrotask(() => {\n if (!disposed) listener(closeReason);\n });\n return { dispose: () => { disposed = true; } };\n }\n return addListener(closeListeners, listener);\n },\n close(reason): void {\n finish(reason ?? 'cancelled');\n void call.cancel(reason);\n },\n };\n}\n\nfunction addListener<T>(listeners: Set<T>, listener: T): Disposable {\n listeners.add(listener);\n return { dispose: () => listeners.delete(listener) };\n}\n","import { defineInterface, requestType } from '@hediet/linkrpc';\nimport {\n discriminatedUnion,\n enum as zEnum,\n literal,\n object,\n optional,\n unknown,\n} from 'zod/mini';\n\nexport const jsonRpcConnectionCloseReasonSchema = zEnum([\n 'cancelled',\n 'closed',\n 'disposed',\n 'remoteClosed',\n]);\n\nexport type JsonRpcConnectionCloseReason =\n | 'cancelled'\n | 'closed'\n | 'disposed'\n | 'remoteClosed';\n\nconst rawFrameSchema = object({ frame: unknown() });\n\n/**\n * Opens a transparent JSON-RPC transport over one duplex LinkRPC request.\n * Frames are intentionally opaque; the endpoint owns JSON-RPC semantics.\n */\nexport const jsonRpcConnectionInterface = defineInterface(\n {\n id: 'jsonRpcConnection',\n description: 'Opens transparent JSON-RPC transports over LinkRPC streams.',\n },\n {\n connectRaw: requestType(\n object({ params: optional(unknown()) }),\n object({ reason: jsonRpcConnectionCloseReasonSchema }),\n {\n description:\n 'Open a transparent JSON-RPC transport tied to this request. '\n + 'Cancellation closes the transport.',\n },\n ).withStream({\n client: rawFrameSchema,\n server: discriminatedUnion('type', [\n object({ type: literal('ready') }),\n object({ type: literal('frame'), frame: unknown() }),\n ]),\n }),\n },\n);\n","import type {\n IMessageTransport,\n JsonRpcMessage,\n JsonValue,\n} from '@hediet/linkrpc';\nimport type { Disposable, JsonRpcTransport } from './transport';\n\nexport interface CloseAwareMessageTransport<\n TIncoming = JsonRpcMessage,\n TOutgoing = JsonRpcMessage,\n> extends IMessageTransport<TIncoming, TOutgoing> {\n readonly closed: boolean;\n readonly closeReason: string | undefined;\n onClose(listener: (reason?: string) => void): Disposable;\n}\n\n/** Adapt a parsed JSON-RPC transport to the transport consumed by LinkRpcConnection. */\nexport function adaptJsonRpcTransport(\n transport: JsonRpcTransport,\n options: { maxPendingMessages?: number } = {},\n): CloseAwareMessageTransport<JsonRpcMessage, JsonRpcMessage> {\n const maxPendingMessages = options.maxPendingMessages ?? 1_024;\n if (!Number.isSafeInteger(maxPendingMessages) || maxPendingMessages < 1) {\n throw new Error('maxPendingMessages must be a positive safe integer');\n }\n let listener: ((message: JsonRpcMessage) => void) | undefined;\n const backlog: JsonRpcMessage[] = [];\n const closeListeners = new Set<(reason?: string) => void>();\n let closeReason: string | undefined;\n let disposed = false;\n const messages = transport.onMessage((frame) => {\n if (transport.closed || disposed) return;\n try {\n const message = assertJsonRpcMessage(frame);\n if (listener) listener(message);\n else if (backlog.length < maxPendingMessages) backlog.push(message);\n else transport.close(`JSON-RPC message backlog exceeded ${maxPendingMessages}`);\n } catch (error) {\n transport.close(error instanceof Error ? error.message : String(error));\n }\n });\n const closeSubscription = transport.onClose((reason) => {\n closeReason = reason;\n listener = undefined;\n backlog.length = 0;\n for (const closeListener of closeListeners) closeListener(reason);\n closeListeners.clear();\n });\n\n return {\n get closed(): boolean { return transport.closed; },\n get closeReason(): string | undefined { return closeReason; },\n send(message): Promise<void> {\n assertJsonRpcMessage(message);\n return transport.send(message as unknown as JsonValue);\n },\n setListener(value): void {\n if (disposed || transport.closed) return;\n listener = value;\n while (listener && backlog.length > 0) listener(backlog.shift()!);\n },\n onClose(closeListener): Disposable {\n if (transport.closed) {\n let disposed = false;\n queueMicrotask(() => { if (!disposed) closeListener(closeReason); });\n return { dispose: () => { disposed = true; } };\n }\n closeListeners.add(closeListener);\n return { dispose: () => closeListeners.delete(closeListener) };\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n messages.dispose();\n listener = undefined;\n backlog.length = 0;\n transport.close('disposed');\n closeSubscription.dispose();\n closeListeners.clear();\n },\n };\n}\n\nexport function assertJsonRpcMessage(value: unknown): JsonRpcMessage {\n if (!isObject(value) || value.jsonrpc !== '2.0') {\n throw new Error('Invalid external JSON-RPC message: expected jsonrpc \"2.0\"');\n }\n const hasMethod = typeof value.method === 'string';\n const hasId = Object.prototype.hasOwnProperty.call(value, 'id');\n if (hasMethod) {\n if (hasId && !isRequestId(value.id)) {\n throw new Error('Invalid external JSON-RPC request id');\n }\n if ('params' in value && value.params === undefined) {\n throw new Error('Invalid external JSON-RPC params');\n }\n return value as unknown as JsonRpcMessage;\n }\n if (!hasId || !(value.id === null || isRequestId(value.id))) {\n throw new Error('Invalid external JSON-RPC response id');\n }\n const hasResult = Object.prototype.hasOwnProperty.call(value, 'result');\n const hasError = Object.prototype.hasOwnProperty.call(value, 'error');\n if (hasResult === hasError) {\n throw new Error('Invalid external JSON-RPC response: expected exactly one of result or error');\n }\n if (hasError) {\n const error = value.error;\n if (!isObject(error) || typeof error.code !== 'number' || typeof error.message !== 'string') {\n throw new Error('Invalid external JSON-RPC error object');\n }\n }\n return value as unknown as JsonRpcMessage;\n}\n\nfunction isObject(value: unknown): value is Record<string, JsonValue> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isRequestId(value: unknown): value is string | number {\n return typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));\n}\n","import type {\n InterfaceRegistration,\n JsonValue,\n LinkRpcConnection,\n} from '@hediet/linkrpc';\nimport {\n jsonRpcConnectionInterface,\n type JsonRpcConnectionCloseReason,\n} from './interface';\nimport type { JsonRpcTransport } from './transport';\n\nexport interface RegisterJsonRpcConnectionServiceOptions {\n readonly connection: LinkRpcConnection<unknown>;\n readonly serviceId?: string;\n readonly openTransport: (\n params: JsonValue | undefined,\n signal: AbortSignal,\n ) => Promise<JsonRpcTransport>;\n}\n\n/** Register a raw JSON-RPC transport factory on a LinkRPC connection. */\nexport function registerJsonRpcConnectionService(\n options: RegisterJsonRpcConnectionServiceOptions,\n): InterfaceRegistration {\n return options.connection.register(\n jsonRpcConnectionInterface,\n {\n connectRaw: async ({ params }, _ctx, stream) => {\n const transport = await options.openTransport(\n params as JsonValue | undefined,\n stream.signal,\n );\n if (stream.signal.aborted) {\n transport.close('cancelled');\n return { reason: 'cancelled' as const };\n }\n if (transport.closed) {\n throw new Error('JSON-RPC transport closed before it was ready');\n }\n\n let settle!: (reason: JsonRpcConnectionCloseReason) => void;\n const closed = new Promise<JsonRpcConnectionCloseReason>((resolve) => {\n let settled = false;\n settle = (reason) => {\n if (settled) return;\n settled = true;\n resolve(reason);\n };\n });\n let outbound = Promise.resolve();\n const messages = transport.onMessage((frame) => {\n outbound = outbound\n .then(() => stream.send({ type: 'frame', frame }))\n .catch(() => settle('remoteClosed'));\n });\n const closure = transport.onClose(() => settle('remoteClosed'));\n let inbound = Promise.resolve();\n stream.onMessage(({ frame }) => {\n inbound = inbound\n .then(() => transport.send(frame as JsonValue))\n .catch(() => settle('remoteClosed'));\n });\n await stream.send({ type: 'ready' });\n\n const abort = waitForAbort(stream.signal);\n const reason = await Promise.race([\n closed,\n abort.promise.then(() => 'cancelled' as const),\n ]);\n abort.dispose();\n messages.dispose();\n closure.dispose();\n transport.close(reason);\n return { reason };\n },\n },\n options.serviceId === undefined ? {} : { serviceId: options.serviceId },\n );\n}\n\nfunction waitForAbort(signal: AbortSignal): { promise: Promise<void>; dispose(): void; } {\n if (signal.aborted) return { promise: Promise.resolve(), dispose() {} };\n let resolve!: () => void;\n const promise = new Promise<void>((r) => { resolve = r; });\n const onAbort = (): void => resolve();\n signal.addEventListener('abort', onAbort, { once: true });\n return {\n promise,\n dispose(): void {\n signal.removeEventListener('abort', onAbort);\n },\n };\n}\n","import * as readline from 'node:readline';\nimport type { JsonValue } from '@hediet/linkrpc';\nimport type { Disposable, JsonRpcTransport } from './transport';\n\nconst MAX_FRAME_BACKLOG = 1_024;\n\nexport interface NdjsonJsonRpcTransportOptions {\n readonly input: NodeJS.ReadableStream;\n readonly output: NodeJS.WritableStream;\n}\n\n/** Adapt newline-delimited JSON-RPC stdio streams to a message transport. */\nexport function createNdjsonJsonRpcTransport(\n options: NdjsonJsonRpcTransportOptions,\n): JsonRpcTransport {\n const messageListeners = new Set<(frame: JsonValue) => void>();\n const closeListeners = new Set<(reason?: string) => void>();\n const backlog: JsonValue[] = [];\n const lines = readline.createInterface({ input: options.input });\n let closed = false;\n let closeReason: string | undefined;\n\n const finish = (reason?: string): void => {\n if (closed) return;\n closed = true;\n closeReason = reason;\n lines.close();\n for (const listener of closeListeners) listener(reason);\n };\n\n lines.on('line', (line) => {\n const trimmed = line.trim();\n if (trimmed.length === 0) return;\n let frame: JsonValue;\n try {\n frame = JSON.parse(trimmed) as JsonValue;\n } catch (error) {\n finish(`Invalid JSON-RPC NDJSON frame: ${error instanceof Error ? error.message : String(error)}`);\n return;\n }\n if (messageListeners.size === 0) {\n if (backlog.length >= MAX_FRAME_BACKLOG) {\n finish('JSON-RPC stdio frame backlog exceeded');\n return;\n }\n backlog.push(frame);\n return;\n }\n for (const listener of messageListeners) listener(frame);\n });\n lines.on('close', () => finish('closed'));\n options.input.on('error', (error) => finish(error.message));\n options.output.on('error', (error) => finish(error.message));\n\n return {\n get closed(): boolean {\n return closed;\n },\n send(frame): Promise<void> {\n if (closed) return Promise.reject(new Error('JSON-RPC stdio transport is closed'));\n return new Promise<void>((resolve, reject) => {\n options.output.write(`${JSON.stringify(frame)}\\n`, (error?: Error | null) => {\n if (error) reject(error);\n else resolve();\n });\n });\n },\n onMessage(listener): Disposable {\n messageListeners.add(listener);\n for (const frame of backlog.splice(0)) listener(frame);\n return { dispose: () => messageListeners.delete(listener) };\n },\n onClose(listener): Disposable {\n if (closed) {\n let disposed = false;\n queueMicrotask(() => {\n if (!disposed) listener(closeReason);\n });\n return { dispose: () => { disposed = true; } };\n }\n closeListeners.add(listener);\n return { dispose: () => closeListeners.delete(listener) };\n },\n close(reason): void {\n finish(reason);\n },\n };\n}\n","import type { JsonValue } from '@hediet/linkrpc';\n\nexport interface Disposable {\n dispose(): void;\n}\n\n/** A message-oriented transport carrying already parsed JSON-RPC frames. */\nexport interface JsonRpcTransport {\n readonly closed: boolean;\n send(frame: JsonValue): Promise<void>;\n onMessage(listener: (frame: JsonValue) => void): Disposable;\n onClose(listener: (reason?: string) => void): Disposable;\n close(reason?: string): void;\n}\n\nexport function connectJsonRpcTransports(\n left: JsonRpcTransport,\n right: JsonRpcTransport,\n): Disposable {\n let disposed = false;\n const subscriptions = [\n left.onMessage((frame) => {\n void right.send(frame).catch((error) => closeBoth(String(error)));\n }),\n right.onMessage((frame) => {\n void left.send(frame).catch((error) => closeBoth(String(error)));\n }),\n left.onClose((reason) => closeBoth(reason)),\n right.onClose((reason) => closeBoth(reason)),\n ];\n const closeBoth = (reason?: string): void => {\n if (disposed) return;\n disposed = true;\n for (const subscription of subscriptions) subscription.dispose();\n left.close(reason);\n right.close(reason);\n };\n return {\n dispose(): void {\n if (disposed) return;\n disposed = true;\n for (const subscription of subscriptions) subscription.dispose();\n },\n };\n}\n\nexport class JsonRpcTransportPair {\n public readonly a: JsonRpcTransport;\n public readonly b: JsonRpcTransport;\n\n public constructor() {\n const a = new InMemoryJsonRpcTransport();\n const b = new InMemoryJsonRpcTransport();\n a.peer = b;\n b.peer = a;\n this.a = a;\n this.b = b;\n }\n}\n\nclass InMemoryJsonRpcTransport implements JsonRpcTransport {\n public peer!: InMemoryJsonRpcTransport;\n private readonly _messageListeners = new Set<(frame: JsonValue) => void>();\n private readonly _closeListeners = new Set<(reason?: string) => void>();\n private readonly _backlog: JsonValue[] = [];\n private _closed = false;\n private _closeReason: string | undefined;\n\n public send(frame: JsonValue): Promise<void> {\n if (this._closed || this.peer._closed) {\n return Promise.reject(new Error('JSON-RPC transport is closed'));\n }\n queueMicrotask(() => this.peer._deliver(structuredClone(frame)));\n return Promise.resolve();\n }\n\n public get closed(): boolean {\n return this._closed;\n }\n\n public onMessage(listener: (frame: JsonValue) => void): Disposable {\n this._messageListeners.add(listener);\n for (const frame of this._backlog.splice(0)) listener(frame);\n return { dispose: () => this._messageListeners.delete(listener) };\n }\n\n public onClose(listener: (reason?: string) => void): Disposable {\n if (this._closed) {\n let disposed = false;\n queueMicrotask(() => {\n if (!disposed) listener(this._closeReason);\n });\n return { dispose: () => { disposed = true; } };\n }\n this._closeListeners.add(listener);\n return { dispose: () => this._closeListeners.delete(listener) };\n }\n\n public close(reason?: string): void {\n if (this._closed) return;\n this._closed = true;\n this._closeReason = reason;\n for (const listener of this._closeListeners) listener(reason);\n this.peer._remoteClosed(reason);\n }\n\n private _deliver(frame: JsonValue): void {\n if (this._closed) return;\n if (this._messageListeners.size === 0) {\n this._backlog.push(frame);\n return;\n }\n for (const listener of this._messageListeners) listener(frame);\n }\n\n private _remoteClosed(reason?: string): void {\n if (this._closed) return;\n this._closed = true;\n this._closeReason = reason;\n for (const listener of this._closeListeners) listener(reason);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { Schema } from "@hediet/linkrpc";
2
+ import { output } from "zod/v4/core";
3
+ //#region src/logging/index.d.ts
4
+ declare const logLevelSchema: import("zod/mini").ZodMiniEnum<{
5
+ trace: "trace";
6
+ debug: "debug";
7
+ info: "info";
8
+ warn: "warn";
9
+ error: "error";
10
+ off: "off";
11
+ }>;
12
+ type LogLevel = output<typeof logLevelSchema>;
13
+ type EmittedLogLevel = Exclude<LogLevel, 'off'>;
14
+ interface LogError {
15
+ readonly name?: string;
16
+ readonly message: string;
17
+ readonly stack?: string;
18
+ }
19
+ interface LogEntry {
20
+ readonly timestamp: string;
21
+ readonly level: EmittedLogLevel;
22
+ readonly message: string;
23
+ readonly attributes?: Readonly<Record<string, unknown>>;
24
+ readonly entries?: readonly LogEntry[];
25
+ readonly durationMs?: number;
26
+ readonly error?: LogError;
27
+ }
28
+ interface LogDocument {
29
+ readonly schemaVersion: 1;
30
+ readonly service: string;
31
+ readonly startedAt: string;
32
+ readonly state: Readonly<Record<string, unknown>>;
33
+ readonly entries: readonly LogEntry[];
34
+ }
35
+ declare const logEntrySchema: Schema<LogEntry>;
36
+ declare const logDocumentSchema: Schema<LogDocument>;
37
+ declare const logStreamEventSchema: import("zod/mini").ZodMiniDiscriminatedUnion<[import("zod/mini").ZodMiniObject<{
38
+ type: import("zod/mini").ZodMiniLiteral<"snapshot">;
39
+ revision: import("zod/mini").ZodMiniNumber<number>;
40
+ document: Schema<LogDocument>;
41
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
42
+ type: import("zod/mini").ZodMiniLiteral<"patch">;
43
+ revision: import("zod/mini").ZodMiniNumber<number>;
44
+ edits: import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniDiscriminatedUnion<[import("zod/mini").ZodMiniObject<{
45
+ op: import("zod/mini").ZodMiniLiteral<"set">;
46
+ path: import("zod/mini").ZodMiniString<string>;
47
+ value: import("zod/mini").ZodMiniUnknown;
48
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
49
+ op: import("zod/mini").ZodMiniLiteral<"remove">;
50
+ path: import("zod/mini").ZodMiniString<string>;
51
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
52
+ op: import("zod/mini").ZodMiniLiteral<"append">;
53
+ path: import("zod/mini").ZodMiniString<string>;
54
+ value: import("zod/mini").ZodMiniString<string>;
55
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
56
+ op: import("zod/mini").ZodMiniLiteral<"splice">;
57
+ path: import("zod/mini").ZodMiniString<string>;
58
+ offset: import("zod/mini").ZodMiniNumber<number>;
59
+ delete: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNumber<number>>;
60
+ insert: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
61
+ }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
62
+ op: import("zod/mini").ZodMiniLiteral<"insert">;
63
+ path: import("zod/mini").ZodMiniString<string>;
64
+ index: import("zod/mini").ZodMiniNumber<number>;
65
+ value: import("zod/mini").ZodMiniUnknown;
66
+ }, import("zod/v4/core").$strip>], "op">>;
67
+ }, import("zod/v4/core").$strip>], "type">;
68
+ type LogStreamEvent = output<typeof logStreamEventSchema>;
69
+ /**
70
+ * Standard structured-log protocol. Implementations choose their own storage,
71
+ * retention, and sinks; this definition specifies only the LinkRPC wire API.
72
+ */
73
+ declare const loggingInterface: import("@hediet/linkrpc").InterfaceDefinition<{
74
+ getLogLevel: import("@hediet/linkrpc").RequestType<Record<string, never>, {
75
+ level: "trace" | "debug" | "info" | "warn" | "error" | "off";
76
+ }, any, never, never>;
77
+ getLogSnapshot: import("@hediet/linkrpc").RequestType<Record<string, never>, {
78
+ revision: number;
79
+ document: LogDocument;
80
+ }, any, never, never>;
81
+ setLogLevel: import("@hediet/linkrpc").RequestType<{
82
+ level: "trace" | "debug" | "info" | "warn" | "error" | "off";
83
+ }, Record<string, never>, any, never, never>;
84
+ watchLog: import("@hediet/linkrpc").RequestType<Record<string, never>, Record<string, never>, void, any, {
85
+ type: "snapshot";
86
+ revision: number;
87
+ document: LogDocument;
88
+ } | {
89
+ type: "patch";
90
+ revision: number;
91
+ edits: ({
92
+ op: "set";
93
+ path: string;
94
+ value: unknown;
95
+ } | {
96
+ op: "remove";
97
+ path: string;
98
+ } | {
99
+ op: "append";
100
+ path: string;
101
+ value: string;
102
+ } | {
103
+ op: "splice";
104
+ path: string;
105
+ offset: number;
106
+ delete?: number | undefined;
107
+ insert?: string | undefined;
108
+ } | {
109
+ op: "insert";
110
+ path: string;
111
+ index: number;
112
+ value: unknown;
113
+ })[];
114
+ }>;
115
+ }>;
116
+ //#endregion
117
+ export { EmittedLogLevel, LogDocument, LogEntry, LogError, LogLevel, LogStreamEvent, logDocumentSchema, logEntrySchema, logLevelSchema, logStreamEventSchema, loggingInterface };
118
+ //# sourceMappingURL=index.d.ts.map