@roj-ai/transport 0.1.35 → 0.2.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.
@@ -92,12 +92,31 @@ export abstract class Connection<TReceive extends ProtocolDef, TSend extends Pro
92
92
 
93
93
  private sendBuffer: string[] = []
94
94
  private readonly maxBufferSize: number = 500
95
+ private droppedSinceLastFlush = 0
96
+
97
+ /**
98
+ * Buffer one message, dropping the OLDEST on overflow.
99
+ *
100
+ * These are ephemeral notifications, so a stale one is worth less than a
101
+ * fresh one — the previous behaviour dropped the newest and kept a queue of
102
+ * 500 obsolete updates. Overflow is logged once per episode rather than per
103
+ * message: silently vanishing notifications leave a "the UI stopped
104
+ * updating" report with no server-side trace at all.
105
+ */
106
+ private buffer(data: string): void {
107
+ if (this.sendBuffer.length >= this.maxBufferSize) {
108
+ this.sendBuffer.shift()
109
+ if (this.droppedSinceLastFlush === 0) {
110
+ console.warn(`[transport] send buffer full (${this.maxBufferSize}), dropping oldest notifications`)
111
+ }
112
+ this.droppedSinceLastFlush++
113
+ }
114
+ this.sendBuffer.push(data)
115
+ }
95
116
 
96
117
  send(data: string): boolean {
97
118
  if (!this.ws || this.ws.readyState !== WebSocketReadyState.OPEN) {
98
- if (this.sendBuffer.length < this.maxBufferSize) {
99
- this.sendBuffer.push(data)
100
- }
119
+ this.buffer(data)
101
120
  return false
102
121
  }
103
122
  this.flushSendBuffer()
@@ -105,9 +124,7 @@ export abstract class Connection<TReceive extends ProtocolDef, TSend extends Pro
105
124
  this.ws.send(data)
106
125
  return true
107
126
  } catch {
108
- if (this.sendBuffer.length < this.maxBufferSize) {
109
- this.sendBuffer.push(data)
110
- }
127
+ this.buffer(data)
111
128
  return false
112
129
  }
113
130
  }
@@ -123,6 +140,10 @@ export abstract class Connection<TReceive extends ProtocolDef, TSend extends Pro
123
140
  break
124
141
  }
125
142
  }
143
+ if (this.sendBuffer.length === 0 && this.droppedSinceLastFlush > 0) {
144
+ console.warn(`[transport] send buffer drained; ${this.droppedSinceLastFlush} notification(s) were dropped`)
145
+ this.droppedSinceLastFlush = 0
146
+ }
126
147
  }
127
148
 
