@andrew_l/binlog 0.2.21 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +188 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +189 -133
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -46,51 +46,62 @@ class Binlog {
|
|
|
46
46
|
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
47
47
|
* @returns Promise resolving to true if successful, false otherwise
|
|
48
48
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await this.#findLatestBinlogFile();
|
|
49
|
+
init() {
|
|
50
|
+
return Promise.resolve().then(() => fs__default.promises.mkdir(this._directory, { recursive: true })).then(() => this.#findLatestBinlogFile());
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
53
|
* Find the latest binlog file to continue writing
|
|
55
54
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
#findLatestBinlogFile() {
|
|
56
|
+
return Promise.resolve().then(() => fs__default.promises.readdir(this._directory)).then((files) => {
|
|
57
|
+
const fileRegEx = filePatternToRegex(this._filePattern);
|
|
58
|
+
this._currentFileIndex = 0;
|
|
59
|
+
for (const fileName of files) {
|
|
60
|
+
const match = fileName.match(fileRegEx);
|
|
61
|
+
if (match) {
|
|
62
|
+
this._currentFileIndex = Math.max(
|
|
63
|
+
this._currentFileIndex,
|
|
64
|
+
parseInt(match[1], 10)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
});
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Open the binlog for writing
|
|
72
72
|
*/
|
|
73
|
-
|
|
73
|
+
open() {
|
|
74
74
|
const filePath = path__default.join(this._directory, this.currentFileName);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
return Promise.resolve().then(() => fs__default.promises.open(filePath, "a+")).then((fileDescription) => {
|
|
76
|
+
this._currentFileDescriptor = fileDescription;
|
|
77
|
+
this._currentFile = filePath;
|
|
78
|
+
return this._currentFileDescriptor.stat();
|
|
79
|
+
}).then((fileStat) => {
|
|
80
|
+
this._currentFileSize = fileStat.size;
|
|
81
|
+
this._isOpen = true;
|
|
82
|
+
if (this._currentFileSize === 0) {
|
|
83
|
+
return this.#writeBinlogHeader();
|
|
84
|
+
} else {
|
|
85
|
+
return this.#checkBinlogHeaders(this._currentFileDescriptor).then(
|
|
86
|
+
toolkit.noop
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
84
90
|
}
|
|
85
91
|
/**
|
|
86
92
|
* Close the current binlog file
|
|
87
93
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
close() {
|
|
95
|
+
return Promise.resolve().then(() => {
|
|
96
|
+
if (!this._isOpen || !this._currentFileDescriptor) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
return this._currentFileDescriptor.close();
|
|
100
|
+
}).then(() => {
|
|
101
|
+
this._currentFileDescriptor = null;
|
|
102
|
+
this._currentFile = null;
|
|
103
|
+
this._isOpen = false;
|
|
104
|
+
});
|
|
94
105
|
}
|
|
95
106
|
/**
|
|
96
107
|
* Write a binlog entry
|
|
@@ -98,65 +109,74 @@ class Binlog {
|
|
|
98
109
|
* @param data - Data to write
|
|
99
110
|
* @returns Promise resolving to true if successful, false otherwise
|
|
100
111
|
*/
|
|
101
|
-
|
|
112
|
+
write(opcode, data) {
|
|
113
|
+
let promise = Promise.resolve();
|
|
102
114
|
if (!this._isOpen) {
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
|
|
106
|
-
await this.rotate();
|
|
115
|
+
promise = promise.then(() => this.open());
|
|
107
116
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (data instanceof tlPack.Structure) {
|
|
112
|
-
data = tlPack.tlEncode(data);
|
|
113
|
-
flags |= this.BINLOG_FLAG.TL;
|
|
114
|
-
} else {
|
|
115
|
-
data = tlPack.tlEncode(data);
|
|
116
|
-
flags |= this.BINLOG_FLAG.TL;
|
|
117
|
+
return promise.then(() => {
|
|
118
|
+
if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
|
|
119
|
+
return this.rotate();
|
|
117
120
|
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
}).then(() => {
|
|
122
|
+
toolkit.assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
123
|
+
let flags = this.BINLOG_FLAG.NONE;
|
|
124
|
+
if (!Buffer.isBuffer(data)) {
|
|
125
|
+
if (data instanceof tlPack.Structure) {
|
|
126
|
+
data = tlPack.tlEncode(data);
|
|
127
|
+
flags |= this.BINLOG_FLAG.TL;
|
|
128
|
+
} else {
|
|
129
|
+
data = tlPack.tlEncode(data);
|
|
130
|
+
flags |= this.BINLOG_FLAG.TL;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const dataLength = data.length;
|
|
134
|
+
const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;
|
|
135
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
|
|
136
|
+
headerBuffer.writeUInt32LE(opcode, 0);
|
|
137
|
+
headerBuffer.writeUInt32LE(flags, 4);
|
|
138
|
+
headerBuffer.writeUInt32LE(toolkit.timestamp(), 8);
|
|
139
|
+
headerBuffer.writeUInt32LE(dataLength, 12);
|
|
140
|
+
headerBuffer.writeUInt32LE(
|
|
141
|
+
toolkit.crc32(
|
|
142
|
+
Buffer.concat([headerBuffer.subarray(0, 16), data])
|
|
143
|
+
) >>> 0,
|
|
144
|
+
16
|
|
145
|
+
);
|
|
146
|
+
return Promise.resolve().then(() => this._currentFileDescriptor.write(headerBuffer)).then(() => this._currentFileDescriptor.write(data)).then(() => {
|
|
147
|
+
if (this._options.syncWrites) {
|
|
148
|
+
return this._currentFileDescriptor.sync();
|
|
149
|
+
}
|
|
150
|
+
}).then(() => {
|
|
151
|
+
this._currentFileSize += totalLength;
|
|
152
|
+
});
|
|
153
|
+
});
|
|
136
154
|
}
|
|
137
155
|
/**
|
|
138
156
|
* Rotate the binlog file
|
|
139
157
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
rotate() {
|
|
159
|
+
return Promise.resolve().then(() => this.close()).then(() => {
|
|
160
|
+
this._currentFileIndex++;
|
|
161
|
+
return this.open();
|
|
162
|
+
});
|
|
144
163
|
}
|
|
145
164
|
/**
|
|
146
165
|
* Write the binlog header
|
|
147
166
|
*/
|
|
148
|
-
|
|
167
|
+
#writeBinlogHeader() {
|
|
149
168
|
toolkit.assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
150
169
|
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
151
170
|
headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);
|
|
152
171
|
headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);
|
|
153
172
|
headerBuffer.writeUInt32LE(toolkit.timestamp(), 8);
|
|
154
173
|
headerBuffer.writeUInt32LE(0, 12);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
174
|
+
return Promise.resolve().then(() => this._currentFileDescriptor.write(headerBuffer)).then(() => {
|
|
175
|
+
this._currentFileSize += headerBuffer.length;
|
|
176
|
+
if (this._options.syncWrites) {
|
|
177
|
+
return this._currentFileDescriptor.sync();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
160
180
|
}
|
|
161
181
|
/**
|
|
162
182
|
* Read and parse all entries from a binlog file
|
|
@@ -164,70 +184,106 @@ class Binlog {
|
|
|
164
184
|
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
165
185
|
* @returns Array of parsed entries
|
|
166
186
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const entries = [];
|
|
180
|
-
while (position < contentSize) {
|
|
181
|
-
const entryHeaderBuffer = Buffer.allocUnsafe(
|
|
182
|
-
this.BINLOG_ENTRY_HEADER_SIZE
|
|
183
|
-
);
|
|
184
|
-
await fileHandle.read(
|
|
185
|
-
entryHeaderBuffer,
|
|
186
|
-
0,
|
|
187
|
-
this.BINLOG_ENTRY_HEADER_SIZE,
|
|
188
|
-
position
|
|
189
|
-
);
|
|
190
|
-
const opcode = entryHeaderBuffer.readUInt32LE(0);
|
|
191
|
-
const flags = entryHeaderBuffer.readUInt32LE(4);
|
|
192
|
-
const timestamp2 = entryHeaderBuffer.readUInt32LE(8);
|
|
193
|
-
const dataLength = entryHeaderBuffer.readUInt32LE(12);
|
|
194
|
-
const crc = entryHeaderBuffer.readUInt32LE(16);
|
|
195
|
-
position += this.BINLOG_ENTRY_HEADER_SIZE;
|
|
196
|
-
let data = Buffer.allocUnsafe(dataLength);
|
|
197
|
-
await fileHandle.read(data, 0, dataLength, position);
|
|
198
|
-
const calculatedCrc = toolkit.crc32(Buffer.concat([entryHeaderBuffer.subarray(0, 16), data])) >>> 0;
|
|
199
|
-
if (calculatedCrc === crc) {
|
|
200
|
-
if (toolkit.checkBitmask(flags, this.BINLOG_FLAG.TL)) {
|
|
201
|
-
data = tlPack.tlDecode(data, {
|
|
202
|
-
structures: this._options.structures
|
|
203
|
-
});
|
|
187
|
+
readEntries(filename, unsafe) {
|
|
188
|
+
var filepath = path__default.join(this._directory, filename);
|
|
189
|
+
var fileHandle;
|
|
190
|
+
var entries = [];
|
|
191
|
+
return fs__default.promises.open(filepath, "r").then((_fileHandle) => {
|
|
192
|
+
fileHandle = _fileHandle;
|
|
193
|
+
return this.#checkBinlogHeaders(fileHandle);
|
|
194
|
+
}).then((isValidHeader) => {
|
|
195
|
+
if (!isValidHeader) {
|
|
196
|
+
if (unsafe) {
|
|
197
|
+
this._log.warn("Invalid binlog file format: " + filename);
|
|
198
|
+
return;
|
|
204
199
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
timestamp: timestamp2,
|
|
208
|
-
data,
|
|
209
|
-
position: position - this.BINLOG_ENTRY_HEADER_SIZE
|
|
210
|
-
});
|
|
211
|
-
} else if (!unsafe) {
|
|
212
|
-
throw new Error(
|
|
213
|
-
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
214
|
-
);
|
|
215
|
-
} else {
|
|
216
|
-
this._log.warn(
|
|
217
|
-
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
200
|
+
return Promise.reject(
|
|
201
|
+
new Error("Invalid binlog file format:\n" + filepath)
|
|
218
202
|
);
|
|
219
203
|
}
|
|
220
|
-
|
|
204
|
+
return fileHandle.stat();
|
|
205
|
+
}).then((stats) => {
|
|
206
|
+
if (!stats) return;
|
|
207
|
+
var contentSize = stats.size - this.BINLOG_HEADER_SIZE;
|
|
208
|
+
var position = this.BINLOG_HEADER_SIZE;
|
|
209
|
+
var headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
var processNextBatch = () => {
|
|
212
|
+
if (position >= contentSize) {
|
|
213
|
+
return resolve();
|
|
214
|
+
}
|
|
215
|
+
fileHandle.read(headerBuffer, 0, this.BINLOG_ENTRY_HEADER_SIZE, position).then(() => {
|
|
216
|
+
var header = this.#readEntryHeader(headerBuffer, position);
|
|
217
|
+
var dataBuffer = Buffer.allocUnsafe(header.dataLength);
|
|
218
|
+
position += this.BINLOG_ENTRY_HEADER_SIZE;
|
|
219
|
+
return fileHandle.read(dataBuffer, 0, header.dataLength, position).then(() => {
|
|
220
|
+
var entry = this.#readEntry(
|
|
221
|
+
headerBuffer,
|
|
222
|
+
header,
|
|
223
|
+
dataBuffer.subarray(0, header.dataLength)
|
|
224
|
+
);
|
|
225
|
+
if (!entry) {
|
|
226
|
+
if (!unsafe) {
|
|
227
|
+
return Promise.reject(
|
|
228
|
+
new Error(
|
|
229
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
} else {
|
|
233
|
+
this._log.warn(
|
|
234
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
entries.push(entry);
|
|
239
|
+
}
|
|
240
|
+
position += header.dataLength;
|
|
241
|
+
}).then(() => processNextBatch()).catch(reject);
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
processNextBatch();
|
|
245
|
+
});
|
|
246
|
+
}).then(() => fileHandle?.close()).then(() => entries).catch((error) => {
|
|
247
|
+
return fileHandle.close().then(() => Promise.reject(error));
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
#readEntryHeader(buffer, position) {
|
|
251
|
+
return {
|
|
252
|
+
opcode: buffer.readUInt32LE(0),
|
|
253
|
+
flags: buffer.readUInt32LE(4),
|
|
254
|
+
timestamp: buffer.readUInt32LE(8),
|
|
255
|
+
dataLength: buffer.readUInt32LE(12),
|
|
256
|
+
crc: buffer.readUInt32LE(16),
|
|
257
|
+
position
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
#readEntry(headerBuffer, header, dataBuffer) {
|
|
261
|
+
var crc = toolkit.crc32([headerBuffer.subarray(0, 16), dataBuffer]) >>> 0;
|
|
262
|
+
if (crc !== header.crc) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
var data;
|
|
266
|
+
if (toolkit.checkBitmask(header.flags, this.BINLOG_FLAG.TL)) {
|
|
267
|
+
data = tlPack.tlDecode(dataBuffer, {
|
|
268
|
+
structures: this._options.structures
|
|
269
|
+
});
|
|
270
|
+
} else {
|
|
271
|
+
data = Buffer.from(dataBuffer);
|
|
221
272
|
}
|
|
222
|
-
|
|
223
|
-
|
|
273
|
+
return {
|
|
274
|
+
opcode: header.opcode,
|
|
275
|
+
timestamp: header.timestamp,
|
|
276
|
+
data,
|
|
277
|
+
position: header.position
|
|
278
|
+
};
|
|
224
279
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
280
|
+
#checkBinlogHeaders(fileHandle) {
|
|
281
|
+
var headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
282
|
+
return Promise.resolve().then(() => fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0)).then(() => {
|
|
283
|
+
var magic = headerBuffer.readUInt32LE(0);
|
|
284
|
+
var version = headerBuffer.readUInt32LE(4);
|
|
285
|
+
return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;
|
|
286
|
+
});
|
|
231
287
|
}
|
|
232
288
|
/**
|
|
233
289
|
* Get the current binlog file index
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/Binlog.ts","../src/index.ts"],"sourcesContent":["import { Structure, tlDecode, tlEncode } from '@andrew_l/tl-pack';\nimport {\n type Logger,\n assert,\n checkBitmask,\n crc32,\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 structures?: Structure.Constructor[];\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 = {\n ...options,\n structures: options.structures ? [...options.structures] : [],\n };\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 if (data instanceof Structure) {\n data = tlEncode(data);\n flags |= this.BINLOG_FLAG.TL;\n } else {\n data = tlEncode(data) as Buffer;\n flags |= this.BINLOG_FLAG.TL;\n }\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 structures: this._options.structures,\n });\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","path","fs","assert","Structure","tlEncode","timestamp","crc32","checkBitmask","tlDecode","deepDefaults"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/Binlog.ts","../src/index.ts"],"sourcesContent":["import { Structure, tlDecode, tlEncode } from '@andrew_l/tl-pack';\nimport {\n type Logger,\n assert,\n checkBitmask,\n crc32,\n logger,\n noop,\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 structures?: Structure.Constructor[];\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\ninterface EntryHeader {\n opcode: number;\n flags: number;\n timestamp: number;\n dataLength: number;\n crc: number;\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 = {\n ...options,\n structures: options.structures ? [...options.structures] : [],\n };\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 init(): Promise<void> {\n return Promise.resolve()\n .then(() => fs.promises.mkdir(this._directory, { recursive: true }))\n .then(() => this.#findLatestBinlogFile());\n }\n\n /**\n * Find the latest binlog file to continue writing\n */\n #findLatestBinlogFile(): Promise<void> {\n return Promise.resolve()\n .then(() => fs.promises.readdir(this._directory))\n .then(files => {\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 /**\n * Open the binlog for writing\n */\n public open(): Promise<void> {\n const filePath = path.join(this._directory, this.currentFileName);\n\n return Promise.resolve()\n .then(() => fs.promises.open(filePath, 'a+'))\n .then(fileDescription => {\n this._currentFileDescriptor = fileDescription;\n this._currentFile = filePath;\n\n return this._currentFileDescriptor.stat();\n })\n .then(fileStat => {\n this._currentFileSize = fileStat.size;\n this._isOpen = true;\n\n // Write the binlog header\n if (this._currentFileSize === 0) {\n return this.#writeBinlogHeader();\n } else {\n return this.#checkBinlogHeaders(this._currentFileDescriptor!).then(\n noop,\n );\n }\n });\n }\n\n /**\n * Close the current binlog file\n */\n public close(): Promise<void> {\n return Promise.resolve()\n .then(() => {\n if (!this._isOpen || !this._currentFileDescriptor) {\n return;\n }\n\n return this._currentFileDescriptor.close();\n })\n .then(() => {\n this._currentFileDescriptor = null;\n this._currentFile = null;\n this._isOpen = false;\n });\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 write(opcode: number, data: unknown): Promise<void> {\n let promise = Promise.resolve();\n\n if (!this._isOpen) {\n promise = promise.then(() => this.open());\n }\n\n // Check if we need to rotate the file\n return promise\n .then(() => {\n if (\n this._options.rotation &&\n this._currentFileSize >= this._options.maxFileSize\n ) {\n return this.rotate();\n }\n })\n .then(() => {\n assert.ok(this._currentFileDescriptor, 'File descriptor is null');\n\n let flags = this.BINLOG_FLAG.NONE;\n\n if (!Buffer.isBuffer(data)) {\n if (data instanceof Structure) {\n data = tlEncode(data);\n flags |= this.BINLOG_FLAG.TL;\n } else {\n data = tlEncode(data) as Buffer;\n flags |= this.BINLOG_FLAG.TL;\n }\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(\n Buffer.concat([headerBuffer.subarray(0, 16), data as Buffer]),\n ) >>> 0,\n 16,\n );\n\n // Write the entry\n return Promise.resolve()\n .then(() => this._currentFileDescriptor!.write(headerBuffer))\n .then(() => this._currentFileDescriptor!.write(data as Buffer))\n .then(() => {\n if (this._options.syncWrites) {\n return this._currentFileDescriptor!.sync();\n }\n })\n .then(() => {\n this._currentFileSize += totalLength;\n });\n });\n }\n\n /**\n * Rotate the binlog file\n */\n public rotate(): Promise<void> {\n return Promise.resolve()\n .then(() => this.close())\n .then(() => {\n this._currentFileIndex++;\n return this.open();\n });\n }\n\n /**\n * Write the binlog header\n */\n #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 return Promise.resolve()\n .then(() => this._currentFileDescriptor!.write(headerBuffer))\n .then(() => {\n this._currentFileSize += headerBuffer.length;\n\n if (this._options.syncWrites) {\n return this._currentFileDescriptor!.sync();\n }\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 readEntries<TData = Buffer>(\n filename: string,\n unsafe?: boolean,\n ): Promise<BinlogEntry<TData>[]> {\n var filepath = path.join(this._directory, filename);\n var fileHandle: fs.promises.FileHandle;\n var entries: BinlogEntry<TData>[] = [];\n\n return fs.promises\n .open(filepath, 'r')\n .then(_fileHandle => {\n fileHandle = _fileHandle;\n return this.#checkBinlogHeaders(fileHandle);\n })\n .then(isValidHeader => {\n if (!isValidHeader) {\n if (unsafe) {\n this._log.warn('Invalid binlog file format: ' + filename);\n return;\n }\n return Promise.reject(\n new Error('Invalid binlog file format:\\n' + filepath),\n );\n }\n\n return fileHandle.stat();\n })\n .then(stats => {\n if (!stats) return;\n\n var contentSize = stats.size - this.BINLOG_HEADER_SIZE;\n var position = this.BINLOG_HEADER_SIZE;\n var headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);\n\n return new Promise<void>((resolve, reject) => {\n var processNextBatch = () => {\n if (position >= contentSize) {\n return resolve();\n }\n\n fileHandle\n .read(headerBuffer, 0, this.BINLOG_ENTRY_HEADER_SIZE, position)\n .then(() => {\n var header = this.#readEntryHeader(headerBuffer, position);\n var dataBuffer = Buffer.allocUnsafe(header.dataLength);\n\n position += this.BINLOG_ENTRY_HEADER_SIZE;\n\n return fileHandle\n .read(dataBuffer, 0, header.dataLength, position)\n .then(() => {\n var entry = this.#readEntry(\n headerBuffer,\n header,\n dataBuffer.subarray(0, header.dataLength),\n );\n\n if (!entry) {\n if (!unsafe) {\n return Promise.reject(\n new Error(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n ),\n );\n } else {\n this._log.warn(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n );\n }\n } else {\n entries.push(entry);\n }\n\n position += header.dataLength;\n })\n .then(() => processNextBatch())\n .catch(reject);\n });\n };\n\n processNextBatch();\n });\n })\n .then(() => fileHandle?.close())\n .then(() => entries)\n .catch(error => {\n return fileHandle.close().then(() => Promise.reject(error));\n });\n }\n\n #readEntryHeader(buffer: Buffer, position: number): EntryHeader {\n return {\n opcode: buffer.readUInt32LE(0),\n flags: buffer.readUInt32LE(4),\n timestamp: buffer.readUInt32LE(8),\n dataLength: buffer.readUInt32LE(12),\n crc: buffer.readUInt32LE(16),\n position,\n };\n }\n\n #readEntry(\n headerBuffer: Buffer,\n header: EntryHeader,\n dataBuffer: Buffer,\n ): BinlogEntry<any> | null {\n // Verify CRC\n var crc = crc32([headerBuffer.subarray(0, 16), dataBuffer]) >>> 0;\n\n if (crc !== header.crc) {\n return null;\n }\n\n var data: any;\n\n if (checkBitmask(header.flags, this.BINLOG_FLAG.TL)) {\n data = tlDecode(dataBuffer, {\n structures: this._options.structures,\n });\n } else {\n // Make copy\n data = Buffer.from(dataBuffer);\n }\n\n return {\n opcode: header.opcode,\n timestamp: header.timestamp,\n data: data,\n position: header.position,\n };\n }\n\n #checkBinlogHeaders(fileHandle: fs.promises.FileHandle): Promise<boolean> {\n var headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);\n\n return Promise.resolve()\n .then(() => fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0))\n .then(() => {\n var magic = headerBuffer.readUInt32LE(0);\n var version = headerBuffer.readUInt32LE(4);\n\n return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;\n });\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","path","fs","noop","assert","Structure","tlEncode","timestamp","crc32","checkBitmask","tlDecode","deepDefaults"],"mappings":";;;;;;;;;;;;AAmFO,MAAM,MAAA,CAAO;AAAA,EACV,QAAA;AAAA,EACA,YAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,sBAAA;AAAA,EACA,gBAAA;AAAA,EACA,iBAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EAES,YAAA,GAAuB,UAAA;AAAA,EACvB,cAAA,GAAyB,CAAA;AAAA,EACzB,kBAAA,GAA6B,EAAA;AAAA,EAC7B,wBAAA,GAAmC,EAAA;AAAA,EACnC,WAAA,GAAc,OAAO,MAAA,CAAO;AAAA,IAC3C,IAAA,EAAM,CAAA;AAAA,IACN,IAAI,CAAA,IAAK;AAAA,GACD,CAAA;AAAA,EAEV,YAAY,OAAA,EAAwB;AAClC,IAAA,IAAA,CAAK,IAAA,GAAOA,eAAO,QAAQ,CAAA;AAC3B,IAAA,IAAA,CAAK,QAAA,GAAW;AAAA,MACd,GAAG,OAAA;AAAA,MACH,UAAA,EAAY,QAAQ,UAAA,GAAa,CAAC,GAAG,OAAA,CAAQ,UAAU,IAAI;AAAC,KAC9D;AACA,IAAA,IAAA,CAAK,YAAA,GAAeC,aAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,IAAI,CAAA;AAC9C,IAAA,IAAA,CAAK,UAAA,GAAaA,aAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAC3C,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,IAAA,IAAA,CAAK,sBAAA,GAAyB,IAAA;AAC9B,IAAA,IAAA,CAAK,gBAAA,GAAmB,CAAA;AACxB,IAAA,IAAA,CAAK,iBAAA,GAAoB,CAAA;AACzB,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,IAAA,GAAsB;AAC3B,IAAA,OAAO,OAAA,CAAQ,SAAQ,CACpB,IAAA,CAAK,MAAMC,WAAA,CAAG,QAAA,CAAS,MAAM,IAAA,CAAK,UAAA,EAAY,EAAE,SAAA,EAAW,IAAA,EAAM,CAAC,CAAA,CAClE,KAAK,MAAM,IAAA,CAAK,uBAAuB,CAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAA,GAAuC;AACrC,IAAA,OAAO,OAAA,CAAQ,OAAA,EAAQ,CACpB,IAAA,CAAK,MAAMA,WAAA,CAAG,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAC,CAAA,CAC/C,KAAK,CAAA,KAAA,KAAS;AACb,MAAA,MAAM,SAAA,GAAY,kBAAA,CAAmB,IAAA,CAAK,YAAY,CAAA;AAEtD,MAAA,IAAA,CAAK,iBAAA,GAAoB,CAAA;AAEzB,MAAA,KAAA,MAAW,YAAY,KAAA,EAAO;AAC5B,QAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,SAAS,CAAA;AAEtC,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,IAAA,CAAK,oBAAoB,IAAA,CAAK,GAAA;AAAA,YAC5B,IAAA,CAAK,iBAAA;AAAA,YACL,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,EAAG,EAAE;AAAA,WACvB;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKO,IAAA,GAAsB;AAC3B,IAAA,MAAM,WAAWD,aAAA,CAAK,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,KAAK,eAAe,CAAA;AAEhE,IAAA,OAAO,OAAA,CAAQ,OAAA,EAAQ,CACpB,IAAA,CAAK,MAAMC,WAAA,CAAG,QAAA,CAAS,IAAA,CAAK,QAAA,EAAU,IAAI,CAAC,CAAA,CAC3C,KAAK,CAAA,eAAA,KAAmB;AACvB,MAAA,IAAA,CAAK,sBAAA,GAAyB,eAAA;AAC9B,MAAA,IAAA,CAAK,YAAA,GAAe,QAAA;AAEpB,MAAA,OAAO,IAAA,CAAK,uBAAuB,IAAA,EAAK;AAAA,IAC1C,CAAC,CAAA,CACA,IAAA,CAAK,CAAA,QAAA,KAAY;AAChB,MAAA,IAAA,CAAK,mBAAmB,QAAA,CAAS,IAAA;AACjC,MAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAGf,MAAA,IAAI,IAAA,CAAK,qBAAqB,CAAA,EAAG;AAC/B,QAAA,OAAO,KAAK,kBAAA,EAAmB;AAAA,MACjC,CAAA,MAAO;AACL,QAAA,OAAO,IAAA,CAAK,mBAAA,CAAoB,IAAA,CAAK,sBAAuB,CAAA,CAAE,IAAA;AAAA,UAC5DC;AAAA,SACF;AAAA,MACF;AAAA,IACF,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKO,KAAA,GAAuB;AAC5B,IAAA,OAAO,OAAA,CAAQ,OAAA,EAAQ,CACpB,IAAA,CAAK,MAAM;AACV,MAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,CAAC,KAAK,sBAAA,EAAwB;AACjD,QAAA;AAAA,MACF;AAEA,MAAA,OAAO,IAAA,CAAK,uBAAuB,KAAA,EAAM;AAAA,IAC3C,CAAC,CAAA,CACA,IAAA,CAAK,MAAM;AACV,MAAA,IAAA,CAAK,sBAAA,GAAyB,IAAA;AAC9B,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AAAA,IACjB,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,KAAA,CAAM,QAAgB,IAAA,EAA8B;AACzD,IAAA,IAAI,OAAA,GAAU,QAAQ,OAAA,EAAQ;AAE9B,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,OAAA,GAAU,OAAA,CAAQ,IAAA,CAAK,MAAM,IAAA,CAAK,MAAM,CAAA;AAAA,IAC1C;AAGA,IAAA,OAAO,OAAA,CACJ,KAAK,MAAM;AACV,MAAA,IACE,KAAK,QAAA,CAAS,QAAA,IACd,KAAK,gBAAA,IAAoB,IAAA,CAAK,SAAS,WAAA,EACvC;AACA,QAAA,OAAO,KAAK,MAAA,EAAO;AAAA,MACrB;AAAA,IACF,CAAC,CAAA,CACA,IAAA,CAAK,MAAM;AACV,MAAAC,cAAA,CAAO,EAAA,CAAG,IAAA,CAAK,sBAAA,EAAwB,yBAAyB,CAAA;AAEhE,MAAA,IAAI,KAAA,GAAQ,KAAK,WAAA,CAAY,IAAA;AAE7B,MAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AAC1B,QAAA,IAAI,gBAAgBC,gBAAA,EAAW;AAC7B,UAAA,IAAA,GAAOC,gBAAS,IAAI,CAAA;AACpB,UAAA,KAAA,IAAS,KAAK,WAAA,CAAY,EAAA;AAAA,QAC5B,CAAA,MAAO;AACL,UAAA,IAAA,GAAOA,gBAAS,IAAI,CAAA;AACpB,UAAA,KAAA,IAAS,KAAK,WAAA,CAAY,EAAA;AAAA,QAC5B;AAAA,MACF;AAGA,MAAA,MAAM,aAAc,IAAA,CAAgB,MAAA;AACpC,MAAA,MAAM,WAAA,GAAc,KAAK,wBAAA,GAA2B,UAAA;AAEpD,MAAA,MAAM,YAAA,GAAe,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,wBAAwB,CAAA;AACrE,MAAA,YAAA,CAAa,aAAA,CAAc,QAAQ,CAAC,CAAA;AACpC,MAAA,YAAA,CAAa,aAAA,CAAc,OAAO,CAAC,CAAA;AACnC,MAAA,YAAA,CAAa,aAAA,CAAcC,iBAAA,EAAU,EAAG,CAAC,CAAA;AACzC,MAAA,YAAA,CAAa,aAAA,CAAc,YAAY,EAAE,CAAA;AAEzC,MAAA,YAAA,CAAa,aAAA;AAAA,QACXC,aAAA;AAAA,UACE,MAAA,CAAO,OAAO,CAAC,YAAA,CAAa,SAAS,CAAA,EAAG,EAAE,CAAA,EAAG,IAAc,CAAC;AAAA,SAC9D,KAAM,CAAA;AAAA,QACN;AAAA,OACF;AAGA,MAAA,OAAO,OAAA,CAAQ,SAAQ,CACpB,IAAA,CAAK,MAAM,IAAA,CAAK,sBAAA,CAAwB,MAAM,YAAY,CAAC,EAC3D,IAAA,CAAK,MAAM,KAAK,sBAAA,CAAwB,KAAA,CAAM,IAAc,CAAC,CAAA,CAC7D,KAAK,MAAM;AACV,QAAA,IAAI,IAAA,CAAK,SAAS,UAAA,EAAY;AAC5B,UAAA,OAAO,IAAA,CAAK,uBAAwB,IAAA,EAAK;AAAA,QAC3C;AAAA,MACF,CAAC,CAAA,CACA,IAAA,CAAK,MAAM;AACV,QAAA,IAAA,CAAK,gBAAA,IAAoB,WAAA;AAAA,MAC3B,CAAC,CAAA;AAAA,IACL,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKO,MAAA,GAAwB;AAC7B,IAAA,OAAO,OAAA,CAAQ,OAAA,EAAQ,CACpB,IAAA,CAAK,MAAM,KAAK,KAAA,EAAO,CAAA,CACvB,IAAA,CAAK,MAAM;AACV,MAAA,IAAA,CAAK,iBAAA,EAAA;AACL,MAAA,OAAO,KAAK,IAAA,EAAK;AAAA,IACnB,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAA,GAAoC;AAClC,IAAAJ,cAAA,CAAO,EAAA,CAAG,IAAA,CAAK,sBAAA,EAAwB,yBAAyB,CAAA;AAEhE,IAAA,MAAM,YAAA,GAAe,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,kBAAkB,CAAA;AAE/D,IAAA,YAAA,CAAa,aAAA,CAAc,IAAA,CAAK,YAAA,EAAc,CAAC,CAAA;AAC/C,IAAA,YAAA,CAAa,aAAA,CAAc,IAAA,CAAK,cAAA,EAAgB,CAAC,CAAA;AACjD,IAAA,YAAA,CAAa,aAAA,CAAcG,iBAAA,EAAU,EAAG,CAAC,CAAA;AACzC,IAAA,YAAA,CAAa,aAAA,CAAc,GAAG,EAAE,CAAA;AAEhC,IAAA,OAAO,OAAA,CAAQ,OAAA,EAAQ,CACpB,IAAA,CAAK,MAAM,IAAA,CAAK,sBAAA,CAAwB,KAAA,CAAM,YAAY,CAAC,CAAA,CAC3D,IAAA,CAAK,MAAM;AACV,MAAA,IAAA,CAAK,oBAAoB,YAAA,CAAa,MAAA;AAEtC,MAAA,IAAI,IAAA,CAAK,SAAS,UAAA,EAAY;AAC5B,QAAA,OAAO,IAAA,CAAK,uBAAwB,IAAA,EAAK;AAAA,MAC3C;AAAA,IACF,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,WAAA,CACL,UACA,MAAA,EAC+B;AAC/B,IAAA,IAAI,QAAA,GAAWN,aAAA,CAAK,IAAA,CAAK,IAAA,CAAK,YAAY,QAAQ,CAAA;AAClD,IAAA,IAAI,UAAA;AACJ,IAAA,IAAI,UAAgC,EAAC;AAErC,IAAA,OAAOC,YAAG,QAAA,CACP,IAAA,CAAK,UAAU,GAAG,CAAA,CAClB,KAAK,CAAA,WAAA,KAAe;AACnB,MAAA,UAAA,GAAa,WAAA;AACb,MAAA,OAAO,IAAA,CAAK,oBAAoB,UAAU,CAAA;AAAA,IAC5C,CAAC,CAAA,CACA,IAAA,CAAK,CAAA,aAAA,KAAiB;AACrB,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,8BAAA,GAAiC,QAAQ,CAAA;AACxD,UAAA;AAAA,QACF;AACA,QAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,UACb,IAAI,KAAA,CAAM,+BAAA,GAAkC,QAAQ;AAAA,SACtD;AAAA,MACF;AAEA,MAAA,OAAO,WAAW,IAAA,EAAK;AAAA,IACzB,CAAC,CAAA,CACA,IAAA,CAAK,CAAA,KAAA,KAAS;AACb,MAAA,IAAI,CAAC,KAAA,EAAO;AAEZ,MAAA,IAAI,WAAA,GAAc,KAAA,CAAM,IAAA,GAAO,IAAA,CAAK,kBAAA;AACpC,MAAA,IAAI,WAAW,IAAA,CAAK,kBAAA;AACpB,MAAA,IAAI,YAAA,GAAe,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,wBAAwB,CAAA;AAEnE,MAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC5C,QAAA,IAAI,mBAAmB,MAAM;AAC3B,UAAA,IAAI,YAAY,WAAA,EAAa;AAC3B,YAAA,OAAO,OAAA,EAAQ;AAAA,UACjB;AAEA,UAAA,UAAA,CACG,IAAA,CAAK,cAAc,CAAA,EAAG,IAAA,CAAK,0BAA0B,QAAQ,CAAA,CAC7D,KAAK,MAAM;AACV,YAAA,IAAI,MAAA,GAAS,IAAA,CAAK,gBAAA,CAAiB,YAAA,EAAc,QAAQ,CAAA;AACzD,YAAA,IAAI,UAAA,GAAa,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,UAAU,CAAA;AAErD,YAAA,QAAA,IAAY,IAAA,CAAK,wBAAA;AAEjB,YAAA,OAAO,UAAA,CACJ,KAAK,UAAA,EAAY,CAAA,EAAG,OAAO,UAAA,EAAY,QAAQ,CAAA,CAC/C,IAAA,CAAK,MAAM;AACV,cAAA,IAAI,QAAQ,IAAA,CAAK,UAAA;AAAA,gBACf,YAAA;AAAA,gBACA,MAAA;AAAA,gBACA,UAAA,CAAW,QAAA,CAAS,CAAA,EAAG,MAAA,CAAO,UAAU;AAAA,eAC1C;AAEA,cAAA,IAAI,CAAC,KAAA,EAAO;AACV,gBAAA,IAAI,CAAC,MAAA,EAAQ;AACX,kBAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,oBACb,IAAI,KAAA;AAAA,sBACF,CAAA,yBAAA,EAA4B,QAAA,GAAW,IAAA,CAAK,wBAAwB,CAAA;AAAA;AACtE,mBACF;AAAA,gBACF,CAAA,MAAO;AACL,kBAAA,IAAA,CAAK,IAAA,CAAK,IAAA;AAAA,oBACR,CAAA,yBAAA,EAA4B,QAAA,GAAW,IAAA,CAAK,wBAAwB,CAAA;AAAA,mBACtE;AAAA,gBACF;AAAA,cACF,CAAA,MAAO;AACL,gBAAA,OAAA,CAAQ,KAAK,KAAK,CAAA;AAAA,cACpB;AAEA,cAAA,QAAA,IAAY,MAAA,CAAO,UAAA;AAAA,YACrB,CAAC,EACA,IAAA,CAAK,MAAM,kBAAkB,CAAA,CAC7B,MAAM,MAAM,CAAA;AAAA,UACjB,CAAC,CAAA;AAAA,QACL,CAAA;AAEA,QAAA,gBAAA,EAAiB;AAAA,MACnB,CAAC,CAAA;AAAA,IACH,CAAC,CAAA,CACA,IAAA,CAAK,MAAM,UAAA,EAAY,KAAA,EAAO,CAAA,CAC9B,IAAA,CAAK,MAAM,OAAO,CAAA,CAClB,MAAM,CAAA,KAAA,KAAS;AACd,MAAA,OAAO,UAAA,CAAW,OAAM,CAAE,IAAA,CAAK,MAAM,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAC5D,CAAC,CAAA;AAAA,EACL;AAAA,EAEA,gBAAA,CAAiB,QAAgB,QAAA,EAA+B;AAC9D,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,MAAA,CAAO,YAAA,CAAa,CAAC,CAAA;AAAA,MAC7B,KAAA,EAAO,MAAA,CAAO,YAAA,CAAa,CAAC,CAAA;AAAA,MAC5B,SAAA,EAAW,MAAA,CAAO,YAAA,CAAa,CAAC,CAAA;AAAA,MAChC,UAAA,EAAY,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA;AAAA,MAClC,GAAA,EAAK,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA;AAAA,MAC3B;AAAA,KACF;AAAA,EACF;AAAA,EAEA,UAAA,CACE,YAAA,EACA,MAAA,EACA,UAAA,EACyB;AAEzB,IAAA,IAAI,GAAA,GAAMM,aAAA,CAAM,CAAC,YAAA,CAAa,QAAA,CAAS,GAAG,EAAE,CAAA,EAAG,UAAU,CAAC,CAAA,KAAM,CAAA;AAEhE,IAAA,IAAI,GAAA,KAAQ,OAAO,GAAA,EAAK;AACtB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IAAI,IAAA;AAEJ,IAAA,IAAIC,qBAAa,MAAA,CAAO,KAAA,EAAO,IAAA,CAAK,WAAA,CAAY,EAAE,CAAA,EAAG;AACnD,MAAA,IAAA,GAAOC,gBAAS,UAAA,EAAY;AAAA,QAC1B,UAAA,EAAY,KAAK,QAAA,CAAS;AAAA,OAC3B,CAAA;AAAA,IACH,CAAA,MAAO;AAEL,MAAA,IAAA,GAAO,MAAA,CAAO,KAAK,UAAU,CAAA;AAAA,IAC/B;AAEA,IAAA,OAAO;AAAA,MACL,QAAQ,MAAA,CAAO,MAAA;AAAA,MACf,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,IAAA;AAAA,MACA,UAAU,MAAA,CAAO;AAAA,KACnB;AAAA,EACF;AAAA,EAEA,oBAAoB,UAAA,EAAsD;AACxE,IAAA,IAAI,YAAA,GAAe,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,kBAAkB,CAAA;AAE7D,IAAA,OAAO,OAAA,CAAQ,OAAA,EAAQ,CACpB,IAAA,CAAK,MAAM,UAAA,CAAW,IAAA,CAAK,YAAA,EAAc,CAAA,EAAG,KAAK,kBAAA,EAAoB,CAAC,CAAC,CAAA,CACvE,KAAK,MAAM;AACV,MAAA,IAAI,KAAA,GAAQ,YAAA,CAAa,YAAA,CAAa,CAAC,CAAA;AACvC,MAAA,IAAI,OAAA,GAAU,YAAA,CAAa,YAAA,CAAa,CAAC,CAAA;AAEzC,MAAA,OAAO,KAAA,KAAU,IAAA,CAAK,YAAA,IAAgB,OAAA,KAAY,IAAA,CAAK,cAAA;AAAA,IACzD,CAAC,CAAA;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,gBAAA,GAA2B;AAC7B,IAAA,OAAO,IAAA,CAAK,iBAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAA,GAA0B;AAC5B,IAAA,OAAO,KAAK,YAAA,CAAa,UAAA;AAAA,MACvB,SAAA;AAAA,MACA,MAAA,CAAO,KAAK,iBAAiB;AAAA,KAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAAA,GAAoB;AACtB,IAAA,OAAO,IAAA,CAAK,UAAA;AAAA,EACd;AACF;AAEA,SAAS,mBAAmB,KAAA,EAAuB;AACjD,EAAA,OAAO,IAAI,MAAA,CAAO,KAAA,CAAM,UAAA,CAAW,SAAA,EAAW,QAAQ,CAAC,CAAA;AACzD;;AC/dA,MAAM,WAAA,GAA6B;AAAA,EACjC,IAAA,EAAM,2BAAA;AAAA,EACN,WAAA,EAAa,SAAA;AAAA,EACb,QAAA,EAAU,IAAA;AAAA,EACV,UAAA,EAAY;AACd,CAAA;AAMO,SAAS,aAAa,OAAA,EAAyC;AACpE,EAAA,OAAO,IAAI,MAAA,CAAOC,oBAAA,CAAa,OAAA,EAAS,WAAW,CAAC,CAAA;AACtD;;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger, assert, timestamp, crc32, checkBitmask, deepDefaults } from '@andrew_l/toolkit';
|
|
1
|
+
import { logger, noop, assert, timestamp, crc32, checkBitmask, deepDefaults } from '@andrew_l/toolkit';
|
|
2
2
|
import { Structure, tlEncode, tlDecode } from '@andrew_l/tl-pack';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -39,51 +39,62 @@ class Binlog {
|
|
|
39
39
|
* Initialize the binlog system, ensuring directory exists and pick most recent binlog file
|
|
40
40
|
* @returns Promise resolving to true if successful, false otherwise
|
|
41
41
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
await this.#findLatestBinlogFile();
|
|
42
|
+
init() {
|
|
43
|
+
return Promise.resolve().then(() => fs.promises.mkdir(this._directory, { recursive: true })).then(() => this.#findLatestBinlogFile());
|
|
45
44
|
}
|
|
46
45
|
/**
|
|
47
46
|
* Find the latest binlog file to continue writing
|
|
48
47
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
#findLatestBinlogFile() {
|
|
49
|
+
return Promise.resolve().then(() => fs.promises.readdir(this._directory)).then((files) => {
|
|
50
|
+
const fileRegEx = filePatternToRegex(this._filePattern);
|
|
51
|
+
this._currentFileIndex = 0;
|
|
52
|
+
for (const fileName of files) {
|
|
53
|
+
const match = fileName.match(fileRegEx);
|
|
54
|
+
if (match) {
|
|
55
|
+
this._currentFileIndex = Math.max(
|
|
56
|
+
this._currentFileIndex,
|
|
57
|
+
parseInt(match[1], 10)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
});
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* Open the binlog for writing
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
open() {
|
|
67
67
|
const filePath = path.join(this._directory, this.currentFileName);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
return Promise.resolve().then(() => fs.promises.open(filePath, "a+")).then((fileDescription) => {
|
|
69
|
+
this._currentFileDescriptor = fileDescription;
|
|
70
|
+
this._currentFile = filePath;
|
|
71
|
+
return this._currentFileDescriptor.stat();
|
|
72
|
+
}).then((fileStat) => {
|
|
73
|
+
this._currentFileSize = fileStat.size;
|
|
74
|
+
this._isOpen = true;
|
|
75
|
+
if (this._currentFileSize === 0) {
|
|
76
|
+
return this.#writeBinlogHeader();
|
|
77
|
+
} else {
|
|
78
|
+
return this.#checkBinlogHeaders(this._currentFileDescriptor).then(
|
|
79
|
+
noop
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
77
83
|
}
|
|
78
84
|
/**
|
|
79
85
|
* Close the current binlog file
|
|
80
86
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
close() {
|
|
88
|
+
return Promise.resolve().then(() => {
|
|
89
|
+
if (!this._isOpen || !this._currentFileDescriptor) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
return this._currentFileDescriptor.close();
|
|
93
|
+
}).then(() => {
|
|
94
|
+
this._currentFileDescriptor = null;
|
|
95
|
+
this._currentFile = null;
|
|
96
|
+
this._isOpen = false;
|
|
97
|
+
});
|
|
87
98
|
}
|
|
88
99
|
/**
|
|
89
100
|
* Write a binlog entry
|
|
@@ -91,65 +102,74 @@ class Binlog {
|
|
|
91
102
|
* @param data - Data to write
|
|
92
103
|
* @returns Promise resolving to true if successful, false otherwise
|
|
93
104
|
*/
|
|
94
|
-
|
|
105
|
+
write(opcode, data) {
|
|
106
|
+
let promise = Promise.resolve();
|
|
95
107
|
if (!this._isOpen) {
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
|
|
99
|
-
await this.rotate();
|
|
108
|
+
promise = promise.then(() => this.open());
|
|
100
109
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (data instanceof Structure) {
|
|
105
|
-
data = tlEncode(data);
|
|
106
|
-
flags |= this.BINLOG_FLAG.TL;
|
|
107
|
-
} else {
|
|
108
|
-
data = tlEncode(data);
|
|
109
|
-
flags |= this.BINLOG_FLAG.TL;
|
|
110
|
+
return promise.then(() => {
|
|
111
|
+
if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
|
|
112
|
+
return this.rotate();
|
|
110
113
|
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
}).then(() => {
|
|
115
|
+
assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
116
|
+
let flags = this.BINLOG_FLAG.NONE;
|
|
117
|
+
if (!Buffer.isBuffer(data)) {
|
|
118
|
+
if (data instanceof Structure) {
|
|
119
|
+
data = tlEncode(data);
|
|
120
|
+
flags |= this.BINLOG_FLAG.TL;
|
|
121
|
+
} else {
|
|
122
|
+
data = tlEncode(data);
|
|
123
|
+
flags |= this.BINLOG_FLAG.TL;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const dataLength = data.length;
|
|
127
|
+
const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;
|
|
128
|
+
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
|
|
129
|
+
headerBuffer.writeUInt32LE(opcode, 0);
|
|
130
|
+
headerBuffer.writeUInt32LE(flags, 4);
|
|
131
|
+
headerBuffer.writeUInt32LE(timestamp(), 8);
|
|
132
|
+
headerBuffer.writeUInt32LE(dataLength, 12);
|
|
133
|
+
headerBuffer.writeUInt32LE(
|
|
134
|
+
crc32(
|
|
135
|
+
Buffer.concat([headerBuffer.subarray(0, 16), data])
|
|
136
|
+
) >>> 0,
|
|
137
|
+
16
|
|
138
|
+
);
|
|
139
|
+
return Promise.resolve().then(() => this._currentFileDescriptor.write(headerBuffer)).then(() => this._currentFileDescriptor.write(data)).then(() => {
|
|
140
|
+
if (this._options.syncWrites) {
|
|
141
|
+
return this._currentFileDescriptor.sync();
|
|
142
|
+
}
|
|
143
|
+
}).then(() => {
|
|
144
|
+
this._currentFileSize += totalLength;
|
|
145
|
+
});
|
|
146
|
+
});
|
|
129
147
|
}
|
|
130
148
|
/**
|
|
131
149
|
* Rotate the binlog file
|
|
132
150
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
rotate() {
|
|
152
|
+
return Promise.resolve().then(() => this.close()).then(() => {
|
|
153
|
+
this._currentFileIndex++;
|
|
154
|
+
return this.open();
|
|
155
|
+
});
|
|
137
156
|
}
|
|
138
157
|
/**
|
|
139
158
|
* Write the binlog header
|
|
140
159
|
*/
|
|
141
|
-
|
|
160
|
+
#writeBinlogHeader() {
|
|
142
161
|
assert.ok(this._currentFileDescriptor, "File descriptor is null");
|
|
143
162
|
const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
144
163
|
headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);
|
|
145
164
|
headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);
|
|
146
165
|
headerBuffer.writeUInt32LE(timestamp(), 8);
|
|
147
166
|
headerBuffer.writeUInt32LE(0, 12);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
return Promise.resolve().then(() => this._currentFileDescriptor.write(headerBuffer)).then(() => {
|
|
168
|
+
this._currentFileSize += headerBuffer.length;
|
|
169
|
+
if (this._options.syncWrites) {
|
|
170
|
+
return this._currentFileDescriptor.sync();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
153
173
|
}
|
|
154
174
|
/**
|
|
155
175
|
* Read and parse all entries from a binlog file
|
|
@@ -157,70 +177,106 @@ class Binlog {
|
|
|
157
177
|
* @param unsafe - Ignore broken binlog records otherwise throws error
|
|
158
178
|
* @returns Array of parsed entries
|
|
159
179
|
*/
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const entries = [];
|
|
173
|
-
while (position < contentSize) {
|
|
174
|
-
const entryHeaderBuffer = Buffer.allocUnsafe(
|
|
175
|
-
this.BINLOG_ENTRY_HEADER_SIZE
|
|
176
|
-
);
|
|
177
|
-
await fileHandle.read(
|
|
178
|
-
entryHeaderBuffer,
|
|
179
|
-
0,
|
|
180
|
-
this.BINLOG_ENTRY_HEADER_SIZE,
|
|
181
|
-
position
|
|
182
|
-
);
|
|
183
|
-
const opcode = entryHeaderBuffer.readUInt32LE(0);
|
|
184
|
-
const flags = entryHeaderBuffer.readUInt32LE(4);
|
|
185
|
-
const timestamp2 = entryHeaderBuffer.readUInt32LE(8);
|
|
186
|
-
const dataLength = entryHeaderBuffer.readUInt32LE(12);
|
|
187
|
-
const crc = entryHeaderBuffer.readUInt32LE(16);
|
|
188
|
-
position += this.BINLOG_ENTRY_HEADER_SIZE;
|
|
189
|
-
let data = Buffer.allocUnsafe(dataLength);
|
|
190
|
-
await fileHandle.read(data, 0, dataLength, position);
|
|
191
|
-
const calculatedCrc = crc32(Buffer.concat([entryHeaderBuffer.subarray(0, 16), data])) >>> 0;
|
|
192
|
-
if (calculatedCrc === crc) {
|
|
193
|
-
if (checkBitmask(flags, this.BINLOG_FLAG.TL)) {
|
|
194
|
-
data = tlDecode(data, {
|
|
195
|
-
structures: this._options.structures
|
|
196
|
-
});
|
|
180
|
+
readEntries(filename, unsafe) {
|
|
181
|
+
var filepath = path.join(this._directory, filename);
|
|
182
|
+
var fileHandle;
|
|
183
|
+
var entries = [];
|
|
184
|
+
return fs.promises.open(filepath, "r").then((_fileHandle) => {
|
|
185
|
+
fileHandle = _fileHandle;
|
|
186
|
+
return this.#checkBinlogHeaders(fileHandle);
|
|
187
|
+
}).then((isValidHeader) => {
|
|
188
|
+
if (!isValidHeader) {
|
|
189
|
+
if (unsafe) {
|
|
190
|
+
this._log.warn("Invalid binlog file format: " + filename);
|
|
191
|
+
return;
|
|
197
192
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
timestamp: timestamp2,
|
|
201
|
-
data,
|
|
202
|
-
position: position - this.BINLOG_ENTRY_HEADER_SIZE
|
|
203
|
-
});
|
|
204
|
-
} else if (!unsafe) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
207
|
-
);
|
|
208
|
-
} else {
|
|
209
|
-
this._log.warn(
|
|
210
|
-
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
193
|
+
return Promise.reject(
|
|
194
|
+
new Error("Invalid binlog file format:\n" + filepath)
|
|
211
195
|
);
|
|
212
196
|
}
|
|
213
|
-
|
|
197
|
+
return fileHandle.stat();
|
|
198
|
+
}).then((stats) => {
|
|
199
|
+
if (!stats) return;
|
|
200
|
+
var contentSize = stats.size - this.BINLOG_HEADER_SIZE;
|
|
201
|
+
var position = this.BINLOG_HEADER_SIZE;
|
|
202
|
+
var headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
var processNextBatch = () => {
|
|
205
|
+
if (position >= contentSize) {
|
|
206
|
+
return resolve();
|
|
207
|
+
}
|
|
208
|
+
fileHandle.read(headerBuffer, 0, this.BINLOG_ENTRY_HEADER_SIZE, position).then(() => {
|
|
209
|
+
var header = this.#readEntryHeader(headerBuffer, position);
|
|
210
|
+
var dataBuffer = Buffer.allocUnsafe(header.dataLength);
|
|
211
|
+
position += this.BINLOG_ENTRY_HEADER_SIZE;
|
|
212
|
+
return fileHandle.read(dataBuffer, 0, header.dataLength, position).then(() => {
|
|
213
|
+
var entry = this.#readEntry(
|
|
214
|
+
headerBuffer,
|
|
215
|
+
header,
|
|
216
|
+
dataBuffer.subarray(0, header.dataLength)
|
|
217
|
+
);
|
|
218
|
+
if (!entry) {
|
|
219
|
+
if (!unsafe) {
|
|
220
|
+
return Promise.reject(
|
|
221
|
+
new Error(
|
|
222
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
} else {
|
|
226
|
+
this._log.warn(
|
|
227
|
+
`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
entries.push(entry);
|
|
232
|
+
}
|
|
233
|
+
position += header.dataLength;
|
|
234
|
+
}).then(() => processNextBatch()).catch(reject);
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
processNextBatch();
|
|
238
|
+
});
|
|
239
|
+
}).then(() => fileHandle?.close()).then(() => entries).catch((error) => {
|
|
240
|
+
return fileHandle.close().then(() => Promise.reject(error));
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
#readEntryHeader(buffer, position) {
|
|
244
|
+
return {
|
|
245
|
+
opcode: buffer.readUInt32LE(0),
|
|
246
|
+
flags: buffer.readUInt32LE(4),
|
|
247
|
+
timestamp: buffer.readUInt32LE(8),
|
|
248
|
+
dataLength: buffer.readUInt32LE(12),
|
|
249
|
+
crc: buffer.readUInt32LE(16),
|
|
250
|
+
position
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
#readEntry(headerBuffer, header, dataBuffer) {
|
|
254
|
+
var crc = crc32([headerBuffer.subarray(0, 16), dataBuffer]) >>> 0;
|
|
255
|
+
if (crc !== header.crc) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
var data;
|
|
259
|
+
if (checkBitmask(header.flags, this.BINLOG_FLAG.TL)) {
|
|
260
|
+
data = tlDecode(dataBuffer, {
|
|
261
|
+
structures: this._options.structures
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
data = Buffer.from(dataBuffer);
|
|
214
265
|
}
|
|
215
|
-
|
|
216
|
-
|
|
266
|
+
return {
|
|
267
|
+
opcode: header.opcode,
|
|
268
|
+
timestamp: header.timestamp,
|
|
269
|
+
data,
|
|
270
|
+
position: header.position
|
|
271
|
+
};
|
|
217
272
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
273
|
+
#checkBinlogHeaders(fileHandle) {
|
|
274
|
+
var headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
|
|
275
|
+
return Promise.resolve().then(() => fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0)).then(() => {
|
|
276
|
+
var magic = headerBuffer.readUInt32LE(0);
|
|
277
|
+
var version = headerBuffer.readUInt32LE(4);
|
|
278
|
+
return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;
|
|
279
|
+
});
|
|
224
280
|
}
|
|
225
281
|
/**
|
|
226
282
|
* Get the current binlog file index
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/Binlog.ts","../src/index.ts"],"sourcesContent":["import { Structure, tlDecode, tlEncode } from '@andrew_l/tl-pack';\nimport {\n type Logger,\n assert,\n checkBitmask,\n crc32,\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 structures?: Structure.Constructor[];\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 = {\n ...options,\n structures: options.structures ? [...options.structures] : [],\n };\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 if (data instanceof Structure) {\n data = tlEncode(data);\n flags |= this.BINLOG_FLAG.TL;\n } else {\n data = tlEncode(data) as Buffer;\n flags |= this.BINLOG_FLAG.TL;\n }\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 structures: this._options.structures,\n });\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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/Binlog.ts","../src/index.ts"],"sourcesContent":["import { Structure, tlDecode, tlEncode } from '@andrew_l/tl-pack';\nimport {\n type Logger,\n assert,\n checkBitmask,\n crc32,\n logger,\n noop,\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 structures?: Structure.Constructor[];\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\ninterface EntryHeader {\n opcode: number;\n flags: number;\n timestamp: number;\n dataLength: number;\n crc: number;\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 = {\n ...options,\n structures: options.structures ? [...options.structures] : [],\n };\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 init(): Promise<void> {\n return Promise.resolve()\n .then(() => fs.promises.mkdir(this._directory, { recursive: true }))\n .then(() => this.#findLatestBinlogFile());\n }\n\n /**\n * Find the latest binlog file to continue writing\n */\n #findLatestBinlogFile(): Promise<void> {\n return Promise.resolve()\n .then(() => fs.promises.readdir(this._directory))\n .then(files => {\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 /**\n * Open the binlog for writing\n */\n public open(): Promise<void> {\n const filePath = path.join(this._directory, this.currentFileName);\n\n return Promise.resolve()\n .then(() => fs.promises.open(filePath, 'a+'))\n .then(fileDescription => {\n this._currentFileDescriptor = fileDescription;\n this._currentFile = filePath;\n\n return this._currentFileDescriptor.stat();\n })\n .then(fileStat => {\n this._currentFileSize = fileStat.size;\n this._isOpen = true;\n\n // Write the binlog header\n if (this._currentFileSize === 0) {\n return this.#writeBinlogHeader();\n } else {\n return this.#checkBinlogHeaders(this._currentFileDescriptor!).then(\n noop,\n );\n }\n });\n }\n\n /**\n * Close the current binlog file\n */\n public close(): Promise<void> {\n return Promise.resolve()\n .then(() => {\n if (!this._isOpen || !this._currentFileDescriptor) {\n return;\n }\n\n return this._currentFileDescriptor.close();\n })\n .then(() => {\n this._currentFileDescriptor = null;\n this._currentFile = null;\n this._isOpen = false;\n });\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 write(opcode: number, data: unknown): Promise<void> {\n let promise = Promise.resolve();\n\n if (!this._isOpen) {\n promise = promise.then(() => this.open());\n }\n\n // Check if we need to rotate the file\n return promise\n .then(() => {\n if (\n this._options.rotation &&\n this._currentFileSize >= this._options.maxFileSize\n ) {\n return this.rotate();\n }\n })\n .then(() => {\n assert.ok(this._currentFileDescriptor, 'File descriptor is null');\n\n let flags = this.BINLOG_FLAG.NONE;\n\n if (!Buffer.isBuffer(data)) {\n if (data instanceof Structure) {\n data = tlEncode(data);\n flags |= this.BINLOG_FLAG.TL;\n } else {\n data = tlEncode(data) as Buffer;\n flags |= this.BINLOG_FLAG.TL;\n }\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(\n Buffer.concat([headerBuffer.subarray(0, 16), data as Buffer]),\n ) >>> 0,\n 16,\n );\n\n // Write the entry\n return Promise.resolve()\n .then(() => this._currentFileDescriptor!.write(headerBuffer))\n .then(() => this._currentFileDescriptor!.write(data as Buffer))\n .then(() => {\n if (this._options.syncWrites) {\n return this._currentFileDescriptor!.sync();\n }\n })\n .then(() => {\n this._currentFileSize += totalLength;\n });\n });\n }\n\n /**\n * Rotate the binlog file\n */\n public rotate(): Promise<void> {\n return Promise.resolve()\n .then(() => this.close())\n .then(() => {\n this._currentFileIndex++;\n return this.open();\n });\n }\n\n /**\n * Write the binlog header\n */\n #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 return Promise.resolve()\n .then(() => this._currentFileDescriptor!.write(headerBuffer))\n .then(() => {\n this._currentFileSize += headerBuffer.length;\n\n if (this._options.syncWrites) {\n return this._currentFileDescriptor!.sync();\n }\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 readEntries<TData = Buffer>(\n filename: string,\n unsafe?: boolean,\n ): Promise<BinlogEntry<TData>[]> {\n var filepath = path.join(this._directory, filename);\n var fileHandle: fs.promises.FileHandle;\n var entries: BinlogEntry<TData>[] = [];\n\n return fs.promises\n .open(filepath, 'r')\n .then(_fileHandle => {\n fileHandle = _fileHandle;\n return this.#checkBinlogHeaders(fileHandle);\n })\n .then(isValidHeader => {\n if (!isValidHeader) {\n if (unsafe) {\n this._log.warn('Invalid binlog file format: ' + filename);\n return;\n }\n return Promise.reject(\n new Error('Invalid binlog file format:\\n' + filepath),\n );\n }\n\n return fileHandle.stat();\n })\n .then(stats => {\n if (!stats) return;\n\n var contentSize = stats.size - this.BINLOG_HEADER_SIZE;\n var position = this.BINLOG_HEADER_SIZE;\n var headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);\n\n return new Promise<void>((resolve, reject) => {\n var processNextBatch = () => {\n if (position >= contentSize) {\n return resolve();\n }\n\n fileHandle\n .read(headerBuffer, 0, this.BINLOG_ENTRY_HEADER_SIZE, position)\n .then(() => {\n var header = this.#readEntryHeader(headerBuffer, position);\n var dataBuffer = Buffer.allocUnsafe(header.dataLength);\n\n position += this.BINLOG_ENTRY_HEADER_SIZE;\n\n return fileHandle\n .read(dataBuffer, 0, header.dataLength, position)\n .then(() => {\n var entry = this.#readEntry(\n headerBuffer,\n header,\n dataBuffer.subarray(0, header.dataLength),\n );\n\n if (!entry) {\n if (!unsafe) {\n return Promise.reject(\n new Error(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n ),\n );\n } else {\n this._log.warn(\n `CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`,\n );\n }\n } else {\n entries.push(entry);\n }\n\n position += header.dataLength;\n })\n .then(() => processNextBatch())\n .catch(reject);\n });\n };\n\n processNextBatch();\n });\n })\n .then(() => fileHandle?.close())\n .then(() => entries)\n .catch(error => {\n return fileHandle.close().then(() => Promise.reject(error));\n });\n }\n\n #readEntryHeader(buffer: Buffer, position: number): EntryHeader {\n return {\n opcode: buffer.readUInt32LE(0),\n flags: buffer.readUInt32LE(4),\n timestamp: buffer.readUInt32LE(8),\n dataLength: buffer.readUInt32LE(12),\n crc: buffer.readUInt32LE(16),\n position,\n };\n }\n\n #readEntry(\n headerBuffer: Buffer,\n header: EntryHeader,\n dataBuffer: Buffer,\n ): BinlogEntry<any> | null {\n // Verify CRC\n var crc = crc32([headerBuffer.subarray(0, 16), dataBuffer]) >>> 0;\n\n if (crc !== header.crc) {\n return null;\n }\n\n var data: any;\n\n if (checkBitmask(header.flags, this.BINLOG_FLAG.TL)) {\n data = tlDecode(dataBuffer, {\n structures: this._options.structures,\n });\n } else {\n // Make copy\n data = Buffer.from(dataBuffer);\n }\n\n return {\n opcode: header.opcode,\n timestamp: header.timestamp,\n data: data,\n position: header.position,\n };\n }\n\n #checkBinlogHeaders(fileHandle: fs.promises.FileHandle): Promise<boolean> {\n var headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);\n\n return Promise.resolve()\n .then(() => fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0))\n .then(() => {\n var magic = headerBuffer.readUInt32LE(0);\n var version = headerBuffer.readUInt32LE(4);\n\n return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;\n });\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":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrew_l/binlog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A high-performance binary logging system for Node.js applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,15 +25,16 @@
|
|
|
25
25
|
],
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "22.10.5",
|
|
28
|
-
"typescript": "~5.
|
|
29
|
-
"unbuild": "3.
|
|
30
|
-
"vitest": "^2.
|
|
28
|
+
"typescript": "~5.8.3",
|
|
29
|
+
"unbuild": "3.5.0",
|
|
30
|
+
"vitest": "^3.2.4"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@andrew_l/tl-pack": "0.
|
|
34
|
-
"@andrew_l/toolkit": "0.
|
|
33
|
+
"@andrew_l/tl-pack": "0.3.0",
|
|
34
|
+
"@andrew_l/toolkit": "0.3.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
+
"check-types": "tsc --noEmit",
|
|
37
38
|
"build": "unbuild",
|
|
38
39
|
"test": "vitest run --typecheck",
|
|
39
40
|
"test:watch": "vitest watch --typecheck"
|