@roj-ai/transport 0.0.2

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 (64) hide show
  1. package/dist/client/client-connection.d.ts +48 -0
  2. package/dist/client/client-connection.d.ts.map +1 -0
  3. package/dist/client/client-connection.js +160 -0
  4. package/dist/client/index.d.ts +10 -0
  5. package/dist/client/index.d.ts.map +1 -0
  6. package/dist/client/index.js +7 -0
  7. package/dist/core/connection.d.ts +59 -0
  8. package/dist/core/connection.d.ts.map +1 -0
  9. package/dist/core/connection.js +165 -0
  10. package/dist/core/index.d.ts +16 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/core/index.js +13 -0
  13. package/dist/core/protocol.d.ts +206 -0
  14. package/dist/core/protocol.d.ts.map +1 -0
  15. package/dist/core/protocol.js +93 -0
  16. package/dist/core/result.d.ts +26 -0
  17. package/dist/core/result.d.ts.map +1 -0
  18. package/dist/core/result.js +20 -0
  19. package/dist/core/router.d.ts +79 -0
  20. package/dist/core/router.d.ts.map +1 -0
  21. package/dist/core/router.js +119 -0
  22. package/dist/core/types.d.ts +44 -0
  23. package/dist/core/types.d.ts.map +1 -0
  24. package/dist/core/types.js +14 -0
  25. package/dist/index.d.ts +24 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +26 -0
  28. package/dist/platform/browser.d.ts +14 -0
  29. package/dist/platform/browser.d.ts.map +1 -0
  30. package/dist/platform/browser.js +65 -0
  31. package/dist/platform/bun.d.ts +69 -0
  32. package/dist/platform/bun.d.ts.map +1 -0
  33. package/dist/platform/bun.js +152 -0
  34. package/dist/platform/types.d.ts +83 -0
  35. package/dist/platform/types.d.ts.map +1 -0
  36. package/dist/platform/types.js +14 -0
  37. package/dist/server/connection-manager.d.ts +106 -0
  38. package/dist/server/connection-manager.d.ts.map +1 -0
  39. package/dist/server/connection-manager.js +234 -0
  40. package/dist/server/index.d.ts +12 -0
  41. package/dist/server/index.d.ts.map +1 -0
  42. package/dist/server/index.js +8 -0
  43. package/dist/server/server-connection.d.ts +50 -0
  44. package/dist/server/server-connection.d.ts.map +1 -0
  45. package/dist/server/server-connection.js +84 -0
  46. package/package.json +52 -0
  47. package/src/__tests__/integration.test.ts +443 -0
  48. package/src/client/client-connection.ts +207 -0
  49. package/src/client/index.ts +24 -0
  50. package/src/core/connection.ts +203 -0
  51. package/src/core/index.ts +52 -0
  52. package/src/core/protocol.test.ts +235 -0
  53. package/src/core/protocol.ts +348 -0
  54. package/src/core/result.ts +35 -0
  55. package/src/core/router.test.ts +248 -0
  56. package/src/core/router.ts +186 -0
  57. package/src/core/types.ts +85 -0
  58. package/src/index.ts +80 -0
  59. package/src/platform/browser.ts +79 -0
  60. package/src/platform/bun.ts +220 -0
  61. package/src/platform/types.ts +102 -0
  62. package/src/server/connection-manager.ts +298 -0
  63. package/src/server/index.ts +26 -0
  64. package/src/server/server-connection.ts +122 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Connection Manager
