@pilaf/backends 1.0.2 → 1.2.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.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * LogCollector - Abstract base class for log collection
3
+ *
4
+ * Defines the interface for collecting raw log data from various sources
5
+ * (Docker containers, log files, syslog, etc.). Concrete implementations
6
+ * must extend this class and implement the abstract methods.
7
+ *
8
+ * @abstract
9
+ * @class
10
+ * @extends EventEmitter
11
+ *
12
+ * Responsibilities (MECE):
13
+ * - ONLY: Connect to log source and receive raw log lines
14
+ * - NOT: Parse log content (LogParser's responsibility)
15
+ * - NOT: Correlate responses (CorrelationStrategy's responsibility)
16
+ *
17
+ * Usage Example:
18
+ * // Extend LogCollector and implement abstract methods
19
+ * function DockerLogCollector() {
20
+ * LogCollector.call(this);
21
+ * }
22
+ * DockerLogCollector.prototype = Object.create(LogCollector.prototype);
23
+ * DockerLogCollector.prototype.connect = async function(config) { ... };
24
+ * DockerLogCollector.prototype.disconnect = async function() { ... };
25
+ */
26
+
27
+ const EventEmitter = require('events');
28
+
29
+ class LogCollector extends EventEmitter {
30
+ /**
31
+ * Create a LogCollector
32
+ * @throws {Error} Direct instantiation of abstract class
33
+ */
34
+ constructor() {
35
+ super();
36
+
37
+ // Prevent direct instantiation of abstract class
38
+ if (this.constructor === LogCollector) {
39
+ throw new Error('LogCollector is abstract and cannot be instantiated directly');
40
+ }
41
+
42
+ /**
43
+ * Connection state
44
+ * @protected
45
+ * @type {boolean}
46
+ */
47
+ this._connected = false;
48
+
49
+ /**
50
+ * Pause state
51
+ * @protected
52
+ * @type {boolean}
53
+ */
54
+ this._paused = false;
55
+
56
+ /**
57
+ * Connection configuration
58
+ * @protected
59
+ * @type {Object|null}
60
+ */
61
+ this._config = null;
62
+ }
63
+
64
+ /**
65
+ * Get connection state
66
+ * @returns {boolean}
67
+ */
68
+ get connected() {
69
+ return this._connected;
70
+ }
71
+
72
+ /**
73
+ * Get pause state
74
+ * @returns {boolean}
75
+ */
76
+ get paused() {
77
+ return this._paused;
78
+ }
79
+
80
+ /**
81
+ * Get connection configuration
82
+ * @returns {Object|null}
83
+ */
84
+ get config() {
85
+ return this._config;
86
+ }
87
+
88
+ /**
89
+ * Connect to the log source
90
+ *
91
+ * Abstract method that must be implemented by concrete classes.
92
+ * Should establish connection to the log source and start emitting
93
+ * 'data' events with raw log lines.
94
+ *
95
+ * @abstract
96
+ * @param {Object} config - Connection configuration
97
+ * @param {string} [config.host] - Host address (for remote sources)
98
+ * @param {number} [config.port] - Port number (for remote sources)
99
+ * @param {string} [config.path] - File path (for file sources)
100
+ * @param {string} [config.containerName] - Container name (for Docker)
101
+ * @returns {Promise<void>}
102
+ * @throws {Error} If connection fails
103
+ */
104
+ async connect(config) {
105
+ throw new Error('Method "connect()" must be implemented by subclass');
106
+ }
107
+
108
+ /**
109
+ * Disconnect from the log source
110
+ *
111
+ * Abstract method that must be implemented by concrete classes.
112
+ * Should clean up resources and stop emitting events.
113
+ *
114
+ * @abstract
115
+ * @returns {Promise<void>}
116
+ */
117
+ async disconnect() {
118
+ throw new Error('Method "disconnect()" must be implemented by subclass');
119
+ }
120
+
121
+ /**
122
+ * Pause log collection
123
+ *
124
+ * Stops emitting 'data' events without disconnecting.
125
+ * Collection can be resumed with resume().
126
+ *
127
+ * @returns {void}
128
+ */
129
+ pause() {
130
+ this._paused = true;
131
+ this.emit('paused');
132
+ }
133
+
134
+ /**
135
+ * Resume log collection
136
+ *
137
+ * Resumes emitting 'data' events after pause().
138
+ *
139
+ * @returns {void}
140
+ */
141
+ resume() {
142
+ this._paused = false;
143
+ this.emit('resumed');
144
+ }
145
+
146
+ /**
147
+ * Emit a log line (protected method for subclasses)
148
+ *
149
+ * Emits 'data' event with raw log line if connected and not paused.
150
+ * Subclasses should call this method when they receive log data.
151
+ *
152
+ * @protected
153
+ * @param {string} line - Raw log line
154
+ * @returns {boolean} - True if event was emitted, false if paused/disconnected
155
+ */
156
+ _emitData(line) {
157
+ if (!this._connected || this._paused) {
158
+ return false;
159
+ }
160
+
161
+ this.emit('data', line);
162
+ return true;
163
+ }
164
+
165
+ /**
166
+ * Emit an error (protected method for subclasses)
167
+ *
168
+ * Emits 'error' event. Subclasses should call this method when
169
+ * errors occur during collection.
170
+ *
171
+ * @protected
172
+ * @param {Error} error - The error to emit
173
+ * @returns {void}
174
+ */
175
+ _emitError(error) {
176
+ this.emit('error', error);
177
+ }
178
+
179
+ /**
180
+ * Emit end event (protected method for subclasses)
181
+ *
182
+ * Emits 'end' event when the log stream terminates.
183
+ * Subclasses should call this when the stream ends naturally.
184
+ *
185
+ * @protected
186
+ * @returns {void}
187
+ */
188
+ _emitEnd() {
189
+ this._connected = false;
190
+ this.emit('end');
191
+ }
192
+ }
193
+
194
+ module.exports = { LogCollector };
@@ -0,0 +1,125 @@
1
+ /**
2
+ * LogParser - Abstract base class for log parsing
3
+ *
4
+ * Defines the interface for parsing raw log lines into structured events.
5
+ * Concrete implementations must extend this class and implement the
6
+ * abstract parse() method.
7
+ *
8
+ * @abstract
9
+ * @class
10
+ *
11
+ * Responsibilities (MECE):
12
+ * - ONLY: Parse raw log lines into structured events
13
+ * - NOT: Collect logs (LogCollector's responsibility)
14
+ * - NOT: Emit events to consumers (LogMonitor's responsibility)
15
+ * - NOT: Correlate responses (CorrelationStrategy's responsibility)
16
+ *
17
+ * Usage:
18
+ * class MinecraftLogParser extends LogParser {
19
+ * parse(line) {
20
+ * // Parse and return { type, data, raw } or null
21
+ * }
22
+ * }
23
+ */
24
+
25
+ class LogParser {
26
+ /**
27
+ * Create a LogParser
28
+ * @throws {Error} Direct instantiation of abstract class
29
+ */
30
+ constructor() {
31
+ // Prevent direct instantiation of abstract class
32
+ if (this.constructor === LogParser) {
33
+ throw new Error('LogParser is abstract and cannot be instantiated directly');
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Parse a log line into a structured event
39
+ *
40
+ * Abstract method that must be implemented by concrete classes.
41
+ * Should analyze the log line and extract structured data.
42
+ *
43
+ * @abstract
44
+ * @param {string} line - Raw log line to parse
45
+ * @returns {Object|null} - Parsed event or null if line doesn't match any pattern
46
+ * @property {string} type - Event type (e.g., 'teleport', 'death', 'command')
47
+ * @property {Object} data - Parsed event data (structure varies by type)
48
+ * @property {string} raw - Original raw log line
49
+ * @example
50
+ * // Returns:
51
+ * // {
52
+ * // type: 'teleport',
53
+ * // data: { player: 'TestPlayer', position: { x: 100, y: 64, z: 100 } },
54
+ * // raw: '[12:34:56] Teleported TestPlayer to 100.0, 64.0, 100.0'
55
+ * // }
56
+ */
57
+ parse(line) {
58
+ throw new Error('Method "parse()" must be implemented by subclass');
59
+ }
60
+
61
+ /**
62
+ * Add a parsing pattern (optional method for pattern-based parsers)
63
+ *
64
+ * Default implementation throws. Pattern-based parsers should override.
65
+ *
66
+ * @param {string} name - Pattern name/identifier
67
+ * @param {RegExp|string} pattern - Regex pattern or string pattern
68
+ * @param {Function} handler - Handler function: (match) => data
69
+ * @returns {void}
70
+ * @throws {Error} If not supported by parser implementation
71
+ */
72
+ addPattern(name, pattern, handler) {
73
+ throw new Error('Method "addPattern()" is not supported by this parser');
74
+ }
75
+
76
+ /**
77
+ * Remove a parsing pattern (optional method for pattern-based parsers)
78
+ *
79
+ * Default implementation throws. Pattern-based parsers should override.
80
+ *
81
+ * @param {string} name - Pattern name to remove
82
+ * @returns {boolean} - True if pattern was removed, false if not found
83
+ * @throws {Error} If not supported by parser implementation
84
+ */
85
+ removePattern(name) {
86
+ throw new Error('Method "removePattern()" is not supported by this parser');
87
+ }
88
+
89
+ /**
90
+ * Get all registered patterns (optional method for pattern-based parsers)
91
+ *
92
+ * Default implementation throws. Pattern-based parsers should override.
93
+ *
94
+ * @returns {Array<string>} - Array of pattern names
95
+ * @throws {Error} If not supported by parser implementation
96
+ */
97
+ getPatterns() {
98
+ throw new Error('Method "getPatterns()" is not supported by this parser');
99
+ }
100
+
101
+ /**
102
+ * Validate parse result format
103
+ *
104
+ * Protected method to ensure parse() returns correct format.
105
+ * Used by concrete implementations to validate their output.
106
+ *
107
+ * @protected
108
+ * @param {Object|null} result - Result from parse()
109
+ * @returns {boolean} - True if format is valid
110
+ */
111
+ _validateResult(result) {
112
+ if (result === null) {
113
+ return true; // null is valid (no match)
114
+ }
115
+
116
+ return (
117
+ typeof result === 'object' &&
118
+ typeof result.type === 'string' &&
119
+ typeof result.data === 'object' &&
120
+ typeof result.raw === 'string'
121
+ );
122
+ }
123
+ }
124
+
125
+ module.exports = { LogParser };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Core Abstractions Index
3
+ *
4
+ * This module exports all core abstraction classes for the Pilaf backend system.
5
+ * These abstractions define the interfaces that concrete implementations must follow.
6
+ *
7
+ * @module core/index
8
+ */
9
+
10
+ const { LogCollector } = require('./LogCollector.js');
11
+ const { LogParser } = require('./LogParser.js');
12
+ const { CommandRouter } = require('./CommandRouter.js');
13
+ const { CorrelationStrategy } = require('./CorrelationStrategy.js');
14
+
15
+ module.exports = {
16
+ // Abstract base classes
17
+ LogCollector,
18
+ LogParser,
19
+ CommandRouter,
20
+ CorrelationStrategy,
21
+
22
+ // Factory functions (implemented by concrete modules)
23
+ collectors: null, // Will be set by collectors/index.js
24
+ parsers: null, // Will be set by parsers/index.js
25
+ strategies: null // Will be set by strategies/index.js
26
+ };
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Pilaf Error Hierarchy
3
+ *
4
+ * Defines all error types used throughout the Pilaf backend system.
5
+ * Follows a hierarchical structure with machine-readable codes and
6
+ * human-readable messages.
7
+ *
8
+ * Error Hierarchy:
9
+ * PilafError (base)
10
+ * ├── ConnectionError
11
+ * │ ├── RconConnectionError
12
+ * │ ├── DockerConnectionError
13
+ * │ └── FileAccessError
14
+ * ├── CommandExecutionError
15
+ * │ ├── CommandTimeoutError
16
+ * │ └── CommandRejectedError
17
+ * ├── ParseError
18
+ * │ ├── MalformedLogError
19
+ * │ └── UnknownPatternError
20
+ * ├── CorrelationError
21
+ * │ ├── ResponseTimeoutError
22
+ * │ └── AmbiguousMatchError
23
+ * └── ResourceError
24
+ * ├── BufferOverflowError
25
+ * └── HandleExhaustedError
26
+ */
27
+
28
+ /**
29
+ * Base Pilaf Error
30
+ *
31
+ * All Pilaf errors extend from this class.
32
+ *
33
+ * @class
34
+ * @extends Error
35
+ * @property {string} code - Machine-readable error code
36
+ * @property {string} message - Human-readable error message
37
+ * @property {Object} details - Additional debugging context
38
+ * @property {Error} [cause] - Original error that caused this error
39
+ */
40
+ class PilafError extends Error {
41
+ /**
42
+ * Create a PilafError
43
+ * @param {string} code - Machine-readable error code (e.g., 'CONNECTION_FAILED')
44
+ * @param {string} message - Human-readable error message
45
+ * @param {Object} [details={}] - Additional debugging context
46
+ * @param {Error} [cause] - Original error that caused this error
47
+ */
48
+ constructor(code, message, details = {}, cause = null) {
49
+ super(message);
50
+
51
+ /**
52
+ * Error name (class name)
53
+ * @type {string}
54
+ */
55
+ this.name = this.constructor.name;
56
+
57
+ /**
58
+ * Machine-readable error code
59
+ * @type {string}
60
+ */
61
+ this.code = code;
62
+
63
+ /**
64
+ * Human-readable message
65
+ * @type {string}
66
+ */
67
+ this.message = message;
68
+
69
+ /**
70
+ * Additional debugging context
71
+ * @type {Object}
72
+ */
73
+ this.details = details;
74
+
75
+ /**
76
+ * Original cause (if any)
77
+ * @type {Error|null}
78
+ */
79
+ this.cause = cause;
80
+
81
+ // Maintain proper stack trace
82
+ if (Error.captureStackTrace) {
83
+ Error.captureStackTrace(this, this.constructor);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Convert error to JSON for logging/serialization
89
+ * @returns {Object} - JSON representation of error
90
+ */
91
+ toJSON() {
92
+ return {
93
+ name: this.name,
94
+ code: this.code,
95
+ message: this.message,
96
+ details: this.details,
97
+ cause: this.cause ? {
98
+ name: this.cause.name,
99
+ message: this.cause.message,
100
+ stack: this.cause.stack
101
+ } : null,
102
+ stack: this.stack
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Pretty-print error for debugging
108
+ * @returns {string} - Formatted error string
109
+ */
110
+ toString() {
111
+ let output = `[${this.code}] ${this.message}`;
112
+
113
+ if (Object.keys(this.details).length > 0) {
114
+ output += `\nDetails: ${JSON.stringify(this.details, null, 2)}`;
115
+ }
116
+
117
+ if (this.cause) {
118
+ output += `\nCaused by: ${this.cause.message}`;
119
+ }
120
+
121
+ return output;
122
+ }
123
+ }
124
+
125
+ // ============================================================================
126
+ // CONNECTION ERRORS
127
+ // ============================================================================
128
+
129
+ /**
130
+ * Base class for connection-related errors
131
+ */
132
+ class ConnectionError extends PilafError {
133
+ constructor(message, details = {}, cause = null) {
134
+ super('CONNECTION_ERROR', message, details, cause);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * RCON connection failure
140
+ */
141
+ class RconConnectionError extends ConnectionError {
142
+ constructor(message, details = {}, cause = null) {
143
+ super(message, { ...details, component: 'RCON' }, cause);
144
+ this.code = 'RCON_CONNECTION_ERROR';
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Docker API connection failure
150
+ */
151
+ class DockerConnectionError extends ConnectionError {
152
+ constructor(message, details = {}, cause = null) {
153
+ super(message, { ...details, component: 'Docker' }, cause);
154
+ this.code = 'DOCKER_CONNECTION_ERROR';
155
+ }
156
+ }
157
+
158
+ /**
159
+ * File access failure
160
+ */
161
+ class FileAccessError extends ConnectionError {
162
+ constructor(message, details = {}, cause = null) {
163
+ super(message, { ...details, component: 'FileSystem' }, cause);
164
+ this.code = 'FILE_ACCESS_ERROR';
165
+ }
166
+ }
167
+
168
+ // ============================================================================
169
+ // COMMAND EXECUTION ERRORS
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Base class for command execution errors
174
+ */
175
+ class CommandExecutionError extends PilafError {
176
+ constructor(message, details = {}, cause = null) {
177
+ super('COMMAND_ERROR', message, details, cause);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Command execution timeout
183
+ */
184
+ class CommandTimeoutError extends CommandExecutionError {
185
+ constructor(command, timeout, details = {}) {
186
+ super(
187
+ `Command timed out after ${timeout}ms`,
188
+ { ...details, command, timeout },
189
+ null
190
+ );
191
+ this.code = 'COMMAND_TIMEOUT';
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Command rejected by server
197
+ */
198
+ class CommandRejectedError extends CommandExecutionError {
199
+ constructor(command, reason, details = {}) {
200
+ super(
201
+ `Command rejected by server: ${reason}`,
202
+ { ...details, command, reason },
203
+ null
204
+ );
205
+ this.code = 'COMMAND_REJECTED';
206
+ }
207
+ }
208
+
209
+ // ============================================================================
210
+ // PARSING ERRORS
211
+ // ============================================================================
212
+
213
+ /**
214
+ * Base class for parsing errors
215
+ */
216
+ class ParseError extends PilafError {
217
+ constructor(message, details = {}, cause = null) {
218
+ super('PARSE_ERROR', message, details, cause);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Malformed log line
224
+ */
225
+ class MalformedLogError extends ParseError {
226
+ constructor(line, reason, details = {}) {
227
+ super(
228
+ `Malformed log line: ${reason}`,
229
+ { ...details, line },
230
+ null
231
+ );
232
+ this.code = 'MALFORMED_LOG';
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Unknown log pattern
238
+ */
239
+ class UnknownPatternError extends ParseError {
240
+ constructor(line, details = {}) {
241
+ super(
242
+ 'Log line does not match any known pattern',
243
+ { ...details, line },
244
+ null
245
+ );
246
+ this.code = 'UNKNOWN_PATTERN';
247
+ }
248
+ }
249
+
250
+ // ============================================================================
251
+ // CORRELATION ERRORS
252
+ // ============================================================================
253
+
254
+ /**
255
+ * Base class for correlation errors
256
+ */
257
+ class CorrelationError extends PilafError {
258
+ constructor(message, details = {}, cause = null) {
259
+ super('CORRELATION_ERROR', message, details, cause);
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Response correlation timeout
265
+ */
266
+ class ResponseTimeoutError extends CorrelationError {
267
+ constructor(command, timeout, details = {}) {
268
+ // Call parent with generic message, then override code
269
+ super(`No response received within ${timeout}ms`, { ...details, command, timeout }, null);
270
+ // Override the parent's code
271
+ this.code = 'CORRELATION_TIMEOUT';
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Ambiguous match (multiple potential responses)
277
+ */
278
+ class AmbiguousMatchError extends CorrelationError {
279
+ constructor(command, matches, details = {}) {
280
+ super(
281
+ `${matches.length} potential responses found for command`,
282
+ { ...details, command, matchCount: matches.length },
283
+ null
284
+ );
285
+ this.code = 'AMBIGUOUS_MATCH';
286
+ }
287
+ }
288
+
289
+ // ============================================================================
290
+ // RESOURCE ERRORS
291
+ // ============================================================================
292
+
293
+ /**
294
+ * Base class for resource-related errors
295
+ */
296
+ class ResourceError extends PilafError {
297
+ constructor(message, details = {}, cause = null) {
298
+ super('RESOURCE_ERROR', message, details, cause);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Buffer overflow
304
+ */
305
+ class BufferOverflowError extends ResourceError {
306
+ constructor(size, limit, details = {}) {
307
+ super(
308
+ `Buffer size (${size}) exceeds limit (${limit})`,
309
+ { ...details, size, limit },
310
+ null
311
+ );
312
+ this.code = 'BUFFER_OVERFLOW';
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Handle/file descriptor exhaustion
318
+ */
319
+ class HandleExhaustedError extends ResourceError {
320
+ constructor(resourceType, details = {}) {
321
+ super(
322
+ `Unable to allocate ${resourceType} handle`,
323
+ { ...details, resourceType },
324
+ null
325
+ );
326
+ this.code = 'HANDLE_EXHAUSTED';
327
+ }
328
+ }
329
+
330
+ // ============================================================================
331
+ // EXPORTS
332
+ // ============================================================================
333
+
334
+ module.exports = {
335
+ // Base error
336
+ PilafError,
337
+
338
+ // Connection errors
339
+ ConnectionError,
340
+ RconConnectionError,
341
+ DockerConnectionError,
342
+ FileAccessError,
343
+
344
+ // Command errors
345
+ CommandExecutionError,
346
+ CommandTimeoutError,
347
+ CommandRejectedError,
348
+
349
+ // Parse errors
350
+ ParseError,
351
+ MalformedLogError,
352
+ UnknownPatternError,
353
+
354
+ // Correlation errors
355
+ CorrelationError,
356
+ ResponseTimeoutError,
357
+ AmbiguousMatchError,
358
+
359
+ // Resource errors
360
+ ResourceError,
361
+ BufferOverflowError,
362
+ HandleExhaustedError
363
+ };