@rljson/server 0.0.9 → 0.0.10

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.
package/README.public.md CHANGED
@@ -164,6 +164,9 @@ This is implemented with `IoMulti` and `BsMulti` internally, but the public API
164
164
  - `connector` – Connector wired to the route and socket (available when route was provided)
165
165
  - `route` – the Route passed to the constructor
166
166
  - `logger` – the `ServerLogger` instance (defaults to `noopLogger`)
167
+ - `isConnected` – whether the socket is currently connected (tracks `disconnect`/`connect` events)
168
+ - `onDisconnect(callback)` – registers a callback that fires when the socket disconnects (receives the reason string)
169
+ - `onReconnect(callback)` – registers a callback that fires when the socket reconnects after a previous disconnect
167
170
 
168
171
  ### Server API
169
172
 
@@ -216,6 +219,60 @@ The same pattern is used for Bs (blob storage).
216
219
  - Peer initialization is guarded by a configurable timeout (`peerInitTimeoutMs`, default 30 s) on both server and client. On the server it prevents `addSocket()` from hanging on unresponsive clients; on the client it prevents `init()` from hanging when the server is unreachable.
217
220
  - Logging is opt-in via `{ logger }` options. Use `ConsoleLogger` for development, `BufferedLogger` for testing, `FilteredLogger` for production. Default is `NoopLogger` (zero overhead).
218
221
 
222
+ ## Reconnect handling
223
+
224
+ The `Client` class tracks socket connection state and provides hooks for upper layers to react to disconnects and reconnections.
225
+
226
+ ### How it works
227
+
228
+ Socket.IO auto-reconnects at the transport level by default. When the connection drops and is restored:
229
+
230
+ 1. The client-side Socket.IO socket emits `'disconnect'` then (later) `'connect'`.
231
+ 2. The `Client` class listens for these events and updates `isConnected`.
232
+ 3. Registered `onDisconnect` / `onReconnect` callbacks fire.
233
+ 4. The server auto-detects the dropped socket (`removeSocket`) and re-registers the reconnected socket via a new `'connection'` event.
234
+ 5. The server sends a bootstrap ref to the reconnected client, triggering a re-sync via the Connector's bootstrap handler.
235
+
236
+ ### Usage
237
+
238
+ ```ts
239
+ import { Client } from '@rljson/server';
240
+
241
+ const client = new Client(socket, io, bs, route, {
242
+ logger: new ConsoleLogger(),
243
+ });
244
+ await client.init();
245
+
246
+ // Track connection state
247
+ console.log(client.isConnected); // true
248
+
249
+ // React to disconnects
250
+ client.onDisconnect((reason) => {
251
+ console.warn(`Disconnected: ${reason}`);
252
+ // Pause user-facing sync indicators, queue local changes, etc.
253
+ });
254
+
255
+ // React to reconnections
256
+ client.onReconnect(() => {
257
+ console.info('Reconnected to server');
258
+ // Resume sync, trigger re-fetch, update UI, etc.
259
+ });
260
+ ```
261
+
262
+ ### What survives a reconnect
263
+
264
+ | Layer | Survives? | Details |
265
+ | ----------------- | --------- | ----------------------------------------------------------------- |
266
+ | Socket.IO | ✅ | Auto-reconnects, reuses the same client-side socket object |
267
+ | SocketIoBridge | ✅ | Wraps the same socket — event listeners are preserved |
268
+ | IoPeer / BsPeer | ✅ | Listeners remain on the client socket; server re-creates its side |
269
+ | Connector | ✅ | Listeners remain; server bootstrap re-syncs state |
270
+ | IoMulti / BsMulti | ✅ | Multi layers are unaffected; local Io/Bs always available |
271
+
272
+ ### Cleanup
273
+
274
+ All connection handlers are cleaned up automatically on `tearDown()`. After tearDown, no callbacks will fire.
275
+
219
276
  ## Logging
220
277
 
221
278
  Both `Server` and `Client` support structured logging via an injectable `ServerLogger` interface. Logging is opt-in — by default a zero-overhead `NoopLogger` is used.
@@ -164,6 +164,9 @@ This is implemented with `IoMulti` and `BsMulti` internally, but the public API
164
164
  - `connector` – Connector wired to the route and socket (available when route was provided)
165
165
  - `route` – the Route passed to the constructor
166
166
  - `logger` – the `ServerLogger` instance (defaults to `noopLogger`)
167
+ - `isConnected` – whether the socket is currently connected (tracks `disconnect`/`connect` events)
168
+ - `onDisconnect(callback)` – registers a callback that fires when the socket disconnects (receives the reason string)
169
+ - `onReconnect(callback)` – registers a callback that fires when the socket reconnects after a previous disconnect
167
170
 
168
171
  ### Server API
169
172
 
