@rljson/network 0.0.1

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.
@@ -0,0 +1,156 @@
1
+ import { NodeId, NodeInfo } from '../types/node-info.ts';
2
+ import { CloudConfig } from '../types/network-config.ts';
3
+ import { NodeIdentity } from '../identity/node-identity.ts';
4
+ import { PeerProbe } from '../types/peer-probe.ts';
5
+ import { DiscoveryLayer, DiscoveryLayerEventName, DiscoveryLayerEvents } from './discovery-layer.ts';
6
+ /** Response from cloud registration / polling */
7
+ export interface CloudPeerListResponse {
8
+ /** Peers known to the cloud for this domain */
9
+ peers: NodeInfo[];
10
+ /** Hub assigned by the cloud (null if not yet decided) */
11
+ assignedHub: NodeId | null;
12
+ }
13
+ /** Abstraction over HTTP fetch for testability */
14
+ export interface CloudHttpClient {
15
+ /**
16
+ * Register this node with the cloud service.
17
+ * @param endpoint - Cloud service base URL
18
+ * @param info - This node's info
19
+ * @param apiKey - Optional API key
20
+ * @returns The peer list response from the cloud
21
+ */
22
+ register(endpoint: string, info: NodeInfo, apiKey?: string): Promise<CloudPeerListResponse>;
23
+ /**
24
+ * Poll the cloud for the latest peer list and hub assignment.
25
+ * @param endpoint - Cloud service base URL
26
+ * @param nodeId - This node's ID
27
+ * @param domain - This node's domain
28
+ * @param apiKey - Optional API key
29
+ * @returns The peer list response from the cloud
30
+ */
31
+ poll(endpoint: string, nodeId: NodeId, domain: string, apiKey?: string): Promise<CloudPeerListResponse>;
32
+ /**
33
+ * Report probe results to the cloud.
34
+ * @param endpoint - Cloud service base URL
35
+ * @param nodeId - This node's ID
36
+ * @param probes - Probe results to report
37
+ * @param apiKey - Optional API key
38
+ */
39
+ reportProbes(endpoint: string, nodeId: NodeId, probes: PeerProbe[], apiKey?: string): Promise<void>;
40
+ }
41
+ /** Factory type for creating a CloudHttpClient */
42
+ export type CreateCloudHttpClient = () => CloudHttpClient;
43
+ /**
44
+ * Create a real HTTP client using globalThis.fetch.
45
+ * @returns A CloudHttpClient backed by the Fetch API
46
+ */
47
+ export declare function defaultCreateCloudHttpClient(): CloudHttpClient;
48
+ /** Injectable dependencies for CloudLayer (testing) */
49
+ export interface CloudLayerDeps {
50
+ /** Custom HTTP client factory — defaults to real fetch */
51
+ createHttpClient?: CreateCloudHttpClient;
52
+ }
53
+ /**
54
+ * Cloud discovery layer — cross-network fallback (Try 2).
55
+ *
56
+ * Registers with a cloud service, periodically polls for peer list and
57
+ * hub assignment, and reports local probe results. The cloud has the full
58
+ * picture across all nodes and **dictates** the hub (unlike broadcast,
59
+ * which uses local election).
60
+ *
61
+ * On startup, registers with the cloud endpoint. If registration fails
62
+ * (endpoint unreachable, auth error), start() returns false and the
63
+ * NetworkManager falls through to the Static layer (Try 3).
64
+ */
65
+ export declare class CloudLayer implements DiscoveryLayer {
66
+ private readonly _config?;
67
+ readonly name = "cloud";
68
+ private _active;
69
+ private _identity;
70
+ private _pollTimer;
71
+ private _peers;
72
+ private _assignedHub;
73
+ private _listeners;
74
+ private readonly _httpClient;
75
+ private _consecutivePollFailures;
76
+ private _basePollIntervalMs;
77
+ private _currentPollIntervalMs;
78
+ private _maxBackoffMs;
79
+ private _reRegisterThreshold;
80
+ /**
81
+ * Create a CloudLayer.
82
+ * @param _config - Cloud configuration (endpoint, apiKey, pollInterval)
83
+ * @param deps - Injectable dependencies for testing
84
+ */
85
+ constructor(_config?: CloudConfig | undefined, deps?: CloudLayerDeps);
86
+ /**
87
+ * Start the cloud layer.
88
+ *
89
+ * 1. Check if cloud is enabled and endpoint configured
90
+ * 2. Register this node with the cloud
91
+ * 3. Process initial peer list and hub assignment
92
+ * 4. Start periodic polling
93
+ * @param identity - This node's identity
94
+ * @returns true if cloud is available, false otherwise
95
+ */
96
+ start(identity: NodeIdentity): Promise<boolean>;
97
+ /** Stop the layer and clean up resources */
98
+ stop(): Promise<void>;
99
+ /** Whether this layer is currently active */
100
+ isActive(): boolean;
101
+ /** Get all currently known peers from cloud discovery */
102
+ getPeers(): NodeInfo[];
103
+ /**
104
+ * Get the hub assigned by the cloud.
105
+ * The cloud **dictates** the hub — it has the full picture.
106
+ */
107
+ getAssignedHub(): NodeId | null;
108
+ /** Get current consecutive poll failure count (for diagnostics/testing) */
109
+ getConsecutivePollFailures(): number;
110
+ /** Get current effective poll interval including backoff (for diagnostics/testing) */
111
+ getCurrentPollIntervalMs(): number;
112
+ /**
113
+ * Report local probe results to the cloud.
114
+ * The cloud uses these to build a connectivity graph and assign hubs.
115
+ * @param probes - Probe results from the local ProbeScheduler
116
+ */
117
+ reportProbes(probes: PeerProbe[]): Promise<void>;
118
+ /**
119
+ * Subscribe to layer events.
120
+ * @param event - Event name
121
+ * @param cb - Callback
122
+ */
123
+ on<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
124
+ /**
125
+ * Unsubscribe from layer events.
126
+ * @param event - Event name
127
+ * @param cb - Callback
128
+ */
129
+ off<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
130
+ /**
131
+ * Schedule the next poll using setTimeout.
132
+ * Uses the current (possibly backed-off) interval.
133
+ */
134
+ private _schedulePoll;
135
+ /**
136
+ * Poll the cloud for latest peer list and hub assignment.
137
+ *
138
+ * After many consecutive failures, attempts re-registration instead
139
+ * of a regular poll (the cloud may have expired our registration).
140
+ *
141
+ * On success: resets failure counter and backoff interval.
142
+ * On failure: increments counter and doubles interval (capped at maxBackoffMs).
143
+ */
144
+ private _poll;
145
+ /**
146
+ * Process a cloud response: update peers and hub assignment.
147
+ * @param response - The cloud's peer list response
148
+ */
149
+ private _processResponse;
150
+ /**
151
+ * Emit a typed event to all registered listeners.
152
+ * @param event - Event name
153
+ * @param args - Event arguments
154
+ */
155
+ private _emit;
156
+ }
@@ -0,0 +1,45 @@
1
+ import { NodeId, NodeInfo } from '../types/node-info.ts';
2
+ import { NodeIdentity } from '../identity/node-identity.ts';
3
+ /** Events emitted by a DiscoveryLayer */
4
+ export interface DiscoveryLayerEvents {
5
+ /** A new peer was discovered */
6
+ 'peer-discovered': (peer: NodeInfo) => void;
7
+ /** A previously known peer is no longer reachable */
8
+ 'peer-lost': (nodeId: string) => void;
9
+ /** This layer is assigning/dictating a specific hub */
10
+ 'hub-assigned': (nodeId: string | null) => void;
11
+ }
12
+ /** Valid event names for DiscoveryLayer */
13
+ export type DiscoveryLayerEventName = keyof DiscoveryLayerEvents;
14
+ /**
15
+ * Contract for all discovery mechanisms.
16
+ *
17
+ * Each layer decides HOW to discover peers. The NetworkManager tries layers
18
+ * in cascade order and uses the most autonomous one that produces a result.
19
+ */
20
+ export interface DiscoveryLayer {
21
+ /** Layer name: 'broadcast' | 'cloud' | 'static' | 'manual' */
22
+ readonly name: string;
23
+ /**
24
+ * Start the layer. Returns false if this layer is not available
25
+ * (e.g., no config, no network interface, etc.).
26
+ * @param identity - This node's identity
27
+ */
28
+ start(identity: NodeIdentity): Promise<boolean>;
29
+ /** Stop the layer and clean up resources */
30
+ stop(): Promise<void>;
31
+ /** Whether this layer is currently active */
32
+ isActive(): boolean;
33
+ /** Get all currently known peers from this layer */
34
+ getPeers(): NodeInfo[];
35
+ /**
36
+ * Get the hub this layer has assigned/dictated.
37
+ * Some layers (static, cloud, manual) dictate the hub.
38
+ * Others (broadcast) return null — hub is elected, not assigned.
39
+ */
40
+ getAssignedHub(): NodeId | null;
41
+ /** Subscribe to layer events */
42
+ on<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
43
+ /** Unsubscribe from layer events */
44
+ off<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
45
+ }
@@ -0,0 +1,56 @@
1
+ import { NodeId, NodeInfo } from '../types/node-info.ts';
2
+ import { NodeIdentity } from '../identity/node-identity.ts';
3
+ import { DiscoveryLayer, DiscoveryLayerEventName, DiscoveryLayerEvents } from './discovery-layer.ts';
4
+ /**
5
+ * Always-present manual override layer.
6
+ *
7
+ * Cannot be disabled. Allows a human (or programmatic caller) to force
8
+ * a specific hub, overriding whatever the automatic cascade decided.
9
+ * Clearing the override returns control to the cascade.
10
+ *
11
+ * ManualLayer does NOT discover peers — it only overrides hub assignment.
12
+ */
13
+ export declare class ManualLayer implements DiscoveryLayer {
14
+ readonly name = "manual";
15
+ private _active;
16
+ private _assignedHub;
17
+ private _listeners;
18
+ /**
19
+ * Start always succeeds — manual layer cannot be disabled.
20
+ * @param _identity - Node identity (unused by manual layer)
21
+ */
22
+ start(_identity: NodeIdentity): Promise<boolean>;
23
+ /** Stop the layer */
24
+ stop(): Promise<void>;
25
+ /** Always active after start */
26
+ isActive(): boolean;
27
+ /** Manual layer does not discover peers */
28
+ getPeers(): NodeInfo[];
29
+ /** Get the manually assigned hub, or null if no override is set */
30
+ getAssignedHub(): NodeId | null;
31
+ /**
32
+ * Force a specific node as the hub.
33
+ * @param nodeId - The nodeId to assign as hub
34
+ */
35
+ assignHub(nodeId: NodeId): void;
36
+ /** Clear the manual override — returns control to the automatic cascade */
37
+ clearOverride(): void;
38
+ /**
39
+ * Subscribe to layer events.
40
+ * @param event - Event name
41
+ * @param cb - Callback
42
+ */
43
+ on<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
44
+ /**
45
+ * Unsubscribe from layer events.
46
+ * @param event - Event name
47
+ * @param cb - Callback
48
+ */
49
+ off<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
50
+ /**
51
+ * Emit a typed event to all registered listeners.
52
+ * @param event - Event name
53
+ * @param args - Event arguments
54
+ */
55
+ private _emit;
56
+ }
@@ -0,0 +1,61 @@
1
+ import { NodeId, NodeInfo } from '../types/node-info.ts';
2
+ import { StaticConfig } from '../types/network-config.ts';
3
+ import { NodeIdentity } from '../identity/node-identity.ts';
4
+ import { DiscoveryLayer, DiscoveryLayerEventName, DiscoveryLayerEvents } from './discovery-layer.ts';
5
+ /**
6
+ * Static discovery layer — last resort fallback (Try 3).
7
+ *
8
+ * Reads a hardcoded `hubAddress` from config. If set, produces a synthetic
9
+ * peer for the hub and returns it as the assigned hub. If not set, `start()`
10
+ * returns false.
11
+ *
12
+ * Static config is not a dead end — if a more autonomous layer (broadcast,
13
+ * cloud) starts producing results, the NetworkManager upgrades automatically.
14
+ */
15
+ export declare class StaticLayer implements DiscoveryLayer {
16
+ private readonly _config?;
17
+ readonly name = "static";
18
+ private _active;
19
+ private _hubAddress;
20
+ private _hubNodeId;
21
+ private _syntheticPeer;
22
+ private _listeners;
23
+ /**
24
+ * Create a StaticLayer.
25
+ * @param _config - Static config with optional hubAddress
26
+ */
27
+ constructor(_config?: StaticConfig | undefined);
28
+ /**
29
+ * Start the layer. Returns false if no hubAddress is configured.
30
+ * @param identity - This node's identity (used for domain info on synthetic peer)
31
+ */
32
+ start(identity: NodeIdentity): Promise<boolean>;
33
+ /** Stop the layer */
34
+ stop(): Promise<void>;
35
+ /** Whether this layer is currently active */
36
+ isActive(): boolean;
37
+ /** Get the synthetic peer for the configured hub (or empty array) */
38
+ getPeers(): NodeInfo[];
39
+ /** Get the statically configured hub nodeId */
40
+ getAssignedHub(): NodeId | null;
41
+ /** Get the raw hub address string ("ip:port") */
42
+ getHubAddress(): string | null;
43
+ /**
44
+ * Subscribe to layer events.
45
+ * @param event - Event name
46
+ * @param cb - Callback
47
+ */
48
+ on<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
49
+ /**
50
+ * Unsubscribe from layer events.
51
+ * @param event - Event name
52
+ * @param cb - Callback
53
+ */
54
+ off<E extends DiscoveryLayerEventName>(event: E, cb: DiscoveryLayerEvents[E]): void;
55
+ /**
56
+ * Emit a typed event to all registered listeners.
57
+ * @param event - Event name
58
+ * @param args - Event arguments
59
+ */
60
+ private _emit;
61
+ }
@@ -0,0 +1,149 @@
1
+ import { NodeId, NodeInfo } from './types/node-info.ts';
2
+ import { NetworkConfig } from './types/network-config.ts';
3
+ import { NetworkTopology } from './types/network-topology.ts';
4
+ import { TopologyChangedEvent, RoleChangedEvent, HubChangedEvent } from './types/network-events.ts';
5
+ import { NodeIdentity } from './identity/node-identity.ts';
6
+ import { BroadcastLayerDeps } from './layers/broadcast-layer.ts';
7
+ import { CloudLayerDeps } from './layers/cloud-layer.ts';
8
+ import { ProbeScheduler, ProbeFn } from './probing/probe-scheduler.ts';
9
+ /** Events emitted by NetworkManager */
10
+ export interface NetworkManagerEvents {
11
+ 'topology-changed': (event: TopologyChangedEvent) => void;
12
+ 'role-changed': (event: RoleChangedEvent) => void;
13
+ 'hub-changed': (event: HubChangedEvent) => void;
14
+ 'peer-joined': (peer: NodeInfo) => void;
15
+ 'peer-left': (nodeId: string) => void;
16
+ }
17
+ /** Valid event names for NetworkManager */
18
+ export type NetworkManagerEventName = keyof NetworkManagerEvents;
19
+ /** Options for NetworkManager constructor */
20
+ export interface NetworkManagerOptions {
21
+ /** Custom probe function (e.g. for testing) */
22
+ probeFn?: ProbeFn;
23
+ /**
24
+ * Number of consecutive probe failures before declaring a peer
25
+ * unreachable (default: 3). Passed through to ProbeScheduler.
26
+ */
27
+ failThreshold?: number;
28
+ /** Injectable dependencies for BroadcastLayer (e.g. mock sockets) */
29
+ broadcastDeps?: BroadcastLayerDeps;
30
+ /** Injectable dependencies for CloudLayer (e.g. mock HTTP client) */
31
+ cloudDeps?: CloudLayerDeps;
32
+ }
33
+ /**
34
+ * Central orchestrator for network topology.
35
+ *
36
+ * Starts all configured discovery layers, merges peer tables,
37
+ * applies the fallback cascade, and emits topology events.
38
+ *
39
+ * Supports ManualLayer + StaticLayer + hub election via probing.
40
+ * Broadcast and Cloud layers will be added in later epics.
41
+ */
42
+ export declare class NetworkManager {
43
+ private readonly _config;
44
+ private _identity;
45
+ private _running;
46
+ /** Always-present manual override layer */
47
+ private readonly _manualLayer;
48
+ /** Try 1: UDP broadcast discovery */
49
+ private readonly _broadcastLayer;
50
+ /** Try 2: Cloud discovery fallback */
51
+ private readonly _cloudLayer;
52
+ /** Try 3: Static config fallback */
53
+ private readonly _staticLayer;
54
+ /** Merged peer table */
55
+ private readonly _peerTable;
56
+ /** Probe scheduler for reachability checking */
57
+ private readonly _probeScheduler;
58
+ /** Event listeners */
59
+ private _listeners;
60
+ /** Current topology snapshot */
61
+ private _currentHubId;
62
+ private _currentRole;
63
+ private _formedBy;
64
+ /**
65
+ * Create a NetworkManager.
66
+ * @param _config - Network configuration
67
+ * @param options - Optional overrides (e.g. custom probe function)
68
+ */
69
+ constructor(_config: NetworkConfig, options?: NetworkManagerOptions);
70
+ /**
71
+ * Start the network manager.
72
+ *
73
+ * Creates node identity, starts all layers, attaches to peer table,
74
+ * and performs initial hub computation.
75
+ */
76
+ start(): Promise<void>;
77
+ /**
78
+ * Stop the network manager.
79
+ *
80
+ * Stops all layers and clears state.
81
+ */
82
+ stop(): Promise<void>;
83
+ /** Whether the manager is currently running */
84
+ isRunning(): boolean;
85
+ /**
86
+ * Get the current topology snapshot.
87
+ * @returns The current network topology
88
+ */
89
+ getTopology(): NetworkTopology;
90
+ /**
91
+ * Get the probe scheduler for direct access to probe results.
92
+ * @returns The ProbeScheduler instance
93
+ */
94
+ getProbeScheduler(): ProbeScheduler;
95
+ /**
96
+ * Get this node's identity.
97
+ * Throws if called before start().
98
+ */
99
+ getIdentity(): NodeIdentity;
100
+ /**
101
+ * Manually assign a hub node, overriding the cascade.
102
+ * @param nodeId - The node to designate as hub
103
+ */
104
+ assignHub(nodeId: NodeId): void;
105
+ /**
106
+ * Clear the manual hub override, returning to cascade logic.
107
+ */
108
+ clearOverride(): void;
109
+ /**
110
+ * Subscribe to network manager events.
111
+ * @param event - Event name
112
+ * @param cb - Callback
113
+ */
114
+ on<E extends NetworkManagerEventName>(event: E, cb: NetworkManagerEvents[E]): void;
115
+ /**
116
+ * Unsubscribe from network manager events.
117
+ * @param event - Event name
118
+ * @param cb - Callback
119
+ */
120
+ off<E extends NetworkManagerEventName>(event: E, cb: NetworkManagerEvents[E]): void;
121
+ /**
122
+ * Compute the hub using the fallback cascade.
123
+ *
124
+ * Priority:
125
+ * 1. Manual override (human knows best)
126
+ * 2. Election among probed peers (most autonomous)
127
+ * - formedBy 'broadcast' if broadcast layer provided peers
128
+ * - formedBy 'election' otherwise
129
+ * 3. Cloud assignment (sees full picture, dictates hub)
130
+ * 4. Static config (last resort)
131
+ * 5. Nothing → unassigned
132
+ */
133
+ private _computeHub;
134
+ /**
135
+ * Recompute topology and emit events if anything changed.
136
+ */
137
+ private _recomputeTopology;
138
+ /**
139
+ * Resolve the hub address ("ip:port") from the current hub.
140
+ * Uses static config's hubAddress if the hub is from static layer.
141
+ */
142
+ private _resolveHubAddress;
143
+ /**
144
+ * Emit a typed event.
145
+ * @param event - Event name
146
+ * @param args - Event arguments
147
+ */
148
+ private _emit;
149
+ }