jmri-client 4.2.0-beta.1 → 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 (60) hide show
  1. package/README.md +18 -1
  2. package/dist/cjs/index.js +2382 -31
  3. package/dist/esm/index.js +2333 -17
  4. package/docs/API.md +39 -4
  5. package/docs/BROWSER.md +4 -4
  6. package/docs/EXAMPLES.md +42 -0
  7. package/docs/MIGRATION.md +30 -1
  8. package/package.json +17 -18
  9. package/dist/cjs/client.js +0 -366
  10. package/dist/cjs/core/connection-state-manager.js +0 -84
  11. package/dist/cjs/core/heartbeat-manager.js +0 -79
  12. package/dist/cjs/core/index.js +0 -25
  13. package/dist/cjs/core/message-queue.js +0 -59
  14. package/dist/cjs/core/reconnection-manager.js +0 -97
  15. package/dist/cjs/core/websocket-adapter.js +0 -135
  16. package/dist/cjs/core/websocket-client.js +0 -388
  17. package/dist/cjs/managers/index.js +0 -25
  18. package/dist/cjs/managers/light-manager.js +0 -111
  19. package/dist/cjs/managers/power-manager.js +0 -90
  20. package/dist/cjs/managers/roster-manager.js +0 -118
  21. package/dist/cjs/managers/system-connections-manager.js +0 -28
  22. package/dist/cjs/managers/throttle-manager.js +0 -233
  23. package/dist/cjs/managers/turnout-manager.js +0 -111
  24. package/dist/cjs/mocks/index.js +0 -12
  25. package/dist/cjs/mocks/mock-data.js +0 -237
  26. package/dist/cjs/mocks/mock-response-manager.js +0 -290
  27. package/dist/cjs/types/client-options.js +0 -66
  28. package/dist/cjs/types/events.js +0 -16
  29. package/dist/cjs/types/index.js +0 -23
  30. package/dist/cjs/types/jmri-messages.js +0 -95
  31. package/dist/cjs/types/throttle.js +0 -19
  32. package/dist/cjs/utils/exponential-backoff.js +0 -40
  33. package/dist/cjs/utils/index.js +0 -21
  34. package/dist/cjs/utils/message-id.js +0 -40
  35. package/dist/esm/client.js +0 -362
  36. package/dist/esm/core/connection-state-manager.js +0 -80
  37. package/dist/esm/core/heartbeat-manager.js +0 -75
  38. package/dist/esm/core/index.js +0 -9
  39. package/dist/esm/core/message-queue.js +0 -55
  40. package/dist/esm/core/reconnection-manager.js +0 -93
  41. package/dist/esm/core/websocket-adapter.js +0 -98
  42. package/dist/esm/core/websocket-client.js +0 -384
  43. package/dist/esm/managers/index.js +0 -9
  44. package/dist/esm/managers/light-manager.js +0 -107
  45. package/dist/esm/managers/power-manager.js +0 -86
  46. package/dist/esm/managers/roster-manager.js +0 -114
  47. package/dist/esm/managers/system-connections-manager.js +0 -24
  48. package/dist/esm/managers/throttle-manager.js +0 -229
  49. package/dist/esm/managers/turnout-manager.js +0 -107
  50. package/dist/esm/mocks/index.js +0 -6
  51. package/dist/esm/mocks/mock-data.js +0 -234
  52. package/dist/esm/mocks/mock-response-manager.js +0 -286
  53. package/dist/esm/types/client-options.js +0 -62
  54. package/dist/esm/types/events.js +0 -13
  55. package/dist/esm/types/index.js +0 -7
  56. package/dist/esm/types/jmri-messages.js +0 -89
  57. package/dist/esm/types/throttle.js +0 -15
  58. package/dist/esm/utils/exponential-backoff.js +0 -36
  59. package/dist/esm/utils/index.js +0 -5
  60. package/dist/esm/utils/message-id.js +0 -36