@@ -216,6 +219,60 @@ The same pattern is used for Bs (blob storage).
216
219
  - Peer initialization is guarded by a configurable timeout (`peerInitTimeoutMs`, default 30 s) on both server and client. On the server it prevents `addSocket()` from hanging on unresponsive clients; on the client it prevents `init()` from hanging when the server is unreachable.
217
220
  - Logging is opt-in via `{ logger }` options. Use `ConsoleLogger` for development, `BufferedLogger` for testing, `FilteredLogger` for production. Default is `NoopLogger` (zero overhead).
218
221
 
222
+ ## Reconnect handling
223
+
224
+ The `Client` class tracks socket connection state and provides hooks for upper layers to react to disconnects and reconnections.
225
+
226
+ ### How it works
227
+
228
+ Socket.IO auto-reconnects at the transport level by default. When the connection drops and is restored:
229
+
230
+ 1. The client-side Socket.IO socket emits `'disconnect'` then (later) `'connect'`.
231
+ 2. The `Client` class listens for these events and updates `isConnected`.
232
+ 3. Registered `onDisconnect` / `onReconnect` callbacks fire.
233
+ 4. The server auto-detects the dropped socket (`removeSocket`) and re-registers the reconnected socket via a new `'connection'` event.
234
+ 5. The server sends a bootstrap ref to the reconnected client, triggering a re-sync via the Connector's bootstrap handler.
235
+
236
+ ### Usage
237
+
238
+ ```ts
239
+ import { Client } from '@rljson/server';
240
+
241
+ const client = new Client(socket, io, bs, route, {
242
+ logger: new ConsoleLogger(),
243
+ });
244
+ await client.init();
245
+
246
+ // Track connection state
247
+ console.log(client.isConnected); // true
248
+
249
+ // React to disconnects
250
+ client.onDisconnect((reason) => {
251
+ console.warn(`Disconnected: ${reason}`);
252
+ // Pause user-facing sync indicators, queue local changes, etc.
253
+ });
254
+
255
+ // React to reconnections
256
+ client.onReconnect(() => {
257
+ console.info('Reconnected to server');
258
+ // Resume sync, trigger re-fetch, update UI, etc.
259
+ });
260
+ ```
261
+
262
+ ### What survives a reconnect
263
+
264
+ | Layer | Survives? | Details |
265
+ | ----------------- | --------- | ----------------------------------------------------------------- |
266
+ | Socket.IO | ✅ | Auto-reconnects, reuses the same client-side socket object |
267
+ | SocketIoBridge | ✅ | Wraps the same socket — event listeners are preserved |
268
+ | IoPeer / BsPeer | ✅ | Listeners remain on the client socket; server re-creates its side |
269
+ | Connector | ✅ | Listeners remain; server bootstrap re-syncs state |
270
+ | IoMulti / BsMulti | ✅ | Multi layers are unaffected; local Io/Bs always available |
271
+
272
+ ### Cleanup
273
+
274
+ All connection handlers are cleaned up automatically on `tearDown()`. After tearDown, no callbacks will fire.
275
+
219
276
  ## Logging
220
277
 
221
278
  Both `Server` and `Client` support structured logging via an injectable `ServerLogger` interface. Logging is opt-in — by default a zero-overhead `NoopLogger` is used.
