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,25 +0,0 @@
1
- "use strict";
2
- /**
3
- * Manager exports
4
- */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
- };
19
- Object.defineProperty(exports, "__esModule", { value: true });
20
- __exportStar(require("./power-manager.js"), exports);
21
- __exportStar(require("./roster-manager.js"), exports);
22
- __exportStar(require("./throttle-manager.js"), exports);
23
- __exportStar(require("./turnout-manager.js"), exports);
24
- __exportStar(require("./light-manager.js"), exports);
25
- __exportStar(require("./system-connections-manager.js"), exports);
@@ -1,111 +0,0 @@
1
- "use strict";
2
- /**
3
- * Light manager
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.LightManager = void 0;
7
- const eventemitter3_1 = require("eventemitter3");
8
- const jmri_messages_js_1 = require("../types/jmri-messages.js");
9
- /**
10
- * Manages JMRI light state
11
- */
12
- class LightManager extends eventemitter3_1.EventEmitter {
13
- constructor(client) {
14
- super();
15
- this.lights = new Map();
16
- this.client = client;
17
- this.client.on('update', (message) => {
18
- if (message.type === 'light') {
19
- this.handleLightUpdate(message);
20
- }
21
- });
22
- }
23
- /**
24
- * Get the current state of a light.
25
- * Also registers a server-side listener so subsequent changes are pushed.
26
- */
27
- async getLight(name) {
28
- const message = {
29
- type: 'light',
30
- data: { name }
31
- };
32
- const response = await this.client.request(message);
33
- const state = response.data?.state ?? jmri_messages_js_1.LightState.UNKNOWN;
34
- this.lights.set(name, state);
35
- return state;
36
- }
37
- /**
38
- * Set a light to the given state
39
- */
40
- async setLight(name, state) {
41
- const message = {
42
- type: 'light',
43
- method: 'post',
44
- data: { name, state }
45
- };
46
- await this.client.request(message);
47
- const oldState = this.lights.get(name);
48
- this.lights.set(name, state);
49
- if (oldState !== state) {
50
- this.emit('light:changed', name, state);
51
- }
52
- }
53
- /**
54
- * Turn a light on
55
- */
56
- async turnOnLight(name) {
57
- return this.setLight(name, jmri_messages_js_1.LightState.ON);
58
- }
59
- /**
60
- * Turn a light off
61
- */
62
- async turnOffLight(name) {
63
- return this.setLight(name, jmri_messages_js_1.LightState.OFF);
64
- }
65
- /**
66
- * List all lights known to JMRI
67
- */
68
- async listLights() {
69
- const message = {
70
- type: 'light',
71
- method: 'list'
72
- };
73
- const response = await this.client.request(message);
74
- const entries = Array.isArray(response?.data)
75
- ? response.data.map((r) => r.data ?? r)
76
- : [];
77
- for (const entry of entries) {
78
- if (entry.name && entry.state !== undefined) {
79
- this.lights.set(entry.name, entry.state);
80
- }
81
- }
82
- return entries;
83
- }
84
- /**
85
- * Get cached light state without a network request
86
- */
87
- getLightState(name) {
88
- return this.lights.get(name);
89
- }
90
- /**
91
- * Get all cached light states
92
- */
93
- getCachedLights() {
94
- return new Map(this.lights);
95
- }
96
- /**
97
- * Handle unsolicited light state updates from JMRI
98
- */
99
- handleLightUpdate(message) {
100
- const name = message.data?.name;
101
- const state = message.data?.state;
102
- if (!name || state === undefined)
103
- return;
104
- const oldState = this.lights.get(name);
105
- this.lights.set(name, state);
106
- if (oldState !== state) {
107
- this.emit('light:changed', name, state);
108
- }
109
- }
110
- }
111
- exports.LightManager = LightManager;
@@ -1,90 +0,0 @@
1
- "use strict";
2
- /**
3
- * Power control manager
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PowerManager = void 0;
7
- const eventemitter3_1 = require("eventemitter3");
8
- const jmri_messages_js_1 = require("../types/jmri-messages.js");
9
- /**
10
- * Manages track power control
11
- */
12
- class PowerManager extends eventemitter3_1.EventEmitter {
13
- constructor(client) {
14
- super();
15
- this.currentState = jmri_messages_js_1.PowerState.UNKNOWN;
16
- this.client = client;
17
- // Listen for power updates
18
- this.client.on('update', (message) => {
19
- if (message.type === 'power') {
20
- this.handlePowerUpdate(message);
21
- }
22
- });
23
- }
24
- /**
25
- * Get current track power state
26
- * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
27
- */
28
- async getPower(prefix) {
29
- const message = {
30
- type: 'power',
31
- ...(prefix !== undefined && { data: { state: jmri_messages_js_1.PowerState.UNKNOWN, prefix } })
32
- };
33
- const response = await this.client.request(message);
34
- if (response.data?.state !== undefined) {
35
- this.currentState = response.data.state;
36
- }
37
- return this.currentState;
38
- }
39
- /**
40
- * Set track power state
41
- * @param state - The desired power state
42
- * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
43
- */
44
- async setPower(state, prefix) {
45
- const message = {
46
- type: 'power',
47
- method: 'post',
48
- data: { state, ...(prefix !== undefined && { prefix }) }
49
- };
50
- await this.client.request(message);
51
- const oldState = this.currentState;
52
- this.currentState = state;
53
- if (oldState !== this.currentState) {
54
- this.emit('power:changed', this.currentState);
55
- }
56
- }
57
- /**
58
- * Turn track power on
59
- * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
60
- */
61
- async powerOn(prefix) {
62
- await this.setPower(jmri_messages_js_1.PowerState.ON, prefix);
63
- }
64
- /**
65
- * Turn track power off
66
- * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
67
- */
68
- async powerOff(prefix) {
69
- await this.setPower(jmri_messages_js_1.PowerState.OFF, prefix);
70
- }
71
- /**
72
- * Get cached power state (no network request)
73
- */
74
- getCachedState() {
75
- return this.currentState;
76
- }
77
- /**
78
- * Handle unsolicited power updates from JMRI
79
- */
80
- handlePowerUpdate(message) {
81
- if (message.data?.state !== undefined) {
82
- const oldState = this.currentState;
83
- this.currentState = message.data.state;
84
- if (oldState !== this.currentState) {
85
- this.emit('power:changed', this.currentState);
86
- }
87
- }
88
- }
89
- }
90
- exports.PowerManager = PowerManager;
@@ -1,118 +0,0 @@
1
- "use strict";
2
- /**
3
- * Roster management
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RosterManager = void 0;
7
- /**
8
- * Manages locomotive roster
9
- */
10
- class RosterManager {
11
- constructor(client) {
12
- this.rosterCache = new Map();
13
- this.client = client;
14
- }
15
- /**
16
- * Get all roster entries
17
- */
18
- async getRoster() {
19
- const message = {
20
- type: 'roster',
21
- method: 'list'
22
- };
23
- const response = await this.client.request(message);
24
- // Parse roster data
25
- if (response.data) {
26
- this.updateCache(response.data);
27
- }
28
- return Array.from(this.rosterCache.values());
29
- }
30
- /**
31
- * Get roster entry by name
32
- */
33
- async getRosterEntryByName(name) {
34
- // Check cache first
35
- if (this.rosterCache.has(name)) {
36
- return this.rosterCache.get(name);
37
- }
38
- // Refresh roster
39
- await this.getRoster();
40
- return this.rosterCache.get(name);
41
- }
42
- /**
43
- * Get roster entry by address
44
- */
45
- async getRosterEntryByAddress(address) {
46
- // Ensure roster is loaded
47
- if (this.rosterCache.size === 0) {
48
- await this.getRoster();
49
- }
50
- const addressStr = address.toString();
51
- for (const wrapper of this.rosterCache.values()) {
52
- if (wrapper.data.address === addressStr) {
53
- return wrapper;
54
- }
55
- }
56
- return undefined;
57
- }
58
- /**
59
- * Search roster by partial name match
60
- */
61
- async searchRoster(query) {
62
- // Ensure roster is loaded
63
- if (this.rosterCache.size === 0) {
64
- await this.getRoster();
65
- }
66
- const lowerQuery = query.toLowerCase();
67
- const results = [];
68
- for (const wrapper of this.rosterCache.values()) {
69
- const entry = wrapper.data;
70
- if (entry.name.toLowerCase().includes(lowerQuery) ||
71
- entry.address.includes(query) ||
72
- entry.road?.toLowerCase().includes(lowerQuery) ||
73
- entry.number?.includes(query)) {
74
- results.push(wrapper);
75
- }
76
- }
77
- return results;
78
- }
79
- /**
80
- * Get cached roster (no network request)
81
- */
82
- getCachedRoster() {
83
- return Array.from(this.rosterCache.values());
84
- }
85
- /**
86
- * Clear roster cache
87
- */
88
- clearCache() {
89
- this.rosterCache.clear();
90
- }
91
- /**
92
- * Update internal cache from roster data
93
- */
94
- updateCache(rosterData) {
95
- this.rosterCache.clear();
96
- // Handle array format (real JMRI server) - store wrapped entries
97
- if (Array.isArray(rosterData)) {
98
- for (const wrapper of rosterData) {
99
- if (wrapper.type === 'rosterEntry' && wrapper.data) {
100
- this.rosterCache.set(wrapper.data.name, wrapper);
101
- }
102
- }
103
- }
104
- // Handle legacy keyed object format (for backward compatibility) - wrap entries
105
- else {
106
- let id = 1;
107
- for (const [name, entry] of Object.entries(rosterData)) {
108
- const wrapper = {
109
- type: 'rosterEntry',
110
- data: entry,
111
- id: id++
112
- };
113
- this.rosterCache.set(name, wrapper);
114
- }
115
- }
116
- }
117
- }
118
- exports.RosterManager = RosterManager;
@@ -1,28 +0,0 @@
1
- "use strict";
2
- /**
3
- * System connections manager — discovers available JMRI hardware connection prefixes
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SystemConnectionsManager = void 0;
7
- class SystemConnectionsManager {
8
- constructor(client) {
9
- this.client = client;
10
- }
11
- /**
12
- * List all available JMRI system connections and their prefixes.
13
- * Use the returned prefix values with power and throttle commands to
14
- * target a specific hardware connection when multiple are configured.
15
- */
16
- async getSystemConnections() {
17
- const message = {
18
- type: 'systemConnections',
19
- method: 'list'
20
- };
21
- const response = await this.client.request(message);
22
- if (!response.data) {
23
- return [];
24
- }
25
- return Array.isArray(response.data) ? response.data : [response.data];
26
- }
27
- }
28
- exports.SystemConnectionsManager = SystemConnectionsManager;
@@ -1,233 +0,0 @@
1
- "use strict";
2
- /**
3
- * Throttle control manager
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ThrottleManager = void 0;
7
- const eventemitter3_1 = require("eventemitter3");
8
- const throttle_js_1 = require("../types/throttle.js");
9
- /**
10
- * Manages multiple throttles
11
- */
12
- class ThrottleManager extends eventemitter3_1.EventEmitter {
13
- constructor(client) {
14
- super();
15
- this.throttles = new Map();
16
- this.client = client;
17
- // Generate a unique client ID
18
- this.clientId = `jmri-client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
19
- // Listen for throttle updates
20
- this.client.on('update', (message) => {
21
- if (message.type === 'throttle') {
22
- this.handleThrottleUpdate(message);
23
- }
24
- });
25
- // Clean up throttles on disconnect
26
- this.client.on('disconnected', () => {
27
- this.handleDisconnect();
28
- });
29
- }
30
- /**
31
- * Acquire a throttle for a locomotive
32
- */
33
- async acquireThrottle(options) {
34
- // Generate a unique throttle name using client ID and address
35
- const throttleName = `${this.clientId}-${options.address}`;
36
- const message = {
37
- type: 'throttle',
38
- data: {
39
- name: throttleName,
40
- address: options.address,
41
- ...(options.prefix !== undefined && { prefix: options.prefix })
42
- }
43
- };
44
- const response = await this.client.request(message);
45
- // JMRI returns the throttle ID in the "throttle" field, or we can use our name
46
- const throttleId = response.data?.throttle || throttleName;
47
- if (!throttleId) {
48
- throw new Error('Failed to acquire throttle: no throttle ID returned');
49
- }
50
- // Initialize throttle state
51
- const state = {
52
- id: throttleId,
53
- address: options.address,
54
- speed: 0,
55
- forward: true,
56
- functions: new Map(),
57
- acquired: true
58
- };
59
- this.throttles.set(throttleId, state);
60
- this.emit('throttle:acquired', throttleId);
61
- return throttleId;
62
- }
63
- /**
64
- * Release a throttle
65
- */
66
- async releaseThrottle(throttleId) {
67
- const state = this.throttles.get(throttleId);
68
- if (!state) {
69
- throw new Error(`Throttle not found: ${throttleId}`);
70
- }
71
- const message = {
72
- type: 'throttle',
73
- data: {
74
- throttle: throttleId,
75
- release: null
76
- }
77
- };
78
- await this.client.request(message);
79
- this.throttles.delete(throttleId);
80
- this.emit('throttle:released', throttleId);
81
- }
82
- /**
83
- * Set throttle speed (0.0 to 1.0)
84
- */
85
- async setSpeed(throttleId, speed) {
86
- const state = this.throttles.get(throttleId);
87
- if (!state) {
88
- throw new Error(`Throttle not found: ${throttleId}`);
89
- }
90
- if (!(0, throttle_js_1.isValidSpeed)(speed)) {
91
- throw new Error(`Invalid speed: ${speed}. Must be between 0.0 and 1.0`);
92
- }
93
- const message = {
94
- type: 'throttle',
95
- data: {
96
- throttle: throttleId,
97
- speed
98
- }
99
- };
100
- // JMRI doesn't send responses for throttle control commands, just send
101
- this.client.send(message);
102
- state.speed = speed;
103
- this.emit('throttle:updated', throttleId, { speed });
104
- }
105
- /**
106
- * Set throttle direction
107
- */
108
- async setDirection(throttleId, forward) {
109
- const state = this.throttles.get(throttleId);
110
- if (!state) {
111
- throw new Error(`Throttle not found: ${throttleId}`);
112
- }
113
- const message = {
114
- type: 'throttle',
115
- data: {
116
- throttle: throttleId,
117
- forward
118
- }
119
- };
120
- // JMRI doesn't send responses for throttle control commands, just send
121
- this.client.send(message);
122
- state.forward = forward;
123
- this.emit('throttle:updated', throttleId, { forward });
124
- }
125
- /**
126
- * Set throttle function (F0-F28)
127
- */
128
- async setFunction(throttleId, functionKey, value) {
129
- const state = this.throttles.get(throttleId);
130
- if (!state) {
131
- throw new Error(`Throttle not found: ${throttleId}`);
132
- }
133
- if (!(0, throttle_js_1.isThrottleFunctionKey)(functionKey)) {
134
- throw new Error(`Invalid function key: ${functionKey}`);
135
- }
136
- const data = {
137
- throttle: throttleId,
138
- [functionKey]: value
139
- };
140
- const message = {
141
- type: 'throttle',
142
- data
143
- };
144
- // JMRI doesn't send responses for throttle control commands, just send
145
- this.client.send(message);
146
- state.functions.set(functionKey, value);
147
- this.emit('throttle:updated', throttleId, { [functionKey]: value });
148
- }
149
- /**
150
- * Emergency stop for a throttle (speed to 0)
151
- */
152
- async emergencyStop(throttleId) {
153
- await this.setSpeed(throttleId, 0);
154
- }
155
- /**
156
- * Set throttle to idle (speed to 0, maintain direction)
157
- */
158
- async idle(throttleId) {
159
- await this.setSpeed(throttleId, 0);
160
- }
161
- /**
162
- * Get throttle state
163
- */
164
- getThrottleState(throttleId) {
165
- return this.throttles.get(throttleId);
166
- }
167
- /**
168
- * Get all throttle IDs
169
- */
170
- getThrottleIds() {
171
- return Array.from(this.throttles.keys());
172
- }
173
- /**
174
- * Get all throttle states
175
- */
176
- getAllThrottles() {
177
- return Array.from(this.throttles.values());
178
- }
179
- /**
180
- * Release all throttles
181
- */
182
- async releaseAllThrottles() {
183
- const throttleIds = this.getThrottleIds();
184
- for (const throttleId of throttleIds) {
185
- try {
186
- await this.releaseThrottle(throttleId);
187
- }
188
- catch (error) {
189
- // Continue releasing others even if one fails
190
- this.emit('error', error);
191
- }
192
- }
193
- }
194
- /**
195
- * Handle unsolicited throttle updates from JMRI
196
- */
197
- handleThrottleUpdate(message) {
198
- const throttleId = message.data?.throttle;
199
- if (!throttleId) {
200
- return;
201
- }
202
- const state = this.throttles.get(throttleId);
203
- if (!state) {
204
- // Unknown throttle, possibly lost
205
- this.emit('throttle:lost', throttleId);
206
- return;
207
- }
208
- // Update state from message
209
- if (message.data.speed !== undefined) {
210
- state.speed = message.data.speed;
211
- }
212
- if (message.data.forward !== undefined) {
213
- state.forward = message.data.forward;
214
- }
215
- // Update functions
216
- for (let i = 0; i <= 28; i++) {
217
- const key = `F${i}`;
218
- if (message.data[key] !== undefined) {
219
- state.functions.set(key, message.data[key]);
220
- }
221
- }
222
- this.emit('throttle:updated', throttleId, message.data);
223
- }
224
- /**
225
- * Handle disconnect - mark all throttles as not acquired
226
- */
227
- handleDisconnect() {
228
- for (const state of this.throttles.values()) {
229
- state.acquired = false;
230
- }
231
- }
232
- }
233
- exports.ThrottleManager = ThrottleManager;