@agentuity/frontend 0.0.100

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 (48) hide show
  1. package/AGENTS.md +80 -0
  2. package/README.md +65 -0
  3. package/dist/env.d.ts +2 -0
  4. package/dist/env.d.ts.map +1 -0
  5. package/dist/env.js +10 -0
  6. package/dist/env.js.map +1 -0
  7. package/dist/eventstream-manager.d.ts +85 -0
  8. package/dist/eventstream-manager.d.ts.map +1 -0
  9. package/dist/eventstream-manager.js +137 -0
  10. package/dist/eventstream-manager.js.map +1 -0
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +8 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memo.d.ts +6 -0
  16. package/dist/memo.d.ts.map +1 -0
  17. package/dist/memo.js +20 -0
  18. package/dist/memo.js.map +1 -0
  19. package/dist/reconnect.d.ts +22 -0
  20. package/dist/reconnect.d.ts.map +1 -0
  21. package/dist/reconnect.js +47 -0
  22. package/dist/reconnect.js.map +1 -0
  23. package/dist/serialization.d.ts +6 -0
  24. package/dist/serialization.d.ts.map +1 -0
  25. package/dist/serialization.js +16 -0
  26. package/dist/serialization.js.map +1 -0
  27. package/dist/types.d.ts +19 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +2 -0
  30. package/dist/types.js.map +1 -0
  31. package/dist/url.d.ts +3 -0
  32. package/dist/url.d.ts.map +1 -0
  33. package/dist/url.js +24 -0
  34. package/dist/url.js.map +1 -0
  35. package/dist/websocket-manager.d.ts +90 -0
  36. package/dist/websocket-manager.d.ts.map +1 -0
  37. package/dist/websocket-manager.js +163 -0
  38. package/dist/websocket-manager.js.map +1 -0
  39. package/package.json +39 -0
  40. package/src/env.ts +9 -0
  41. package/src/eventstream-manager.ts +203 -0
  42. package/src/index.ts +20 -0
  43. package/src/memo.ts +16 -0
  44. package/src/reconnect.ts +73 -0
  45. package/src/serialization.ts +14 -0
  46. package/src/types.ts +29 -0
  47. package/src/url.ts +32 -0
  48. package/src/websocket-manager.ts +234 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Message handler callback type
