@djodjonx/x32-simulator 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.
- package/.commitlintrc.json +3 -0
- package/.github/workflows/publish.yml +38 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.oxlintrc.json +56 -0
- package/CHANGELOG.md +11 -0
- package/INSTALL.md +107 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/UdpNetworkGateway-BrroQ6-Q.mjs +1189 -0
- package/dist/UdpNetworkGateway-Ccdd7Us5.cjs +1265 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +207 -0
- package/dist/index.d.mts +207 -0
- package/dist/index.mjs +3 -0
- package/dist/server.cjs +1060 -0
- package/dist/server.d.cts +10 -0
- package/dist/server.d.mts +10 -0
- package/dist/server.mjs +1055 -0
- package/docs/OSC-Communication.md +184 -0
- package/docs/X32-INTERNAL.md +262 -0
- package/docs/X32-OSC.pdf +0 -0
- package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
- package/package.json +68 -0
- package/src/application/use-cases/BroadcastUpdatesUseCase.ts +120 -0
- package/src/application/use-cases/ManageSessionsUseCase.ts +9 -0
- package/src/application/use-cases/ProcessPacketUseCase.ts +26 -0
- package/src/application/use-cases/SimulationService.ts +122 -0
- package/src/domain/entities/SubscriptionManager.ts +126 -0
- package/src/domain/entities/X32State.ts +78 -0
- package/src/domain/models/MeterConfig.ts +22 -0
- package/src/domain/models/MeterData.ts +59 -0
- package/src/domain/models/OscMessage.ts +93 -0
- package/src/domain/models/X32Address.ts +78 -0
- package/src/domain/models/X32Node.ts +43 -0
- package/src/domain/models/types.ts +96 -0
- package/src/domain/ports/ILogger.ts +27 -0
- package/src/domain/ports/INetworkGateway.ts +8 -0
- package/src/domain/ports/IStateRepository.ts +16 -0
- package/src/domain/services/MeterService.ts +46 -0
- package/src/domain/services/OscMessageHandler.ts +88 -0
- package/src/domain/services/SchemaFactory.ts +308 -0
- package/src/domain/services/SchemaRegistry.ts +67 -0
- package/src/domain/services/StaticResponseService.ts +52 -0
- package/src/domain/services/strategies/BatchStrategy.ts +74 -0
- package/src/domain/services/strategies/MeterStrategy.ts +45 -0
- package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +36 -0
- package/src/domain/services/strategies/OscCommandStrategy.ts +22 -0
- package/src/domain/services/strategies/StateAccessStrategy.ts +71 -0
- package/src/domain/services/strategies/StaticResponseStrategy.ts +42 -0
- package/src/domain/services/strategies/SubscriptionStrategy.ts +56 -0
- package/src/infrastructure/mappers/OscCodec.ts +54 -0
- package/src/infrastructure/repositories/InMemoryStateRepository.ts +21 -0
- package/src/infrastructure/services/ConsoleLogger.ts +177 -0
- package/src/infrastructure/services/UdpNetworkGateway.ts +71 -0
- package/src/presentation/cli/server.ts +194 -0
- package/src/presentation/library/library.ts +9 -0
- package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +104 -0
- package/tests/application/use-cases/ManageSessionsUseCase.test.ts +12 -0
- package/tests/application/use-cases/ProcessPacketUseCase.test.ts +49 -0
- package/tests/application/use-cases/SimulationService.test.ts +77 -0
- package/tests/domain/entities/SubscriptionManager.test.ts +50 -0
- package/tests/domain/entities/X32State.test.ts +52 -0
- package/tests/domain/models/MeterData.test.ts +23 -0
- package/tests/domain/models/OscMessage.test.ts +38 -0
- package/tests/domain/models/X32Address.test.ts +30 -0
- package/tests/domain/models/X32Node.test.ts +30 -0
- package/tests/domain/services/MeterService.test.ts +27 -0
- package/tests/domain/services/OscMessageHandler.test.ts +51 -0
- package/tests/domain/services/SchemaRegistry.test.ts +47 -0
- package/tests/domain/services/StaticResponseService.test.ts +15 -0
- package/tests/domain/services/strategies/BatchStrategy.test.ts +41 -0
- package/tests/domain/services/strategies/MeterStrategy.test.ts +19 -0
- package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +22 -0
- package/tests/domain/services/strategies/StateAccessStrategy.test.ts +49 -0
- package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +15 -0
- package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +45 -0
- package/tests/infrastructure/mappers/OscCodec.test.ts +41 -0
- package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +29 -0
- package/tests/infrastructure/services/ConsoleLogger.test.ts +74 -0
- package/tests/infrastructure/services/UdpNetworkGateway.test.ts +61 -0
- package/tests/presentation/cli/server.test.ts +178 -0
- package/tests/presentation/library/library.test.ts +13 -0
- package/tsconfig.json +21 -0
- package/tsdown.config.ts +15 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
serviceInstance: {
|
|
6
|
+
start: vi.fn().mockResolvedValue(undefined),
|
|
7
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
8
|
+
resetState: vi.fn()
|
|
9
|
+
},
|
|
10
|
+
rl: {
|
|
11
|
+
on: vi.fn(),
|
|
12
|
+
close: vi.fn()
|
|
13
|
+
}
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Mock fs
|
|
17
|
+
vi.mock('fs', () => ({
|
|
18
|
+
existsSync: vi.fn(),
|
|
19
|
+
readFileSync: vi.fn()
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
// Mock SimulationService
|
|
23
|
+
vi.mock('../../../src/application/use-cases/SimulationService', () => {
|
|
24
|
+
const MockService = vi.fn();
|
|
25
|
+
MockService.prototype.start = mocks.serviceInstance.start;
|
|
26
|
+
MockService.prototype.stop = mocks.serviceInstance.stop;
|
|
27
|
+
MockService.prototype.resetState = mocks.serviceInstance.resetState;
|
|
28
|
+
return {
|
|
29
|
+
SimulationService: MockService
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Mock ConsoleLogger
|
|
34
|
+
vi.mock('../../../src/infrastructure/services/ConsoleLogger', () => ({
|
|
35
|
+
ConsoleLogger: {
|
|
36
|
+
getInstance: vi.fn().mockReturnValue({ setLevel: vi.fn() })
|
|
37
|
+
},
|
|
38
|
+
LogLevel: { DEBUG: 0 }
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// Mock readline
|
|
42
|
+
vi.mock('readline', () => ({
|
|
43
|
+
createInterface: vi.fn().mockReturnValue(mocks.rl)
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
import * as Server from '../../../src/presentation/cli/server';
|
|
47
|
+
import { SimulationService } from '../../../src/application/use-cases/SimulationService';
|
|
48
|
+
|
|
49
|
+
describe('CLI Server', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.clearAllMocks();
|
|
52
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
53
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
54
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
55
|
+
vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
|
56
|
+
vi.spyOn(process, 'on').mockImplementation(() => process);
|
|
57
|
+
|
|
58
|
+
// Reset specific mock implementations if needed
|
|
59
|
+
mocks.serviceInstance.start.mockResolvedValue(undefined);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
vi.restoreAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('parseArgs', () => {
|
|
67
|
+
it('should parse port and host flags', () => {
|
|
68
|
+
const args = ['node', 'server.ts', '--port', '1234', '--host', '1.2.3.4'];
|
|
69
|
+
const result = Server.parseArgs(args);
|
|
70
|
+
expect(result.PORT).toBe(1234);
|
|
71
|
+
expect(result.HOST).toBe('1.2.3.4');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should parse short flags', () => {
|
|
75
|
+
const args = ['node', 'server.ts', '-p', '1234', '-h', '1.2.3.4'];
|
|
76
|
+
const result = Server.parseArgs(args);
|
|
77
|
+
expect(result.PORT).toBe(1234);
|
|
78
|
+
expect(result.HOST).toBe('1.2.3.4');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should parse positional arguments', () => {
|
|
82
|
+
const args = ['node', 'server.ts', '1.2.3.4', '1234'];
|
|
83
|
+
const result = Server.parseArgs(args);
|
|
84
|
+
expect(result.PORT).toBe(1234);
|
|
85
|
+
expect(result.HOST).toBe('1.2.3.4');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should use defaults', () => {
|
|
89
|
+
const args = ['node', 'server.ts'];
|
|
90
|
+
const result = Server.parseArgs(args);
|
|
91
|
+
expect(result.PORT).toBe(10023);
|
|
92
|
+
expect(result.HOST).toBe('0.0.0.0');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('loadEnv', () => {
|
|
97
|
+
it('should load .env file', () => {
|
|
98
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
99
|
+
vi.mocked(fs.readFileSync).mockReturnValue('X32_PORT=9999\nX32_IP=10.0.0.1');
|
|
100
|
+
|
|
101
|
+
Server.loadEnv();
|
|
102
|
+
|
|
103
|
+
expect(process.env.X32_PORT).toBe('9999');
|
|
104
|
+
expect(process.env.X32_IP).toBe('10.0.0.1');
|
|
105
|
+
|
|
106
|
+
delete process.env.X32_PORT;
|
|
107
|
+
delete process.env.X32_IP;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle errors gracefully', () => {
|
|
111
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
112
|
+
vi.mocked(fs.readFileSync).mockImplementation(() => { throw new Error('Fail'); });
|
|
113
|
+
|
|
114
|
+
Server.loadEnv();
|
|
115
|
+
|
|
116
|
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to load'), expect.any(String));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('bootstrap', () => {
|
|
121
|
+
it('should start simulation service', async () => {
|
|
122
|
+
await Server.bootstrap();
|
|
123
|
+
expect(SimulationService).toHaveBeenCalled();
|
|
124
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('X32 SIMULATION SERVER ACTIVE'));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle EADDRNOTAVAIL error', async () => {
|
|
128
|
+
const error = new Error('Bind failed');
|
|
129
|
+
(error as any).code = 'EADDRNOTAVAIL';
|
|
130
|
+
mocks.serviceInstance.start.mockRejectedValueOnce(error);
|
|
131
|
+
|
|
132
|
+
// Mock platform
|
|
133
|
+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
134
|
+
|
|
135
|
+
await Server.bootstrap();
|
|
136
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('FAILED TO BIND'));
|
|
137
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle generic startup errors', async () => {
|
|
141
|
+
mocks.serviceInstance.start.mockRejectedValueOnce(new Error('Generic fail'));
|
|
142
|
+
await Server.bootstrap();
|
|
143
|
+
expect(console.error).toHaveBeenCalledWith('Failed to start server:', expect.any(Error));
|
|
144
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle CLI commands', async () => {
|
|
148
|
+
await Server.bootstrap();
|
|
149
|
+
|
|
150
|
+
const lineHandler = mocks.rl.on.mock.calls.find(call => call[0] === 'line')?.[1];
|
|
151
|
+
expect(lineHandler).toBeDefined();
|
|
152
|
+
|
|
153
|
+
await lineHandler('reset');
|
|
154
|
+
expect(mocks.serviceInstance.resetState).toHaveBeenCalled();
|
|
155
|
+
|
|
156
|
+
await lineHandler('stop');
|
|
157
|
+
expect(mocks.serviceInstance.stop).toHaveBeenCalled();
|
|
158
|
+
expect(process.exit).toHaveBeenCalledWith(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle unknown CLI commands', async () => {
|
|
162
|
+
await Server.bootstrap();
|
|
163
|
+
const lineHandler = mocks.rl.on.mock.calls.find(call => call[0] === 'line')?.[1];
|
|
164
|
+
await lineHandler('unknown');
|
|
165
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Unknown command'));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should handle SIGINT', async () => {
|
|
169
|
+
await Server.bootstrap();
|
|
170
|
+
const sigintHandler = vi.mocked(process.on).mock.calls.find(call => call[0] === 'SIGINT')?.[1] as Function;
|
|
171
|
+
expect(sigintHandler).toBeDefined();
|
|
172
|
+
|
|
173
|
+
await sigintHandler();
|
|
174
|
+
expect(mocks.serviceInstance.stop).toHaveBeenCalled();
|
|
175
|
+
expect(process.exit).toHaveBeenCalledWith(0);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as Lib from '../../../src/presentation/library/library';
|
|
3
|
+
|
|
4
|
+
describe('Library Entry', () => {
|
|
5
|
+
it('should export all public members', () => {
|
|
6
|
+
expect(Lib.SimulationService).toBeDefined();
|
|
7
|
+
expect(Lib.ConsoleLogger).toBeDefined();
|
|
8
|
+
expect(Lib.UdpNetworkGateway).toBeDefined();
|
|
9
|
+
expect(Lib.InMemoryStateRepository).toBeDefined();
|
|
10
|
+
expect(Lib.LogCategory).toBeDefined();
|
|
11
|
+
// Types aren't runtime values, so we can't check them with toBeDefined(), but the import * works.
|
|
12
|
+
});
|
|
13
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ESNext", "DOM"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"outDir": "dist",
|
|
14
|
+
"baseUrl": ".",
|
|
15
|
+
"paths": {
|
|
16
|
+
"@/*": ["src/*"]
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*", "tests/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist"]
|
|
21
|
+
}
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'tsdown';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
clean: true,
|
|
5
|
+
dts: true,
|
|
6
|
+
entry: {
|
|
7
|
+
index: 'src/presentation/library/library.ts',
|
|
8
|
+
server: 'src/presentation/cli/server.ts',
|
|
9
|
+
},
|
|
10
|
+
format: ['cjs', 'esm'],
|
|
11
|
+
platform: 'node',
|
|
12
|
+
// Bundle all dependencies
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
noExternal: [/(.*)/],
|
|
15
|
+
});
|