@rljson/server 0.0.9 → 0.0.11

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.
@@ -77,6 +77,41 @@ In default setups you can reuse a single socket for all four channels; the code
77
77
 
78
78
  ## Core Components
79
79
 
80
+ ### 0. Node (Self-Organizing Orchestrator)
81
+
82
+ The `Node` class sits above `Server` and `Client`, bridging `@rljson/network` topology events into role transitions. It:
83
+
84
+ 1. **Owns storage**: Creates a single `IoMem`/`BsMem` pair at `start()`, reused across all role transitions. Data survives hub↔client switches because `IoMem.close()` only flips `_isOpen` — the in-memory data is never cleared.
85
+ 2. **Reacts to topology**: Subscribes to `NetworkManager`'s `role-changed` event. On `'hub'`, tears down any Client and creates a Server. On `'client'`, tears down any Server and creates a Client.
86
+ 3. **Manages transport**: Uses injectable factories (`CreateHubTransport`/`CreateClientTransport`) to create the transport layer, keeping the Node class transport-agnostic.
87
+ 4. **Agent lifecycle**: An optional `createAgent` factory in `NodeDeps` is called on every `ready` event. The returned `AgentHandle.stop()` is called before the next role transition. This enables application-level wiring (e.g. FsAgent) without circular dependencies.
88
+ 5. **Serialized transitions**: Role transitions are queued — a new `role-changed` event waits for the previous transition to complete before starting. This prevents race conditions between teardown and setup.
89
+ 6. **Error resilience**: Errors in user-provided code (agent factories, transport factories) are caught and logged. The node continues functioning — a failed transport degrades connectivity but doesn't crash, a failed agent leaves the node's core intact.
90
+
91
+ ```text
92
+ ┌─────────────────────────────────────────┐
93
+ │ Node │
94
+ │ ┌──────┐ ┌──────┐ │
95
+ │ │IoMem │ │BsMem │ ← owned by Node │
96
+ │ └──┬───┘ └──┬───┘ │
97
+ │ │ │ │
98
+ │ ┌──▼────────▼───┐ ┌────────────────┐ │
99
+ │ │ Server/Client │──│ HubTransport │ │
100
+ │ │ (role-based) │ │ or ClientSocket│ │
101
+ │ └───────┬───────┘ └────────────────┘ │
102
+ │ │ │
103
+ │ ┌───────▼───────┐ │
104
+ │ │ AgentHandle │ ← optional, wired │
105
+ │ │ (e.g. FsAgent)│ via createAgent │
106
+ │ └───────────────┘ │
107
+ │ ▲ │
108
+ │ │ role-changed │
109
+ │ ┌──┴───────────┐ │
110
+ │ │NetworkManager│ │
111
+ │ └──────────────┘ │
112
+ └─────────────────────────────────────────┘
113
+ ```
114
+
80
115
  ### 1. Client
81
116
 
82
117
  The `Client` class provides a unified interface for data access by combining local storage with server storage.
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.
@@ -580,6 +637,84 @@ await server.removeSocket(clientIds[0]);
580
637
  // Automatic: clients are removed when their socket emits 'disconnect'
