jmri-client 4.2.0-beta.2 → 5.0.0

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 (58) hide show
  1. package/README.md +3 -1
  2. package/dist/cjs/index.js +2382 -31
  3. package/dist/esm/index.js +2333 -17
  4. package/docs/BROWSER.md +4 -4
  5. package/docs/MIGRATION.md +30 -1
  6. package/package.json +17 -18
  7. package/dist/cjs/client.js +0 -366
  8. package/dist/cjs/core/connection-state-manager.js +0 -84
  9. package/dist/cjs/core/heartbeat-manager.js +0 -79
  10. package/dist/cjs/core/index.js +0 -25
  11. package/dist/cjs/core/message-queue.js +0 -59
  12. package/dist/cjs/core/reconnection-manager.js +0 -97
  13. package/dist/cjs/core/websocket-adapter.js +0 -135
  14. package/dist/cjs/core/websocket-client.js +0 -388
  15. package/dist/cjs/managers/index.js +0 -25
  16. package/dist/cjs/managers/light-manager.js +0 -111
  17. package/dist/cjs/managers/power-manager.js +0 -90
  18. package/dist/cjs/managers/roster-manager.js +0 -118
  19. package/dist/cjs/managers/system-connections-manager.js +0 -28
  20. package/dist/cjs/managers/throttle-manager.js +0 -233
  21. package/dist/cjs/managers/turnout-manager.js +0 -111
  22. package/dist/cjs/mocks/index.js +0 -12
  23. package/dist/cjs/mocks/mock-data.js +0 -237
  24. package/dist/cjs/mocks/mock-response-manager.js +0 -290
  25. package/dist/cjs/types/client-options.js +0 -66
  26. package/dist/cjs/types/events.js +0 -16
  27. package/dist/cjs/types/index.js +0 -23
  28. package/dist/cjs/types/jmri-messages.js +0 -95
  29. package/dist/cjs/types/throttle.js +0 -19
  30. package/dist/cjs/utils/exponential-backoff.js +0 -40
  31. package/dist/cjs/utils/index.js +0 -21
  32. package/dist/cjs/utils/message-id.js +0 -40
  33. package/dist/esm/client.js +0 -362
  34. package/dist/esm/core/connection-state-manager.js +0 -80
  35. package/dist/esm/core/heartbeat-manager.js +0 -75
  36. package/dist/esm/core/index.js +0 -9
  37. package/dist/esm/core/message-queue.js +0 -55
  38. package/dist/esm/core/reconnection-manager.js +0 -93
  39. package/dist/esm/core/websocket-adapter.js +0 -98
  40. package/dist/esm/core/websocket-client.js +0 -384
  41. package/dist/esm/managers/index.js +0 -9
  42. package/dist/esm/managers/light-manager.js +0 -107
  43. package/dist/esm/managers/power-manager.js +0 -86
  44. package/dist/esm/managers/roster-manager.js +0 -114
  45. package/dist/esm/managers/system-connections-manager.js +0 -24
  46. package/dist/esm/managers/throttle-manager.js +0 -229
  47. package/dist/esm/managers/turnout-manager.js +0 -107
  48. package/dist/esm/mocks/index.js +0 -6
  49. package/dist/esm/mocks/mock-data.js +0 -234
  50. package/dist/esm/mocks/mock-response-manager.js +0 -286
  51. package/dist/esm/types/client-options.js +0 -62
  52. package/dist/esm/types/events.js +0 -13
  53. package/dist/esm/types/index.js +0 -7
  54. package/dist/esm/types/jmri-messages.js +0 -89
  55. package/dist/esm/types/throttle.js +0 -15
  56. package/dist/esm/utils/exponential-backoff.js +0 -36
  57. package/dist/esm/utils/index.js +0 -5
  58. package/dist/esm/utils/message-id.js +0 -36
