@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.d.mts +99 -101
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +211 -311
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
- package/dist/index.cjs +0 -326
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -123
- package/dist/index.d.ts +0 -123
package/dist/index.mjs
CHANGED
|
@@ -1,318 +1,218 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Structure,
|
|
3
|
-
import fs from
|
|
4
|
-
import path from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
205
|
+
return new RegExp(value.replaceAll("{index}", "(\\d+)"));
|
|
305
206
|
}
|
|
306
|
-
|
|
307
207
|
const DEF_OPTIONS = {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
208
|
+
path: "./binlogs/log-{index}.bin",
|
|
209
|
+
maxFileSize: 104857600,
|
|
210
|
+
rotation: true,
|
|
211
|
+
syncWrites: true
|
|
312
212
|
};
|
|
313
213
|
function createBinlog(options) {
|
|
314
|
-
|
|
214
|
+
return new Binlog(deepDefaults(options, DEF_OPTIONS));
|
|
315
215
|
}
|
|
316
|
-
|
|
317
216
|
export { Binlog, createBinlog };
|
|
318
|
-
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=index.mjs.map
|
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 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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"}
|