@andrew_l/binlog 0.2.14
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/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/index.cjs +260 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +120 -0
- package/dist/index.d.mts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.mjs +252 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Andrew L. <andrew.io.dev@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Binlog
|
|
2
|
+
|
|
3
|
+
 <!-- omit in toc -->
|
|
4
|
+
 <!-- omit in toc -->
|
|
5
|
+
 <!-- omit in toc -->
|
|
6
|
+
|
|
7
|
+
A high-performance binary logging system for Node.js applications, inspired by VK's KPHP KDB binlog implementation. This library provides a robust, efficient way to record and replay binary log entries with integrity checking and rotation support.
|
|
8
|
+
|
|
9
|
+
## 📋 Features
|
|
10
|
+
|
|
11
|
+
- **Automatic log rotation** based on file size
|
|
12
|
+
- **TL serialization** integration for complex data types
|
|
13
|
+
- **Data integrity** verification via CRC32 checksums
|
|
14
|
+
- **File system sync control** for durability vs performance tradeoffs
|
|
15
|
+
|
|
16
|
+
<!-- install placeholder -->
|
|
17
|
+
|
|
18
|
+
## 🔧 Usage
|
|
19
|
+
|
|
20
|
+
### Basic Example
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createBinlog } from '@andrew_l/binlog';
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
// Create a binlog with default options
|
|
27
|
+
const binlog = createBinlog({
|
|
28
|
+
path: './logs/app-{index}.bin',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Initialize binlog system
|
|
32
|
+
await binlog.init();
|
|
33
|
+
|
|
34
|
+
// Open the binlog for writing
|
|
35
|
+
await binlog.open();
|
|
36
|
+
|
|
37
|
+
// Write entries with various opcodes and data
|
|
38
|
+
await binlog.write(1, { message: 'Application started' });
|
|
39
|
+
await binlog.write(2, { user: 'john', action: 'login' });
|
|
40
|
+
|
|
41
|
+
// Close the binlog when done
|
|
42
|
+
await binlog.close();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main().catch(console.error);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Reading Binlog Entries
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { createBinlog } from '@andrew_l/binlog';
|
|
52
|
+
|
|
53
|
+
async function readLogs() {
|
|
54
|
+
const binlog = createBinlog({
|
|
55
|
+
path: './logs/app-{index}.bin',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await binlog.init();
|
|
59
|
+
|
|
60
|
+
// Read entries from a specific binlog file
|
|
61
|
+
const entries = await binlog.readEntries<{
|
|
62
|
+
message?: string;
|
|
63
|
+
user?: string;
|
|
64
|
+
action?: string;
|
|
65
|
+
}>('app-0.bin');
|
|
66
|
+
|
|
67
|
+
// Process entries
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
console.log(
|
|
70
|
+
`[${new Date(entry.timestamp * 1000).toISOString()}] Op: ${entry.opcode}`,
|
|
71
|
+
);
|
|
72
|
+
console.log(entry.data);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
readLogs().catch(console.error);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Configuration Options
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createBinlog } from '@andrew_l/binlog';
|
|
83
|
+
|
|
84
|
+
const binlog = createBinlog({
|
|
85
|
+
// File path pattern with {index} placeholder for rotation
|
|
86
|
+
path: './logs/application-{index}.bin',
|
|
87
|
+
|
|
88
|
+
// Maximum file size before rotation (100MB)
|
|
89
|
+
maxFileSize: 104857600,
|
|
90
|
+
|
|
91
|
+
// Enable/disable log rotation
|
|
92
|
+
rotation: true,
|
|
93
|
+
|
|
94
|
+
// Control synchronous writes for durability
|
|
95
|
+
syncWrites: true,
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ⚙️ API Reference
|
|
100
|
+
|
|
101
|
+
#### Methods
|
|
102
|
+
|
|
103
|
+
- **`async init(): Promise<void>`**
|
|
104
|
+
Initialize the binlog system, ensuring directory exists and pick most recent binlog file.
|
|
105
|
+
|
|
106
|
+
- **`async open(): Promise<void>`**
|
|
107
|
+
Open the binlog for writing.
|
|
108
|
+
|
|
109
|
+
- **`async close(): Promise<void>`**
|
|
110
|
+
Close the current binlog file.
|
|
111
|
+
|
|
112
|
+
- **`async write(opcode: number, data: unknown): Promise<void>`**
|
|
113
|
+
Write a binlog entry with the specified opcode and data.
|
|
114
|
+
|
|
115
|
+
- **`async rotate(): Promise<void>`**
|
|
116
|
+
Rotate the binlog file to a new one.
|
|
117
|
+
|
|
118
|
+
- **`async readEntries<TData = Buffer>(filename: string, unsafe?: boolean): Promise<BinlogEntry<TData>[]>`**
|
|
119
|
+
Read and parse all entries from a binlog file.
|
|
120
|
+
|
|
121
|
+
#### Properties
|
|
122
|
+
|
|
123
|
+
- **`currentFileIndex: number`**
|
|
124
|
+
Get the current binlog file index.
|
|
125
|
+
|
|
126
|
+
- **`currentFileName: string`**
|
|
127
|
+
Get current binlog file name.
|
|
128
|
+
|
|
129
|
+
- **`directory: string`**
|
|
130
|
+
Get current binlog directory.
|
|
131
|
+
|
|
132
|
+
## 🛡️ Binlog File Format
|
|
133
|
+
|
|
134
|
+
Each binlog file has:
|
|
135
|
+
|
|
136
|
+
- **File Header** (16 bytes)
|
|
137
|
+
|
|
138
|
+
- Magic number (4 bytes): 0x4442544B
|
|
139
|
+
- Version (4 bytes): Currently 1
|
|
140
|
+
- Timestamp (4 bytes)
|
|
141
|
+
- Reserved (4 bytes)
|
|
142
|
+
|
|
143
|
+
- **Entry Headers** (20 bytes each)
|
|
144
|
+
|
|
145
|
+
- Operation code (4 bytes)
|
|
146
|
+
- Flags (4 bytes)
|
|
147
|
+
- Timestamp (4 bytes)
|
|
148
|
+
- Data length (4 bytes)
|
|
149
|
+
- CRC32 checksum (4 bytes)
|
|
150
|
+
|
|
151
|
+
- **Entry Data** (variable length)
|
|
152
|
+
- Raw binary data or TL-encoded data
|
|
153
|
+
|
|
154
|
+
## 🔄 Integration with TL-Pack
|
|
155
|
+
|
|
156
|
+
Binlog automatically uses TL serialization for non-Buffer data types using the `@andrew_l/tl-pack` library. This allows for efficient serialization of complex JavaScript objects.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Data will be automatically TL-encoded
|
|
160
|
+
await binlog.write(1, {
|
|
161
|
+
user: 'alice',
|
|
162
|
+
permissions: ['read', 'write'],
|
|
163
|
+
metadata: {
|
|
164
|
+
lastLogin: new Date(),
|
|
165
|
+
requestCount: 42,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 📝 License
|
|
171
|
+
|
|
172
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const toolkit = require('@andrew_l/toolkit');
|
|
4
|
+
const tlPack = require('@andrew_l/tl-pack');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
9
|
+
|
|
10
|
+
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
11
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
12
|
+
|
|
13
|
+
class Binlog {
|
|
14
|
+
_options;
|
|
15
|
+
_filePattern;
|
|
16
|
+
_directory;
|
|
17
|
+
_currentFile;
|
|
18
|
+
_currentFileDescriptor;
|
|
19
|
+
_currentFileSize;
|
|
20
|
+
_currentFileIndex;
|
|
21
|
+
_isOpen;
|
|
22
|
+
_log;
|
|
23
|
+
BINLOG_MAGIC = 1145197643;
|
|
24
|
+
BINLOG_VERSION = 1;
|
|
25
|
+
BINLOG_HEADER_SIZE = 16;
|
|
26
|
+
BINLOG_ENTRY_HEADER_SIZE = 20;
|
|
27
|
+
BINLOG_FLAG = Object.freeze({
|
|
28
|
+
NONE: 0,
|
|
29
|
+
TL: 1 << 0
|
|
30
|
+
});
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this._log = toolkit.logger("Binlog");
|
|
33
|
+
this._options = toolkit.deepClone(options);
|
|
34
|
+
this._filePattern = path__default.basename(options.path);
|
|
35
|
+
this._directory = path__default.dirname(options.path);
|
|
36
|
+
this._currentFile = null;
|
|
37
|
+
this._currentFileDescriptor = null;
|
|
38
|
+
this._currentFileSize = 0;
|
|
39
|
+
this._currentFileIndex = 0;
|
|
40
|
+
this._isOpen = false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
44
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
45
|
+
*/
|
|
46
|
+
async init() {
|
|
47
|
+
await fs__default.promises.mkdir(this._directory, { recursive: true });
|
|
48
|
+
await this.#findLatestBinlogFile();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Find the latest binlog file to continue writing
|
|
52
|
+
*/
|
|
53
|
+
async #findLatestBinlogFile() {
|
|
54
|
+
const files = await fs__default.promises.readdir(this._directory);
|
|
55
|
+
const fileRegEx = filePatternToRegex(this._filePattern);
|
|
56
|
+
this._currentFileIndex = 0;
|
|
57
|
+
for (const fileName of files) {
|
|
58
|
+
const match = fileName.match(fileRegEx);
|
|
59
|
+
if (match) {
|
|
60
|
+
this._currentFileIndex = Math.max(
|
|
61
|
+
this._currentFileIndex,
|
|
62
|
+
parseInt(match[1], 10)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Open the binlog for writing
|
|
69
|
+
*/
|
|
70
|
+
async open() {
|
|
71
|
+
const filePath = path__default.join(this._directory, this.currentFileName);
|
|
72
|
+
this._currentFileDescriptor = await fs__default.promises.open(filePath, "a+");
|
|
73
|
+
this._currentFile = filePath;
|
|
74
|
+
this._currentFileSize = (await this._currentFileDescriptor.stat()).size;
|
|
75
|
+
this._isOpen = true;
|
|
76
|
+
if (this._currentFileSize === 0) {
|
|
77
|
+
await this.#writeBinlogHeader();
|
|
78
|
+
} else {
|
|
79
|
+
await this.#checkBinlogHeaders(this._currentFileDescriptor);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Close the current binlog file
|
|
84
|
+
*/
|
|
85
|
+
async close() {
|
|
86
|
+
if (!this._isOpen || !this._currentFileDescriptor) return;
|
|
87
|
+
await this._currentFileDescriptor.close();
|
|
88
|
+
this._currentFileDescriptor = null;
|
|
89
|
+
this._currentFile = null;
|
|
90
|
+
this._isOpen = false;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Write a binlog entry
|
|
94
|
+
* @param opcode - Operation code
|
|
95
|
+
* @param data - Data to write
|
|
96
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
97
|
+
*/
|
|
98
|
+
async write(opcode, data) {
|
|
99
|
+
if (!this._isOpen) {
|
|
100
|
+
await this.open();
|
|
101
|
+
}
|
|
102
|
+
if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
|
|
103
|
+
await this.rotate();
|
|
104
|
+
}
|
|
105
|
+
toolkit.assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
106
|
+
let flags = this.BINLOG_FLAG.NONE;
|
|
107
|
+
if (!Buffer.isBuffer(data)) {
|
|
108
|
+
data = tlPack.tlEncode(data);
|
|
109
|
+
flags |= this.BINLOG_FLAG.TL;
|
|
110
|
+
}
|
|
111
|
+
const dataLength = data.length;
|
|
112
|
+
const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;
|
|
113
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
|
|
114
|
+
headerBuffer.writeUInt32LE(opcode, 0);
|
|
115
|
+
headerBuffer.writeUInt32LE(flags, 4);
|
|
116
|
+
headerBuffer.writeUInt32LE(toolkit.timestamp(), 8);
|
|
117
|
+
headerBuffer.writeUInt32LE(dataLength, 12);
|
|
118
|
+
headerBuffer.writeUInt32LE(
|
|
119
|
+
toolkit.crc32(Buffer.concat([headerBuffer.subarray(0, 16), data])) >>> 0,
|
|
120
|
+
16
|
|
121
|
+
);
|
|
122
|
+
await this._currentFileDescriptor.write(headerBuffer);
|
|
123
|
+
await this._currentFileDescriptor.write(data);
|
|
124
|
+
if (this._options.syncWrites) {
|
|
125
|
+
await this._currentFileDescriptor.sync();
|
|
126
|
+
}
|
|
127
|
+
this._currentFileSize += totalLength;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Rotate the binlog file
|
|
131
|
+
*/
|
|
132
|
+
async rotate() {
|
|
133
|
+
await this.close();
|
|
134
|
+
this._currentFileIndex++;
|
|
135
|
+
await this.open();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Write the binlog header
|
|
139
|
+
*/
|
|
140
|
+
async #writeBinlogHeader() {
|
|
141
|
+
toolkit.assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
142
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
143
|
+
headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);
|
|
144
|
+
headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);
|
|
145
|
+
headerBuffer.writeUInt32LE(toolkit.timestamp(), 8);
|
|
146
|
+
headerBuffer.writeUInt32LE(0, 12);
|
|
147
|
+
await this._currentFileDescriptor.write(headerBuffer);
|
|
148
|
+
this._currentFileSize += headerBuffer.length;
|
|
149
|
+
if (this._options.syncWrites) {
|
|
150
|
+
await this._currentFileDescriptor.sync();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Read and parse all entries from a binlog file
|
|
155
|
+
* @param filename - Binlog file to read
|
|
156
|
+
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
157
|
+
* @returns Array of parsed entries
|
|
158
|
+
*/
|
|
159
|
+
async readEntries(filename, unsafe) {
|
|
160
|
+
const filepath = path__default.join(this._directory, filename);
|
|
161
|
+
const fileHandle = await fs__default.promises.open(filepath, "r");
|
|
162
|
+
if (!await this.#checkBinlogHeaders(fileHandle)) {
|
|
163
|
+
if (unsafe) {
|
|
164
|
+
this._log.warn("Invalid binlog file format: " + filename);
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
throw new Error("Invalid binlog file format:\n" + filepath);
|
|
168
|
+
}
|
|
169
|
+
const contentSize = (await fileHandle.stat()).size - this.BINLOG_HEADER_SIZE;
|
|
170
|
+
let position = this.BINLOG_HEADER_SIZE;
|
|
171
|
+
const entries = [];
|
|
172
|
+
while (position < contentSize) {
|
|
173
|
+
const entryHeaderBuffer = Buffer.allocUnsafe(
|
|
174
|
+
this.BINLOG_ENTRY_HEADER_SIZE
|
|
175
|
+
);
|
|
176
|
+
await fileHandle.read(
|
|
177
|
+
entryHeaderBuffer,
|
|
178
|
+
0,
|
|
179
|
+
this.BINLOG_ENTRY_HEADER_SIZE,
|
|
180
|
+
position
|
|
181
|
+
);
|
|
182
|
+
const opcode = entryHeaderBuffer.readUInt32LE(0);
|
|
183
|
+
const flags = entryHeaderBuffer.readUInt32LE(4);
|
|
184
|
+
const timestamp2 = entryHeaderBuffer.readUInt32LE(8);
|
|
185
|
+
const dataLength = entryHeaderBuffer.readUInt32LE(12);
|
|
186
|
+
const crc = entryHeaderBuffer.readUInt32LE(16);
|
|
187
|
+
position += this.BINLOG_ENTRY_HEADER_SIZE;
|
|
188
|
+
let data = Buffer.allocUnsafe(dataLength);
|
|
189
|
+
await fileHandle.read(data, 0, dataLength, position);
|
|
190
|
+
const calculatedCrc = toolkit.crc32(Buffer.concat([entryHeaderBuffer.subarray(0, 16), data])) >>> 0;
|
|
191
|
+
if (calculatedCrc === crc) {
|
|
192
|
+
if (toolkit.checkBitmask(flags, this.BINLOG_FLAG.TL)) {
|
|
193
|
+
data = tlPack.tlDecode(data);
|
|
194
|
+
}
|
|
195
|
+
entries.push({
|
|
196
|
+
opcode,
|
|
197
|
+
timestamp: timestamp2,
|
|
198
|
+
data,
|
|
199
|
+
position: position - this.BINLOG_ENTRY_HEADER_SIZE
|
|
200
|
+
});
|
|
201
|
+
} else if (!unsafe) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
204
|
+
);
|
|
205
|
+
} else {
|
|
206
|
+
this._log.warn(
|
|
207
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
position += dataLength;
|
|
211
|
+
}
|
|
212
|
+
await fileHandle.close();
|
|
213
|
+
return entries;
|
|
214
|
+
}
|
|
215
|
+
async #checkBinlogHeaders(fileHandle) {
|
|
216
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
217
|
+
await fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0);
|
|
218
|
+
const magic = headerBuffer.readUInt32LE(0);
|
|
219
|
+
const version = headerBuffer.readUInt32LE(4);
|
|
220
|
+
return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get the current binlog file index
|
|
224
|
+
*/
|
|
225
|
+
get currentFileIndex() {
|
|
226
|
+
return this._currentFileIndex;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get current binlog file name
|
|
230
|
+
*/
|
|
231
|
+
get currentFileName() {
|
|
232
|
+
return this._filePattern.replaceAll(
|
|
233
|
+
"{index}",
|
|
234
|
+
String(this._currentFileIndex)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get current binlog directory
|
|
239
|
+
*/
|
|
240
|
+
get directory() {
|
|
241
|
+
return this._directory;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function filePatternToRegex(value) {
|
|
245
|
+
return new RegExp(value.replaceAll("{index}", "(\\d+)"));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const DEF_OPTIONS = {
|
|
249
|
+
path: "./binlogs/log-{index}.bin",
|
|
250
|
+
maxFileSize: 104857600,
|
|
251
|
+
rotation: true,
|
|
252
|
+
syncWrites: true
|
|
253
|
+
};
|
|
254
|
+
function createBinlog(options) {
|
|
255
|
+
return new Binlog(toolkit.deepDefaults(options, DEF_OPTIONS));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
exports.Binlog = Binlog;
|
|
259
|
+
exports.createBinlog = createBinlog;
|
|
260
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/Binlog.ts","../src/index.ts"],"sourcesContent":["import { tlDecode, tlEncode } from '@andrew_l/tl-pack';\nimport {\n type Logger,\n assert,\n checkBitmask,\n crc32,\n deepClone,\n logger,\n timestamp,\n} from '@andrew_l/toolkit';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Options for configuring the KdbBinlog\n */\nexport interface BinlogOptions {\n /**\n * File location to store binlog files\n * @default './binlogs/log-{index}.bin'\n */\n path: string;\n\n /**\n * Maximum size of each binlog file before rotation (in bytes)\n * @default 104857600\n */\n maxFileSize: number;\n\n /**\n * Whether to enable log rotation\n * @default true\n */\n rotation: boolean;\n\n /**\n * Whether to sync writes to disk immediately\n * @default true\n */\n syncWrites: boolean;\n}\n\n/**\n * Represents a single binlog entry\n */\nexport interface BinlogEntry<TData = Buffer> {\n /**\n * Operation code defining the type of operation\n */\n opcode: number;\n\n /**\n * Unix timestamp when the entry was created\n */\n timestamp: number;\n\n /**\n * Binary data of the entry\n */\n data: TData | Buffer;\n\n /**\n * Position in the file where this entry starts\n */\n position: number;\n}\n\n/**\n * TypeScript implementation of binlog system\n * Adapted from https://github.com/vk-com/kphp-kdb/blob/master/binlog/kdb-binlog-common.c\n * @group Main\n */\nexport class Binlog {\n private _options: Required<BinlogOptions>;\n private _filePattern: string;\n private _directory: string;\n private _currentFile: string | null;\n private _currentFileDescriptor: fs.promises.FileHandle | null;\n private _currentFileSize: number;\n private _currentFileIndex: number;\n private _isOpen: boolean;\n private _log: Logger;\n\n private readonly BINLOG_MAGIC: number = 0x4442544b;\n private readonly BINLOG_VERSION: number = 1;\n private readonly BINLOG_HEADER_SIZE: number = 16;\n private readonly BINLOG_ENTRY_HEADER_SIZE: number = 20;\n private readonly BINLOG_FLAG = Object.freeze({\n NONE: 0,\n TL: 1 << 0,\n } as const);\n\n constructor(options: BinlogOptions) {\n this._log = logger('Binlog');\n this._options = deepClone(options);\n this._filePattern = path.basename(options.path);\n this._directory = path.dirname(options.path);\n this._currentFile = null;\n this._currentFileDescriptor = null;\n this._currentFileSize = 0;\n this._currentFileIndex = 0;\n this._isOpen = false;\n }\n\n /**\n * Initialize the binlog system, ensuring directory exists and pick most recent binlog file\n * @returns Promise resolving to true if successful, false otherwise\n */\n public async init(): Promise<void> {\n await fs.promises.mkdir(this._directory, { recursive: true });\n await this.#findLatestBinlogFile();\n }\n\n /**\n * Find the latest binlog file to continue writing\n */\n async #findLatestBinlogFile(): Promise<void> {\n const files = await fs.promises.readdir(this._directory);\n const fileRegEx = filePatternToRegex(this._filePattern);\n\n this._currentFileIndex = 0;\n\n for (const fileName of files) {\n const match = fileName.match(fileRegEx);\n\n if (match) {\n this._currentFileIndex = Math.max(\n this._currentFileIndex,\n parseInt(match[1], 10),\n );\n }\n }\n }\n\n /**\n * Open the binlog for writing\n */\n public async open(): Promise<void> {\n const filePath = path.join(this._directory, this.currentFileName);\n\n this._currentFileDescriptor = await fs.promises.open(filePath, 'a+');\n this._currentFile = filePath;\n this._currentFileSize = (await this._currentFileDescriptor.stat()).size;\n this._isOpen = true;\n\n // Write the binlog header\n if (this._currentFileSize === 0) {\n await this.#writeBinlogHeader();\n } else {\n await this.#checkBinlogHeaders(this._currentFileDescriptor);\n }\n }\n\n /**\n * Close the current binlog file\n */\n public async close(): Promise<void> {\n if (!this._isOpen || !this._currentFileDescriptor) return;\n\n // Write footer\n await this._currentFileDescriptor.close();\n\n this._currentFileDescriptor = null;\n this._currentFile = null;\n this._isOpen = false;\n }\n\n /**\n * Write a binlog entry\n * @param opcode - Operation code\n * @param data - Data to write\n * @returns Promise resolving to true if successful, false otherwise\n */\n public async write(opcode: number, data: unknown): Promise<void> {\n if (!this._isOpen) {\n await this.open();\n }\n\n // Check if we need to rotate the file\n if (\n this._options.rotation &&\n this._currentFileSize >= this._options.maxFileSize\n ) {\n await this.rotate();\n }\n\n assert.ok(this._currentFileDescriptor, 'File descriptor is null');\n\n let flags = this.BINLOG_FLAG.NONE;\n\n if (!Buffer.isBuffer(data)) {\n data = tlEncode(data) as Buffer;\n flags |= this.BINLOG_FLAG.TL;\n }\n\n // Create entry header\n const dataLength = (data as Buffer).length;\n const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;\n\n const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);\n headerBuffer.writeUInt32LE(opcode, 0);\n headerBuffer.writeUInt32LE(flags, 4);\n headerBuffer.writeUInt32LE(timestamp(), 8);\n headerBuffer.writeUInt32LE(dataLength, 12);\n\n headerBuffer.writeUInt32LE(\n crc32(Buffer.concat([headerBuffer.subarray(0, 16), data as Buffer])) >>>\n 0,\n 16,\n );\n\n // Write the entry\n await this._currentFileDescriptor.write(headerBuffer);\n await this._currentFileDescriptor.write(data as Buffer);\n\n if (this._options.syncWrites) {\n await this._currentFileDescriptor.sync();\n }\n\n this._currentFileSize += totalLength;\n }\n\n /**\n * Rotate the binlog file\n */\n public async rotate(): Promise<void> {\n await this.close();\n this._currentFileIndex++;\n await this.open();\n }\n\n /**\n * Write the binlog header\n */\n async #writeBinlogHeader(): Promise<void> {\n assert.ok(this._currentFileDescriptor, 'File descriptor is null');\n\n const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);\n\n headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);\n headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);\n headerBuffer.writeUInt32LE(timestamp(), 8);\n headerBuffer.writeUInt32LE(0, 12); // Reserved field\n\n await this._currentFileDescriptor.write(headerBuffer);\n this._currentFileSize += headerBuffer.length;\n\n if (this._options.syncWrites) {\n await this._currentFileDescriptor.sync();\n }\n }\n\n /**\n * Read and parse all entries from a binlog file\n * @param filename - Binlog file to read\n * @param unsafe - Ignore broken binlog records otherwise throws error\n * @returns Array of parsed entries\n */\n public async readEntries<TData = Buffer>(\n filename: string,\n unsafe?: boolean,\n ): Promise<BinlogEntry<TData>[]> {\n const filepath = path.join(this._directory, filename);\n const fileHandle = await fs.promises.open(filepath, 'r');\n\n if (!(await this.#checkBinlogHeaders(fileHandle))) {\n if (unsafe) {\n this._log.warn('Invalid binlog file format: ' + filename);\n return [];\n }\n\n throw new Error('Invalid binlog file format:\\n' + filepath);\n }\n\n const contentSize =\n (await fileHandle.stat()).size - this.BINLOG_HEADER_SIZE;\n\n let position = this.BINLOG_HEADER_SIZE;\n\n const entries: BinlogEntry<TData>[] = [];\n\n // Read entries until we reach the end or footer\n while (position < contentSize) {\n const entryHeaderBuffer = Buffer.allocUnsafe(\n this.BINLOG_ENTRY_HEADER_SIZE,\n );\n await fileHandle.read(\n entryHeaderBuffer,\n 0,\n this.BINLOG_ENTRY_HEADER_SIZE,\n position,\n );\n\n const opcode = entryHeaderBuffer.readUInt32LE(0);\n const flags = entryHeaderBuffer.readUInt32LE(4);\n const timestamp = entryHeaderBuffer.readUInt32LE(8);\n const dataLength = entryHeaderBuffer.readUInt32LE(12);\n const crc = entryHeaderBuffer.readUInt32LE(16);\n\n position += this.BINLOG_ENTRY_HEADER_SIZE;\n\n // Read data\n let data = Buffer.allocUnsafe(dataLength);\n\n await fileHandle.read(data, 0, dataLength, position);\n\n // Verify CRC\n const calculatedCrc =\n crc32(Buffer.concat([entryHeaderBuffer.subarray(0, 16), data])) >>> 0;\n\n if (calculatedCrc === crc) {\n if (checkBitmask(flags, this.BINLOG_FLAG.TL)) {\n data = tlDecode(data);\n }\n\n entries.push({\n opcode,\n timestamp,\n data: data,\n position: position - this.BINLOG_ENTRY_HEADER_SIZE,\n });\n } else if (!unsafe) {\n throw new Error(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n );\n } else {\n this._log.warn(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n );\n }\n\n position += dataLength;\n }\n\n // Close file\n await fileHandle.close();\n\n return entries;\n }\n\n async #checkBinlogHeaders(\n fileHandle: fs.promises.FileHandle,\n ): Promise<boolean> {\n const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);\n await fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0);\n\n const magic = headerBuffer.readUInt32LE(0);\n const version = headerBuffer.readUInt32LE(4);\n\n return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;\n }\n\n /**\n * Get the current binlog file index\n */\n get currentFileIndex(): number {\n return this._currentFileIndex;\n }\n\n /**\n * Get current binlog file name\n */\n get currentFileName(): string {\n return this._filePattern.replaceAll(\n '{index}',\n String(this._currentFileIndex),\n );\n }\n\n /**\n * Get current binlog directory\n */\n get directory(): string {\n return this._directory;\n }\n}\n\nfunction filePatternToRegex(value: string): RegExp {\n return new RegExp(value.replaceAll('{index}', '(\\\\d+)'));\n}\n","import { deepDefaults } from '@andrew_l/toolkit';\nimport { Binlog, type BinlogOptions } from './Binlog.js';\n\nexport * from './Binlog.js';\n\nconst DEF_OPTIONS: BinlogOptions = {\n path: './binlogs/log-{index}.bin',\n maxFileSize: 104857600,\n rotation: true,\n syncWrites: true,\n};\n\n/**\n * Create binlog instance\n * @group Main\n */\nexport function createBinlog(options: Partial<BinlogOptions>): Binlog {\n return new Binlog(deepDefaults(options, DEF_OPTIONS));\n}\n"],"names":["logger","deepClone","path","fs","assert","tlEncode","timestamp","crc32","checkBitmask","tlDecode","deepDefaults"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for configuring the KdbBinlog
|
|
3
|
+
*/
|
|
4
|
+
interface BinlogOptions {
|
|
5
|
+
/**
|
|
6
|
+
* File location to store binlog files
|
|
7
|
+
* @default './binlogs/log-{index}.bin'
|
|
8
|
+
*/
|
|
9
|
+
path: string;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum size of each binlog file before rotation (in bytes)
|
|
12
|
+
* @default 104857600
|
|
13
|
+
*/
|
|
14
|
+
maxFileSize: number;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to enable log rotation
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
rotation: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to sync writes to disk immediately
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
syncWrites: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Represents a single binlog entry
|
|
28
|
+
*/
|
|
29
|
+
interface BinlogEntry<TData = Buffer> {
|
|
30
|
+
/**
|
|
31
|
+
* Operation code defining the type of operation
|
|
32
|
+
*/
|
|
33
|
+
opcode: number;
|
|
34
|
+
/**
|
|
35
|
+
* Unix timestamp when the entry was created
|
|
36
|
+
*/
|
|
37
|
+
timestamp: number;
|
|
38
|
+
/**
|
|
39
|
+
* Binary data of the entry
|
|
40
|
+
*/
|
|
41
|
+
data: TData | Buffer;
|
|
42
|
+
/**
|
|
43
|
+
* Position in the file where this entry starts
|
|
44
|
+
*/
|
|
45
|
+
position: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* TypeScript implementation of binlog system
|
|
49
|
+
* Adapted from https://github.com/vk-com/kphp-kdb/blob/master/binlog/kdb-binlog-common.c
|
|
50
|
+
* @group Main
|
|
51
|
+
*/
|
|
52
|
+
declare class Binlog {
|
|
53
|
+
#private;
|
|
54
|
+
private _options;
|
|
55
|
+
private _filePattern;
|
|
56
|
+
private _directory;
|
|
57
|
+
private _currentFile;
|
|
58
|
+
private _currentFileDescriptor;
|
|
59
|
+
private _currentFileSize;
|
|
60
|
+
private _currentFileIndex;
|
|
61
|
+
private _isOpen;
|
|
62
|
+
private _log;
|
|
63
|
+
private readonly BINLOG_MAGIC;
|
|
64
|
+
private readonly BINLOG_VERSION;
|
|
65
|
+
private readonly BINLOG_HEADER_SIZE;
|
|
66
|
+
private readonly BINLOG_ENTRY_HEADER_SIZE;
|
|
67
|
+
private readonly BINLOG_FLAG;
|
|
68
|
+
constructor(options: BinlogOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
71
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
72
|
+
*/
|
|
73
|
+
init(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Open the binlog for writing
|
|
76
|
+
*/
|
|
77
|
+
open(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Close the current binlog file
|
|
80
|
+
*/
|
|
81
|
+
close(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Write a binlog entry
|
|
84
|
+
* @param opcode - Operation code
|
|
85
|
+
* @param data - Data to write
|
|
86
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
87
|
+
*/
|
|
88
|
+
write(opcode: number, data: unknown): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Rotate the binlog file
|
|
91
|
+
*/
|
|
92
|
+
rotate(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Read and parse all entries from a binlog file
|
|
95
|
+
* @param filename - Binlog file to read
|
|
96
|
+
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
97
|
+
* @returns Array of parsed entries
|
|
98
|
+
*/
|
|
99
|
+
readEntries<TData = Buffer>(filename: string, unsafe?: boolean): Promise<BinlogEntry<TData>[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Get the current binlog file index
|
|
102
|
+
*/
|
|
103
|
+
get currentFileIndex(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Get current binlog file name
|
|
106
|
+
*/
|
|
107
|
+
get currentFileName(): string;
|
|
108
|
+
/**
|
|
109
|
+
* Get current binlog directory
|
|
110
|
+
*/
|
|
111
|
+
get directory(): string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create binlog instance
|
|
116
|
+
* @group Main
|
|
117
|
+
*/
|
|
118
|
+
declare function createBinlog(options: Partial<BinlogOptions>): Binlog;
|
|
119
|
+
|
|
120
|
+
export { Binlog, type BinlogEntry, type BinlogOptions, createBinlog };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for configuring the KdbBinlog
|
|
3
|
+
*/
|
|
4
|
+
interface BinlogOptions {
|
|
5
|
+
/**
|
|
6
|
+
* File location to store binlog files
|
|
7
|
+
* @default './binlogs/log-{index}.bin'
|
|
8
|
+
*/
|
|
9
|
+
path: string;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum size of each binlog file before rotation (in bytes)
|
|
12
|
+
* @default 104857600
|
|
13
|
+
*/
|
|
14
|
+
maxFileSize: number;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to enable log rotation
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
rotation: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to sync writes to disk immediately
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
syncWrites: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Represents a single binlog entry
|
|
28
|
+
*/
|
|
29
|
+
interface BinlogEntry<TData = Buffer> {
|
|
30
|
+
/**
|
|
31
|
+
* Operation code defining the type of operation
|
|
32
|
+
*/
|
|
33
|
+
opcode: number;
|
|
34
|
+
/**
|
|
35
|
+
* Unix timestamp when the entry was created
|
|
36
|
+
*/
|
|
37
|
+
timestamp: number;
|
|
38
|
+
/**
|
|
39
|
+
* Binary data of the entry
|
|
40
|
+
*/
|
|
41
|
+
data: TData | Buffer;
|
|
42
|
+
/**
|
|
43
|
+
* Position in the file where this entry starts
|
|
44
|
+
*/
|
|
45
|
+
position: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* TypeScript implementation of binlog system
|
|
49
|
+
* Adapted from https://github.com/vk-com/kphp-kdb/blob/master/binlog/kdb-binlog-common.c
|
|
50
|
+
* @group Main
|
|
51
|
+
*/
|
|
52
|
+
declare class Binlog {
|
|
53
|
+
#private;
|
|
54
|
+
private _options;
|
|
55
|
+
private _filePattern;
|
|
56
|
+
private _directory;
|
|
57
|
+
private _currentFile;
|
|
58
|
+
private _currentFileDescriptor;
|
|
59
|
+
private _currentFileSize;
|
|
60
|
+
private _currentFileIndex;
|
|
61
|
+
private _isOpen;
|
|
62
|
+
private _log;
|
|
63
|
+
private readonly BINLOG_MAGIC;
|
|
64
|
+
private readonly BINLOG_VERSION;
|
|
65
|
+
private readonly BINLOG_HEADER_SIZE;
|
|
66
|
+
private readonly BINLOG_ENTRY_HEADER_SIZE;
|
|
67
|
+
private readonly BINLOG_FLAG;
|
|
68
|
+
constructor(options: BinlogOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
71
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
72
|
+
*/
|
|
73
|
+
init(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Open the binlog for writing
|
|
76
|
+
*/
|
|
77
|
+
open(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Close the current binlog file
|
|
80
|
+
*/
|
|
81
|
+
close(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Write a binlog entry
|
|
84
|
+
* @param opcode - Operation code
|
|
85
|
+
* @param data - Data to write
|
|
86
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
87
|
+
*/
|
|
88
|
+
write(opcode: number, data: unknown): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Rotate the binlog file
|
|
91
|
+
*/
|
|
92
|
+
rotate(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Read and parse all entries from a binlog file
|
|
95
|
+
* @param filename - Binlog file to read
|
|
96
|
+
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
97
|
+
* @returns Array of parsed entries
|
|
98
|
+
*/
|
|
99
|
+
readEntries<TData = Buffer>(filename: string, unsafe?: boolean): Promise<BinlogEntry<TData>[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Get the current binlog file index
|
|
102
|
+
*/
|
|
103
|
+
get currentFileIndex(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Get current binlog file name
|
|
106
|
+
*/
|
|
107
|
+
get currentFileName(): string;
|
|
108
|
+
/**
|
|
109
|
+
* Get current binlog directory
|
|
110
|
+
*/
|
|
111
|
+
get directory(): string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create binlog instance
|
|
116
|
+
* @group Main
|
|
117
|
+
*/
|
|
118
|
+
declare function createBinlog(options: Partial<BinlogOptions>): Binlog;
|
|
119
|
+
|
|
120
|
+
export { Binlog, type BinlogEntry, type BinlogOptions, createBinlog };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for configuring the KdbBinlog
|
|
3
|
+
*/
|
|
4
|
+
interface BinlogOptions {
|
|
5
|
+
/**
|
|
6
|
+
* File location to store binlog files
|
|
7
|
+
* @default './binlogs/log-{index}.bin'
|
|
8
|
+
*/
|
|
9
|
+
path: string;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum size of each binlog file before rotation (in bytes)
|
|
12
|
+
* @default 104857600
|
|
13
|
+
*/
|
|
14
|
+
maxFileSize: number;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to enable log rotation
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
rotation: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to sync writes to disk immediately
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
syncWrites: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Represents a single binlog entry
|
|
28
|
+
*/
|
|
29
|
+
interface BinlogEntry<TData = Buffer> {
|
|
30
|
+
/**
|
|
31
|
+
* Operation code defining the type of operation
|
|
32
|
+
*/
|
|
33
|
+
opcode: number;
|
|
34
|
+
/**
|
|
35
|
+
* Unix timestamp when the entry was created
|
|
36
|
+
*/
|
|
37
|
+
timestamp: number;
|
|
38
|
+
/**
|
|
39
|
+
* Binary data of the entry
|
|
40
|
+
*/
|
|
41
|
+
data: TData | Buffer;
|
|
42
|
+
/**
|
|
43
|
+
* Position in the file where this entry starts
|
|
44
|
+
*/
|
|
45
|
+
position: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* TypeScript implementation of binlog system
|
|
49
|
+
* Adapted from https://github.com/vk-com/kphp-kdb/blob/master/binlog/kdb-binlog-common.c
|
|
50
|
+
* @group Main
|
|
51
|
+
*/
|
|
52
|
+
declare class Binlog {
|
|
53
|
+
#private;
|
|
54
|
+
private _options;
|
|
55
|
+
private _filePattern;
|
|
56
|
+
private _directory;
|
|
57
|
+
private _currentFile;
|
|
58
|
+
private _currentFileDescriptor;
|
|
59
|
+
private _currentFileSize;
|
|
60
|
+
private _currentFileIndex;
|
|
61
|
+
private _isOpen;
|
|
62
|
+
private _log;
|
|
63
|
+
private readonly BINLOG_MAGIC;
|
|
64
|
+
private readonly BINLOG_VERSION;
|
|
65
|
+
private readonly BINLOG_HEADER_SIZE;
|
|
66
|
+
private readonly BINLOG_ENTRY_HEADER_SIZE;
|
|
67
|
+
private readonly BINLOG_FLAG;
|
|
68
|
+
constructor(options: BinlogOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
71
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
72
|
+
*/
|
|
73
|
+
init(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Open the binlog for writing
|
|
76
|
+
*/
|
|
77
|
+
open(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Close the current binlog file
|
|
80
|
+
*/
|
|
81
|
+
close(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Write a binlog entry
|
|
84
|
+
* @param opcode - Operation code
|
|
85
|
+
* @param data - Data to write
|
|
86
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
87
|
+
*/
|
|
88
|
+
write(opcode: number, data: unknown): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Rotate the binlog file
|
|
91
|
+
*/
|
|
92
|
+
rotate(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Read and parse all entries from a binlog file
|
|
95
|
+
* @param filename - Binlog file to read
|
|
96
|
+
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
97
|
+
* @returns Array of parsed entries
|
|
98
|
+
*/
|
|
99
|
+
readEntries<TData = Buffer>(filename: string, unsafe?: boolean): Promise<BinlogEntry<TData>[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Get the current binlog file index
|
|
102
|
+
*/
|
|
103
|
+
get currentFileIndex(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Get current binlog file name
|
|
106
|
+
*/
|
|
107
|
+
get currentFileName(): string;
|
|
108
|
+
/**
|
|
109
|
+
* Get current binlog directory
|
|
110
|
+
*/
|
|
111
|
+
get directory(): string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create binlog instance
|
|
116
|
+
* @group Main
|
|
117
|
+
*/
|
|
118
|
+
declare function createBinlog(options: Partial<BinlogOptions>): Binlog;
|
|
119
|
+
|
|
120
|
+
export { Binlog, type BinlogEntry, type BinlogOptions, createBinlog };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { logger, deepClone, assert, timestamp, crc32, checkBitmask, deepDefaults } from '@andrew_l/toolkit';
|
|
2
|
+
import { tlEncode, tlDecode } from '@andrew_l/tl-pack';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
class Binlog {
|
|
7
|
+
_options;
|
|
8
|
+
_filePattern;
|
|
9
|
+
_directory;
|
|
10
|
+
_currentFile;
|
|
11
|
+
_currentFileDescriptor;
|
|
12
|
+
_currentFileSize;
|
|
13
|
+
_currentFileIndex;
|
|
14
|
+
_isOpen;
|
|
15
|
+
_log;
|
|
16
|
+
BINLOG_MAGIC = 1145197643;
|
|
17
|
+
BINLOG_VERSION = 1;
|
|
18
|
+
BINLOG_HEADER_SIZE = 16;
|
|
19
|
+
BINLOG_ENTRY_HEADER_SIZE = 20;
|
|
20
|
+
BINLOG_FLAG = Object.freeze({
|
|
21
|
+
NONE: 0,
|
|
22
|
+
TL: 1 << 0
|
|
23
|
+
});
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this._log = logger("Binlog");
|
|
26
|
+
this._options = deepClone(options);
|
|
27
|
+
this._filePattern = path.basename(options.path);
|
|
28
|
+
this._directory = path.dirname(options.path);
|
|
29
|
+
this._currentFile = null;
|
|
30
|
+
this._currentFileDescriptor = null;
|
|
31
|
+
this._currentFileSize = 0;
|
|
32
|
+
this._currentFileIndex = 0;
|
|
33
|
+
this._isOpen = false;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
37
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
38
|
+
*/
|
|
39
|
+
async init() {
|
|
40
|
+
await fs.promises.mkdir(this._directory, { recursive: true });
|
|
41
|
+
await this.#findLatestBinlogFile();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find the latest binlog file to continue writing
|
|
45
|
+
*/
|
|
46
|
+
async #findLatestBinlogFile() {
|
|
47
|
+
const files = await fs.promises.readdir(this._directory);
|
|
48
|
+
const fileRegEx = filePatternToRegex(this._filePattern);
|
|
49
|
+
this._currentFileIndex = 0;
|
|
50
|
+
for (const fileName of files) {
|
|
51
|
+
const match = fileName.match(fileRegEx);
|
|
52
|
+
if (match) {
|
|
53
|
+
this._currentFileIndex = Math.max(
|
|
54
|
+
this._currentFileIndex,
|
|
55
|
+
parseInt(match[1], 10)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Open the binlog for writing
|
|
62
|
+
*/
|
|
63
|
+
async open() {
|
|
64
|
+
const filePath = path.join(this._directory, this.currentFileName);
|
|
65
|
+
this._currentFileDescriptor = await fs.promises.open(filePath, "a+");
|
|
66
|
+
this._currentFile = filePath;
|
|
67
|
+
this._currentFileSize = (await this._currentFileDescriptor.stat()).size;
|
|
68
|
+
this._isOpen = true;
|
|
69
|
+
if (this._currentFileSize === 0) {
|
|
70
|
+
await this.#writeBinlogHeader();
|
|
71
|
+
} else {
|
|
72
|
+
await this.#checkBinlogHeaders(this._currentFileDescriptor);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Close the current binlog file
|
|
77
|
+
*/
|
|
78
|
+
async close() {
|
|
79
|
+
if (!this._isOpen || !this._currentFileDescriptor) return;
|
|
80
|
+
await this._currentFileDescriptor.close();
|
|
81
|
+
this._currentFileDescriptor = null;
|
|
82
|
+
this._currentFile = null;
|
|
83
|
+
this._isOpen = false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Write a binlog entry
|
|
87
|
+
* @param opcode - Operation code
|
|
88
|
+
* @param data - Data to write
|
|
89
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
90
|
+
*/
|
|
91
|
+
async write(opcode, data) {
|
|
92
|
+
if (!this._isOpen) {
|
|
93
|
+
await this.open();
|
|
94
|
+
}
|
|
95
|
+
if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
|
|
96
|
+
await this.rotate();
|
|
97
|
+
}
|
|
98
|
+
assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
99
|
+
let flags = this.BINLOG_FLAG.NONE;
|
|
100
|
+
if (!Buffer.isBuffer(data)) {
|
|
101
|
+
data = tlEncode(data);
|
|
102
|
+
flags |= this.BINLOG_FLAG.TL;
|
|
103
|
+
}
|
|
104
|
+
const dataLength = data.length;
|
|
105
|
+
const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;
|
|
106
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
|
|
107
|
+
headerBuffer.writeUInt32LE(opcode, 0);
|
|
108
|
+
headerBuffer.writeUInt32LE(flags, 4);
|
|
109
|
+
headerBuffer.writeUInt32LE(timestamp(), 8);
|
|
110
|
+
headerBuffer.writeUInt32LE(dataLength, 12);
|
|
111
|
+
headerBuffer.writeUInt32LE(
|
|
112
|
+
crc32(Buffer.concat([headerBuffer.subarray(0, 16), data])) >>> 0,
|
|
113
|
+
16
|
|
114
|
+
);
|
|
115
|
+
await this._currentFileDescriptor.write(headerBuffer);
|
|
116
|
+
await this._currentFileDescriptor.write(data);
|
|
117
|
+
if (this._options.syncWrites) {
|
|
118
|
+
await this._currentFileDescriptor.sync();
|
|
119
|
+
}
|
|
120
|
+
this._currentFileSize += totalLength;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Rotate the binlog file
|
|
124
|
+
*/
|
|
125
|
+
async rotate() {
|
|
126
|
+
await this.close();
|
|
127
|
+
this._currentFileIndex++;
|
|
128
|
+
await this.open();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Write the binlog header
|
|
132
|
+
*/
|
|
133
|
+
async #writeBinlogHeader() {
|
|
134
|
+
assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
135
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
136
|
+
headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);
|
|
137
|
+
headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);
|
|
138
|
+
headerBuffer.writeUInt32LE(timestamp(), 8);
|
|
139
|
+
headerBuffer.writeUInt32LE(0, 12);
|
|
140
|
+
await this._currentFileDescriptor.write(headerBuffer);
|
|
141
|
+
this._currentFileSize += headerBuffer.length;
|
|
142
|
+
if (this._options.syncWrites) {
|
|
143
|
+
await this._currentFileDescriptor.sync();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Read and parse all entries from a binlog file
|
|
148
|
+
* @param filename - Binlog file to read
|
|
149
|
+
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
150
|
+
* @returns Array of parsed entries
|
|
151
|
+
*/
|
|
152
|
+
async readEntries(filename, unsafe) {
|
|
153
|
+
const filepath = path.join(this._directory, filename);
|
|
154
|
+
const fileHandle = await fs.promises.open(filepath, "r");
|
|
155
|
+
if (!await this.#checkBinlogHeaders(fileHandle)) {
|
|
156
|
+
if (unsafe) {
|
|
157
|
+
this._log.warn("Invalid binlog file format: " + filename);
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
throw new Error("Invalid binlog file format:\n" + filepath);
|
|
161
|
+
}
|
|
162
|
+
const contentSize = (await fileHandle.stat()).size - this.BINLOG_HEADER_SIZE;
|
|
163
|
+
let position = this.BINLOG_HEADER_SIZE;
|
|
164
|
+
const entries = [];
|
|
165
|
+
while (position < contentSize) {
|
|
166
|
+
const entryHeaderBuffer = Buffer.allocUnsafe(
|
|
167
|
+
this.BINLOG_ENTRY_HEADER_SIZE
|
|
168
|
+
);
|
|
169
|
+
await fileHandle.read(
|
|
170
|
+
entryHeaderBuffer,
|
|
171
|
+
0,
|
|
172
|
+
this.BINLOG_ENTRY_HEADER_SIZE,
|
|
173
|
+
position
|
|
174
|
+
);
|
|
175
|
+
const opcode = entryHeaderBuffer.readUInt32LE(0);
|
|
176
|
+
const flags = entryHeaderBuffer.readUInt32LE(4);
|
|
177
|
+
const timestamp2 = entryHeaderBuffer.readUInt32LE(8);
|
|
178
|
+
const dataLength = entryHeaderBuffer.readUInt32LE(12);
|
|
179
|
+
const crc = entryHeaderBuffer.readUInt32LE(16);
|
|
180
|
+
position += this.BINLOG_ENTRY_HEADER_SIZE;
|
|
181
|
+
let data = Buffer.allocUnsafe(dataLength);
|
|
182
|
+
await fileHandle.read(data, 0, dataLength, position);
|
|
183
|
+
const calculatedCrc = crc32(Buffer.concat([entryHeaderBuffer.subarray(0, 16), data])) >>> 0;
|
|
184
|
+
if (calculatedCrc === crc) {
|
|
185
|
+
if (checkBitmask(flags, this.BINLOG_FLAG.TL)) {
|
|
186
|
+
data = tlDecode(data);
|
|
187
|
+
}
|
|
188
|
+
entries.push({
|
|
189
|
+
opcode,
|
|
190
|
+
timestamp: timestamp2,
|
|
191
|
+
data,
|
|
192
|
+
position: position - this.BINLOG_ENTRY_HEADER_SIZE
|
|
193
|
+
});
|
|
194
|
+
} else if (!unsafe) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
this._log.warn(
|
|
200
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
position += dataLength;
|
|
204
|
+
}
|
|
205
|
+
await fileHandle.close();
|
|
206
|
+
return entries;
|
|
207
|
+
}
|
|
208
|
+
async #checkBinlogHeaders(fileHandle) {
|
|
209
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
210
|
+
await fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0);
|
|
211
|
+
const magic = headerBuffer.readUInt32LE(0);
|
|
212
|
+
const version = headerBuffer.readUInt32LE(4);
|
|
213
|
+
return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get the current binlog file index
|
|
217
|
+
*/
|
|
218
|
+
get currentFileIndex() {
|
|
219
|
+
return this._currentFileIndex;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get current binlog file name
|
|
223
|
+
*/
|
|
224
|
+
get currentFileName() {
|
|
225
|
+
return this._filePattern.replaceAll(
|
|
226
|
+
"{index}",
|
|
227
|
+
String(this._currentFileIndex)
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get current binlog directory
|
|
232
|
+
*/
|
|
233
|
+
get directory() {
|
|
234
|
+
return this._directory;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function filePatternToRegex(value) {
|
|
238
|
+
return new RegExp(value.replaceAll("{index}", "(\\d+)"));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const DEF_OPTIONS = {
|
|
242
|
+
path: "./binlogs/log-{index}.bin",
|
|
243
|
+
maxFileSize: 104857600,
|
|
244
|
+
rotation: true,
|
|
245
|
+
syncWrites: true
|
|
246
|
+
};
|
|
247
|
+
function createBinlog(options) {
|
|
248
|
+
return new Binlog(deepDefaults(options, DEF_OPTIONS));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { Binlog, createBinlog };
|
|
252
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/Binlog.ts","../src/index.ts"],"sourcesContent":["import { tlDecode, tlEncode } from '@andrew_l/tl-pack';\nimport {\n type Logger,\n assert,\n checkBitmask,\n crc32,\n deepClone,\n logger,\n timestamp,\n} from '@andrew_l/toolkit';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Options for configuring the KdbBinlog\n */\nexport interface BinlogOptions {\n /**\n * File location to store binlog files\n * @default './binlogs/log-{index}.bin'\n */\n path: string;\n\n /**\n * Maximum size of each binlog file before rotation (in bytes)\n * @default 104857600\n */\n maxFileSize: number;\n\n /**\n * Whether to enable log rotation\n * @default true\n */\n rotation: boolean;\n\n /**\n * Whether to sync writes to disk immediately\n * @default true\n */\n syncWrites: boolean;\n}\n\n/**\n * Represents a single binlog entry\n */\nexport interface BinlogEntry<TData = Buffer> {\n /**\n * Operation code defining the type of operation\n */\n opcode: number;\n\n /**\n * Unix timestamp when the entry was created\n */\n timestamp: number;\n\n /**\n * Binary data of the entry\n */\n data: TData | Buffer;\n\n /**\n * Position in the file where this entry starts\n */\n position: number;\n}\n\n/**\n * TypeScript implementation of binlog system\n * Adapted from https://github.com/vk-com/kphp-kdb/blob/master/binlog/kdb-binlog-common.c\n * @group Main\n */\nexport class Binlog {\n private _options: Required<BinlogOptions>;\n private _filePattern: string;\n private _directory: string;\n private _currentFile: string | null;\n private _currentFileDescriptor: fs.promises.FileHandle | null;\n private _currentFileSize: number;\n private _currentFileIndex: number;\n private _isOpen: boolean;\n private _log: Logger;\n\n private readonly BINLOG_MAGIC: number = 0x4442544b;\n private readonly BINLOG_VERSION: number = 1;\n private readonly BINLOG_HEADER_SIZE: number = 16;\n private readonly BINLOG_ENTRY_HEADER_SIZE: number = 20;\n private readonly BINLOG_FLAG = Object.freeze({\n NONE: 0,\n TL: 1 << 0,\n } as const);\n\n constructor(options: BinlogOptions) {\n this._log = logger('Binlog');\n this._options = deepClone(options);\n this._filePattern = path.basename(options.path);\n this._directory = path.dirname(options.path);\n this._currentFile = null;\n this._currentFileDescriptor = null;\n this._currentFileSize = 0;\n this._currentFileIndex = 0;\n this._isOpen = false;\n }\n\n /**\n * Initialize the binlog system, ensuring directory exists and pick most recent binlog file\n * @returns Promise resolving to true if successful, false otherwise\n */\n public async init(): Promise<void> {\n await fs.promises.mkdir(this._directory, { recursive: true });\n await this.#findLatestBinlogFile();\n }\n\n /**\n * Find the latest binlog file to continue writing\n */\n async #findLatestBinlogFile(): Promise<void> {\n const files = await fs.promises.readdir(this._directory);\n const fileRegEx = filePatternToRegex(this._filePattern);\n\n this._currentFileIndex = 0;\n\n for (const fileName of files) {\n const match = fileName.match(fileRegEx);\n\n if (match) {\n this._currentFileIndex = Math.max(\n this._currentFileIndex,\n parseInt(match[1], 10),\n );\n }\n }\n }\n\n /**\n * Open the binlog for writing\n */\n public async open(): Promise<void> {\n const filePath = path.join(this._directory, this.currentFileName);\n\n this._currentFileDescriptor = await fs.promises.open(filePath, 'a+');\n this._currentFile = filePath;\n this._currentFileSize = (await this._currentFileDescriptor.stat()).size;\n this._isOpen = true;\n\n // Write the binlog header\n if (this._currentFileSize === 0) {\n await this.#writeBinlogHeader();\n } else {\n await this.#checkBinlogHeaders(this._currentFileDescriptor);\n }\n }\n\n /**\n * Close the current binlog file\n */\n public async close(): Promise<void> {\n if (!this._isOpen || !this._currentFileDescriptor) return;\n\n // Write footer\n await this._currentFileDescriptor.close();\n\n this._currentFileDescriptor = null;\n this._currentFile = null;\n this._isOpen = false;\n }\n\n /**\n * Write a binlog entry\n * @param opcode - Operation code\n * @param data - Data to write\n * @returns Promise resolving to true if successful, false otherwise\n */\n public async write(opcode: number, data: unknown): Promise<void> {\n if (!this._isOpen) {\n await this.open();\n }\n\n // Check if we need to rotate the file\n if (\n this._options.rotation &&\n this._currentFileSize >= this._options.maxFileSize\n ) {\n await this.rotate();\n }\n\n assert.ok(this._currentFileDescriptor, 'File descriptor is null');\n\n let flags = this.BINLOG_FLAG.NONE;\n\n if (!Buffer.isBuffer(data)) {\n data = tlEncode(data) as Buffer;\n flags |= this.BINLOG_FLAG.TL;\n }\n\n // Create entry header\n const dataLength = (data as Buffer).length;\n const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;\n\n const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);\n headerBuffer.writeUInt32LE(opcode, 0);\n headerBuffer.writeUInt32LE(flags, 4);\n headerBuffer.writeUInt32LE(timestamp(), 8);\n headerBuffer.writeUInt32LE(dataLength, 12);\n\n headerBuffer.writeUInt32LE(\n crc32(Buffer.concat([headerBuffer.subarray(0, 16), data as Buffer])) >>>\n 0,\n 16,\n );\n\n // Write the entry\n await this._currentFileDescriptor.write(headerBuffer);\n await this._currentFileDescriptor.write(data as Buffer);\n\n if (this._options.syncWrites) {\n await this._currentFileDescriptor.sync();\n }\n\n this._currentFileSize += totalLength;\n }\n\n /**\n * Rotate the binlog file\n */\n public async rotate(): Promise<void> {\n await this.close();\n this._currentFileIndex++;\n await this.open();\n }\n\n /**\n * Write the binlog header\n */\n async #writeBinlogHeader(): Promise<void> {\n assert.ok(this._currentFileDescriptor, 'File descriptor is null');\n\n const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);\n\n headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);\n headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);\n headerBuffer.writeUInt32LE(timestamp(), 8);\n headerBuffer.writeUInt32LE(0, 12); // Reserved field\n\n await this._currentFileDescriptor.write(headerBuffer);\n this._currentFileSize += headerBuffer.length;\n\n if (this._options.syncWrites) {\n await this._currentFileDescriptor.sync();\n }\n }\n\n /**\n * Read and parse all entries from a binlog file\n * @param filename - Binlog file to read\n * @param unsafe - Ignore broken binlog records otherwise throws error\n * @returns Array of parsed entries\n */\n public async readEntries<TData = Buffer>(\n filename: string,\n unsafe?: boolean,\n ): Promise<BinlogEntry<TData>[]> {\n const filepath = path.join(this._directory, filename);\n const fileHandle = await fs.promises.open(filepath, 'r');\n\n if (!(await this.#checkBinlogHeaders(fileHandle))) {\n if (unsafe) {\n this._log.warn('Invalid binlog file format: ' + filename);\n return [];\n }\n\n throw new Error('Invalid binlog file format:\\n' + filepath);\n }\n\n const contentSize =\n (await fileHandle.stat()).size - this.BINLOG_HEADER_SIZE;\n\n let position = this.BINLOG_HEADER_SIZE;\n\n const entries: BinlogEntry<TData>[] = [];\n\n // Read entries until we reach the end or footer\n while (position < contentSize) {\n const entryHeaderBuffer = Buffer.allocUnsafe(\n this.BINLOG_ENTRY_HEADER_SIZE,\n );\n await fileHandle.read(\n entryHeaderBuffer,\n 0,\n this.BINLOG_ENTRY_HEADER_SIZE,\n position,\n );\n\n const opcode = entryHeaderBuffer.readUInt32LE(0);\n const flags = entryHeaderBuffer.readUInt32LE(4);\n const timestamp = entryHeaderBuffer.readUInt32LE(8);\n const dataLength = entryHeaderBuffer.readUInt32LE(12);\n const crc = entryHeaderBuffer.readUInt32LE(16);\n\n position += this.BINLOG_ENTRY_HEADER_SIZE;\n\n // Read data\n let data = Buffer.allocUnsafe(dataLength);\n\n await fileHandle.read(data, 0, dataLength, position);\n\n // Verify CRC\n const calculatedCrc =\n crc32(Buffer.concat([entryHeaderBuffer.subarray(0, 16), data])) >>> 0;\n\n if (calculatedCrc === crc) {\n if (checkBitmask(flags, this.BINLOG_FLAG.TL)) {\n data = tlDecode(data);\n }\n\n entries.push({\n opcode,\n timestamp,\n data: data,\n position: position - this.BINLOG_ENTRY_HEADER_SIZE,\n });\n } else if (!unsafe) {\n throw new Error(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n );\n } else {\n this._log.warn(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n );\n }\n\n position += dataLength;\n }\n\n // Close file\n await fileHandle.close();\n\n return entries;\n }\n\n async #checkBinlogHeaders(\n fileHandle: fs.promises.FileHandle,\n ): Promise<boolean> {\n const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);\n await fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0);\n\n const magic = headerBuffer.readUInt32LE(0);\n const version = headerBuffer.readUInt32LE(4);\n\n return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;\n }\n\n /**\n * Get the current binlog file index\n */\n get currentFileIndex(): number {\n return this._currentFileIndex;\n }\n\n /**\n * Get current binlog file name\n */\n get currentFileName(): string {\n return this._filePattern.replaceAll(\n '{index}',\n String(this._currentFileIndex),\n );\n }\n\n /**\n * Get current binlog directory\n */\n get directory(): string {\n return this._directory;\n }\n}\n\nfunction filePatternToRegex(value: string): RegExp {\n return new RegExp(value.replaceAll('{index}', '(\\\\d+)'));\n}\n","import { deepDefaults } from '@andrew_l/toolkit';\nimport { Binlog, type BinlogOptions } from './Binlog.js';\n\nexport * from './Binlog.js';\n\nconst DEF_OPTIONS: BinlogOptions = {\n path: './binlogs/log-{index}.bin',\n maxFileSize: 104857600,\n rotation: true,\n syncWrites: true,\n};\n\n/**\n * Create binlog instance\n * @group Main\n */\nexport function createBinlog(options: Partial<BinlogOptions>): Binlog {\n return new Binlog(deepDefaults(options, DEF_OPTIONS));\n}\n"],"names":["timestamp"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@andrew_l/binlog",
|
|
3
|
+
"version": "0.2.14",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"binlog"
|
|
8
|
+
],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/men232/toolkit.git",
|
|
12
|
+
"directory": "packages/binlog"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "22.10.5",
|
|
27
|
+
"typescript": "~5.6.2",
|
|
28
|
+
"unbuild": "3.0.0-rc.11",
|
|
29
|
+
"vitest": "^2.1.3"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@andrew_l/tl-pack": "0.2.14",
|
|
33
|
+
"@andrew_l/toolkit": "0.2.14"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "unbuild",
|
|
37
|
+
"test": "vitest run --typecheck",
|
|
38
|
+
"test:watch": "vitest watch --typecheck"
|
|
39
|
+
}
|
|
40
|
+
}
|