581
638
  ```
582
639
 
640
+ ## Self-Organizing Node
641
+
642
+ The `Node` class bridges `@rljson/network` peer discovery with `Server`/`Client` role transitions. Every node runs the same code — on startup it discovers peers, elects a hub, and automatically assumes the correct role.
643
+
644
+ ### Quick start
645
+
646
+ ```ts
647
+ import { Node } from '@rljson/server';
648
+ import { Route } from '@rljson/rljson';
649
+
650
+ const node = new Node(
651
+ {
652
+ domain: 'my-app',
653
+ port: 3000,
654
+ route: Route.fromFlat('/sharedTree'),
655
+ },
656
+ {
657
+ createHubTransport: async (port) => { /* start HTTP+Socket.IO server */ },
658
+ createClientTransport: async (hubAddress) => { /* connect to hub */ },
659
+ },
660
+ );
661
+
662
+ await node.start();
663
+
664
+ // The node auto-discovers peers and assumes a role:
665
+ node.on('ready', ({ role, client, server, socket }) => {
666
+ console.log(`Role: ${role}`); // 'hub' or 'client'
667
+ console.log(`Server: ${server}`); // Server instance (hub only)
668
+ console.log(`Client: ${client}`); // Client instance (client only)
669
+ console.log(`Socket: ${socket}`); // Socket to hub (client only)
670
+ });
671
+ ```
672
+
673
+ ### Data preservation across role transitions
674
+
675
+ The `Node` owns a single `IoMem`/`BsMem` pair, created once at `start()` and reused across all role transitions. When a role changes (e.g., hub → client → hub), the underlying storage is re-initialized without losing data — `IoMem.close()` only sets `_isOpen = false`, the data in memory is preserved.
676
+
677
+ This means:
678
+ - Data written while hub survives a transition to client
679
+ - Data written while client survives a transition to hub
680
+ - A re-elected hub still serves all previously stored blobs and tables
681
+
682
+ ### Agent lifecycle
683
+
684
+ The `createAgent` factory in `NodeDeps` is called on every role transition. The returned `AgentHandle.stop()` is called before the next transition or when the node stops. This is how you wire application-level agents (e.g. `FsAgent`) without creating circular dependencies:
685
+
686
+ ```ts
687
+ const node = new Node(config, {
688
+ createHubTransport: ...,
689
+ createClientTransport: ...,
690
+ createAgent: async ({ role, client, server, socket }) => {
691
+ // Wire your agent here — called on every role transition
692
+ const stopSync = await startMySync(role, client, server);
693
+ return { stop: () => stopSync() };
694
+ },
695
+ });
696
+ ```
697
+
698
+ The Node serializes transitions — a new transition waits for the previous one to complete, ensuring agents are always stopped cleanly.
699
+
700
+ Errors in `createAgent` or `agentHandle.stop()` are caught and logged — they never crash the node. Similarly, transport factory failures degrade connectivity but leave the node functional locally.
701
+
702
+ ### API
703
+
704
+ | Property / Method | Description |
705
+ | --------------------- | ----------------------------------------------------- |
706
+ | `node.start()` | Begin peer discovery and role assignment |
707
+ | `node.stop()` | Tear down current role and stop discovery |
708
+ | `node.role` | Current role: `'hub'`, `'client'`, or `'unassigned'` |
709
+ | `node.io` | IoMulti from active Server or Client |
710
+ | `node.bs` | BsMulti from active Server or Client |
711
+ | `node.server` | Server instance (when hub), else `undefined` |
712
+ | `node.client` | Client instance (when client), else `undefined` |
713
+ | `node.socket` | Socket to hub (when client), else `undefined` |
714
+ | `node.topology` | Current network topology snapshot |
715
+ | `node.on(event, cb)` | Subscribe to `'ready'`, `'role-changed'`, `'stopped'` |
716
+ | `node.off(event, cb)` | Unsubscribe |
717
+
583
718
  ## Architecture Overview
584
719
 
585
720
  ### Pull-Based Reference Architecture
@@ -77,6 +77,41 @@ In default setups you can reuse a single socket for all four channels; the code
77
77
 
78
78
  ## Core Components
79
79
 
80
+ ### 0. Node (Self-Organizing Orchestrator)
81
+
82
+ The `Node` class sits above `Server` and `Client`, bridging `@rljson/network` topology events into role transitions. It:
83
+
84
+ 1. **Owns storage**: Creates a single `IoMem`/`BsMem` pair at `start()`, reused across all role transitions. Data survives hub↔client switches because `IoMem.close()` only flips `_isOpen` — the in-memory data is never cleared.
85
+ 2. **Reacts to topology**: Subscribes to `NetworkManager`'s `role-changed` event. On `'hub'`, tears down any Client and creates a Server. On `'client'`, tears down any Server and creates a Client.
86
+ 3. **Manages transport**: Uses injectable factories (`CreateHubTransport`/`CreateClientTransport`) to create the transport layer, keeping the Node class transport-agnostic.
87
+ 4. **Agent lifecycle**: An optional `createAgent` factory in `NodeDeps` is called on every `ready` event. The returned `AgentHandle.stop()` is called before the next role transition. This enables application-level wiring (e.g. FsAgent) without circular dependencies.
88
+ 5. **Serialized transitions**: Role transitions are queued — a new `role-changed` event waits for the previous transition to complete before starting. This prevents race conditions between teardown and setup.
89
+ 6. **Error resilience**: Errors in user-provided code (agent factories, transport factories) are caught and logged. The node continues functioning — a failed transport degrades connectivity but doesn't crash, a failed agent leaves the node's core intact.
90
+
91
+ ```text
92
+ ┌─────────────────────────────────────────┐
93
+ │ Node │
94
+ │ ┌──────┐ ┌──────┐ │
95
+ │ │IoMem │ │BsMem │ ← owned by Node │
96
+ │ └──┬───┘ └──┬───┘ │
97
+ │ │ │ │
98
+ │ ┌──▼────────▼───┐ ┌────────────────┐ │
99
+ │ │ Server/Client │──│ HubTransport │ │
100
+ │ │ (role-based) │ │ or ClientSocket│ │
101
+ │ └───────┬───────┘ └────────────────┘ │
102
+ │ │ │
103
+ │ ┌───────▼───────┐ │
104
+ │ │ AgentHandle │ ← optional, wired │
105
+ │ │ (e.g. FsAgent)│ via createAgent │
106
+ │ └───────────────┘ │
107
+ │ ▲ │
108
+ │ │ role-changed │
109
+ │ ┌──┴───────────┐ │
110
+ │ │NetworkManager│ │
111
+ │ └──────────────┘ │
112
+ └─────────────────────────────────────────┘
113
+ ```
114
+
80
115
  ### 1. Client
81
116
 
82
117
  The `Client` class provides a unified interface for data access by combining local storage with server storage.
@@ -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.
@@ -580,6 +637,84 @@ await server.removeSocket(clientIds[0]);
580
637
  // Automatic: clients are removed when their socket emits 'disconnect'
581
638
  ```
