@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.
- package/README.md +1170 -10
- package/lib/collectors/DockerLogCollector.js +334 -0
- package/lib/collectors/index.js +9 -0
- package/lib/core/CommandRouter.js +154 -0
- package/lib/core/CorrelationStrategy.js +172 -0
- package/lib/core/LogCollector.js +194 -0
- package/lib/core/LogParser.js +125 -0
- package/lib/core/index.js +26 -0
- package/lib/errors/index.js +363 -0
- package/lib/helpers/EventObserver.js +267 -0
- package/lib/helpers/QueryHelper.js +279 -0
- package/lib/helpers/index.js +13 -0
- package/lib/index.js +72 -1
- package/lib/mineflayer-backend.js +266 -1
- package/lib/monitoring/CircularBuffer.js +202 -0
- package/lib/monitoring/LogMonitor.js +303 -0
- package/lib/monitoring/correlations/TagCorrelationStrategy.js +214 -0
- package/lib/monitoring/correlations/UsernameCorrelationStrategy.js +233 -0
- package/lib/monitoring/correlations/index.js +13 -0
- package/lib/monitoring/index.js +16 -0
- package/lib/parsers/MinecraftLogParser.js +512 -0
- package/lib/parsers/PatternRegistry.js +366 -0
- package/lib/parsers/fixtures/minecraft-logs.js +188 -0
- package/lib/parsers/index.js +13 -0
- package/lib/rcon-backend.js +42 -26
- package/package.json +2 -1
|
@@ -8,6 +8,9 @@ const { PilafBackend } = require('./backend.js');
|
|
|
8
8
|
const { BotPool } = require('./BotPool.js');
|
|
9
9
|
const { BotLifecycleManager } = require('./BotLifecycleManager.js');
|
|
10
10
|
const { ServerHealthChecker } = require('./ServerHealthChecker.js');
|
|
11
|
+
const { RconBackend } = require('./rcon-backend.js');
|
|
12
|
+
const { QueryHelper } = require('./helpers/QueryHelper.js');
|
|
13
|
+
const { EventObserver } = require('./helpers/EventObserver.js');
|
|
11
14
|
|
|
12
15
|
class MineflayerBackend extends PilafBackend {
|
|
13
16
|
/**
|
|
@@ -22,6 +25,9 @@ class MineflayerBackend extends PilafBackend {
|
|
|
22
25
|
this._connectConfig = {};
|
|
23
26
|
this._botPool = new BotPool();
|
|
24
27
|
this._healthChecker = null;
|
|
28
|
+
this._rconBackend = null; // Optional RconBackend for QueryHelper
|
|
29
|
+
this._queryHelper = null;
|
|
30
|
+
this._eventObserver = null;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
/**
|
|
@@ -82,16 +88,36 @@ class MineflayerBackend extends PilafBackend {
|
|
|
82
88
|
rconPassword: config?.rconPassword || 'cavarest'
|
|
83
89
|
});
|
|
84
90
|
|
|
91
|
+
// Create RconBackend if RCON config is provided (for QueryHelper)
|
|
92
|
+
if (config?.rconHost || config?.rconPort || config?.rconPassword) {
|
|
93
|
+
this._rconBackend = new RconBackend();
|
|
94
|
+
await this._rconBackend.connect({
|
|
95
|
+
host: config?.rconHost || this._host,
|
|
96
|
+
port: config?.rconPort || 25575,
|
|
97
|
+
password: config?.rconPassword || 'cavarest'
|
|
98
|
+
});
|
|
99
|
+
// Initialize query helper with the RCON backend
|
|
100
|
+
this._queryHelper = new QueryHelper(this._rconBackend);
|
|
101
|
+
} else {
|
|
102
|
+
this._queryHelper = null;
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
return this;
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
/**
|
|
89
|
-
* Disconnect all bots
|
|
109
|
+
* Disconnect all bots and RCON
|
|
90
110
|
* @returns {Promise<void>}
|
|
91
111
|
*/
|
|
92
112
|
async disconnect() {
|
|
93
113
|
const results = await this._botPool.quitAll({ disconnectTimeout: 10000 });
|
|
94
114
|
this._botPool.clear();
|
|
115
|
+
|
|
116
|
+
// Disconnect RCON backend if it was created
|
|
117
|
+
if (this._rconBackend) {
|
|
118
|
+
await this._rconBackend.disconnect();
|
|
119
|
+
this._rconBackend = null;
|
|
120
|
+
}
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
/**
|
|
@@ -201,6 +227,245 @@ class MineflayerBackend extends PilafBackend {
|
|
|
201
227
|
return this._botPool;
|
|
202
228
|
}
|
|
203
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Get query helper instance
|
|
232
|
+
* @returns {QueryHelper}
|
|
233
|
+
*/
|
|
234
|
+
getQueryHelper() {
|
|
235
|
+
return this._queryHelper;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get event observer instance
|
|
240
|
+
* @returns {EventObserver}
|
|
241
|
+
*/
|
|
242
|
+
getEventObserver() {
|
|
243
|
+
return this._eventObserver;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ==========================================================================
|
|
247
|
+
// QUERY HELPER METHODS
|
|
248
|
+
// ==========================================================================
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get player information via RCON
|
|
252
|
+
* @param {string} username - Player username
|
|
253
|
+
* @returns {Promise<Object>} Player information
|
|
254
|
+
*/
|
|
255
|
+
async getPlayerInfo(username) {
|
|
256
|
+
if (!this._queryHelper) {
|
|
257
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
258
|
+
}
|
|
259
|
+
return await this._queryHelper.getPlayerInfo(username);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* List all online players
|
|
264
|
+
* @returns {Promise<Object>} List of players
|
|
265
|
+
*/
|
|
266
|
+
async listPlayers() {
|
|
267
|
+
if (!this._queryHelper) {
|
|
268
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
269
|
+
}
|
|
270
|
+
return await this._queryHelper.listPlayers();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get current world time
|
|
275
|
+
* @returns {Promise<Object>} World time information
|
|
276
|
+
*/
|
|
277
|
+
async getWorldTime() {
|
|
278
|
+
if (!this._queryHelper) {
|
|
279
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
280
|
+
}
|
|
281
|
+
return await this._queryHelper.getWorldTime();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get current weather
|
|
286
|
+
* @returns {Promise<Object>} Weather information
|
|
287
|
+
*/
|
|
288
|
+
async getWeather() {
|
|
289
|
+
if (!this._queryHelper) {
|
|
290
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
291
|
+
}
|
|
292
|
+
return await this._queryHelper.getWeather();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get difficulty level
|
|
297
|
+
* @returns {Promise<Object>} Difficulty information
|
|
298
|
+
*/
|
|
299
|
+
async getDifficulty() {
|
|
300
|
+
if (!this._queryHelper) {
|
|
301
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
302
|
+
}
|
|
303
|
+
return await this._queryHelper.getDifficulty();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get game mode
|
|
308
|
+
* @param {string} [player='@s'] - Target player selector
|
|
309
|
+
* @returns {Promise<Object>} Game mode information
|
|
310
|
+
*/
|
|
311
|
+
async getGameMode(player = '@s') {
|
|
312
|
+
if (!this._queryHelper) {
|
|
313
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
314
|
+
}
|
|
315
|
+
return await this._queryHelper.getGameMode(player);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get server TPS
|
|
320
|
+
* @returns {Promise<Object>} TPS information
|
|
321
|
+
*/
|
|
322
|
+
async getTPS() {
|
|
323
|
+
if (!this._queryHelper) {
|
|
324
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
325
|
+
}
|
|
326
|
+
return await this._queryHelper.getTPS();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get world seed
|
|
331
|
+
* @returns {Promise<Object>} Seed information
|
|
332
|
+
*/
|
|
333
|
+
async getSeed() {
|
|
334
|
+
if (!this._queryHelper) {
|
|
335
|
+
throw new Error('Backend not connected. Call connect() first.');
|
|
336
|
+
}
|
|
337
|
+
return await this._queryHelper.getSeed();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ==========================================================================
|
|
341
|
+
// EVENT OBSERVATION METHODS
|
|
342
|
+
// ==========================================================================
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Subscribe to server events
|
|
346
|
+
* @param {string} pattern - Event pattern (supports wildcards)
|
|
347
|
+
* @param {Function} callback - Event callback
|
|
348
|
+
* @returns {Function} Unsubscribe function
|
|
349
|
+
*/
|
|
350
|
+
onEvent(pattern, callback) {
|
|
351
|
+
this._ensureEventObserver();
|
|
352
|
+
return this._eventObserver.onEvent(pattern, callback);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Subscribe to player join events
|
|
357
|
+
* @param {Function} callback - Event callback
|
|
358
|
+
* @returns {Function} Unsubscribe function
|
|
359
|
+
*/
|
|
360
|
+
onPlayerJoin(callback) {
|
|
361
|
+
this._ensureEventObserver();
|
|
362
|
+
return this._eventObserver.onPlayerJoin(callback);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Subscribe to player leave events
|
|
367
|
+
* @param {Function} callback - Event callback
|
|
368
|
+
* @returns {Function} Unsubscribe function
|
|
369
|
+
*/
|
|
370
|
+
onPlayerLeave(callback) {
|
|
371
|
+
this._ensureEventObserver();
|
|
372
|
+
return this._eventObserver.onPlayerLeave(callback);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Subscribe to player death events
|
|
377
|
+
* @param {Function} callback - Event callback
|
|
378
|
+
* @returns {Function} Unsubscribe function
|
|
379
|
+
*/
|
|
380
|
+
onPlayerDeath(callback) {
|
|
381
|
+
this._ensureEventObserver();
|
|
382
|
+
return this._eventObserver.onPlayerDeath(callback);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Subscribe to command events
|
|
387
|
+
* @param {Function} callback - Event callback
|
|
388
|
+
* @returns {Function} Unsubscribe function
|
|
389
|
+
*/
|
|
390
|
+
onCommand(callback) {
|
|
391
|
+
this._ensureEventObserver();
|
|
392
|
+
return this._eventObserver.onCommand(callback);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Subscribe to world events
|
|
397
|
+
* @param {Function} callback - Event callback
|
|
398
|
+
* @returns {Function} Unsubscribe function
|
|
399
|
+
*/
|
|
400
|
+
onWorldEvent(callback) {
|
|
401
|
+
this._ensureEventObserver();
|
|
402
|
+
return this._eventObserver.onWorldEvent(callback);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Start observing server events
|
|
407
|
+
* @returns {Promise<void>}
|
|
408
|
+
*/
|
|
409
|
+
async observe() {
|
|
410
|
+
this._ensureEventObserver();
|
|
411
|
+
await this._eventObserver.start();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Stop observing server events
|
|
416
|
+
* @returns {void}
|
|
417
|
+
*/
|
|
418
|
+
unobserve() {
|
|
419
|
+
if (this._eventObserver) {
|
|
420
|
+
this._eventObserver.stop();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Check if currently observing events
|
|
426
|
+
* @returns {boolean}
|
|
427
|
+
*/
|
|
428
|
+
isObserving() {
|
|
429
|
+
return this._eventObserver && this._eventObserver.isObserving;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Ensure event observer is initialized
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
_ensureEventObserver() {
|
|
437
|
+
if (!this._eventObserver) {
|
|
438
|
+
// Lazy initialization - requires LogMonitor and parser
|
|
439
|
+
const { LogMonitor } = require('./monitoring/index.js');
|
|
440
|
+
const { MinecraftLogParser } = require('./parsers/index.js');
|
|
441
|
+
const { DockerLogCollector } = require('./collectors/index.js');
|
|
442
|
+
const { UsernameCorrelationStrategy } = require('./monitoring/correlations/index.js');
|
|
443
|
+
|
|
444
|
+
// Create log collector for this server
|
|
445
|
+
const collector = new DockerLogCollector({
|
|
446
|
+
container: this._host, // Use server host as container name
|
|
447
|
+
follow: true
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Create parser
|
|
451
|
+
const parser = new MinecraftLogParser();
|
|
452
|
+
|
|
453
|
+
// Create log monitor
|
|
454
|
+
const logMonitor = new LogMonitor({
|
|
455
|
+
collector,
|
|
456
|
+
parser,
|
|
457
|
+
correlation: new UsernameCorrelationStrategy(),
|
|
458
|
+
bufferSize: 1000
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Create event observer
|
|
462
|
+
this._eventObserver = new EventObserver({
|
|
463
|
+
logMonitor,
|
|
464
|
+
parser
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
204
469
|
/**
|
|
205
470
|
* Get entities from all bots
|
|
206
471
|
* @param {string} selector - Entity selector (optional, unused in Mineflayer)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CircularBuffer - Fixed-size circular buffer implementation
|
|
3
|
+
*
|
|
4
|
+
* A circular buffer is a fixed-size data structure that uses a single,
|
|
5
|
+
* contiguous block of memory. When the buffer is full, new data overwrites
|
|
6
|
+
* the oldest data.
|
|
7
|
+
*
|
|
8
|
+
* This provides O(1) time complexity for push and pop operations,
|
|
9
|
+
* making it ideal for streaming data and log monitoring scenarios.
|
|
10
|
+
*
|
|
11
|
+
* Usage Example:
|
|
12
|
+
* const buffer = new CircularBuffer(3);
|
|
13
|
+
* buffer.push(1); // [1]
|
|
14
|
+
* buffer.push(2); // [1, 2]
|
|
15
|
+
* buffer.push(3); // [1, 2, 3]
|
|
16
|
+
* buffer.push(4); // [2, 3, 4] - 1 was overwritten
|
|
17
|
+
* buffer.pop(); // Returns 2, buffer is [3, 4]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* CircularBuffer class for efficient fixed-size buffer operations
|
|
22
|
+
*/
|
|
23
|
+
class CircularBuffer {
|
|
24
|
+
/**
|
|
25
|
+
* Create a CircularBuffer
|
|
26
|
+
*
|
|
27
|
+
* @param {number} capacity - Maximum number of elements (must be > 0)
|
|
28
|
+
* @throws {Error} If capacity is not a positive integer
|
|
29
|
+
*/
|
|
30
|
+
constructor(capacity) {
|
|
31
|
+
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
32
|
+
throw new Error(`Capacity must be a positive integer, got: ${capacity}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Maximum capacity of the buffer
|
|
37
|
+
* @private
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
this._capacity = capacity;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Internal array to store elements
|
|
44
|
+
* @private
|
|
45
|
+
* @type {Array}
|
|
46
|
+
*/
|
|
47
|
+
this._buffer = new Array(capacity);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Index of the oldest element (front of queue)
|
|
51
|
+
* @private
|
|
52
|
+
* @type {number}
|
|
53
|
+
*/
|
|
54
|
+
this._head = 0;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Index where the next element will be inserted (back of queue)
|
|
58
|
+
* @private
|
|
59
|
+
* @type {number}
|
|
60
|
+
*/
|
|
61
|
+
this._tail = 0;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Current number of elements in the buffer
|
|
65
|
+
* @private
|
|
66
|
+
* @type {number}
|
|
67
|
+
*/
|
|
68
|
+
this._size = 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Add a value to the buffer
|
|
73
|
+
*
|
|
74
|
+
* If the buffer is full, the oldest value is removed and returned.
|
|
75
|
+
*
|
|
76
|
+
* @param {*} value - Value to add
|
|
77
|
+
* @returns {*} - The displaced value if buffer was full, undefined otherwise
|
|
78
|
+
*/
|
|
79
|
+
push(value) {
|
|
80
|
+
const displaced = this.isFull ? this._buffer[this._head] : undefined;
|
|
81
|
+
|
|
82
|
+
this._buffer[this._tail] = value;
|
|
83
|
+
this._tail = (this._tail + 1) % this._capacity;
|
|
84
|
+
|
|
85
|
+
if (this.isFull) {
|
|
86
|
+
this._head = (this._head + 1) % this._capacity;
|
|
87
|
+
} else {
|
|
88
|
+
this._size++;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return displaced;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Remove and return the oldest value
|
|
96
|
+
*
|
|
97
|
+
* @returns {*} - The oldest value, or undefined if buffer is empty
|
|
98
|
+
*/
|
|
99
|
+
pop() {
|
|
100
|
+
if (this.isEmpty) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const value = this._buffer[this._head];
|
|
105
|
+
this._buffer[this._head] = undefined; // Clear reference
|
|
106
|
+
this._head = (this._head + 1) % this._capacity;
|
|
107
|
+
this._size--;
|
|
108
|
+
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get value at logical index (0 = oldest, size-1 = newest)
|
|
114
|
+
*
|
|
115
|
+
* @param {number} index - Logical index
|
|
116
|
+
* @returns {*} - Value at index, or undefined if index is out of bounds
|
|
117
|
+
*/
|
|
118
|
+
get(index) {
|
|
119
|
+
if (index < 0 || index >= this._size) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const physicalIndex = (this._head + index) % this._capacity;
|
|
124
|
+
return this._buffer[physicalIndex];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert buffer to a regular array (ordered from oldest to newest)
|
|
129
|
+
*
|
|
130
|
+
* @returns {Array} - Array containing all elements
|
|
131
|
+
*/
|
|
132
|
+
toArray() {
|
|
133
|
+
const result = [];
|
|
134
|
+
for (let i = 0; i < this._size; i++) {
|
|
135
|
+
result.push(this.get(i));
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extract a slice of the buffer
|
|
142
|
+
*
|
|
143
|
+
* @param {number} start - Start index (default: 0)
|
|
144
|
+
* @param {number} end - End index (default: size)
|
|
145
|
+
* @returns {Array} - Array containing elements from start to end-1
|
|
146
|
+
*/
|
|
147
|
+
slice(start = 0, end = this._size) {
|
|
148
|
+
const result = [];
|
|
149
|
+
for (let i = start; i < Math.min(end, this._size); i++) {
|
|
150
|
+
result.push(this.get(i));
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clear all elements from the buffer
|
|
157
|
+
*/
|
|
158
|
+
clear() {
|
|
159
|
+
this._buffer = new Array(this._capacity);
|
|
160
|
+
this._head = 0;
|
|
161
|
+
this._tail = 0;
|
|
162
|
+
this._size = 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the current number of elements
|
|
167
|
+
*
|
|
168
|
+
* @type {number}
|
|
169
|
+
*/
|
|
170
|
+
get size() {
|
|
171
|
+
return this._size;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get the maximum capacity
|
|
176
|
+
*
|
|
177
|
+
* @type {number}
|
|
178
|
+
*/
|
|
179
|
+
get capacity() {
|
|
180
|
+
return this._capacity;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if buffer is empty
|
|
185
|
+
*
|
|
186
|
+
* @type {boolean}
|
|
187
|
+
*/
|
|
188
|
+
get isEmpty() {
|
|
189
|
+
return this._size === 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Check if buffer is full
|
|
194
|
+
*
|
|
195
|
+
* @type {boolean}
|
|
196
|
+
*/
|
|
197
|
+
get isFull() {
|
|
198
|
+
return this._size === this._capacity;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = { CircularBuffer };
|