@djodjonx/x32-simulator 0.0.6 → 0.0.7

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.
package/dist/server.cjs CHANGED
@@ -1,12 +1,164 @@
1
1
  #!/usr/bin/env node
2
- const require_SchemaRegistry = require('./SchemaRegistry-DE6iObDv.cjs');
2
+ const require_SchemaRegistry = require('./SchemaRegistry-Ddy5AEKs.cjs');
3
3
  let node_readline = require("node:readline");
4
4
  node_readline = require_SchemaRegistry.__toESM(node_readline);
5
5
  let node_fs = require("node:fs");
6
6
  node_fs = require_SchemaRegistry.__toESM(node_fs);
7
7
  let node_path = require("node:path");
8
8
  node_path = require_SchemaRegistry.__toESM(node_path);
9
+ let node_fs_promises = require("node:fs/promises");
9
10
 
11
+ //#region src/infrastructure/repositories/JsonFileStateRepository.ts
12
+ /**
13
+ * JSON file-based state repository.
14
+ * Persists state to a JSON file on disk.
15
+ * Supports auto-save and manual flush.
16
+ */
17
+ var JsonFileStateRepository = class {
18
+ state = /* @__PURE__ */ new Map();
19
+ dirty = false;
20
+ autoSaveTimer = null;
21
+ saving = false;
22
+ /**
23
+ * Creates a new JSON file repository.
24
+ * @param filePath - Path to the JSON file.
25
+ * @param autoSaveInterval - Auto-save interval in ms (0 = disabled).
26
+ */
27
+ constructor(filePath, autoSaveInterval = 5e3) {
28
+ this.filePath = filePath;
29
+ this.autoSaveInterval = autoSaveInterval;
30
+ if (this.autoSaveInterval > 0) this.startAutoSave();
31
+ }
32
+ /**
33
+ * Starts the auto-save timer.
34
+ */
35
+ startAutoSave() {
36
+ this.autoSaveTimer = setInterval(async () => {
37
+ if (this.dirty && !this.saving) await this.flush();
38
+ }, this.autoSaveInterval);
39
+ this.autoSaveTimer.unref();
40
+ }
41
+ /**
42
+ * Stops the auto-save timer.
43
+ */
44
+ stopAutoSave() {
45
+ if (this.autoSaveTimer) {
46
+ clearInterval(this.autoSaveTimer);
47
+ this.autoSaveTimer = null;
48
+ }
49
+ }
50
+ /**
51
+ * Checks if the file exists.
52
+ */
53
+ async fileExists() {
54
+ try {
55
+ await (0, node_fs_promises.access)(this.filePath, node_fs.constants.F_OK);
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ /**
62
+ * Loads state from JSON file.
63
+ */
64
+ async load() {
65
+ if (await this.fileExists()) try {
66
+ const content = await (0, node_fs_promises.readFile)(this.filePath, "utf-8");
67
+ const data = JSON.parse(content);
68
+ this.state.clear();
69
+ for (const [key, value] of Object.entries(data)) this.state.set(key, value);
70
+ this.dirty = false;
71
+ return data;
72
+ } catch (error) {
73
+ console.error(`[JsonFileStateRepository] Failed to load state from ${this.filePath}:`, error);
74
+ return {};
75
+ }
76
+ return {};
77
+ }
78
+ /**
79
+ * Saves complete state to JSON file.
80
+ */
81
+ async save(state) {
82
+ this.state.clear();
83
+ for (const [key, value] of Object.entries(state)) this.state.set(key, value);
84
+ this.dirty = true;
85
+ await this.flush();
86
+ }
87
+ /**
88
+ * Gets a single value.
89
+ */
90
+ get(address) {
91
+ return this.state.get(address);
92
+ }
93
+ /**
94
+ * Sets a single value and marks as dirty.
95
+ */
96
+ set(address, value) {
97
+ this.state.set(address, value);
98
+ this.dirty = true;
99
+ }
100
+ /**
101
+ * Gets all entries.
102
+ */
103
+ getAll() {
104
+ const result = {};
105
+ this.state.forEach((value, key) => {
106
+ result[key] = value;
107
+ });
108
+ return result;
109
+ }
110
+ /**
111
+ * Resets state to defaults and marks as dirty.
112
+ */
113
+ reset(defaults) {
114
+ this.state.clear();
115
+ for (const [key, value] of Object.entries(defaults)) this.state.set(key, value);
116
+ this.dirty = true;
117
+ }
118
+ /**
119
+ * Writes state to JSON file if dirty.
120
+ */
121
+ async flush() {
122
+ if (!this.dirty || this.saving) return;
123
+ this.saving = true;
124
+ try {
125
+ const data = this.getAll();
126
+ const json = JSON.stringify(data, null, 2);
127
+ await (0, node_fs_promises.writeFile)(this.filePath, json, "utf-8");
128
+ this.dirty = false;
129
+ } catch (error) {
130
+ console.error(`[JsonFileStateRepository] Failed to save state to ${this.filePath}:`, error);
131
+ } finally {
132
+ this.saving = false;
133
+ }
134
+ }
135
+ /**
136
+ * Cleanup: stop auto-save and flush pending changes.
137
+ */
138
+ async dispose() {
139
+ this.stopAutoSave();
140
+ await this.flush();
141
+ }
142
+ };
143
+
144
+ //#endregion
145
+ //#region src/infrastructure/repositories/StateRepositoryFactory.ts
146
+ /**
147
+ * Creates a state repository based on configuration.
148
+ * @param config - Repository configuration.
149
+ * @returns The configured state repository.
150
+ */
151
+ function createStateRepository(config) {
152
+ switch (config.type) {
153
+ case "json":
154
+ if (!config.filePath) throw new Error("JSON repository requires a filePath");
155
+ return new JsonFileStateRepository(config.filePath, config.autoSaveInterval ?? 5e3);
156
+ case "memory":
157
+ default: return new require_SchemaRegistry.InMemoryStateRepository();
158
+ }
159
+ }
160
+
161
+ //#endregion
10
162
  //#region src/presentation/cli/server.ts
11
163
  const loadEnv = () => {
12
164
  try {
@@ -28,6 +180,8 @@ const parseArgs = (argv) => {
28
180
  const args = argv.slice(2);
29
181
  let cliPort;
30
182
  let cliHost;
183
+ let storageType = "memory";
184
+ let stateFilePath;
31
185
  for (let i = 0; i < args.length; i++) {
32
186
  const arg = args[i];
33
187
  if (arg === "--port" || arg === "-p") {
@@ -46,29 +200,64 @@ const parseArgs = (argv) => {
46
200
  continue;
47
201
  }
48
202
  }
49
- if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) {
50
- cliHost = arg;
51
- continue;
203
+ if (arg === "--storage" || arg === "-s") {
204
+ const next = args[i + 1];
205
+ if (next && (next === "memory" || next === "json")) {
206
+ storageType = next;
207
+ i++;
208
+ }
52
209
  }
53
- if (!cliPort && /^\d+$/.test(arg)) {
54
- cliPort = parseInt(arg, 10);
55
- continue;
210
+ if (arg === "--state-file" || arg === "-f") {
211
+ const next = args[i + 1];
212
+ if (next) {
213
+ stateFilePath = next;
214
+ storageType = "json";
215
+ i++;
216
+ }
217
+ }
218
+ if (arg === "--help") {
219
+ console.log(`
220
+ X32 Simulator - Usage:
221
+ x32-simulator [options]
222
+
223
+ Options:
224
+ -p, --port <port> UDP port to listen on (default: 10023)
225
+ -h, --host <ip> IP address to bind to (default: 0.0.0.0)
226
+ -s, --storage <type> Storage type: 'memory' or 'json' (default: memory)
227
+ -f, --state-file <file> Path to JSON state file (implies --storage json)
228
+ --help Show this help message
229
+
230
+ Examples:
231
+ x32-simulator --port 10023 --host 192.168.1.100
232
+ x32-simulator --storage json --state-file ./x32-state.json
233
+ x32-simulator -p 10023 -f ./my-mixer-state.json
234
+ `);
235
+ process.exit(0);
56
236
  }
237
+ if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) cliHost = arg;
238
+ if (!cliPort && /^\d+$/.test(arg)) cliPort = parseInt(arg, 10);
57
239
  }
58
240
  return {
59
241
  PORT: cliPort || parseInt(process.env.X32_PORT || "10023", 10),
60
- HOST: cliHost || process.env.X32_IP || "0.0.0.0"
242
+ HOST: cliHost || process.env.X32_IP || "0.0.0.0",
243
+ storageType,
244
+ stateFilePath
61
245
  };
62
246
  };
63
247
  const bootstrap = async () => {
64
248
  loadEnv();
65
- const { PORT, HOST } = parseArgs(process.argv);
249
+ const { PORT, HOST, storageType, stateFilePath } = parseArgs(process.argv);
66
250
  const logger = require_SchemaRegistry.ConsoleLogger.getInstance();
67
251
  logger.setLevel(require_SchemaRegistry.LogLevel.DEBUG);
68
252
  const schemaRegistry = new require_SchemaRegistry.SchemaRegistry(new require_SchemaRegistry.SchemaFactory());
69
- const service = new require_SchemaRegistry.SimulationService(new require_SchemaRegistry.UdpNetworkGateway(logger, new require_SchemaRegistry.OscCodec(schemaRegistry)), logger, new require_SchemaRegistry.InMemoryStateRepository(logger, schemaRegistry), schemaRegistry, PORT, HOST);
253
+ const service = new require_SchemaRegistry.SimulationService(new require_SchemaRegistry.UdpNetworkGateway(logger, new require_SchemaRegistry.OscCodec(schemaRegistry)), logger, createStateRepository({
254
+ type: storageType,
255
+ filePath: stateFilePath,
256
+ autoSaveInterval: 5e3
257
+ }), schemaRegistry, PORT, HOST);
70
258
  try {
71
259
  await service.start();
260
+ const storageInfo = storageType === "json" ? `JSON (${stateFilePath})` : "In-Memory";
72
261
  console.log(`
73
262
  ╔════════════════════════════════════════╗
74
263
  ║ 🚀 X32 SIMULATION SERVER ACTIVE ║
@@ -76,10 +265,12 @@ const bootstrap = async () => {
76
265
  • IP: ${HOST}
77
266
  • Port: ${PORT}
78
267
  • Protocol: UDP (Strict X32 Schema)
268
+ • Storage: ${storageInfo}
79
269
  • Logging: ENHANCED (DEBUG)
80
270
 
81
271
  👉 Type "exit" or "stop" to shut down.
82
272
  👉 Type "reset" to reset faders to default.
273
+ 👉 Type "save" to force save state (JSON mode).
83
274
  `);
84
275
  } catch (err) {
85
276
  if (err.code === "EADDRNOTAVAIL") {
@@ -121,6 +312,10 @@ const bootstrap = async () => {
121
312
  service.resetState();
122
313
  console.log("✨ Console state reset to defaults.");
123
314
  break;
315
+ case "save":
316
+ await service.saveState();
317
+ console.log("💾 State saved to storage.");
318
+ break;
124
319
  default:
125
320
  if (command) console.log(`Unknown command: "${command}"`);
126
321
  break;
package/dist/server.mjs CHANGED
@@ -1,9 +1,162 @@
1
1
  #!/usr/bin/env node
2
- import { a as ConsoleLogger, i as UdpNetworkGateway, n as SchemaFactory, o as LogLevel, r as OscCodec, s as InMemoryStateRepository, t as SchemaRegistry, u as SimulationService } from "./SchemaRegistry-D4eIJord.mjs";
2
+ import { a as ConsoleLogger, i as UdpNetworkGateway, l as SimulationService, n as SchemaFactory, o as LogLevel, r as OscCodec, s as InMemoryStateRepository, t as SchemaRegistry } from "./SchemaRegistry-BBzyicoy.mjs";
3
3
  import * as readline from "node:readline";
4
4
  import * as fs from "node:fs";
5
+ import { constants } from "node:fs";
5
6
  import * as path from "node:path";
7
+ import { access, readFile, writeFile } from "node:fs/promises";
6
8
 
9
+ //#region src/infrastructure/repositories/JsonFileStateRepository.ts
10
+ /**
11
+ * JSON file-based state repository.
12
+ * Persists state to a JSON file on disk.
13
+ * Supports auto-save and manual flush.
14
+ */
15
+ var JsonFileStateRepository = class {
16
+ state = /* @__PURE__ */ new Map();
17
+ dirty = false;
18
+ autoSaveTimer = null;
19
+ saving = false;
20
+ /**
21
+ * Creates a new JSON file repository.
22
+ * @param filePath - Path to the JSON file.
23
+ * @param autoSaveInterval - Auto-save interval in ms (0 = disabled).
24
+ */
25
+ constructor(filePath, autoSaveInterval = 5e3) {
26
+ this.filePath = filePath;
27
+ this.autoSaveInterval = autoSaveInterval;
28
+ if (this.autoSaveInterval > 0) this.startAutoSave();
29
+ }
30
+ /**
31
+ * Starts the auto-save timer.
32
+ */
33
+ startAutoSave() {
34
+ this.autoSaveTimer = setInterval(async () => {
35
+ if (this.dirty && !this.saving) await this.flush();
36
+ }, this.autoSaveInterval);
37
+ this.autoSaveTimer.unref();
38
+ }
39
+ /**
40
+ * Stops the auto-save timer.
41
+ */
42
+ stopAutoSave() {
43
+ if (this.autoSaveTimer) {
44
+ clearInterval(this.autoSaveTimer);
45
+ this.autoSaveTimer = null;
46
+ }
47
+ }
48
+ /**
49
+ * Checks if the file exists.
50
+ */
51
+ async fileExists() {
52
+ try {
53
+ await access(this.filePath, constants.F_OK);
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Loads state from JSON file.
61
+ */
62
+ async load() {
63
+ if (await this.fileExists()) try {
64
+ const content = await readFile(this.filePath, "utf-8");
65
+ const data = JSON.parse(content);
66
+ this.state.clear();
67
+ for (const [key, value] of Object.entries(data)) this.state.set(key, value);
68
+ this.dirty = false;
69
+ return data;
70
+ } catch (error) {
71
+ console.error(`[JsonFileStateRepository] Failed to load state from ${this.filePath}:`, error);
72
+ return {};
73
+ }
74
+ return {};
75
+ }
76
+ /**
77
+ * Saves complete state to JSON file.
78
+ */
79
+ async save(state) {
80
+ this.state.clear();
81
+ for (const [key, value] of Object.entries(state)) this.state.set(key, value);
82
+ this.dirty = true;
83
+ await this.flush();
84
+ }
85
+ /**
86
+ * Gets a single value.
87
+ */
88
+ get(address) {
89
+ return this.state.get(address);
90
+ }
91
+ /**
92
+ * Sets a single value and marks as dirty.
93
+ */
94
+ set(address, value) {
95
+ this.state.set(address, value);
96
+ this.dirty = true;
97
+ }
98
+ /**
99
+ * Gets all entries.
100
+ */
101
+ getAll() {
102
+ const result = {};
103
+ this.state.forEach((value, key) => {
104
+ result[key] = value;
105
+ });
106
+ return result;
107
+ }
108
+ /**
109
+ * Resets state to defaults and marks as dirty.
110
+ */
111
+ reset(defaults) {
112
+ this.state.clear();
113
+ for (const [key, value] of Object.entries(defaults)) this.state.set(key, value);
114
+ this.dirty = true;
115
+ }
116
+ /**
117
+ * Writes state to JSON file if dirty.
118
+ */
119
+ async flush() {
120
+ if (!this.dirty || this.saving) return;
121
+ this.saving = true;
122
+ try {
123
+ const data = this.getAll();
124
+ const json = JSON.stringify(data, null, 2);
125
+ await writeFile(this.filePath, json, "utf-8");
126
+ this.dirty = false;
127
+ } catch (error) {
128
+ console.error(`[JsonFileStateRepository] Failed to save state to ${this.filePath}:`, error);
129
+ } finally {
130
+ this.saving = false;
131
+ }
132
+ }
133
+ /**
134
+ * Cleanup: stop auto-save and flush pending changes.
135
+ */
136
+ async dispose() {
137
+ this.stopAutoSave();
138
+ await this.flush();
139
+ }
140
+ };
141
+
142
+ //#endregion
143
+ //#region src/infrastructure/repositories/StateRepositoryFactory.ts
144
+ /**
145
+ * Creates a state repository based on configuration.
146
+ * @param config - Repository configuration.
147
+ * @returns The configured state repository.
148
+ */
149
+ function createStateRepository(config) {
150
+ switch (config.type) {
151
+ case "json":
152
+ if (!config.filePath) throw new Error("JSON repository requires a filePath");
153
+ return new JsonFileStateRepository(config.filePath, config.autoSaveInterval ?? 5e3);
154
+ case "memory":
155
+ default: return new InMemoryStateRepository();
156
+ }
157
+ }
158
+
159
+ //#endregion
7
160
  //#region src/presentation/cli/server.ts
8
161
  const loadEnv = () => {
9
162
  try {
@@ -25,6 +178,8 @@ const parseArgs = (argv) => {
25
178
  const args = argv.slice(2);
26
179
  let cliPort;
27
180
  let cliHost;
181
+ let storageType = "memory";
182
+ let stateFilePath;
28
183
  for (let i = 0; i < args.length; i++) {
29
184
  const arg = args[i];
30
185
  if (arg === "--port" || arg === "-p") {
@@ -43,29 +198,64 @@ const parseArgs = (argv) => {
43
198
  continue;
44
199
  }
45
200
  }
46
- if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) {
47
- cliHost = arg;
48
- continue;
201
+ if (arg === "--storage" || arg === "-s") {
202
+ const next = args[i + 1];
203
+ if (next && (next === "memory" || next === "json")) {
204
+ storageType = next;
205
+ i++;
206
+ }
49
207
  }
50
- if (!cliPort && /^\d+$/.test(arg)) {
51
- cliPort = parseInt(arg, 10);
52
- continue;
208
+ if (arg === "--state-file" || arg === "-f") {
209
+ const next = args[i + 1];
210
+ if (next) {
211
+ stateFilePath = next;
212
+ storageType = "json";
213
+ i++;
214
+ }
215
+ }
216
+ if (arg === "--help") {
217
+ console.log(`
218
+ X32 Simulator - Usage:
219
+ x32-simulator [options]
220
+
221
+ Options:
222
+ -p, --port <port> UDP port to listen on (default: 10023)
223
+ -h, --host <ip> IP address to bind to (default: 0.0.0.0)
224
+ -s, --storage <type> Storage type: 'memory' or 'json' (default: memory)
225
+ -f, --state-file <file> Path to JSON state file (implies --storage json)
226
+ --help Show this help message
227
+
228
+ Examples:
229
+ x32-simulator --port 10023 --host 192.168.1.100
230
+ x32-simulator --storage json --state-file ./x32-state.json
231
+ x32-simulator -p 10023 -f ./my-mixer-state.json
232
+ `);
233
+ process.exit(0);
53
234
  }
235
+ if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) cliHost = arg;
236
+ if (!cliPort && /^\d+$/.test(arg)) cliPort = parseInt(arg, 10);
54
237
  }
55
238
  return {
56
239
  PORT: cliPort || parseInt(process.env.X32_PORT || "10023", 10),
57
- HOST: cliHost || process.env.X32_IP || "0.0.0.0"
240
+ HOST: cliHost || process.env.X32_IP || "0.0.0.0",
241
+ storageType,
242
+ stateFilePath
58
243
  };
59
244
  };
60
245
  const bootstrap = async () => {
61
246
  loadEnv();
62
- const { PORT, HOST } = parseArgs(process.argv);
247
+ const { PORT, HOST, storageType, stateFilePath } = parseArgs(process.argv);
63
248
  const logger = ConsoleLogger.getInstance();
64
249
  logger.setLevel(LogLevel.DEBUG);
65
250
  const schemaRegistry = new SchemaRegistry(new SchemaFactory());
66
- const service = new SimulationService(new UdpNetworkGateway(logger, new OscCodec(schemaRegistry)), logger, new InMemoryStateRepository(logger, schemaRegistry), schemaRegistry, PORT, HOST);
251
+ const service = new SimulationService(new UdpNetworkGateway(logger, new OscCodec(schemaRegistry)), logger, createStateRepository({
252
+ type: storageType,
253
+ filePath: stateFilePath,
254
+ autoSaveInterval: 5e3
255
+ }), schemaRegistry, PORT, HOST);
67
256
  try {
68
257
  await service.start();
258
+ const storageInfo = storageType === "json" ? `JSON (${stateFilePath})` : "In-Memory";
69
259
  console.log(`
70
260
  ╔════════════════════════════════════════╗
71
261
  ║ 🚀 X32 SIMULATION SERVER ACTIVE ║
@@ -73,10 +263,12 @@ const bootstrap = async () => {
73
263
  • IP: ${HOST}
74
264
  • Port: ${PORT}
75
265
  • Protocol: UDP (Strict X32 Schema)
266
+ • Storage: ${storageInfo}
76
267
  • Logging: ENHANCED (DEBUG)
77
268
 
78
269
  👉 Type "exit" or "stop" to shut down.
79
270
  👉 Type "reset" to reset faders to default.
271
+ 👉 Type "save" to force save state (JSON mode).
80
272
  `);
81
273
  } catch (err) {
82
274
  if (err.code === "EADDRNOTAVAIL") {
@@ -118,6 +310,10 @@ const bootstrap = async () => {
118
310
  service.resetState();
119
311
  console.log("✨ Console state reset to defaults.");
120
312
  break;
313
+ case "save":
314
+ await service.saveState();
315
+ console.log("💾 State saved to storage.");
316
+ break;
121
317
  default:
122
318
  if (command) console.log(`Unknown command: "${command}"`);
123
319
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djodjonx/x32-simulator",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "X32 Simulator",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",