582
639
 
640
+ ## Self-Organizing Node
641
+
642
+ The `Node` class bridges `@rljson/network` peer discovery with `Server`/`Client` role transitions. Every node runs the same code — on startup it discovers peers, elects a hub, and automatically assumes the correct role.
643
+
644
+ ### Quick start
645
+
646
+ ```ts
647
+ import { Node } from '@rljson/server';
648
+ import { Route } from '@rljson/rljson';
649
+
650
+ const node = new Node(
651
+ {
652
+ domain: 'my-app',
653
+ port: 3000,
654
+ route: Route.fromFlat('/sharedTree'),
655
+ },
656
+ {
657
+ createHubTransport: async (port) => { /* start HTTP+Socket.IO server */ },
658
+ createClientTransport: async (hubAddress) => { /* connect to hub */ },
659
+ },
660
+ );
661
+
662
+ await node.start();
663
+
664
+ // The node auto-discovers peers and assumes a role:
665
+ node.on('ready', ({ role, client, server, socket }) => {
666
+ console.log(`Role: ${role}`); // 'hub' or 'client'
667
+ console.log(`Server: ${server}`); // Server instance (hub only)
668
+ console.log(`Client: ${client}`); // Client instance (client only)
669
+ console.log(`Socket: ${socket}`); // Socket to hub (client only)
670
+ });
671
+ ```
672
+
673
+ ### Data preservation across role transitions
674
+
675
+ The `Node` owns a single `IoMem`/`BsMem` pair, created once at `start()` and reused across all role transitions. When a role changes (e.g., hub → client → hub), the underlying storage is re-initialized without losing data — `IoMem.close()` only sets `_isOpen = false`, the data in memory is preserved.
676
+
677
+ This means:
678
+ - Data written while hub survives a transition to client
679
+ - Data written while client survives a transition to hub
680
+ - A re-elected hub still serves all previously stored blobs and tables
681
+
682
+ ### Agent lifecycle
683
+
684
+ The `createAgent` factory in `NodeDeps` is called on every role transition. The returned `AgentHandle.stop()` is called before the next transition or when the node stops. This is how you wire application-level agents (e.g. `FsAgent`) without creating circular dependencies:
685
+
686
+ ```ts
687
+ const node = new Node(config, {
688
+ createHubTransport: ...,
689
+ createClientTransport: ...,
690
+ createAgent: async ({ role, client, server, socket }) => {
691
+ // Wire your agent here — called on every role transition
692
+ const stopSync = await startMySync(role, client, server);
693
+ return { stop: () => stopSync() };
694
+ },
695
+ });
696
+ ```
697
+
698
+ The Node serializes transitions — a new transition waits for the previous one to complete, ensuring agents are always stopped cleanly.
699
+
700
+ Errors in `createAgent` or `agentHandle.stop()` are caught and logged — they never crash the node. Similarly, transport factory failures degrade connectivity but leave the node functional locally.
701
+
702
+ ### API
703
+
704
+ | Property / Method | Description |
705
+ | --------------------- | ----------------------------------------------------- |
706
+ | `node.start()` | Begin peer discovery and role assignment |
707
+ | `node.stop()` | Tear down current role and stop discovery |
708
+ | `node.role` | Current role: `'hub'`, `'client'`, or `'unassigned'` |
709
+ | `node.io` | IoMulti from active Server or Client |
710
+ | `node.bs` | BsMulti from active Server or Client |
711
+ | `node.server` | Server instance (when hub), else `undefined` |
712
+ | `node.client` | Client instance (when client), else `undefined` |
713
+ | `node.socket` | Socket to hub (when client), else `undefined` |
714
+ | `node.topology` | Current network topology snapshot |
715
+ | `node.on(event, cb)` | Subscribe to `'ready'`, `'role-changed'`, `'stopped'` |
716
+ | `node.off(event, cb)` | Unsubscribe |
717
+
583
718
  ## Architecture Overview
