@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 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
+ ![license](https://img.shields.io/npm/l/%40andrew_l%2Fbinlog) <!-- omit in toc -->
4
+ ![npm version](https://img.shields.io/npm/v/%40andrew_l%2Fbinlog) <!-- omit in toc -->
5
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40andrew_l%2Fbinlog) <!-- 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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"}
@@ -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 };
@@ -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 };
@@ -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
+ }