128
149
  clearSendBuffer(): void {
package/src/core/index.ts CHANGED
@@ -14,33 +14,19 @@ export type { Result } from './result.js'
14
14
  export { Err, flatMapResult, isErr, isOk, mapResult, Ok, unwrapOr, unwrapOrThrow } from './result.js'
15
15
 
16
16
  // Protocol Definition
17
- export { defineProtocol, method, notification } from './protocol.js'
17
+ export { defineProtocol, notification } from './protocol.js'
18
18
  export type {
19
- CallerFor,
20
- EndpointDef,
21
- EndpointHandler,
22
- ExtractProtocolDef,
23
19
  HandlerContext,
24
- HandlersFor,
25
- InferError,
26
20
  InferInput,
27
- InferOutput,
28
- IsMethod,
29
- IsNotification,
30
- MethodCaller,
31
- MethodDef,
32
- MethodHandler,
33
- MethodOptions,
34
21
  NotificationDef,
35
22
  NotificationHandler,
36
23
  NotificationNotifier,
37
24
  NotificationOptions,
38
- NotifierFor,
39
25
  Protocol,
40
- ProtocolCaller,
41
26
  ProtocolDef,
42
27
  ProtocolHandlers,
43
28
  ProtocolNotifier,
29
+ SafeParseResult,
44
30
  } from './protocol.js'
45
31
 
46
32
  // Message Router
@@ -1,31 +1,16 @@
1
1
  /**
2
2
  * Protocol Definition API
3
3
  *
4
- * Provides a type-safe way to define RPC-like protocols for WebSocket communication.
5
- * Inspired by tRPC but designed for bidirectional WebSocket protocols.
4
+ * Type-safe definition of the notification protocols carried over the WebSocket
5
+ * link. Fire-and-forget only — request/response lives on HTTP `/rpc`.
6
6
  */
7
7
 
8
8
  import { z } from 'zod'
9
- import type { Result } from './result.js'
10
9
 
11
10
  // ============================================================================
12
- // Method/Notification Definition Types
11
+ // Notification Definition Types
13
12
  // ============================================================================
14
13
 
15
- /**
16
- * Definition for a request-response method.
17
- */
18
- export interface MethodDef<
19
- TInput extends z.ZodTypeAny = z.ZodTypeAny,
20
- TOutput extends z.ZodTypeAny = z.ZodTypeAny,
21
- TError extends z.ZodTypeAny = z.ZodTypeAny,
22
- > {
23
- _type: 'method'
24
- input: TInput
25
- output: TOutput
26
- error: TError
27
- }
28
-
29
14
  /**
30
15
  * Definition for a fire-and-forget notification.
31
16
  */
@@ -35,15 +20,10 @@ export interface NotificationDef<TInput extends z.ZodTypeAny = z.ZodTypeAny> {
35
20
  }
36
21
 
37
22
  /**
38
- * A single endpoint definition (method or notification).
39
- */
40
- export type EndpointDef = MethodDef | NotificationDef
41
-
42
- /**
43
- * Protocol definition - a collection of named endpoints.
23
+ * Protocol definition - a collection of named notifications.
44
24
  */
45
25
  export interface ProtocolDef {
46
- [key: string]: EndpointDef
26
+ [key: string]: NotificationDef
47
27
  }
48
28
 
49
29
  // ============================================================================
@@ -51,40 +31,16 @@ export interface ProtocolDef {
51
31
  // ============================================================================
52
32
 
53
33
  /**
54
- * Extract input type from an endpoint definition.
55
- */
56
- export type InferInput<T extends EndpointDef> = T extends MethodDef<infer I, infer _O, infer _E> ? z.infer<I>
57
- : T extends NotificationDef<infer I> ? z.infer<I>
58
- : never
59
-
60
- /**
61
- * Extract output type from a method definition.
34
+ * Extract input type from a notification definition.
62
35
  */
63
- export type InferOutput<T extends MethodDef> = T extends MethodDef<infer _I, infer O, infer _E> ? z.infer<O>
64
- : never
65
-
66
- /**
67
- * Extract error type from a method definition.
68
- */
69
- export type InferError<T extends MethodDef> = T extends MethodDef<infer _I, infer _O, infer E> ? z.infer<E>
70
- : never
71
-
72
- /**
73
- * Check if endpoint is a method.
74
- */
75
- export type IsMethod<T extends EndpointDef> = T extends MethodDef ? true : false
76
-
77
- /**
78
- * Check if endpoint is a notification.
79
- */
80
- export type IsNotification<T extends EndpointDef> = T extends NotificationDef ? true : false
36
+ export type InferInput<T extends NotificationDef> = z.infer<T['input']>
81
37
 
82
38
  // ============================================================================
83
39
  // Handler Types
84
40
  // ============================================================================
85
41
 
86
42
  /**
87
- * Handler context passed to method/notification handlers.
43
+ * Handler context passed to notification handlers.
88
44
  */
89
45
  export interface HandlerContext {
90
46
  /** Connection identifier */
@@ -93,14 +49,6 @@ export interface HandlerContext {
93
49
  [key: string]: unknown
94
50
  }
95
51
 
96
- /**
97
- * Handler for a method endpoint.
98
- */
99
- export type MethodHandler<T extends MethodDef> = (
100
- input: InferInput<T>,
101
- ctx: HandlerContext,
102
- ) => Promise<Result<InferOutput<T>, InferError<T>>>
103
-
104
52
  /**
105
53
  * Handler for a notification endpoint.
106
54
  */
@@ -109,31 +57,17 @@ export type NotificationHandler<T extends NotificationDef> = (
109
57
  ctx: HandlerContext,
110
58
  ) => Promise<void>
111
59
 
112
- /**
113
- * Handler for an endpoint (method or notification).
114
- */
115
- export type EndpointHandler<T extends EndpointDef> = T extends MethodDef ? MethodHandler<T>
116
- : T extends NotificationDef ? NotificationHandler<T>
117
- : never
118
-
119
60
  /**
120
61
  * Handlers object for a protocol.
121
62
  */
122
63
  export type ProtocolHandlers<T extends ProtocolDef> = {
123
- [K in keyof T]: EndpointHandler<T[K]>
64
+ [K in keyof T]: NotificationHandler<T[K]>
124
65
  }
125
66
 
126
67
  // ============================================================================
127
- // Caller Types (for making outbound calls)
68
+ // Notifier Types (for making outbound calls)
128
69
  // ============================================================================
129
70
 
130
- /**
131
- * Caller function for a method endpoint.
132
- */
133
- export type MethodCaller<T extends MethodDef> = (
134
- input: InferInput<T>,
135
- ) => Promise<Result<InferOutput<T>, InferError<T>>>
136
-
137
71
  /**
138
72
  * Notifier function for a notification endpoint.
139
73
  */
@@ -142,59 +76,16 @@ export type NotificationNotifier<T extends NotificationDef> = (
142
76
  ) => void
143
77
 
144
78
  /**
145
- * Caller object for methods in a protocol.
146
- * Only includes method endpoints.
147
- */
148
- export type ProtocolCaller<T extends ProtocolDef> = {
149
- [K in keyof T as T[K] extends MethodDef ? K : never]: T[K] extends MethodDef ? MethodCaller<T[K]>
150
- : never
151
- }
152
-
153
- /**
154
- * Notifier object for notifications in a protocol.
155
- * Only includes notification endpoints.
79
+ * Notifier object for the notifications in a protocol.
156
80
  */
157
81
  export type ProtocolNotifier<T extends ProtocolDef> = {
158
- [K in keyof T as T[K] extends NotificationDef ? K : never]: T[K] extends NotificationDef ? NotificationNotifier<T[K]>
159
- : never
82
+ [K in keyof T]: NotificationNotifier<T[K]>
160
83
  }
161
84
 
162
85
  // ============================================================================
163
86
  // Definition Functions
164
87
  // ============================================================================
165
88
 
166
- /**
167
- * Options for method definition.
168
- */
169
- export interface MethodOptions<
170
- TInput extends z.ZodTypeAny,
171
- TOutput extends z.ZodTypeAny,
172
- TError extends z.ZodTypeAny,
173
- > {
174
- /** Input schema (what the caller sends) */
175
- input: TInput
176
- /** Output schema (what the handler returns on success) */
177
- output: TOutput
178
- /** Error schema (what the handler returns on failure) */
179
- error: TError
180
- }
181
-
182
- /**
183
- * Define a request-response method endpoint.
184
- */
185
- export function method<
186
- TInput extends z.ZodTypeAny,
187
- TOutput extends z.ZodTypeAny,
188
- TError extends z.ZodTypeAny,
189
- >(options: MethodOptions<TInput, TOutput, TError>): MethodDef<TInput, TOutput, TError> {
190
- return {
191
- _type: 'method',
192
- input: options.input,
193
- output: options.output,
194
- error: options.error,
195
- }
196
- }
197
-
198
89
  /**
199
90
  * Options for notification definition.
200
91
  */
@@ -226,47 +117,32 @@ export type SafeParseResult<T> =
226
117
  | { success: true; data: T }
227
118
  | { success: false; error: { message: string } }
228
119
 
120
+ /** Parse through a generic schema type so the result keeps the schema's output type. */
121
+ function safeParseWith<S extends z.ZodTypeAny>(schema: S, input: unknown): SafeParseResult<z.infer<S>> {
122
+ const result = schema.safeParse(input)
123
+ if (result.success) {
124
+ return { success: true, data: result.data }
125
+ }
126
+ return { success: false, error: { message: result.error.message } }
127
+ }
128
+
229
129
  /**
230
130
  * Protocol object with definition and metadata.
231
131
  */
232
132
  export interface Protocol<T extends ProtocolDef> {
233
133
  /** The protocol definition */
234
134
  _def: T
235
- /** Get endpoint definition by name */
236
- getEndpoint<K extends keyof T>(name: K): T[K]
237
- /** Check if endpoint is a method */
238
- isMethod(name: keyof T): boolean
239
- /** Check if endpoint is a notification */
240
- isNotification(name: keyof T): boolean
241
- /** Get all method names */
242
- getMethodNames(): (keyof T)[]
243
135
  /** Get all notification names */
244
136
  getNotificationNames(): (keyof T)[]
245
- /** Validate input for an endpoint */
137
+ /** Validate input for a notification */
246
138
  validateInput<K extends keyof T>(name: K, input: unknown): SafeParseResult<InferInput<T[K]>>
247
- /** Validate output for a method endpoint */
248
- validateOutput<K extends keyof T>(name: K, output: unknown): SafeParseResult<unknown>
249
139
  }
250
140
 
251
141
  /**
252
- * Define a protocol with typed endpoints.
142
+ * Define a protocol with typed notifications.
253
143
  *
254
144
  * @example
255
145
  * ```typescript
256
- * const clientProtocol = defineProtocol({
257
- * subscribe: method({
258
- * input: z.object({ sessionId: z.string() }),
259
- * output: z.void(),
260
- * error: z.object({ code: z.literal('SESSION_NOT_FOUND') }),
261
- * }),
262
- *
263
- * userMessage: method({
264
- * input: z.object({ sessionId: z.string(), content: z.string() }),
265
- * output: z.object({ messageId: z.string() }),
266
- * error: z.object({ code: z.literal('AGENT_BUSY') }),
267
- * }),
268
- * });
269
- *
270
146
  * const serverProtocol = defineProtocol({
271
147
  * agentMessage: notification({
272
148
  * input: z.object({ sessionId: z.string(), content: z.string() }),
@@ -278,71 +154,12 @@ export function defineProtocol<T extends ProtocolDef>(def: T): Protocol<T> {
278
154
  return {
279
155
  _def: def,
280
156
 
281
- getEndpoint<K extends keyof T>(name: K): T[K] {
282
- return def[name]
283
- },
284
-
285
- isMethod(name: keyof T): boolean {
286
- const endpoint = def[name]
287
- return endpoint?._type === 'method'
288
- },
289
-
290
- isNotification(name: keyof T): boolean {
291
- const endpoint = def[name]
292
- return endpoint?._type === 'notification'
293
- },
294
-
295
- getMethodNames(): (keyof T)[] {
296
- return Object.keys(def).filter((key) => def[key]._type === 'method') as (keyof T)[]
297
- },
298
-
299
157
  getNotificationNames(): (keyof T)[] {
300
- return Object.keys(def).filter((key) => def[key]._type === 'notification') as (keyof T)[]
158
+ return Object.keys(def)
301
159
  },
302
160
 
303
161
  validateInput<K extends keyof T>(name: K, input: unknown): SafeParseResult<InferInput<T[K]>> {
304
- const endpoint = def[name]
305
- const result = endpoint.input.safeParse(input)
306
- if (result.success) {
307
- return { success: true, data: result.data as InferInput<T[K]> }
308
- }
309
- return { success: false, error: { message: result.error.message } }
310
- },
311
-
312
- validateOutput<K extends keyof T>(name: K, output: unknown): SafeParseResult<unknown> {
313
- const endpoint = def[name]
314
- if (endpoint._type !== 'method') {
315
- throw new Error(`Endpoint ${String(name)} is not a method`)
316
- }
317
- const result = endpoint.output.safeParse(output)
318
- if (result.success) {
319
- return { success: true, data: result.data }
320
- }
321
- return { success: false, error: { message: result.error.message } }
162
+ return safeParseWith<T[K]['input']>(def[name].input, input)
322
163
  },
323
164
  }
324
165
  }
325
-
326
- // ============================================================================
327
- // Type Extraction Helpers
328
- // ============================================================================
329
-
330
- /**
331
- * Extract the protocol definition type from a Protocol.
332
- */
333
- export type ExtractProtocolDef<T> = T extends Protocol<infer P> ? P : never
334
-
335
- /**
336
- * Create a typed handlers object type for a protocol.
337
- */
338
- export type HandlersFor<T extends Protocol<ProtocolDef>> = ProtocolHandlers<ExtractProtocolDef<T>>
339
-
340
- /**
341
- * Create a typed caller object type for a protocol.
342
- */
343
- export type CallerFor<T extends Protocol<ProtocolDef>> = ProtocolCaller<ExtractProtocolDef<T>>
344
-
345
- /**
346
- * Create a typed notifier object type for a protocol.
347
- */
348
- export type NotifierFor<T extends Protocol<ProtocolDef>> = ProtocolNotifier<ExtractProtocolDef<T>>
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @roj-ai/transport
3
3
  *
4
- * Platform-agnostic WebSocket transport infrastructure with RPC-like protocol API.
4
+ * Platform-agnostic WebSocket transport infrastructure with a typed notification protocol API.
5
5
  */
6
6
 
7
7
  // ============================================================================
@@ -9,33 +9,19 @@
9
9
  // ============================================================================
10
10
 
11
11
  // Protocol Definition API
12
- export { defineProtocol, method, notification } from './core/protocol.js'
12
+ export { defineProtocol, notification } from './core/protocol.js'
13
13
  export type {
14
- CallerFor,
15
- EndpointDef,
16
- EndpointHandler,
17
- ExtractProtocolDef,
18
14
  HandlerContext,
19
- HandlersFor,
20
- InferError,
21
15
  InferInput,
22
- InferOutput,
23
- IsMethod,
24
- IsNotification,
25
- MethodCaller,
26
- MethodDef,
27
- MethodHandler,
28
- MethodOptions,
29
16
  NotificationDef,
30
17
  NotificationHandler,
31
18
  NotificationNotifier,
32
19
  NotificationOptions,
33
- NotifierFor,
34
20
  Protocol,
35
- ProtocolCaller,
36
21
  ProtocolDef,
37
22
  ProtocolHandlers,
38
23
  ProtocolNotifier,
24
+ SafeParseResult,
39
25
  } from './core/protocol.js'
40
26
 
41
27
  // Result Type
@@ -15,12 +15,12 @@ export type {
15
15
  ConnectionState,
16
16
  HandlerContext,
17
17
  Protocol,
18
- ProtocolCaller,
19
18
  ProtocolDef,
20
19
  ProtocolHandlers,
21
20
  ProtocolNotifier,
21
+ SafeParseResult,
22
22
  TransportEvent,
23
23
  TransportEventListener,
24
24
  } from '../core/index.js'
25
25
 
26
- export { defineProtocol, method, notification } from '../core/index.js'
26
+ export { defineProtocol, notification } from '../core/index.js'