@@ -1,286 +0,0 @@
1
- /**
2
- * Mock Response Manager
3
- * Generates mock JMRI responses for testing and demo purposes
4
- */
5
- import { PowerState, TurnoutState, LightState } from '../types/jmri-messages.js';
6
- import { mockData } from './mock-data.js';
7
- /**
8
- * Manages mock responses for JMRI protocol
9
- */
10
- export class MockResponseManager {
11
- constructor(options = {}) {
12
- this.throttles = new Map();
13
- this.lights = new Map([
14
- ['IL1', LightState.OFF],
15
- ['IL2', LightState.OFF],
16
- ['IL3', LightState.ON]
17
- ]);
18
- this.turnouts = new Map([
19
- ['LT1', TurnoutState.CLOSED],
20
- ['LT2', TurnoutState.CLOSED],
21
- ['LT3', TurnoutState.THROWN]
22
- ]);
23
- this.responseDelay = options.responseDelay ?? 50;
24
- this.powerState = options.initialPowerState ?? PowerState.OFF;
25
- }
26
- /**
27
- * Get a mock response for a given message
28
- */
29
- async getMockResponse(message) {
30
- // Simulate network delay
31
- if (this.responseDelay > 0) {
32
- await this.delay(this.responseDelay);
33
- }
34
- // Route to appropriate handler based on message type
35
- switch (message.type) {
36
- case 'hello':
37
- return this.getHelloResponse();
38
- case 'power':
39
- return this.getPowerResponse(message);
40
- case 'roster':
41
- return this.getRosterResponse(message);
42
- case 'throttle':
43
- return this.getThrottleResponse(message);
44
- case 'light':
45
- return this.getLightResponse(message);
46
- case 'turnout':
47
- return this.getTurnoutResponse(message);
48
- case 'ping':
49
- return this.getPingResponse();
50
- case 'goodbye':
51
- return this.getGoodbyeResponse();
52
- default:
53
- return null;
54
- }
55
- }
56
- /**
57
- * Get hello response (connection establishment)
58
- */
59
- getHelloResponse() {
60
- return JSON.parse(JSON.stringify(mockData.hello));
61
- }
62
- /**
63
- * Get power response
64
- */
65
- getPowerResponse(message) {
66
- // Handle power state change
67
- if (message.data?.state !== undefined) {
68
- this.powerState = message.data.state;
69
- return {
70
- type: 'power',
71
- data: { state: this.powerState }
72
- };
73
- }
74
- // Return current power state
75
- return {
76
- type: 'power',
77
- data: { state: this.powerState }
78
- };
79
- }
80
- /**
81
- * Get roster response
82
- */
83
- getRosterResponse(message) {
84
- if (message.type === 'roster' && message.method === 'list') {
85
- return {
86
- type: 'roster',
87
- data: JSON.parse(JSON.stringify(mockData.roster.list))
88
- };
89
- }
90
- return {
91
- type: 'roster',
92
- data: []
93
- };
94
- }
95
- /**
96
- * Get throttle response
97
- */
98
- getThrottleResponse(message) {
99
- const data = message.data || {};
100
- // Acquire throttle
101
- if (data.address !== undefined && !data.throttle) {
102
- const throttleId = data.name || `MOCK-${data.address}`;
103
- const throttleState = {
104
- throttle: throttleId,
105
- address: data.address,
106
- speed: 0,
107
- forward: true,
108
- F0: false,
109
- F1: false,
110
- F2: false,
111
- F3: false,
112
- F4: false
113
- };
114
- this.throttles.set(throttleId, throttleState);
115
- return {
116
- type: 'throttle',
117
- data: { ...throttleState }
118
- };
119
- }
120
- // Release throttle
121
- if (data.release !== undefined && data.throttle) {
122
- this.throttles.delete(data.throttle);
123
- return {
124
- type: 'throttle',
125
- data: {}
126
- };
127
- }
128
- // Throttle control (speed, direction, functions)
129
- if (data.throttle) {
130
- const throttleState = this.throttles.get(data.throttle);
131
- if (!throttleState) {
132
- // Throttle not found - this shouldn't normally happen in mock mode
133
- // but we'll create it on the fly
134
- const newState = {
135
- throttle: data.throttle,
136
- address: 0,
137
- speed: 0,
138
- forward: true
139
- };
140
- this.throttles.set(data.throttle, newState);
141
- return {
142
- type: 'throttle',
143
- data: { ...newState }
144
- };
145
- }
146
- // Update throttle state
147
- if (data.speed !== undefined) {
148
- throttleState.speed = data.speed;
149
- }
150
- if (data.forward !== undefined) {
151
- throttleState.forward = data.forward;
152
- }
153
- // Update function keys
154
- for (let i = 0; i <= 28; i++) {
155
- const key = `F${i}`;
156
- if (data[key] !== undefined) {
157
- throttleState[key] = data[key];
158
- }
159
- }
160
- // Return updated state (no response for throttle control commands)
161
- return {
162
- type: 'throttle',
163
- data: {}
164
- };
165
- }
166
- return {
167
- type: 'throttle',
168
- data: {}
169
- };
170
- }
171
- /**
172
- * Get light response
173
- */
174
- getLightResponse(message) {
175
- // List all lights
176
- if (message.method === 'list') {
177
- return {
178
- type: 'light',
179
- data: JSON.parse(JSON.stringify(mockData.light.list))
180
- };
181
- }
182
- const name = message.data?.name;
183
- if (!name) {
184
- return { type: 'light', data: { name: '', state: LightState.UNKNOWN } };
185
- }
186
- // Set light state
187
- if (message.method === 'post' && message.data?.state !== undefined) {
188
- this.lights.set(name, message.data.state);
189
- }
190
- // Get or confirm current state
191
- const state = this.lights.get(name) ?? LightState.UNKNOWN;
192
- return { type: 'light', data: { name, state } };
193
- }
194
- /**
195
- * Get turnout response
196
- */
197
- getTurnoutResponse(message) {
198
- // List all turnouts
199
- if (message.method === 'list') {
200
- return {
201
- type: 'turnout',
202
- data: JSON.parse(JSON.stringify(mockData.turnout.list))
203
- };
204
- }
205
- const name = message.data?.name;
206
- if (!name) {
207
- return { type: 'turnout', data: { name: '', state: TurnoutState.UNKNOWN } };
208
- }
209
- // Set turnout state
210
- if (message.method === 'post' && message.data?.state !== undefined) {
211
- this.turnouts.set(name, message.data.state);
212
- }
213
- // Get or confirm current state
214
- const state = this.turnouts.get(name) ?? TurnoutState.UNKNOWN;
215
- return { type: 'turnout', data: { name, state } };
216
- }
217
- /**
218
- * Get ping response (pong)
219
- */
220
- getPingResponse() {
221
- return JSON.parse(JSON.stringify(mockData.pong));
222
- }
223
- /**
224
- * Get goodbye response
225
- */
226
- getGoodbyeResponse() {
227
- return JSON.parse(JSON.stringify(mockData.goodbye));
228
- }
229
- /**
230
- * Get current power state
231
- */
232
- getPowerState() {
233
- return this.powerState;
234
- }
235
- /**
236
- * Set power state (for testing)
237
- */
238
- setPowerState(state) {
239
- this.powerState = state;
240
- }
241
- /**
242
- * Get all throttles (for testing)
243
- */
244
- getThrottles() {
245
- return this.throttles;
246
- }
247
- /**
248
- * Get all light states (for testing)
249
- */
250
- getLights() {
251
- return this.lights;
252
- }
253
- /**
254
- * Get all turnout states (for testing)
255
- */
256
- getTurnouts() {
257
- return this.turnouts;
258
- }
259
- /**
260
- * Reset all state (for testing)
261
- */
262
- reset() {
263
- this.powerState = PowerState.OFF;
264
- this.throttles.clear();
265
- this.lights = new Map([
266
- ['IL1', LightState.OFF],
267
- ['IL2', LightState.OFF],
268
- ['IL3', LightState.ON]
269
- ]);
270
- this.turnouts = new Map([
271
- ['LT1', TurnoutState.CLOSED],
272
- ['LT2', TurnoutState.CLOSED],
273
- ['LT3', TurnoutState.THROWN]
274
- ]);
275
- }
276
- /**
277
- * Delay helper
278
- */
279
- delay(ms) {
280
- return new Promise(resolve => setTimeout(resolve, ms));
281
- }
282
- }
283
- /**
284
- * Singleton instance for shared use across tests
285
- */
286
- export const mockResponseManager = new MockResponseManager({ responseDelay: 0 });
@@ -1,62 +0,0 @@
1
- /**
2
- * Client configuration options
3
- */
4
- /**
5
- * Default client options
6
- */
7
- export const DEFAULT_CLIENT_OPTIONS = {
8
- host: 'localhost',
9
- port: 12080,
10
- protocol: 'ws',
11
- autoConnect: true,
12
- reconnection: {
13
- enabled: true,
14
- maxAttempts: 0, // infinite
15
- initialDelay: 1000,
16
- maxDelay: 30000,
17
- multiplier: 1.5,
18
- jitter: true
19
- },
20
- heartbeat: {
21
- enabled: true,
22
- interval: 30000,
23
- timeout: 5000
24
- },
25
- messageQueueSize: 100,
26
- requestTimeout: 10000,
27
- mock: {
28
- enabled: false,
29
- responseDelay: 50
30
- }
31
- };
32
- /**
33
- * Merge user options with defaults
34
- */
35
- export function mergeOptions(userOptions) {
36
- const options = { ...DEFAULT_CLIENT_OPTIONS };
37
- if (!userOptions) {
38
- return options;
39
- }
40
- if (userOptions.host !== undefined)
41
- options.host = userOptions.host;
42
- if (userOptions.port !== undefined)
43
- options.port = userOptions.port;
44
- if (userOptions.protocol !== undefined)
45
- options.protocol = userOptions.protocol;
46
- if (userOptions.autoConnect !== undefined)
47
- options.autoConnect = userOptions.autoConnect;
48
- if (userOptions.messageQueueSize !== undefined)
49
- options.messageQueueSize = userOptions.messageQueueSize;
50
- if (userOptions.requestTimeout !== undefined)
51
- options.requestTimeout = userOptions.requestTimeout;
52
- if (userOptions.reconnection) {
53
- options.reconnection = { ...DEFAULT_CLIENT_OPTIONS.reconnection, ...userOptions.reconnection };
54
- }
55
- if (userOptions.heartbeat) {
56
- options.heartbeat = { ...DEFAULT_CLIENT_OPTIONS.heartbeat, ...userOptions.heartbeat };
57
- }
58
- if (userOptions.mock) {
59
- options.mock = { ...DEFAULT_CLIENT_OPTIONS.mock, ...userOptions.mock };
60
- }
61
- return options;
62
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Event types for JmriClient EventEmitter
3
- */
4
- /**
5
- * Connection states
6
- */
7
- export var ConnectionState;
8
- (function (ConnectionState) {
9
- ConnectionState["DISCONNECTED"] = "disconnected";
10
- ConnectionState["CONNECTING"] = "connecting";
11
- ConnectionState["CONNECTED"] = "connected";
12
- ConnectionState["RECONNECTING"] = "reconnecting";
13
- })(ConnectionState || (ConnectionState = {}));
@@ -1,7 +0,0 @@
1
- /**
2
- * Type definitions for jmri-client
3
- */
4
- export * from './jmri-messages.js';
5
- export * from './events.js';
6
- export * from './throttle.js';
7
- export * from './client-options.js';
@@ -1,89 +0,0 @@
1
- /**
2
- * JMRI WebSocket Protocol Message Types
3
- * Based on JMRI JSON protocol specification
4
- */
5
- /**
6
- * Power state values (from JMRI JSON protocol constants)
7
- * UNKNOWN = 0 (state cannot be determined)
8
- * ON = 2 (power is on)
9
- * OFF = 4 (power is off)
10
- */
11
- export var PowerState;
12
- (function (PowerState) {
13
- PowerState[PowerState["UNKNOWN"] = 0] = "UNKNOWN";
14
- PowerState[PowerState["ON"] = 2] = "ON";
15
- PowerState[PowerState["OFF"] = 4] = "OFF";
16
- })(PowerState || (PowerState = {}));
17
- /**
18
- * Convert PowerState enum to human-readable string
19
- * @param state - The power state
20
- * @returns 'ON', 'OFF', or 'UNKNOWN'
21
- */
22
- export function powerStateToString(state) {
23
- switch (state) {
24
- case PowerState.ON:
25
- return 'ON';
26
- case PowerState.OFF:
27
- return 'OFF';
28
- case PowerState.UNKNOWN:
29
- return 'UNKNOWN';
30
- default:
31
- return 'UNKNOWN';
32
- }
33
- }
34
- /**
35
- * Turnout state values (from JMRI JSON protocol constants)
36
- * UNKNOWN = 0 (state cannot be determined)
37
- * CLOSED = 2 (straight through / normal position)
38
- * THROWN = 4 (diverging route position)
39
- * INCONSISTENT = 8 (contradictory feedback state)
40
- */
41
- export var TurnoutState;
42
- (function (TurnoutState) {
43
- TurnoutState[TurnoutState["UNKNOWN"] = 0] = "UNKNOWN";
44
- TurnoutState[TurnoutState["CLOSED"] = 2] = "CLOSED";
45
- TurnoutState[TurnoutState["THROWN"] = 4] = "THROWN";
46
- TurnoutState[TurnoutState["INCONSISTENT"] = 8] = "INCONSISTENT";
47
- })(TurnoutState || (TurnoutState = {}));
48
- /**
49
- * Convert TurnoutState enum to human-readable string
50
- */
51
- export function turnoutStateToString(state) {
52
- switch (state) {
53
- case TurnoutState.CLOSED:
54
- return 'CLOSED';
55
- case TurnoutState.THROWN:
56
- return 'THROWN';
57
- case TurnoutState.INCONSISTENT:
58
- return 'INCONSISTENT';
59
- case TurnoutState.UNKNOWN:
60
- default:
61
- return 'UNKNOWN';
62
- }
63
- }
64
- /**
65
- * Light state values (from JMRI JSON protocol constants)
66
- * UNKNOWN = 0 (state cannot be determined)
67
- * ON = 2 (light is on)
68
- * OFF = 4 (light is off)
69
- */
70
- export var LightState;
71
- (function (LightState) {
72
- LightState[LightState["UNKNOWN"] = 0] = "UNKNOWN";
73
- LightState[LightState["ON"] = 2] = "ON";
74
- LightState[LightState["OFF"] = 4] = "OFF";
75
- })(LightState || (LightState = {}));
76
- /**
77
- * Convert LightState enum to human-readable string
78
- */
79
- export function lightStateToString(state) {
80
- switch (state) {
81
- case LightState.ON:
82
- return 'ON';
83
- case LightState.OFF:
84
- return 'OFF';
85
- case LightState.UNKNOWN:
86
- default:
87
- return 'UNKNOWN';
88
- }
89
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Throttle-specific types
3
- */
4
- /**
5
- * Validates that a value is a valid throttle function key
6
- */
7
- export function isThrottleFunctionKey(key) {
8
- return /^F([0-9]|1[0-9]|2[0-8])$/.test(key);
9
- }
10
- /**
11
- * Validates that speed is in valid range (0.0 to 1.0)
12
- */
13
- export function isValidSpeed(speed) {
14
- return typeof speed === 'number' && speed >= 0 && speed <= 1;
15
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Exponential backoff calculator with jitter
3
- */
4
- /**
5
- * Calculate next reconnection delay using exponential backoff
6
- *
7
- * @param attempt - Current attempt number (1-indexed)
8
- * @param options - Reconnection options
9
- * @returns Delay in milliseconds
10
- */
11
- export function calculateBackoffDelay(attempt, options) {
12
- // Base delay calculation: initialDelay * (multiplier ^ (attempt - 1))
13
- const exponentialDelay = options.initialDelay * Math.pow(options.multiplier, attempt - 1);
14
- // Cap at maxDelay
15
- const cappedDelay = Math.min(exponentialDelay, options.maxDelay);
16
- // Add jitter if enabled (±25%)
17
- if (options.jitter) {
18
- const jitterAmount = cappedDelay * 0.25;
19
- const jitter = (Math.random() * 2 - 1) * jitterAmount; // Random between -25% and +25%
20
- return Math.max(0, Math.round(cappedDelay + jitter));
21
- }
22
- return Math.round(cappedDelay);
23
- }
24
- /**
25
- * Check if should attempt reconnection
26
- *
27
- * @param attempt - Current attempt number (1-indexed)
28
- * @param maxAttempts - Maximum attempts (0 = infinite)
29
- * @returns True if should attempt reconnection
30
- */
31
- export function shouldReconnect(attempt, maxAttempts) {
32
- if (maxAttempts === 0) {
33
- return true; // Infinite attempts
34
- }
35
- return attempt <= maxAttempts;
36
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Utility functions
3
- */
4
- export * from './message-id.js';
5
- export * from './exponential-backoff.js';
@@ -1,36 +0,0 @@
1
- /**
2
- * Message ID generator for request/response correlation
3
- */
4
- /**
5
- * Sequential message ID generator
6
- * Provides unique IDs for correlating requests and responses
7
- */
8
- export class MessageIdGenerator {
9
- constructor() {
10
- this.currentId = 0;
11
- this.maxId = Number.MAX_SAFE_INTEGER;
12
- }
13
- /**
14
- * Generate next sequential ID
15
- * Wraps around at MAX_SAFE_INTEGER
16
- */
17
- next() {
18
- this.currentId++;
19
- if (this.currentId >= this.maxId) {
20
- this.currentId = 1;
21
- }
22
- return this.currentId;
23
- }
24
- /**
25
- * Reset ID counter to 0
26
- */
27
- reset() {
28
- this.currentId = 0;
29
- }
30
- /**
31
- * Get current ID without incrementing
32
- */
33
- current() {
34
- return this.currentId;
35
- }
36
- }