3
+ */
4
+ export type MessageHandler<T = unknown> = (data: T) => void;
5
+ /**
6
+ * WebSocket state change callback types
7
+ */
8
+ export interface WebSocketCallbacks<TOutput = unknown> {
9
+ /** Called when connection is established */
10
+ onConnect?: () => void;
11
+ /** Called when connection is closed */
12
+ onDisconnect?: () => void;
13
+ /** Called when an error occurs */
14
+ onError?: (error: Error) => void;
15
+ /** Called when a message is received */
16
+ onMessage?: MessageHandler<TOutput>;
17
+ }
18
+ /**
19
+ * Options for WebSocketManager
20
+ */
21
+ export interface WebSocketManagerOptions<TOutput = unknown> {
22
+ /** WebSocket URL */
23
+ url: string;
24
+ /** Callbacks for state changes */
25
+ callbacks?: WebSocketCallbacks<TOutput>;
26
+ /** Reconnection configuration */
27
+ reconnect?: {
28
+ threshold?: number;
29
+ baseDelay?: number;
30
+ factor?: number;
31
+ maxDelay?: number;
32
+ jitter?: number;
33
+ };
34
+ }
35
+ /**
36
+ * WebSocket manager state
37
+ */
38
+ export interface WebSocketManagerState {
39
+ /** Whether WebSocket is currently connected */
40
+ isConnected: boolean;
41
+ /** Current error, if any */
42
+ error: Error | null;
43
+ /** WebSocket ready state */
44
+ readyState: number;
45
+ }
46
+ /**
47
+ * Generic WebSocket connection manager with automatic reconnection,
48
+ * message queuing, and handler management.
49
+ *
50
+ * Framework-agnostic - can be used with React, Svelte, Vue, or vanilla JS.
51
+ */
52
+ export declare class WebSocketManager<TInput = unknown, TOutput = unknown> {
53
+ private ws;
54
+ private manualClose;
55
+ private pendingMessages;
56
+ private queuedMessages;
57
+ private messageHandler;
58
+ private reconnectManager;
59
+ private callbacks;
60
+ private url;
61
+ private reconnectConfig;
62
+ constructor(options: WebSocketManagerOptions<TOutput>);
63
+ /**
64
+ * Connect to the WebSocket server
65
+ */
66
+ connect(): void;
67
+ /**
68
+ * Send data through the WebSocket.
69
+ * Messages are queued if not currently connected.
70
+ */
71
+ send(data: TInput): void;
72
+ /**
73
+ * Set the message handler.
74
+ * Any buffered messages will be delivered immediately.
75
+ */
76
+ setMessageHandler(handler: MessageHandler<TOutput>): void;
77
+ /**
78
+ * Get current state
79
+ */
80
+ getState(): WebSocketManagerState;
81
+ /**
82
+ * Close the WebSocket connection and cleanup
83
+ */
84
+ close(): void;
85
+ /**
86
+ * Dispose of the manager (alias for close)
87
+ */
88
+ dispose(): void;
89
+ }
90
+ //# sourceMappingURL=websocket-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-manager.d.ts","sourceRoot":"","sources":["../src/websocket-manager.ts"],"names":[],"mappings":"AAqBA;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,OAAO;IACpD,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,kCAAkC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,wCAAwC;IACxC,SAAS,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB,CAAC,OAAO,GAAG,OAAO;IACzD,oBAAoB;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,SAAS,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACxC,iCAAiC;IACjC,SAAS,CAAC,EAAE;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,+CAA+C;IAC/C,WAAW,EAAE,OAAO,CAAC;IACrB,4BAA4B;IAC5B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;IAChE,OAAO,CAAC,EAAE,CAAwB;IAClC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,eAAe,CAAuE;gBAElF,OAAO,EAAE,uBAAuB,CAAC,OAAO,CAAC;IAYrD;;OAEG;IACH,OAAO,IAAI,IAAI;IAmEf;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQxB;;;OAGG;IACH,iBAAiB,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI;IAOzD;;OAEG;IACH,QAAQ,IAAI,qBAAqB;IAQjC;;OAEG;IACH,KAAK,IAAI,IAAI;IAqBb;;OAEG;IACH,OAAO,IAAI,IAAI;CAGf"}
@@ -0,0 +1,163 @@
1
+ import { createReconnectManager } from './reconnect';
2
+ import { deserializeData } from './serialization';
3
+ /**
4
+ * Serialize data for WebSocket transmission
5
+ */
6
+ const serializeWSData = (data) => {
7
+ if (typeof data === 'string') {
8
+ return data;
9
+ }
10
+ if (typeof data === 'object') {
11
+ if (data instanceof ArrayBuffer || ArrayBuffer.isView(data) || data instanceof Blob) {
12
+ return data;
13
+ }
14
+ return JSON.stringify(data);
15
+ }
16
+ throw new Error('unsupported data type for websocket: ' + typeof data);
17
+ };
18
+ /**
19
+ * Generic WebSocket connection manager with automatic reconnection,
20
+ * message queuing, and handler management.
21
+ *
22
+ * Framework-agnostic - can be used with React, Svelte, Vue, or vanilla JS.
23
+ */
24
+ export class WebSocketManager {
25
+ ws;
26
+ manualClose = false;
27
+ pendingMessages = [];
28
+ queuedMessages = [];
29
+ messageHandler;
30
+ reconnectManager;
31
+ callbacks;
32
+ url;
33
+ reconnectConfig;
34
+ constructor(options) {
35
+ this.url = options.url;
36
+ this.callbacks = options.callbacks || {};
37
+ this.reconnectConfig = {
38
+ threshold: options.reconnect?.threshold ?? 0,
39
+ baseDelay: options.reconnect?.baseDelay ?? 500,
40
+ factor: options.reconnect?.factor ?? 2,
41
+ maxDelay: options.reconnect?.maxDelay ?? 30000,
42
+ jitter: options.reconnect?.jitter ?? 500,
43
+ };
44
+ }
45
+ /**
46
+ * Connect to the WebSocket server
47
+ */
48
+ connect() {
49
+ if (this.manualClose)
50
+ return;
51
+ this.ws = new WebSocket(this.url);
52
+ this.ws.onopen = () => {
53
+ this.reconnectManager?.recordSuccess();
54
+ this.callbacks.onConnect?.();
55
+ // Flush queued messages
56
+ if (this.queuedMessages.length > 0) {
57
+ this.queuedMessages.forEach((msg) => this.ws.send(serializeWSData(msg)));
58
+ this.queuedMessages = [];
59
+ }
60
+ };
61
+ this.ws.onerror = () => {
62
+ const error = new Error('WebSocket error');
63
+ this.callbacks.onError?.(error);
64
+ };
65
+ this.ws.onclose = (evt) => {
66
+ this.ws = undefined;
67
+ this.callbacks.onDisconnect?.();
68
+ if (this.manualClose) {
69
+ this.queuedMessages = [];
70
+ return;
71
+ }
72
+ if (evt.code !== 1000) {
73
+ const error = new Error(`WebSocket closed: ${evt.code} ${evt.reason || ''}`);
74
+ this.callbacks.onError?.(error);
75
+ }
76
+ this.reconnectManager?.recordFailure();
77
+ };
78
+ this.ws.onmessage = (event) => {
79
+ const payload = deserializeData(event.data);
80
+ // Call the registered message handler
81
+ if (this.messageHandler) {
82
+ this.messageHandler(payload);
83
+ }
84
+ else {
85
+ // Buffer messages until a handler is set
86
+ this.pendingMessages.push(payload);
87
+ }
88
+ // Also call the callback if provided
89
+ this.callbacks.onMessage?.(payload);
90
+ };
91
+ // Setup reconnect manager
92
+ if (!this.reconnectManager) {
93
+ this.reconnectManager = createReconnectManager({
94
+ onReconnect: () => this.connect(),
95
+ threshold: this.reconnectConfig.threshold,
96
+ baseDelay: this.reconnectConfig.baseDelay,
97
+ factor: this.reconnectConfig.factor,
98
+ maxDelay: this.reconnectConfig.maxDelay,
99
+ jitter: this.reconnectConfig.jitter,
100
+ enabled: () => !this.manualClose,
101
+ });
102
+ }
103
+ }
104
+ /**
105
+ * Send data through the WebSocket.
106
+ * Messages are queued if not currently connected.
107
+ */
108
+ send(data) {
109
+ if (this.ws?.readyState === WebSocket.OPEN) {
110
+ this.ws.send(serializeWSData(data));
111
+ }
112
+ else {
113
+ this.queuedMessages.push(data);
114
+ }
115
+ }
116
+ /**
117
+ * Set the message handler.
118
+ * Any buffered messages will be delivered immediately.
119
+ */
120
+ setMessageHandler(handler) {
121
+ this.messageHandler = handler;
122
+ // Flush pending messages
123
+ this.pendingMessages.forEach(handler);
124
+ this.pendingMessages = [];
125
+ }
126
+ /**
127
+ * Get current state
128
+ */
129
+ getState() {
130
+ return {
131
+ isConnected: this.ws?.readyState === WebSocket.OPEN,
132
+ error: null, // Error state managed externally via callbacks
133
+ readyState: this.ws?.readyState ?? WebSocket.CLOSED,
134
+ };
135
+ }
136
+ /**
137
+ * Close the WebSocket connection and cleanup
138
+ */
139
+ close() {
140
+ this.manualClose = true;
141
+ this.reconnectManager?.dispose();
142
+ if (this.ws) {
143
+ this.ws.onopen = null;
144
+ this.ws.onerror = null;
145
+ this.ws.onclose = null;
146
+ this.ws.onmessage = null;
147
+ this.ws.close();
148
+ }
149
+ this.ws = undefined;
150
+ this.messageHandler = undefined;
151
+ this.pendingMessages = [];
152
+ this.queuedMessages = [];
153
+ // Notify disconnect callback
154
+ this.callbacks.onDisconnect?.();
155
+ }
156
+ /**
157
+ * Dispose of the manager (alias for close)
158
+ */
159
+ dispose() {
160
+ this.close();
161
+ }
162
+ }
163
+ //# sourceMappingURL=websocket-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-manager.js","sourceRoot":"","sources":["../src/websocket-manager.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@agentuity/frontend",
3
+ "version": "0.0.100",
4
+ "license": "Apache-2.0",
5
+ "author": "Agentuity employees and contributors",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "AGENTS.md",
17
+ "README.md",
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "scripts": {
22
+ "clean": "rm -rf dist",
23
+ "build": "bunx tsc --build --force",
24
+ "typecheck": "bunx tsc --noEmit",
25
+ "test": "bun test",
26
+ "prepublishOnly": "bun run clean && bun run build"
27
+ },
28
+ "dependencies": {},
29
+ "devDependencies": {
30
+ "@agentuity/test-utils": "workspace:*",
31
+ "@types/bun": "latest",
32
+ "bun-types": "latest",
33
+ "typescript": "^5.9.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "sideEffects": false
39
+ }
package/src/env.ts ADDED
@@ -0,0 +1,9 @@
1
+ export const getProcessEnv = (key: string): string | undefined => {
2
+ if (typeof process !== 'undefined' && process.env) {
3
+ return process.env[key];
4
+ }
5
+ if (typeof import.meta.env !== 'undefined') {
6
+ return import.meta.env[key];
7
+ }
8
+ return undefined;
9
+ };
@@ -0,0 +1,203 @@
1
+ import { createReconnectManager, type ReconnectManager } from './reconnect';
2
+ import { deserializeData } from './serialization';
3
+
4
+ /**
5
+ * Message handler callback type
6
+ */
7
+ export type MessageHandler<T = unknown> = (data: T) => void;
8
+
9
+ /**
10
+ * EventStream state change callback types
11
+ */
12
+ export interface EventStreamCallbacks<TOutput = unknown> {
13
+ /** Called when connection is established */
14
+ onConnect?: () => void;
15
+ /** Called when connection is closed */
16
+ onDisconnect?: () => void;
17
+ /** Called when an error occurs */
18
+ onError?: (error: Error) => void;
19
+ /** Called when a message is received */
20
+ onMessage?: MessageHandler<TOutput>;
21
+ }
22
+
23
+ /**
24
+ * Options for EventStreamManager
25
+ */
26
+ export interface EventStreamManagerOptions<TOutput = unknown> {
27
+ /** EventStream URL */
28
+ url: string;
29
+ /** Callbacks for state changes */
30
+ callbacks?: EventStreamCallbacks<TOutput>;
31
+ /** Reconnection configuration */
32
+ reconnect?: {
33
+ threshold?: number;
34
+ baseDelay?: number;
35
+ factor?: number;
36
+ maxDelay?: number;
37
+ jitter?: number;
38
+ };
39
+ }
40
+
41
+ /**
42
+ * EventStream manager state
43
+ */
44
+ export interface EventStreamManagerState {
45
+ /** Whether EventStream is currently connected */
46
+ isConnected: boolean;
47
+ /** Current error, if any */
48
+ error: Error | null;
49
+ /** EventStream ready state */
50
+ readyState: number;
51
+ }
52
+
53
+ /**
54
+ * Generic EventStream (SSE) connection manager with automatic reconnection
55
+ * and handler management.
56
+ *
57
+ * Framework-agnostic - can be used with React, Svelte, Vue, or vanilla JS.
58
+ */
59
+ export class EventStreamManager<TOutput = unknown> {
60
+ private es: EventSource | undefined;
61
+ private manualClose = false;
62
+ private pendingMessages: TOutput[] = [];
63
+ private messageHandler: MessageHandler<TOutput> | undefined;
64
+ private reconnectManager: ReconnectManager | undefined;
65
+ private callbacks: EventStreamCallbacks<TOutput>;
66
+ private url: string;
67
+ private reconnectConfig: Required<NonNullable<EventStreamManagerOptions<TOutput>['reconnect']>>;
68
+ private firstMessageReceived = false;
69
+
70
+ constructor(options: EventStreamManagerOptions<TOutput>) {
71
+ this.url = options.url;
72
+ this.callbacks = options.callbacks || {};
73
+ this.reconnectConfig = {
74
+ threshold: options.reconnect?.threshold ?? 3,
75
+ baseDelay: options.reconnect?.baseDelay ?? 500,
76
+ factor: options.reconnect?.factor ?? 2,
77
+ maxDelay: options.reconnect?.maxDelay ?? 30000,
78
+ jitter: options.reconnect?.jitter ?? 250,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Connect to the EventStream server
84
+ */
85
+ connect(): void {
86
+ if (this.manualClose) return;
87
+
88
+ this.es = new EventSource(this.url);
89
+ this.firstMessageReceived = false;
90
+
91
+ this.es.onopen = () => {
92
+ this.reconnectManager?.recordSuccess();
93
+ this.callbacks.onConnect?.();
94
+ };
95
+
96
+ this.es.onerror = () => {
97
+ const error = new Error('EventStream error');
98
+ this.callbacks.onError?.(error);
99
+ this.callbacks.onDisconnect?.();
100
+
101
+ if (this.manualClose) {
102
+ return;
103
+ }
104
+
105
+ const result = this.reconnectManager?.recordFailure();
106
+ if (result?.scheduled) {
107
+ // Close current connection before reconnecting
108
+ if (this.es) {
109
+ this.es.onopen = null;
110
+ this.es.onerror = null;
111
+ this.es.onmessage = null;
112
+ this.es.close();
113
+ }
114
+ this.es = undefined;
115
+ }
116
+ };
117
+
118
+ this.es.onmessage = (event: MessageEvent) => {
119
+ // Record success on first message (not just on open)
120
+ if (!this.firstMessageReceived) {
121
+ this.reconnectManager?.recordSuccess();
122
+ this.firstMessageReceived = true;
123
+ }
124
+
125
+ const payload = deserializeData<TOutput>(event.data);
126
+
127
+ // Call the registered message handler
128
+ if (this.messageHandler) {
129
+ this.messageHandler(payload);
130
+ } else {
131
+ // Buffer messages until a handler is set
132
+ this.pendingMessages.push(payload);
133
+ }
134
+
135
+ // Also call the callback if provided
136
+ this.callbacks.onMessage?.(payload);
137
+ };
138
+
139
+ // Setup reconnect manager
140
+ if (!this.reconnectManager) {
141
+ this.reconnectManager = createReconnectManager({
142
+ onReconnect: () => this.connect(),
143
+ threshold: this.reconnectConfig.threshold,
144
+ baseDelay: this.reconnectConfig.baseDelay,
145
+ factor: this.reconnectConfig.factor,
146
+ maxDelay: this.reconnectConfig.maxDelay,
147
+ jitter: this.reconnectConfig.jitter,
148
+ enabled: () => !this.manualClose,
149
+ });
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Set the message handler.
155
+ * Any buffered messages will be delivered immediately.
156
+ */
157
+ setMessageHandler(handler: MessageHandler<TOutput>): void {
158
+ this.messageHandler = handler;
159
+ // Flush pending messages
160
+ this.pendingMessages.forEach(handler);
161
+ this.pendingMessages = [];
162
+ }
163
+
164
+ /**
165
+ * Get current state
166
+ */
167
+ getState(): EventStreamManagerState {
168
+ return {
169
+ isConnected: this.es?.readyState === EventSource.OPEN,
170
+ error: null, // Error state managed externally via callbacks
171
+ readyState: this.es?.readyState ?? EventSource.CLOSED,
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Close the EventStream connection and cleanup
177
+ */
178
+ close(): void {
179
+ this.manualClose = true;
180
+ this.reconnectManager?.dispose();
181
+
182
+ if (this.es) {
183
+ this.es.onopen = null;
184
+ this.es.onerror = null;
185
+ this.es.onmessage = null;
186
+ this.es.close();
187
+ }
188
+
189
+ this.es = undefined;
190
+ this.messageHandler = undefined;
191
+ this.pendingMessages = [];
192
+
193
+ // Notify disconnect callback
194
+ this.callbacks.onDisconnect?.();
195
+ }
196
+
197
+ /**
198
+ * Dispose of the manager (alias for close)
199
+ */
200
+ dispose(): void {
201
+ this.close();
202
+ }
203
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export { getProcessEnv } from './env';
2
+ export { buildUrl, defaultBaseUrl } from './url';
3
+ export { deserializeData } from './serialization';
4
+ export { createReconnectManager, type ReconnectOptions, type ReconnectManager } from './reconnect';
5
+ export { type RouteRegistry, type WebSocketRouteRegistry, type SSERouteRegistry } from './types';
6
+ export { jsonEqual } from './memo';
7
+ export {
8
+ WebSocketManager,
9
+ type MessageHandler as WebSocketMessageHandler,
10
+ type WebSocketCallbacks,
11
+ type WebSocketManagerOptions,
12
+ type WebSocketManagerState,
13
+ } from './websocket-manager';
14
+ export {
15
+ EventStreamManager,
16
+ type MessageHandler as EventStreamMessageHandler,
17
+ type EventStreamCallbacks,
18
+ type EventStreamManagerOptions,
19
+ type EventStreamManagerState,
20
+ } from './eventstream-manager';
package/src/memo.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Simple JSON-based equality check for memoization.
3
+ * Compares stringified JSON to avoid deep equality overhead.
4
+ */
5
+ export function jsonEqual<T>(a: T, b: T): boolean {
6
+ if (a === b) return true;
7
+ if (a === undefined || b === undefined) return false;
8
+ if (a === null || b === null) return a === b;
9
+
10
+ try {
11
+ return JSON.stringify(a) === JSON.stringify(b);
12
+ } catch {
13
+ // Fallback for non-serializable values
14
+ return false;
15
+ }
16
+ }
@@ -0,0 +1,73 @@
1
+ export interface ReconnectOptions {
2
+ onReconnect: () => void;
3
+ threshold?: number;
4
+ baseDelay?: number;
5
+ factor?: number;
6
+ maxDelay?: number;
7
+ jitter?: number;
8
+ enabled?: () => boolean;
9
+ }
10
+
11
+ export interface ReconnectManager {
12
+ recordFailure: () => { scheduled: boolean; delay: number | null };
13
+ recordSuccess: () => void;
14
+ cancel: () => void;
15
+ reset: () => void;
16
+ dispose: () => void;
17
+ getAttempts: () => number;
18
+ }
19
+
20
+ export function createReconnectManager(opts: ReconnectOptions): ReconnectManager {
21
+ let attempts = 0;
22
+ let timer: ReturnType<typeof setTimeout> | null = null;
23
+
24
+ const cancel = () => {
25
+ if (timer) {
26
+ clearTimeout(timer);
27
+ timer = null;
28
+ }
29
+ };
30
+
31
+ const reset = () => {
32
+ attempts = 0;
33
+ cancel();
34
+ };
35
+
36
+ const recordSuccess = () => reset();
37
+
38
+ const computeDelay = (attemptAfterThreshold: number) => {
39
+ const base = opts.baseDelay ?? 500;
40
+ const factor = opts.factor ?? 2;
41
+ const max = opts.maxDelay ?? 30000;
42
+ const jitterMax = opts.jitter ?? 250;
43
+ const backoff = Math.min(base * Math.pow(factor, attemptAfterThreshold), max);
44
+ const jitter = jitterMax > 0 ? Math.random() * jitterMax : 0;
45
+ return backoff + jitter;
46
+ };
47
+
48
+ const recordFailure = () => {
49
+ attempts += 1;
50
+ const threshold = opts.threshold ?? 0;
51
+ if (opts.enabled && !opts.enabled()) {
52
+ return { scheduled: false, delay: null };
53
+ }
54
+
55
+ if (attempts - threshold >= 0) {
56
+ const after = Math.max(0, attempts - threshold);
57
+ const delay = computeDelay(after);
58
+ cancel();
59
+ timer = setTimeout(() => {
60
+ if (opts.enabled && !opts.enabled()) return;
61
+ opts.onReconnect();
62
+ }, delay);
63
+ return { scheduled: true, delay };
64
+ }
65
+ return { scheduled: false, delay: null };
66
+ };
67
+
68
+ const dispose = () => cancel();
69
+
70
+ const getAttempts = () => attempts;
71
+
72
+ return { recordFailure, recordSuccess, cancel, reset, dispose, getAttempts };
73
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Deserialize data received from WebSocket or EventStream.
3
+ * Attempts to parse as JSON if the data looks like JSON, otherwise returns as-is.
4
+ */
5
+ export const deserializeData = <T>(data: string): T => {
6
+ if (data) {
7
+ try {
8
+ return JSON.parse(data) as T;
9
+ } catch {
10
+ /* */
11
+ }
12
+ }
13
+ return data as T;
14
+ };
package/src/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Route registry containing all typed API routes in the application.
3
+ * Auto-generated by the build tool from routes that use validator() middleware.
4
+ */
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
6
+ export interface RouteRegistry {
7
+ // Will be augmented by generated code
8
+ // Format: 'METHOD /path': { inputSchema: ..., outputSchema: ..., stream?: boolean }
9
+ }
10
+
11
+ /**
12
+ * WebSocket route registry containing all typed WebSocket routes in the application.
13
+ * Auto-generated by the build tool from routes that use validator() middleware.
14
+ */
15
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
16
+ export interface WebSocketRouteRegistry {
17
+ // Will be augmented by generated code
18
+ // Format: '/path': { inputSchema: ..., outputSchema: ... }
19
+ }
20
+
21
+ /**
22
+ * SSE route registry containing all typed SSE routes in the application.
23
+ * Auto-generated by the build tool from routes that use validator() middleware.
24
+ */
25
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
26
+ export interface SSERouteRegistry {
27
+ // Will be augmented by generated code
28
+ // Format: '/path': { inputSchema: ..., outputSchema: ... }
29
+ }