@mono-labs/dev 0.1.276 → 0.1.278

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/websocket/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,KAAK,EAAgB,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAEhE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACzE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,mBAAmB;;;;;;0BA6I9D,SAAS;EAEhC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/websocket/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,KAAK,EAAgB,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAEhE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACzE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,mBAAmB;;;;;;0BAsL9D,SAAS;EAEhC"}
@@ -75,6 +75,36 @@ function attachSocketAdapter(wss, config) {
75
75
  const disconnectHandler = config?.disconnectHandler ?? (async () => { });
76
76
  // Reverse lookup: WebSocket → connectionId
77
77
  const wsToConnectionId = new WeakMap();
78
+ // Heartbeat tracking
79
+ const pingEnabled = config?.ping ?? true;
80
+ const pingInterval = config?.pingInterval ?? 30_000;
81
+ const pingTimeout = config?.pingTimeout ?? 10_000;
82
+ const aliveSet = new Set();
83
+ let heartbeatTimer;
84
+ if (pingEnabled) {
85
+ heartbeatTimer = setInterval(() => {
86
+ for (const ws of wss.clients) {
87
+ if (!aliveSet.has(ws)) {
88
+ const cid = wsToConnectionId.get(ws);
89
+ if (debug)
90
+ console.log(`[socket-adapter] ping timeout — terminating connectionId=${cid}`);
91
+ ws.terminate();
92
+ continue;
93
+ }
94
+ aliveSet.delete(ws);
95
+ ws.ping();
96
+ if (debug) {
97
+ const cid = wsToConnectionId.get(ws);
98
+ console.log(`[socket-adapter] ping sent to connectionId=${cid}`);
99
+ }
100
+ }
101
+ }, pingInterval);
102
+ wss.on('close', () => {
103
+ clearInterval(heartbeatTimer);
104
+ if (debug)
105
+ console.log(`[socket-adapter] heartbeat interval cleared`);
106
+ });
107
+ }
78
108
  wss.on('connection', async (ws, req) => {
79
109
  // 1. Extract token from query string
80
110
  const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);
@@ -113,6 +143,15 @@ function attachSocketAdapter(wss, config) {
113
143
  ws.send(JSON.stringify(welcomeMessage));
114
144
  if (debug)
115
145
  console.log(`[socket-adapter] welcome sent to ${connectionId}${userContext ? ` userId=${userContext.userId}` : ''}`);
146
+ // 6b. Register for heartbeat
147
+ if (pingEnabled) {
148
+ aliveSet.add(ws);
149
+ ws.on('pong', () => {
150
+ aliveSet.add(ws);
151
+ if (debug)
152
+ console.log(`[socket-adapter] pong received from connectionId=${connectionId}`);
153
+ });
154
+ }
116
155
  // 7. Route incoming messages through ActionRouter
117
156
  ws.on('message', async (raw) => {
118
157
  const rawBody = raw.toString();
@@ -137,6 +176,7 @@ function attachSocketAdapter(wss, config) {
137
176
  if (debug) {
138
177
  console.log(`[socket-adapter] $disconnect connectionId=${connectionId} code=${code} reason=${reason.toString()}`);
139
178
  }
179
+ aliveSet.delete(ws);
140
180
  await channelStore.removeAll(connectionId);
141
181
  await disconnectHandler(connectionId);
142
182
  connectionRegistry.unregister(connectionId);
@@ -58,5 +58,11 @@ export interface SocketAdapterConfig {
58
58
  channelStore?: import('./channel-store').ChannelStore;
59
59
  useRedis?: boolean;
60
60
  redis?: RedisConfig;
61
+ /** Enable ws.ping heartbeat (default: true) */
62
+ ping?: boolean;
63
+ /** Ping interval in ms (default: 30000) */
64
+ pingInterval?: number;
65
+ /** Time to wait for pong before considering connection dead (default: 10000) */
66
+ pingTimeout?: number;
61
67
  }
62
68
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/websocket/types.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAED,sFAAsF;AACtF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA;AAEjC,6FAA6F;AAC7F,MAAM,MAAM,kBAAkB,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE7F,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IACnC,YAAY,EAAE,YAAY,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAA;CAC/C;AAED,wCAAwC;AACxC,MAAM,WAAW,oBAAoB;IACpC,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,EAAE,mBAAmB,CAAA;IACnC,gBAAgB,EAAE,kBAAkB,CAAA;IACpC,WAAW,EAAE,oBAAoB,CAAA;CACjC;AAED,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAED,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GAAG,CAC3B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,oBAAoB,KACrB,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAEjC,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAC9B,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KACtB,OAAO,CAAC;IACZ,QAAQ,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,WAAW,CAAC,EAAE,oBAAoB,CAAA;CAClC,CAAC,CAAA;AAEF,oCAAoC;AACpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE/E,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,iBAAiB,CAAC,EAAE,mBAAmB,CAAA;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACtC,cAAc,CAAC,EAAE,aAAa,CAAA;IAC9B,YAAY,CAAC,EAAE,OAAO,iBAAiB,EAAE,YAAY,CAAA;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,WAAW,CAAA;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/websocket/types.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAED,sFAAsF;AACtF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA;AAEjC,6FAA6F;AAC7F,MAAM,MAAM,kBAAkB,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE7F,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IACnC,YAAY,EAAE,YAAY,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAA;CAC/C;AAED,wCAAwC;AACxC,MAAM,WAAW,oBAAoB;IACpC,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,EAAE,mBAAmB,CAAA;IACnC,gBAAgB,EAAE,kBAAkB,CAAA;IACpC,WAAW,EAAE,oBAAoB,CAAA;CACjC;AAED,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAED,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GAAG,CAC3B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,oBAAoB,KACrB,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAEjC,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAC9B,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KACtB,OAAO,CAAC;IACZ,QAAQ,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,WAAW,CAAC,EAAE,oBAAoB,CAAA;CAClC,CAAC,CAAA;AAEF,oCAAoC;AACpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE/E,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,iBAAiB,CAAC,EAAE,mBAAmB,CAAA;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACtC,cAAc,CAAC,EAAE,aAAa,CAAA;IAC9B,YAAY,CAAC,EAAE,OAAO,iBAAiB,EAAE,YAAY,CAAA;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-labs/dev",
3
- "version": "0.1.276",
3
+ "version": "0.1.278",
4
4
  "type": "commonjs",
5
5
  "description": "Local development server and WebSocket adapter for mono-labs",
6
6
  "main": "dist/index.js",
@@ -79,6 +79,37 @@ export function attachSocketAdapter(wss: WebSocketServer, config?: SocketAdapter
79
79
  // Reverse lookup: WebSocket → connectionId
80
80
  const wsToConnectionId = new WeakMap<WebSocket, ConnectionId>()
81
81
 
82
+ // Heartbeat tracking
83
+ const pingEnabled = config?.ping ?? true
84
+ const pingInterval = config?.pingInterval ?? 30_000
85
+ const pingTimeout = config?.pingTimeout ?? 10_000
86
+ const aliveSet = new Set<WebSocket>()
87
+ let heartbeatTimer: ReturnType<typeof setInterval> | undefined
88
+
89
+ if (pingEnabled) {
90
+ heartbeatTimer = setInterval(() => {
91
+ for (const ws of wss.clients) {
92
+ if (!aliveSet.has(ws)) {
93
+ const cid = wsToConnectionId.get(ws)
94
+ if (debug) console.log(`[socket-adapter] ping timeout — terminating connectionId=${cid}`)
95
+ ws.terminate()
96
+ continue
97
+ }
98
+ aliveSet.delete(ws)
99
+ ws.ping()
100
+ if (debug) {
101
+ const cid = wsToConnectionId.get(ws)
102
+ console.log(`[socket-adapter] ping sent to connectionId=${cid}`)
103
+ }
104
+ }
105
+ }, pingInterval)
106
+
107
+ wss.on('close', () => {
108
+ clearInterval(heartbeatTimer)
109
+ if (debug) console.log(`[socket-adapter] heartbeat interval cleared`)
110
+ })
111
+ }
112
+
82
113
  wss.on('connection', async (ws: WebSocket, req) => {
83
114
  // 1. Extract token from query string
84
115
  const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`)
@@ -118,6 +149,15 @@ export function attachSocketAdapter(wss: WebSocketServer, config?: SocketAdapter
118
149
  ws.send(JSON.stringify(welcomeMessage))
119
150
  if (debug) console.log(`[socket-adapter] welcome sent to ${connectionId}${userContext ? ` userId=${userContext.userId}` : ''}`)
120
151
 
152
+ // 6b. Register for heartbeat
153
+ if (pingEnabled) {
154
+ aliveSet.add(ws)
155
+ ws.on('pong', () => {
156
+ aliveSet.add(ws)
157
+ if (debug) console.log(`[socket-adapter] pong received from connectionId=${connectionId}`)
158
+ })
159
+ }
160
+
121
161
  // 7. Route incoming messages through ActionRouter
122
162
  ws.on('message', async (raw) => {
123
163
  const rawBody = raw.toString()
@@ -150,6 +190,7 @@ export function attachSocketAdapter(wss: WebSocketServer, config?: SocketAdapter
150
190
  )
151
191
  }
152
192
 
193
+ aliveSet.delete(ws)
153
194
  await channelStore.removeAll(connectionId)
154
195
  await disconnectHandler(connectionId)
155
196
  connectionRegistry.unregister(connectionId)
@@ -71,4 +71,10 @@ export interface SocketAdapterConfig {
71
71
  channelStore?: import('./channel-store').ChannelStore
72
72
  useRedis?: boolean
73
73
  redis?: RedisConfig
74
+ /** Enable ws.ping heartbeat (default: true) */
75
+ ping?: boolean
76
+ /** Ping interval in ms (default: 30000) */
77
+ pingInterval?: number
78
+ /** Time to wait for pong before considering connection dead (default: 10000) */
79
+ pingTimeout?: number
74
80
  }