@@ -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;
@@ -1,111 +0,0 @@
1
- "use strict";
2
- /**
3
- * Turnout (switch) manager
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TurnoutManager = void 0;
7
- const eventemitter3_1 = require("eventemitter3");
8
- const jmri_messages_js_1 = require("../types/jmri-messages.js");
9
- /**
10
- * Manages JMRI turnout (track switch) state
11
- */
12
- class TurnoutManager extends eventemitter3_1.EventEmitter {
13
- constructor(client) {
14
- super();
15
- this.turnouts = new Map();
16
- this.client = client;
17
- this.client.on('update', (message) => {
18
- if (message.type === 'turnout') {
19
- this.handleTurnoutUpdate(message);
20
- }
21
- });
22
- }
23
- /**
24
- * Get the current state of a turnout.
25
- * Also registers a server-side listener so subsequent changes are pushed.
26
- */
27
- async getTurnout(name) {
28
- const message = {
29
- type: 'turnout',
30
- data: { name }
31
- };
32
- const response = await this.client.request(message);
33
- const state = response.data?.state ?? jmri_messages_js_1.TurnoutState.UNKNOWN;
34
- this.turnouts.set(name, state);
35
- return state;
36
- }
37
- /**
38
- * Set a turnout to the given state
39
- */
40
- async setTurnout(name, state) {
41
- const message = {
42
- type: 'turnout',
43
- method: 'post',
44
- data: { name, state }
45
- };
46
- await this.client.request(message);
47
- const oldState = this.turnouts.get(name);
48
- this.turnouts.set(name, state);
49
- if (oldState !== state) {
50
- this.emit('turnout:changed', name, state);
51
- }
52
- }
53
- /**
54
- * Throw a turnout (diverging route)
55
- */
56
- async throwTurnout(name) {
57
- return this.setTurnout(name, jmri_messages_js_1.TurnoutState.THROWN);
58
- }
59
- /**
60
- * Close a turnout (straight through / normal)
61
- */
62
- async closeTurnout(name) {
63
- return this.setTurnout(name, jmri_messages_js_1.TurnoutState.CLOSED);
64
- }
65
- /**
66
- * List all turnouts known to JMRI
67
- */
68
- async listTurnouts() {
69
- const message = {
70
- type: 'turnout',
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.turnouts.set(entry.name, entry.state);
80
- }
81
- }
82
- return entries;
83
- }
84
- /**
85
- * Get cached turnout state without a network request
86
- */
87
- getTurnoutState(name) {
88
- return this.turnouts.get(name);
89
- }
90
- /**
91
- * Get all cached turnout states
92
- */
93
- getCachedTurnouts() {
94
- return new Map(this.turnouts);
95
- }
96
- /**
97
- * Handle unsolicited turnout state updates from JMRI
98
- */
99
- handleTurnoutUpdate(message) {
100
- const name = message.data?.name;
101
- const state = message.data?.state;
102
- if (!name || state === undefined)
103
- return;
104
- const oldState = this.turnouts.get(name);
105
- this.turnouts.set(name, state);
106
- if (oldState !== state) {
107
- this.emit('turnout:changed', name, state);
108
- }
109
- }
110
- }
111
- exports.TurnoutManager = TurnoutManager;
@@ -1,12 +0,0 @@
1
- "use strict";
2
- /**
3
- * Mock system exports
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.mockData = exports.mockResponseManager = exports.MockResponseManager = void 0;
7
- var mock_response_manager_js_1 = require("./mock-response-manager.js");
8
- Object.defineProperty(exports, "MockResponseManager", { enumerable: true, get: function () { return mock_response_manager_js_1.MockResponseManager; } });
9
- Object.defineProperty(exports, "mockResponseManager", { enumerable: true, get: function () { return mock_response_manager_js_1.mockResponseManager; } });
10
- // Re-export mock data for direct access if needed
11
- var mock_data_js_1 = require("./mock-data.js");
12
- Object.defineProperty(exports, "mockData", { enumerable: true, get: function () { return mock_data_js_1.mockData; } });