3
+ *
4
+ * Manages multiple WebSocket connections and session subscriptions.
5
+ */
6
+ // ============================================================================
7
+ // Connection Manager
8
+ // ============================================================================
9
+ /**
10
+ * Manages multiple server connections and session subscriptions.
11
+ *
12
+ * Features:
13
+ * - Track all active connections
14
+ * - Manage session subscriptions
15
+ * - Broadcast to subscribed connections
16
+ * - Connection statistics
17
+ */
18
+ export class ConnectionManager {
19
+ connections = new Map();
20
+ sessionSubscriptions = new Map(); // sessionId -> connectionIds
21
+ // ============================================================================
22
+ // Connection Management
23
+ // ============================================================================
24
+ /**
25
+ * Add a connection.
26
+ */
27
+ add(connection) {
28
+ this.connections.set(connection.getConnectionId(), connection);
29
+ }
30
+ /**
31
+ * Remove a connection.
32
+ */
33
+ remove(connectionId) {
34
+ const connection = this.connections.get(connectionId);
35
+ if (!connection)
36
+ return;
37
+ // Clean up subscriptions
38
+ for (const sessionId of connection.getSubscribedSessions()) {
39
+ this.unsubscribeInternal(connectionId, sessionId);
40
+ }
41
+ this.connections.delete(connectionId);
42
+ }
43
+ /**
44
+ * Get a connection by ID.
45
+ */
46
+ get(connectionId) {
47
+ return this.connections.get(connectionId);
48
+ }
49
+ /**
50
+ * Check if a connection exists.
51
+ */
52
+ has(connectionId) {
53
+ return this.connections.has(connectionId);
54
+ }
55
+ /**
56
+ * Get all connections.
57
+ */
58
+ getAll() {
59
+ return this.connections.values();
60
+ }
61
+ /**
62
+ * Get all connection IDs.
63
+ */
64
+ getAllIds() {
65
+ return this.connections.keys();
66
+ }
67
+ /**
68
+ * Get connection count.
69
+ */
70
+ get size() {
71
+ return this.connections.size;
72
+ }
73
+ // ============================================================================
74
+ // Session Subscriptions
75
+ // ============================================================================
76
+ /**
77
+ * Subscribe a connection to a session.
78
+ */
79
+ subscribe(connectionId, sessionId) {
80
+ const connection = this.connections.get(connectionId);
81
+ if (!connection)
82
+ return false;
83
+ connection.subscribe(sessionId);
84
+ let subscribers = this.sessionSubscriptions.get(sessionId);
85
+ if (!subscribers) {
86
+ subscribers = new Set();
87
+ this.sessionSubscriptions.set(sessionId, subscribers);
88
+ }
89
+ subscribers.add(connectionId);
90
+ return true;
91
+ }
92
+ /**
93
+ * Unsubscribe a connection from a session.
94
+ */
95
+ unsubscribe(connectionId, sessionId) {
96
+ const connection = this.connections.get(connectionId);
97
+ if (!connection)
98
+ return false;
99
+ connection.unsubscribe(sessionId);
100
+ this.unsubscribeInternal(connectionId, sessionId);
101
+ return true;
102
+ }
103
+ /**
104
+ * Get all subscribers for a session.
105
+ */
106
+ getSubscribers(sessionId) {
107
+ const subscriberIds = this.sessionSubscriptions.get(sessionId);
108
+ if (!subscriberIds)
109
+ return [];
110
+ const subscribers = [];
111
+ const deadIds = [];
112
+ for (const connectionId of subscriberIds) {
113
+ const connection = this.connections.get(connectionId);
114
+ if (connection?.isConnected()) {
115
+ subscribers.push(connection);
116
+ }
117
+ else {
118
+ deadIds.push(connectionId);
119
+ }
120
+ }
121
+ // Clean up dead subscriptions
122
+ for (const id of deadIds) {
123
+ subscriberIds.delete(id);
124
+ }
125
+ if (subscriberIds.size === 0) {
126
+ this.sessionSubscriptions.delete(sessionId);
127
+ }
128
+ return subscribers;
129
+ }
130
+ /**
131
+ * Get subscriber count for a session.
132
+ */
133
+ getSubscriberCount(sessionId) {
134
+ return this.sessionSubscriptions.get(sessionId)?.size ?? 0;
135
+ }
136
+ /**
137
+ * Check if a session has any subscribers.
138
+ */
139
+ hasSubscribers(sessionId) {
140
+ return this.getSubscriberCount(sessionId) > 0;
141
+ }
142
+ // ============================================================================
143
+ // Broadcasting
144
+ // ============================================================================
145
+ /**
146
+ * Broadcast a raw message to all subscribers of a session.
147
+ */
148
+ broadcast(sessionId, message) {
149
+ const subscribers = this.getSubscribers(sessionId);
150
+ let sent = 0;
151
+ for (const connection of subscribers) {
152
+ if (connection.send(message)) {
153
+ sent++;
154
+ }
155
+ }
156
+ return sent;
157
+ }
158
+ /**
159
+ * Broadcast to all connections.
160
+ */
161
+ broadcastToAll(message) {
162
+ let sent = 0;
163
+ for (const connection of this.connections.values()) {
164
+ if (connection.isConnected() && connection.send(message)) {
165
+ sent++;
166
+ }
167
+ }
168
+ return sent;
169
+ }
170
+ // ============================================================================
171
+ // Statistics
172
+ // ============================================================================
173
+ /**
174
+ * Get connection statistics.
175
+ */
176
+ getStats() {
177
+ const byClientType = {};
178
+ let activeConnections = 0;
179
+ for (const connection of this.connections.values()) {
180
+ const clientType = connection.getClientType();
181
+ byClientType[clientType] = (byClientType[clientType] ?? 0) + 1;
182
+ if (connection.isConnected()) {
183
+ activeConnections++;
184
+ }
185
+ }
186
+ return {
187
+ totalConnections: this.connections.size,
188
+ activeConnections,
189
+ activeSessions: this.sessionSubscriptions.size,
190
+ byClientType,
191
+ };
192
+ }
193
+ // ============================================================================
194
+ // Cleanup
195
+ // ============================================================================
196
+ /**
197
+ * Clear all connections.
198
+ */
199
+ clear() {
200
+ for (const connection of this.connections.values()) {
201
+ connection.disconnect();
202
+ }
203
+ this.connections.clear();
204
+ this.sessionSubscriptions.clear();
205
+ }
206
+ /**
207
+ * Remove disconnected connections.
208
+ */
209
+ cleanup() {
210
+ let removed = 0;
211
+ for (const [connectionId, connection] of this.connections) {
212
+ if (!connection.isConnected()) {
213
+ this.remove(connectionId);
214
+ removed++;
215
+ }
216
+ }
217
+ return removed;
218
+ }
219
+ // ============================================================================
220
+ // Private Methods
221
+ // ============================================================================
222
+ /**
223
+ * Internal unsubscribe (updates subscription map only).
224
+ */
225
+ unsubscribeInternal(connectionId, sessionId) {
226
+ const subscribers = this.sessionSubscriptions.get(sessionId);
227
+ if (subscribers) {
228
+ subscribers.delete(connectionId);
229
+ if (subscribers.size === 0) {
230
+ this.sessionSubscriptions.delete(sessionId);
231
+ }
232
+ }
233
+ }
234
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Server Connection Module
3
+ *
4
+ * WebSocket server for handling incoming client connections.
5
+ */
6
+ export { createServerConnection, ServerConnection } from './server-connection.js';
7
+ export type { ServerConnectionConfig, ServerHandlerContext } from './server-connection.js';
8
+ export { ConnectionManager } from './connection-manager.js';
9
+ export type { ConnectionStats } from './connection-manager.js';
10
+ export type { ConnectionState, HandlerContext, Protocol, ProtocolCaller, ProtocolDef, ProtocolHandlers, ProtocolNotifier, TransportEvent, TransportEventListener, } from '../core/index.js';
11
+ export { defineProtocol, method, notification } from '../core/index.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACjF,YAAY,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAE1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,YAAY,EACX,eAAe,EACf,cAAc,EACd,QAAQ,EACR,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,sBAAsB,GACtB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Server Connection Module
3
+ *
4
+ * WebSocket server for handling incoming client connections.
5
+ */
6
+ export { createServerConnection, ServerConnection } from './server-connection.js';
7
+ export { ConnectionManager } from './connection-manager.js';
8
+ export { defineProtocol, method, notification } from '../core/index.js';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Server Connection
3
+ *
4
+ * Represents a single client connection on the server side.
5
+ * Simplified: no handshake - connection is ready immediately when attached.
6
+ */
7
+ import { Connection } from '../core/connection.js';
8
+ import type { ConnectionConfig } from '../core/connection.js';
9
+ import type { HandlerContext, ProtocolDef } from '../core/protocol.js';
10
+ import type { IServerWebSocket } from '../platform/types.js';
11
+ /**
12
+ * Server connection configuration.
13
+ */
14
+ export interface ServerConnectionConfig<TReceive extends ProtocolDef, TSend extends ProtocolDef> extends ConnectionConfig<TReceive, TSend> {
15
+ connectionId?: string;
16
+ }
17
+ /**
18
+ * Extended handler context for server connections.
19
+ */
20
+ export interface ServerHandlerContext extends HandlerContext {
21
+ clientType?: string;
22
+ subscribedSessions: Set<string>;
23
+ }
24
+ /**
25
+ * Server-side WebSocket connection representing a single client.
26
+ */
27
+ export declare class ServerConnection<TReceive extends ProtocolDef, TSend extends ProtocolDef> extends Connection<TReceive, TSend> {
28
+ private serverWs;
29
+ private clientType;
30
+ private subscribedSessions;
31
+ constructor(config: ServerConnectionConfig<TReceive, TSend>);
32
+ /**
33
+ * Attach an already-accepted WebSocket connection.
34
+ * Connection is immediately ready - no handshake needed.
35
+ */
36
+ attach(ws: IServerWebSocket): void;
37
+ connect(): Promise<void>;
38
+ disconnect(): Promise<void>;
39
+ getClientType(): string;
40
+ setClientType(clientType: string): void;
41
+ getSubscribedSessions(): ReadonlySet<string>;
42
+ subscribe(sessionId: string): void;
43
+ unsubscribe(sessionId: string): void;
44
+ isSubscribedTo(sessionId: string): boolean;
45
+ publishToSession(sessionId: string, message: string): void;
46
+ getHandlerContext(): ServerHandlerContext;
47
+ private setupServerWebSocket;
48
+ }
49
+ export declare function createServerConnection<TReceive extends ProtocolDef, TSend extends ProtocolDef>(config: ServerConnectionConfig<TReceive, TSend>): ServerConnection<TReceive, TSend>;
50
+ //# sourceMappingURL=server-connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-connection.d.ts","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,QAAQ,SAAS,WAAW,EAAE,KAAK,SAAS,WAAW,CAAE,SAAQ,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzI,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC/B;AAED;;GAEG;AACH,qBAAa,gBAAgB,CAAC,QAAQ,SAAS,WAAW,EAAE,KAAK,SAAS,WAAW,CAAE,SAAQ,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzH,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,kBAAkB,CAAoB;gBAElC,MAAM,EAAE,sBAAsB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAO3D;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI;IAM5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC,aAAa,IAAI,MAAM;IAIvB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC,qBAAqB,IAAI,WAAW,CAAC,MAAM,CAAC;IAI5C,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKpC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI1C,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI1D,iBAAiB,IAAI,oBAAoB;IASzC,OAAO,CAAC,oBAAoB;CAO5B;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,SAAS,WAAW,EAAE,KAAK,SAAS,WAAW,EAC7F,MAAM,EAAE,sBAAsB,CAAC,QAAQ,EAAE,KAAK,CAAC,GAC7C,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAEnC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Server Connection
3
+ *
4
+ * Represents a single client connection on the server side.
5
+ * Simplified: no handshake - connection is ready immediately when attached.
6
+ */
7
+ import { Connection } from '../core/connection.js';
8
+ import { WebSocketReadyState } from '../platform/types.js';
9
+ /**
10
+ * Server-side WebSocket connection representing a single client.
11
+ */
12
+ export class ServerConnection extends Connection {
13
+ serverWs = null;
14
+ clientType = 'unknown';
15
+ subscribedSessions = new Set();
16
+ constructor(config) {
17
+ super(config);
18
+ if (config.connectionId) {
19
+ this.connectionId = config.connectionId;
20
+ }
21
+ }
22
+ /**
23
+ * Attach an already-accepted WebSocket connection.
24
+ * Connection is immediately ready - no handshake needed.
25
+ */
26
+ attach(ws) {
27
+ this.serverWs = ws;
28
+ this.setupServerWebSocket(ws);
29
+ this.setState('connected');
30
+ }
31
+ async connect() {
32
+ throw new Error('Server connections use attach() instead of connect()');
33
+ }
34
+ async disconnect() {
35
+ this.cleanup();
36
+ if (this.serverWs && this.serverWs.readyState !== WebSocketReadyState.CLOSED) {
37
+ this.serverWs.close(1000, 'Server disconnect');
38
+ }
39
+ this.serverWs = null;
40
+ this.ws = null;
41
+ this.setState('disconnected');
42
+ }
43
+ getClientType() {
44
+ return this.clientType;
45
+ }
46
+ setClientType(clientType) {
47
+ this.clientType = clientType;
48
+ }
49
+ getSubscribedSessions() {
50
+ return this.subscribedSessions;
51
+ }
52
+ subscribe(sessionId) {
53
+ this.subscribedSessions.add(sessionId);
54
+ this.serverWs?.subscribe(`session:${sessionId}`);
55
+ }
56
+ unsubscribe(sessionId) {
57
+ this.subscribedSessions.delete(sessionId);
58
+ this.serverWs?.unsubscribe(`session:${sessionId}`);
59
+ }
60
+ isSubscribedTo(sessionId) {
61
+ return this.subscribedSessions.has(sessionId);
62
+ }
63
+ publishToSession(sessionId, message) {
64
+ this.serverWs?.publish(`session:${sessionId}`, message);
65
+ }
66
+ getHandlerContext() {
67
+ return {
68
+ connectionId: this.connectionId,
69
+ clientType: this.clientType,
70
+ subscribedSessions: this.subscribedSessions,
71
+ };
72
+ }
73
+ // Use server context for handlers
74
+ setupServerWebSocket(ws) {
75
+ this.ws = ws;
76
+ this.router.setSender((message) => this.send(message));
77
+ ws.onmessage = (data) => this.router.handleMessage(data, this.getHandlerContext());
78
+ ws.onerror = (error) => this.emit('error', error);
79
+ ws.onclose = (event) => this.handleClose(event.code, event.reason);
80
+ }
81
+ }
82
+ export function createServerConnection(config) {
83
+ return new ServerConnection(config);
84
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@roj-ai/transport",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "main": "./src/index.ts",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./src/index.ts",
10
+ "default": "./src/index.ts"
11
+ },
12
+ "./client": {
13
+ "types": "./dist/client/index.d.ts",
14
+ "import": "./src/client/index.ts",
15
+ "default": "./src/client/index.ts"
16
+ },
17
+ "./server": {
18
+ "types": "./dist/server/index.d.ts",
19
+ "import": "./src/server/index.ts",
20
+ "default": "./src/server/index.ts"
21
+ },
22
+ "./browser": {
23
+ "types": "./dist/platform/browser.d.ts",
24
+ "import": "./src/platform/browser.ts",
25
+ "default": "./src/platform/browser.ts"
26
+ },
27
+ "./bun": {
28
+ "types": "./dist/platform/bun.d.ts",
29
+ "import": "./src/platform/bun.ts",
30
+ "default": "./src/platform/bun.ts"
31
+ }
32
+ },
33
+ "files": [
34
+ "src",
35
+ "dist"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "type-check": "tsc --noEmit"
43
+ },
44
+ "dependencies": {
45
+ "zod": "4.3.6"
46
+ },
47
+ "devDependencies": {
48
+ "@types/bun": "latest",
49
+ "typescript": "^5.7.2"
50
+ },
51
+ "types": "./dist/index.d.ts"
52
+ }