584
719
 
585
720
  ### Pull-Based Reference Architecture
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/index.d.ts CHANGED
@@ -4,8 +4,10 @@ export { FileLogger } from './file-logger.ts';
4
4
  export type { FileLoggerOptions } from './file-logger.ts';
5
5
  export { BufferedLogger, ConsoleLogger, FilteredLogger, NoopLogger, noopLogger, } from './logger.ts';
6
6
  export type { LogEntry, ServerLogger } from './logger.ts';
7
+ export { Node } from './node.ts';
8
+ export type { AgentHandle, CreateAgent, CreateClientTransport, CreateHubTransport, HubTransport, NodeConfig, NodeDeps, NodeEventName, NodeEvents, ReadyContext, } from './node.ts';
7
9
  export { Server } from './server.ts';
8
10
  export type { ServerOptions } from './server.ts';
9
11
  export { SocketIoBridge } from './socket-io-bridge.ts';
10
- export type { AckPayload, ConnectorPayload, GapFillRequest, GapFillResponse, SyncConfig, SyncEventNames, } from '@rljson/rljson';
11
12
  export { syncEvents } from '@rljson/rljson';
13
+ export type { AckPayload, ConnectorPayload, GapFillRequest, GapFillResponse, SyncConfig, SyncEventNames, } from '@rljson/rljson';
package/dist/node.d.ts ADDED
@@ -0,0 +1,174 @@
1
+ import { Bs } from '@rljson/bs';
2
+ import { Io } from '@rljson/io';
3
+ import { NetworkConfig, NetworkManagerOptions, NetworkTopology, NodeRole, RoleChangedEvent, NetworkManager } from '@rljson/network';
4
+ import { Route } from '@rljson/rljson';
5
+ import { Client, ClientOptions } from './client.ts';
6
+ import { ServerLogger } from './logger.ts';
7
+ import { Server, ServerOptions } from './server.ts';
8
+ import { SocketLike } from './socket-bundle.ts';
9
+ /**
10
+ * Transport handle returned by {@link CreateHubTransport}.
11
+ *
12
+ * Abstracts the "server side" of the transport layer so the Node class
13
+ * can accept incoming connections without knowing whether Socket.IO,
14
+ * WebSocket, or mock sockets are being used.
15
+ */
16
+ export interface HubTransport {
17
+ /** Register a callback for incoming client connections. */
18
+ onConnection: (callback: (socket: SocketLike) => void) => void;
19
+ /** Shut down the transport (close HTTP server, etc.). */
20
+ close: () => Promise<void>;
21
+ }
22
+ /**
23
+ * Factory that creates a hub-side transport (e.g. HTTP + Socket.IO server).
24
+ * Called when this node becomes the hub.
25
+ */
26
+ export type CreateHubTransport = (port: number) => Promise<HubTransport>;
27
+ /**
28
+ * Factory that creates a client-side socket to connect to the hub.
29
+ * Called when this node becomes a client.
30
+ */
31
+ export type CreateClientTransport = (hubAddress: string) => Promise<SocketLike>;
32
+ /**
33
+ * Injectable dependencies for the Node class.
34
+ */
35
+ export interface NodeDeps {
36
+ /** Factory to create hub-side transport. */
37
+ createHubTransport: CreateHubTransport;
38
+ /** Factory to create client-side socket to hub. */
39
+ createClientTransport: CreateClientTransport;
40
+ /** Options passed to NetworkManager (e.g. mock probe function). */
41
+ networkManagerOptions?: NetworkManagerOptions;
42
+ /**
43
+ * Optional factory for application-level agents (e.g. FsAgent).
44
+ * Called on every `ready` event. The returned handle's `stop()`
45
+ * is called before the next role transition or on `node.stop()`.
46
+ */
47
+ createAgent?: CreateAgent;
48
+ }
49
+ /**
50
+ * Configuration for a self-organizing Node.
51
+ */
52
+ export interface NodeConfig {
53
+ /** Network domain for peer discovery. */
54
+ domain: string;
55
+ /** Port for this node's hub server / probing. */
56
+ port: number;
57
+ /** Data route for Server/Client sync. */
58
+ route: Route;
59
+ /** Full network configuration (broadcast, cloud, static, probing). */
60
+ network?: Partial<NetworkConfig>;
61
+ /** Options forwarded to the Server constructor. */
62
+ serverOptions?: ServerOptions;
63
+ /** Options forwarded to the Client constructor. */
64
+ clientOptions?: ClientOptions;
65
+ /** Logger shared across Node, Server, and Client. */
66
+ logger?: ServerLogger;
67
+ /** Directory for persistent node identity. */
68
+ identityDir?: string;
69
+ }
70
+ /**
71
+ * Context passed to the `ready` event and to {@link CreateAgent}.
72
+ */
73
+ export interface ReadyContext {
74
+ /** The node's current role. */
75
+ role: NodeRole;
76
+ /** The Client instance (defined when role is `'client'`). */
77
+ client?: Client;
78
+ /** The Server instance (defined when role is `'hub'`). */
79
+ server?: Server;
80
+ /** The socket used to connect to the hub (defined when role is `'client'`). */
81
+ socket?: SocketLike;
82
+ }
83
+ /**
84
+ * Handle returned by {@link CreateAgent}.
85
+ * Node calls `stop()` before every role transition and on `node.stop()`.
86
+ */
87
+ export interface AgentHandle {
88
+ stop: () => Promise<void> | void;
89
+ }
90
+ /**
91
+ * Factory called after each role transition to wire application-level agents
92
+ * (e.g. FsAgent). Return an {@link AgentHandle} whose `stop()` will be called
93
+ * before the next role transition or when the node stops.
94
+ */
95
+ export type CreateAgent = (context: ReadyContext) => Promise<AgentHandle>;
96
+ /** Events emitted by Node. */
97
+ export interface NodeEvents {
98
+ /** Emitted when the node has assumed its role and is ready. */
99
+ ready: (context: ReadyContext) => void;
100
+ /** Emitted when the node's role changes. */
101
+ 'role-changed': (event: RoleChangedEvent) => void;
102
+ /** Emitted when the node is stopped. */
103
+ stopped: () => void;
104
+ }
105
+ /** Valid event names for Node. */
106
+ export type NodeEventName = keyof NodeEvents;
107
+ /**
108
+ * Self-organizing node that automatically transitions between
109
+ * hub (Server) and client (Client) roles based on network topology.
110
+ */
111
+ export declare class Node {
112
+ private readonly _config;
113
+ private readonly _deps;
114
+ private _networkManager;
115
+ private _server?;
116
+ private _client?;
117
+ private _hubTransport?;
118
+ private _clientSocket?;
119
+ private _agentHandle?;
120
+ private _ioMem?;
121
+ private _bsMem?;
122
+ private _role;
123
+ private _running;
124
+ private _transitioning?;
125
+ private _listeners;
126
+ private readonly _logger;
127
+ constructor(_config: NodeConfig, _deps: NodeDeps);
128
+ /**
129
+ * Start the node. Begins network discovery and role assignment.
130
+ */
131
+ start(): Promise<void>;
132
+ /**
133
+ * Stop the node. Tears down Server/Client and network discovery.
134
+ */
135
+ stop(): Promise<void>;
136
+ /** This node's current role. */
137
+ get role(): NodeRole;
138
+ /** Current network topology snapshot. */
139
+ get topology(): NetworkTopology;
140
+ /** The Io instance (from Server or Client), or undefined if unassigned. */
141
+ get io(): Io | undefined;
142
+ /** The Bs instance (from Server or Client), or undefined if unassigned. */
143
+ get bs(): Bs | undefined;
144
+ /** The Server instance when this node is the hub, or undefined. */
145
+ get server(): Server | undefined;
146
+ /** The Client instance when this node is a client, or undefined. */
147
+ get client(): Client | undefined;
148
+ /** The socket used to connect to the hub (defined when role is `'client'`). */
149
+ get socket(): SocketLike | undefined;
150
+ /** Whether the node is currently running. */
151
+ get isRunning(): boolean;
152
+ /** The underlying NetworkManager. */
153
+ get networkManager(): NetworkManager;
154
+ /**
155
+ * Subscribe to node events.
156
+ * @param event - Event name
157
+ * @param cb - Callback
158
+ */
159
+ on<E extends NodeEventName>(event: E, cb: NodeEvents[E]): void;
160
+ /**
161
+ * Unsubscribe from node events.
162
+ * @param event - Event name
163
+ * @param cb - Callback
164
+ */
165
+ off<E extends NodeEventName>(event: E, cb: NodeEvents[E]): void;
166
+ private _onRoleChanged;
167
+ private _performTransition;
168
+ private _becomeHub;
169
+ private _becomeClient;
170
+ private _tearDownCurrentRole;
171
+ private _startAgent;
172
+ private _stopAgent;
173
+ private _emit;
174
+ }