@andrew_l/binlog 0.3.21 → 0.4.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.mjs CHANGED
@@ -1,318 +1,218 @@
1
- import { logger, noop, assert, timestamp, crc32, checkBitmask, deepDefaults } from '@andrew_l/toolkit';
2
- import { Structure, 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 = {
27
- ...options,
28
- structures: options.structures ? [...options.structures] : []
29
- };
30
- this._filePattern = path.basename(options.path);
31
- this._directory = path.dirname(options.path);
32
- this._currentFile = null;
33
- this._currentFileDescriptor = null;
34
- this._currentFileSize = 0;
35
- this._currentFileIndex = 0;
36
- this._isOpen = false;
37
- }
38
- /**
39
- * Initialize the binlog system, ensuring directory exists and pick most recent binlog file
40
- * @returns Promise resolving to true if successful, false otherwise
41
- */
42
- init() {
43
- return Promise.resolve().then(() => fs.promises.mkdir(this._directory, { recursive: true })).then(() => this.#findLatestBinlogFile());
44
- }
45
- /**
46
- * Find the latest binlog file to continue writing
47
- */
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
- }
61
- });
62
- }
63
- /**
64
- * Open the binlog for writing
65
- */
66
- open() {
67
- const filePath = path.join(this._directory, this.currentFileName);
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
- });
83
- }
84
- /**
85
- * Close the current binlog file
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
- });
98
- }
99
- /**
100
- * Write a binlog entry
101
- * @param opcode - Operation code
102
- * @param data - Data to write
103
- * @returns Promise resolving to true if successful, false otherwise
104
- */
105
- write(opcode, data) {
106
- let promise = Promise.resolve();
107
- if (!this._isOpen) {
108
- promise = promise.then(() => this.open());
109
- }
110
- return promise.then(() => {
111
- if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) {
112
- return this.rotate();
113
- }
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
- });
147
- }
148
- /**
149
- * Rotate the binlog file
150
- */
151
- rotate() {
152
- return Promise.resolve().then(() => this.close()).then(() => {
153
- this._currentFileIndex++;
154
- return this.open();
155
- });
156
- }
157
- /**
158
- * Write the binlog header
159
- */
160
- #writeBinlogHeader() {
161
- assert.ok(this._currentFileDescriptor, "File descriptor is null");
162
- const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
163
- headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);
164
- headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);
165
- headerBuffer.writeUInt32LE(timestamp(), 8);
166
- headerBuffer.writeUInt32LE(0, 12);
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
- });
173
- }
174
- /**
175
- * Read and parse all entries from a binlog file
176
- * @param filename - Binlog file to read
177
- * @param unsafe - Ignore broken binlog records otherwise throws error
178
- * @returns Array of parsed entries
179
- */
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;
192
- }
193
- return Promise.reject(
194
- new Error("Invalid binlog file format:\n" + filepath)
195
- );
196
- }
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);
265
- }
266
- return {
267
- opcode: header.opcode,
268
- timestamp: header.timestamp,
269
- data,
270
- position: header.position
271
- };
272
- }
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
- });
280
- }
281
- /**
282
- * Get the current binlog file index
283
- */
284
- get currentFileIndex() {
285
- return this._currentFileIndex;
286
- }
287
- /**
288
- * Get current binlog file name
289
- */
290
- get currentFileName() {
291
- return this._filePattern.replaceAll(
292
- "{index}",
293
- String(this._currentFileIndex)
294
- );
295
- }
296
- /**
297
- * Get current binlog directory
298
- */
299
- get directory() {
300
- return this._directory;
301
- }
302
- }
1
+ import { assert, checkBitmask, crc32, deepDefaults, logger, noop, timestamp } from "@andrew_l/toolkit";
2
+ import { Structure, tlDecode, tlEncode } from "@andrew_l/tl-pack";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ var Binlog = class {
6
+ _options;
7
+ _filePattern;
8
+ _directory;
9
+ _currentFile;
10
+ _currentFileDescriptor;
11
+ _currentFileSize;
12
+ _currentFileIndex;
13
+ _isOpen;
14
+ _log;
15
+ BINLOG_MAGIC = 1145197643;
16
+ BINLOG_VERSION = 1;
17
+ BINLOG_HEADER_SIZE = 16;
18
+ BINLOG_ENTRY_HEADER_SIZE = 20;
19
+ BINLOG_FLAG = Object.freeze({
20
+ NONE: 0,
21
+ TL: 1
22
+ });
23
+ constructor(options) {
24
+ this._log = logger("Binlog");
25
+ this._options = {
26
+ ...options,
27
+ structures: options.structures ? [...options.structures] : []
28
+ };
29
+ this._filePattern = path.basename(options.path);
30
+ this._directory = path.dirname(options.path);
31
+ this._currentFile = null;
32
+ this._currentFileDescriptor = null;
33
+ this._currentFileSize = 0;
34
+ this._currentFileIndex = 0;
35
+ this._isOpen = false;
36
+ }
37
+ init() {
38
+ return Promise.resolve().then(() => fs.promises.mkdir(this._directory, { recursive: true })).then(() => this.#findLatestBinlogFile());
39
+ }
40
+ #findLatestBinlogFile() {
41
+ return Promise.resolve().then(() => fs.promises.readdir(this._directory)).then((files) => {
42
+ const fileRegEx = filePatternToRegex(this._filePattern);
43
+ this._currentFileIndex = 0;
44
+ for (const fileName of files) {
45
+ const match = fileName.match(fileRegEx);
46
+ if (match) this._currentFileIndex = Math.max(this._currentFileIndex, parseInt(match[1], 10));
47
+ }
48
+ });
49
+ }
50
+ open() {
51
+ const filePath = path.join(this._directory, this.currentFileName);
52
+ return Promise.resolve().then(() => fs.promises.open(filePath, "a+")).then((fileDescription) => {
53
+ this._currentFileDescriptor = fileDescription;
54
+ this._currentFile = filePath;
55
+ return this._currentFileDescriptor.stat();
56
+ }).then((fileStat) => {
57
+ this._currentFileSize = fileStat.size;
58
+ this._isOpen = true;
59
+ if (this._currentFileSize === 0) return this.#writeBinlogHeader();
60
+ else return this.#checkBinlogHeaders(this._currentFileDescriptor).then(noop);
61
+ });
62
+ }
63
+ close() {
64
+ return Promise.resolve().then(() => {
65
+ if (!this._isOpen || !this._currentFileDescriptor) return;
66
+ return this._currentFileDescriptor.close();
67
+ }).then(() => {
68
+ this._currentFileDescriptor = null;
69
+ this._currentFile = null;
70
+ this._isOpen = false;
71
+ });
72
+ }
73
+ write(opcode, data) {
74
+ let promise = Promise.resolve();
75
+ if (!this._isOpen) promise = promise.then(() => this.open());
76
+ return promise.then(() => {
77
+ if (this._options.rotation && this._currentFileSize >= this._options.maxFileSize) return this.rotate();
78
+ }).then(() => {
79
+ assert.ok(this._currentFileDescriptor, "File descriptor is null");
80
+ let flags = this.BINLOG_FLAG.NONE;
81
+ if (!Buffer.isBuffer(data)) if (data instanceof Structure) {
82
+ data = tlEncode(data);
83
+ flags |= this.BINLOG_FLAG.TL;
84
+ } else {
85
+ data = tlEncode(data);
86
+ flags |= this.BINLOG_FLAG.TL;
87
+ }
88
+ const dataLength = data.length;
89
+ const totalLength = this.BINLOG_ENTRY_HEADER_SIZE + dataLength;
90
+ const headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
91
+ headerBuffer.writeUInt32LE(opcode, 0);
92
+ headerBuffer.writeUInt32LE(flags, 4);
93
+ headerBuffer.writeUInt32LE(timestamp(), 8);
94
+ headerBuffer.writeUInt32LE(dataLength, 12);
95
+ headerBuffer.writeUInt32LE(crc32(Buffer.concat([headerBuffer.subarray(0, 16), data])) >>> 0, 16);
96
+ return Promise.resolve().then(() => this._currentFileDescriptor.write(headerBuffer)).then(() => this._currentFileDescriptor.write(data)).then(() => {
97
+ if (this._options.syncWrites) return this._currentFileDescriptor.sync();
98
+ }).then(() => {
99
+ this._currentFileSize += totalLength;
100
+ });
101
+ });
102
+ }
103
+ rotate() {
104
+ return Promise.resolve().then(() => this.close()).then(() => {
105
+ this._currentFileIndex++;
106
+ return this.open();
107
+ });
108
+ }
109
+ #writeBinlogHeader() {
110
+ assert.ok(this._currentFileDescriptor, "File descriptor is null");
111
+ const headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
112
+ headerBuffer.writeUInt32LE(this.BINLOG_MAGIC, 0);
113
+ headerBuffer.writeUInt32LE(this.BINLOG_VERSION, 4);
114
+ headerBuffer.writeUInt32LE(timestamp(), 8);
115
+ headerBuffer.writeUInt32LE(0, 12);
116
+ return Promise.resolve().then(() => this._currentFileDescriptor.write(headerBuffer)).then(() => {
117
+ this._currentFileSize += headerBuffer.length;
118
+ if (this._options.syncWrites) return this._currentFileDescriptor.sync();
119
+ });
120
+ }
121
+ readEntries(filename, unsafe) {
122
+ var filepath = path.join(this._directory, filename);
123
+ var fileHandle;
124
+ var entries = [];
125
+ return fs.promises.open(filepath, "r").then((_fileHandle) => {
126
+ fileHandle = _fileHandle;
127
+ return this.#checkBinlogHeaders(fileHandle);
128
+ }).then((isValidHeader) => {
129
+ if (!isValidHeader) {
130
+ if (unsafe) {
131
+ this._log.warn("Invalid binlog file format: " + filename);
132
+ return;
133
+ }
134
+ return Promise.reject(/* @__PURE__ */ new Error("Invalid binlog file format:\n" + filepath));
135
+ }
136
+ return fileHandle.stat();
137
+ }).then((stats) => {
138
+ if (!stats) return;
139
+ var contentSize = stats.size - this.BINLOG_HEADER_SIZE;
140
+ var position = this.BINLOG_HEADER_SIZE;
141
+ var headerBuffer = Buffer.allocUnsafe(this.BINLOG_ENTRY_HEADER_SIZE);
142
+ return new Promise((resolve, reject) => {
143
+ var processNextBatch = () => {
144
+ if (position >= contentSize) return resolve();
145
+ fileHandle.read(headerBuffer, 0, this.BINLOG_ENTRY_HEADER_SIZE, position).then(() => {
146
+ var header = this.#readEntryHeader(headerBuffer, position);
147
+ var dataBuffer = Buffer.allocUnsafe(header.dataLength);
148
+ position += this.BINLOG_ENTRY_HEADER_SIZE;
149
+ return fileHandle.read(dataBuffer, 0, header.dataLength, position).then(() => {
150
+ var entry = this.#readEntry(headerBuffer, header, dataBuffer.subarray(0, header.dataLength));
151
+ if (!entry) if (!unsafe) return Promise.reject(/* @__PURE__ */ new Error(`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`));
152
+ else this._log.warn(`CRC mismatch at position ${position - this.BINLOG_ENTRY_HEADER_SIZE}`);
153
+ else entries.push(entry);
154
+ position += header.dataLength;
155
+ }).then(() => processNextBatch()).catch(reject);
156
+ });
157
+ };
158
+ processNextBatch();
159
+ });
160
+ }).then(() => fileHandle?.close()).then(() => entries).catch((error) => {
161
+ return fileHandle.close().then(() => Promise.reject(error));
162
+ });
163
+ }
164
+ #readEntryHeader(buffer, position) {
165
+ return {
166
+ opcode: buffer.readUInt32LE(0),
167
+ flags: buffer.readUInt32LE(4),
168
+ timestamp: buffer.readUInt32LE(8),
169
+ dataLength: buffer.readUInt32LE(12),
170
+ crc: buffer.readUInt32LE(16),
171
+ position
172
+ };
173
+ }
174
+ #readEntry(headerBuffer, header, dataBuffer) {
175
+ if (crc32([headerBuffer.subarray(0, 16), dataBuffer]) >>> 0 !== header.crc) return null;
176
+ var data;
177
+ if (checkBitmask(header.flags, this.BINLOG_FLAG.TL)) data = tlDecode(dataBuffer, { structures: this._options.structures });
178
+ else data = Buffer.from(dataBuffer);
179
+ return {
180
+ opcode: header.opcode,
181
+ timestamp: header.timestamp,
182
+ data,
183
+ position: header.position
184
+ };
185
+ }
186
+ #checkBinlogHeaders(fileHandle) {
187
+ var headerBuffer = Buffer.allocUnsafe(this.BINLOG_HEADER_SIZE);
188
+ return Promise.resolve().then(() => fileHandle.read(headerBuffer, 0, this.BINLOG_HEADER_SIZE, 0)).then(() => {
189
+ var magic = headerBuffer.readUInt32LE(0);
190
+ var version = headerBuffer.readUInt32LE(4);
191
+ return magic === this.BINLOG_MAGIC && version === this.BINLOG_VERSION;
192
+ });
193
+ }
194
+ get currentFileIndex() {
195
+ return this._currentFileIndex;
196
+ }
197
+ get currentFileName() {
198
+ return this._filePattern.replaceAll("{index}", String(this._currentFileIndex));
199
+ }
200
+ get directory() {
201
+ return this._directory;
202
+ }
203
+ };
303
204
  function filePatternToRegex(value) {
304
- return new RegExp(value.replaceAll("{index}", "(\\d+)"));
205
+ return new RegExp(value.replaceAll("{index}", "(\\d+)"));
305
206
  }
306
-
307
207
  const DEF_OPTIONS = {
308
- path: "./binlogs/log-{index}.bin",
309
- maxFileSize: 104857600,
310
- rotation: true,
311
- syncWrites: true
208
+ path: "./binlogs/log-{index}.bin",
209
+ maxFileSize: 104857600,
210
+ rotation: true,
211
+ syncWrites: true
312
212
  };
313
213
  function createBinlog(options) {
314
- return new Binlog(deepDefaults(options, DEF_OPTIONS));
214
+ return new Binlog(deepDefaults(options, DEF_OPTIONS));
315
215
  }
316
-
317
216
  export { Binlog, createBinlog };
318
- //# sourceMappingURL=index.mjs.map
217
+
218
+ //# sourceMappingURL=index.mjs.map
@@ -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 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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"}
1
+ {"version":3,"file":"index.mjs","names":["#findLatestBinlogFile","#writeBinlogHeader","#checkBinlogHeaders","#readEntryHeader","#readEntry"],"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"],"mappings":"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"}