package/dist/client.d.ts CHANGED
@@ -45,6 +45,10 @@ export declare class Client extends BaseNode {
45
45
  private _syncConfig?;
46
46
  private _clientIdentity?;
47
47
  private _peerInitTimeoutMs;
48
+ private _isConnected;
49
+ private _disconnectCallbacks;
50
+ private _reconnectCallbacks;
51
+ private _connectionCleanup?;
48
52
  /**
49
53
  * Creates a Client instance
50
54
  * @param _socketToServer - Socket or namespace bundle to connect to server
@@ -91,11 +95,34 @@ export declare class Client extends BaseNode {
91
95
  * Returns the logger instance.
92
96
  */
93
97
  get logger(): ServerLogger;
98
+ /**
99
+ * Whether the client is currently connected to the server.
100
+ * Tracks socket-level connection state via `disconnect` and `connect` events.
101
+ */
102
+ get isConnected(): boolean;
103
+ /**
104
+ * Registers a callback that fires when the socket disconnects.
105
+ * The callback receives the disconnect reason string.
106
+ * @param callback - Invoked with the disconnect reason
107
+ */
108
+ onDisconnect(callback: (reason: string) => void): void;
109
+ /**
110
+ * Registers a callback that fires when the socket reconnects
111
+ * after a previous disconnect.
112
+ * @param callback - Invoked on reconnection
113
+ */
114
+ onReconnect(callback: () => void): void;
94
115
  /**
95
116
  * Creates Db and Connector from the route and IoMulti.
96
117
  * Called during init() when a route was provided.
97
118
  */
98
119
  private _setupDbAndConnector;
120
+ /**
121
+ * Registers socket-level disconnect/connect listeners.
122
+ * Logs state transitions and invokes registered callbacks.
123
+ * The `connect` callback only fires on RE-connections (not the initial connect).
124
+ */
125
+ private _registerConnectionHandlers;
99
126
  /**
100
127
  * Builds the Io multi with local and peer layers.
101
128
  */
package/dist/server.js CHANGED
@@ -1159,6 +1159,11 @@ class Client extends BaseNode {
1159
1159
  _syncConfig;
1160
1160
  _clientIdentity;
1161
1161
  _peerInitTimeoutMs;
1162
+ // Connection state
1163
+ _isConnected = true;
1164
+ _disconnectCallbacks = [];
1165
+ _reconnectCallbacks = [];
1166
+ _connectionCleanup;
1162
1167
  /**
1163
1168
  * Initializes Io and Bs multis and their peer bridges.
1164
1169
  * @returns The initialized Io implementation.
@@ -1171,6 +1176,7 @@ class Client extends BaseNode {
1171
1176
  if (this._route) {
1172
1177
  this._setupDbAndConnector();
1173
1178
  }
1179
+ this._registerConnectionHandlers();
1174
1180
  await this.ready();
1175
1181
  this._logger.info("Client", "Client initialized successfully", {
1176
1182
  hasRoute: !!this._route,
@@ -1196,6 +1202,12 @@ class Client extends BaseNode {
1196
1202
  */
1197
1203
  async tearDown() {
1198
1204
  this._logger.info("Client", "Tearing down client");
1205
+ if (this._connectionCleanup) {
1206
+ this._connectionCleanup();
1207
+ this._connectionCleanup = void 0;
1208
+ }
1209
+ this._disconnectCallbacks = [];
1210
+ this._reconnectCallbacks = [];
1199
1211
  if (this._ioMulti && this._ioMulti.isOpen) {
1200
1212
  this._ioMulti.close();
1201
1213
  }
@@ -1244,6 +1256,29 @@ class Client extends BaseNode {
1244
1256
  get logger() {
1245
1257
  return this._logger;
1246
1258
  }
1259
+ /**
1260
+ * Whether the client is currently connected to the server.
1261
+ * Tracks socket-level connection state via `disconnect` and `connect` events.
1262
+ */
1263
+ get isConnected() {
1264
+ return this._isConnected;
1265
+ }
1266
+ /**
1267
+ * Registers a callback that fires when the socket disconnects.
1268
+ * The callback receives the disconnect reason string.
1269
+ * @param callback - Invoked with the disconnect reason
1270
+ */
1271
+ onDisconnect(callback) {
1272
+ this._disconnectCallbacks.push(callback);
1273
+ }
1274
+ /**
1275
+ * Registers a callback that fires when the socket reconnects
1276
+ * after a previous disconnect.
1277
+ * @param callback - Invoked on reconnection
1278
+ */
1279
+ onReconnect(callback) {
1280
+ this._reconnectCallbacks.push(callback);
1281
+ }
1247
1282
  /**
1248
1283
  * Creates Db and Connector from the route and IoMulti.
1249
1284
  * Called during init() when a route was provided.
@@ -1263,6 +1298,44 @@ class Client extends BaseNode {
1263
1298
  );
1264
1299
  this._logger.info("Client", "Db and Connector created");
1265
1300
  }
1301
+ /**
1302
+ * Registers socket-level disconnect/connect listeners.
1303
+ * Logs state transitions and invokes registered callbacks.
1304
+ * The `connect` callback only fires on RE-connections (not the initial connect).
1305
+ */
1306
+ _registerConnectionHandlers() {
1307
+ const sockets = normalizeSocketBundle(this._socketToServer);
1308
+ const socket = sockets.ioUp;
1309
+ const disconnectHandler = (...args) => {
1310
+ const reason = typeof args[0] === "string" ? args[0] : "unknown";
1311
+ this._isConnected = false;
1312
+ this._logger.warn("Client", "Disconnected from server", { reason });
1313
+ for (const cb of this._disconnectCallbacks) {
1314
+ try {
1315
+ cb(reason);
1316
+ } catch {
1317
+ }
1318
+ }
1319
+ };
1320
+ const reconnectHandler = () => {
1321
+ if (!this._isConnected) {
1322
+ this._isConnected = true;
1323
+ this._logger.info("Client", "Reconnected to server");
1324
+ for (const cb of this._reconnectCallbacks) {
1325
+ try {
1326
+ cb();
1327
+ } catch {
1328
+ }
1329
+ }
1330
+ }
1331
+ };
1332
+ socket.on("disconnect", disconnectHandler);
1333
+ socket.on("connect", reconnectHandler);
1334
+ this._connectionCleanup = () => {
1335
+ socket.off("disconnect", disconnectHandler);
1336
+ socket.off("connect", reconnectHandler);
1337
+ };
1338
+ }
1266
1339
  /**
1267
1340
  * Builds the Io multi with local and peer layers.
1268
1341
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/server",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Rljson server description",
5
5
  "homepage": "https://github.com/rljson/server",
6
6
  "bugs": "https://github.com/rljson/server/issues",