@harperfast/rocksdb-js 1.3.0 → 1.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/bin/rocksdb-js.mjs +81 -19
- package/dist/index.cjs +23 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.mts +7 -2
- package/dist/index.mjs +23 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -19
package/bin/rocksdb-js.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { RocksDatabase, parseTransactionLog, versions } from '../dist/index.mjs';
|
|
4
4
|
import { existsSync, readdirSync } from 'node:fs';
|
|
5
|
-
import { mkdir, readdir } from 'node:fs/promises';
|
|
5
|
+
import { mkdir, readdir, stat } from 'node:fs/promises';
|
|
6
6
|
import { join, resolve } from 'node:path';
|
|
7
7
|
import { stdin, stdout } from 'node:process';
|
|
8
8
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -19,6 +19,7 @@ const COMMANDS = {
|
|
|
19
19
|
get: getCommand,
|
|
20
20
|
help: helpCommand,
|
|
21
21
|
log: logCommand,
|
|
22
|
+
logs: logCommand, // alias for log
|
|
22
23
|
prop: propCommand,
|
|
23
24
|
'purge-logs': purgeLogsCommand,
|
|
24
25
|
put: putCommand,
|
|
@@ -66,12 +67,12 @@ function completer(line) {
|
|
|
66
67
|
return [hits.length ? hits : columns, arg];
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
if (command === 'log' && currentDB) {
|
|
70
|
+
if ((command === 'log' || command === 'logs') && currentDB) {
|
|
70
71
|
if (parts.length === 2) {
|
|
71
72
|
const arg = parts[1];
|
|
72
73
|
const logs = currentDB.listLogs();
|
|
73
74
|
const hits = logs.filter((l) => l.startsWith(arg));
|
|
74
|
-
return [hits.length ? hits : logs, arg];
|
|
75
|
+
return [(hits.length ? hits : logs).map((s) => `${s} `), arg];
|
|
75
76
|
} else if (parts.length === 3) {
|
|
76
77
|
const [name, store] = parts.slice(1);
|
|
77
78
|
const logs = currentDB.listLogs();
|
|
@@ -81,7 +82,7 @@ function completer(line) {
|
|
|
81
82
|
const logFiles = readdirSync(logPath).filter(
|
|
82
83
|
(f) => (!store || f.startsWith(store)) && f.endsWith('.txnlog')
|
|
83
84
|
);
|
|
84
|
-
return [logFiles.length ? logFiles : [], store, name];
|
|
85
|
+
return [logFiles.length ? logFiles.map((f) => `${f} `) : [], store, name];
|
|
85
86
|
}
|
|
86
87
|
return [[], store, name];
|
|
87
88
|
}
|
|
@@ -149,6 +150,34 @@ function wrapColumns(columns) {
|
|
|
149
150
|
return prefix + lines.join('\n' + indent);
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
function formatHexDump(data) {
|
|
154
|
+
const bytesPerRow = 16;
|
|
155
|
+
const lines = [];
|
|
156
|
+
for (let offset = 0; offset < data.length; offset += bytesPerRow) {
|
|
157
|
+
const chunk = data.subarray(offset, offset + bytesPerRow);
|
|
158
|
+
const hexParts = [];
|
|
159
|
+
for (let i = 0; i < bytesPerRow; i++) {
|
|
160
|
+
if (i === 8) hexParts.push('');
|
|
161
|
+
hexParts.push(i < chunk.length ? chunk[i].toString(16).padStart(2, '0') : ' ');
|
|
162
|
+
}
|
|
163
|
+
let ascii = '';
|
|
164
|
+
for (const byte of chunk) {
|
|
165
|
+
ascii += byte >= 0x20 && byte <= 0x7e ? String.fromCharCode(byte) : '.';
|
|
166
|
+
}
|
|
167
|
+
lines.push(
|
|
168
|
+
`${note(offset.toString(16).padStart(8, '0'))} ${hl(hexParts.join(' '))} ${note('|')}${hl(ascii)}${note('|')}`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return lines.join('\n');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function formatBytes(bytes) {
|
|
175
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
176
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
|
177
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
178
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
179
|
+
}
|
|
180
|
+
|
|
152
181
|
async function run(fn) {
|
|
153
182
|
const start = Date.now();
|
|
154
183
|
const result = await fn();
|
|
@@ -320,40 +349,72 @@ async function logCommand(args) {
|
|
|
320
349
|
}
|
|
321
350
|
const { data } = log.entries[entry - 1];
|
|
322
351
|
console.log(`Entry ${entry} (${data.length.toLocaleString()} bytes)\n`);
|
|
323
|
-
console.log(
|
|
324
|
-
hl(
|
|
325
|
-
data
|
|
326
|
-
.toString('hex')
|
|
327
|
-
.match(/.{1,2}/g)
|
|
328
|
-
.join(' ')
|
|
329
|
-
)
|
|
330
|
-
);
|
|
352
|
+
console.log(formatHexDump(data));
|
|
331
353
|
console.log();
|
|
332
354
|
return;
|
|
333
355
|
}
|
|
334
356
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
console.log(`Entries = ${hl(log.entries.length)}`);
|
|
357
|
+
for (const anomaly of log.anomalies) {
|
|
358
|
+
console.log(bad(`! ${anomaly}`));
|
|
359
|
+
}
|
|
339
360
|
let i = 1;
|
|
340
361
|
for (const entry of log.entries) {
|
|
362
|
+
const ts =
|
|
363
|
+
Number.isFinite(entry.timestamp) && entry.timestamp > 0
|
|
364
|
+
? new Date(entry.timestamp).toISOString()
|
|
365
|
+
: String(entry.timestamp);
|
|
341
366
|
console.log(
|
|
342
|
-
`${String(i++).padStart(3)}) ${hl(
|
|
367
|
+
`${String(i++).padStart(3)}) ${hl(ts)} ${entry.length.toLocaleString()} bytes ${entry.flags & 0x01 ? note('(end of transaction)') : ''}`
|
|
343
368
|
);
|
|
369
|
+
if (entry.anomalies) {
|
|
370
|
+
for (const anomaly of entry.anomalies) {
|
|
371
|
+
console.log(` ${bad(`! ${anomaly}`)}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
344
374
|
}
|
|
345
375
|
console.log();
|
|
346
376
|
return;
|
|
347
377
|
}
|
|
378
|
+
|
|
348
379
|
const logPath = join(currentDB.path, 'transaction_logs', name);
|
|
349
380
|
const logFiles = await readdir(logPath);
|
|
350
381
|
for (const logFile of logFiles) {
|
|
351
382
|
if (!logFile.endsWith('.txnlog')) continue;
|
|
352
383
|
console.log(hl(logFile));
|
|
384
|
+
|
|
385
|
+
const log = parseTransactionLog(join(logPath, logFile), { skipData: true });
|
|
386
|
+
const ts =
|
|
387
|
+
Number.isFinite(log.timestamp) && log.timestamp > 0
|
|
388
|
+
? new Date(log.timestamp).toISOString()
|
|
389
|
+
: String(log.timestamp);
|
|
390
|
+
console.log(` Size = ${hl(`${log.size.toLocaleString()} bytes`)}`);
|
|
391
|
+
console.log(` Version = ${hl(log.version)}`);
|
|
392
|
+
console.log(` Timestamp = ${hl(ts)}`);
|
|
393
|
+
console.log(` Entries = ${hl(log.entries.length)}`);
|
|
394
|
+
const totalAnomalies = log.anomalies.length + log.entryAnomalyCount;
|
|
395
|
+
if (totalAnomalies > 0) {
|
|
396
|
+
console.log(` Anomalies = ${bad(totalAnomalies)}`);
|
|
397
|
+
for (const anomaly of log.anomalies) {
|
|
398
|
+
console.log(` ${bad(`! ${anomaly}`)}`);
|
|
399
|
+
}
|
|
400
|
+
if (log.entryAnomalyCount > 0) {
|
|
401
|
+
console.log(
|
|
402
|
+
` ${bad(`! ${log.entryAnomalyCount} entr${log.entryAnomalyCount === 1 ? 'y has' : 'ies have'} anomalies (run "log ${name} ${logFile}" for details)`)}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
353
406
|
}
|
|
354
407
|
} else if (logs.length > 0) {
|
|
355
|
-
for (const
|
|
356
|
-
|
|
408
|
+
for (const name of logs) {
|
|
409
|
+
const files = (await readdir(join(currentDB.path, 'transaction_logs', name))).filter((f) =>
|
|
410
|
+
f.endsWith('.txnlog')
|
|
411
|
+
);
|
|
412
|
+
const maxlen = files.reduce((max, file) => Math.max(max, file.length), 0);
|
|
413
|
+
console.log(hl(name));
|
|
414
|
+
for (const file of files) {
|
|
415
|
+
const { size } = await stat(join(currentDB.path, 'transaction_logs', name, file));
|
|
416
|
+
console.log(` ${file.padEnd(maxlen)} ${note(`(${formatBytes(size)})`)}`);
|
|
417
|
+
}
|
|
357
418
|
}
|
|
358
419
|
} else {
|
|
359
420
|
console.log('No log stores found');
|
|
@@ -533,6 +594,7 @@ async function main() {
|
|
|
533
594
|
const command = line[0];
|
|
534
595
|
if (!command) continue;
|
|
535
596
|
const commandFn = COMMANDS[command];
|
|
597
|
+
console.log();
|
|
536
598
|
if (commandFn) {
|
|
537
599
|
try {
|
|
538
600
|
await commandFn(line.slice(1));
|
package/dist/index.cjs
CHANGED
|
@@ -595,6 +595,8 @@ const VALUE_BUFFER = createFixedBuffer(64 * 1024);
|
|
|
595
595
|
const ITERATOR_STATE_BUFFER = Buffer.allocUnsafeSlow(8);
|
|
596
596
|
const ITERATOR_STATE = new Uint32Array(ITERATOR_STATE_BUFFER.buffer, ITERATOR_STATE_BUFFER.byteOffset, 2);
|
|
597
597
|
const MAX_KEY_SIZE = 1024 * 1024;
|
|
598
|
+
const RESET_BUFFER_MODE = 1024;
|
|
599
|
+
const REUSE_BUFFER_MODE = 512;
|
|
598
600
|
const SAVE_BUFFER_SIZE = 8192;
|
|
599
601
|
/**
|
|
600
602
|
* A store wraps the `NativeDatabase` binding and database settings so that a
|
|
@@ -884,7 +886,7 @@ var Store = class {
|
|
|
884
886
|
encodeValue(value) {
|
|
885
887
|
if (value && value["binary-data"]) return value["binary-data"];
|
|
886
888
|
if (typeof this.encoder?.encode === "function") {
|
|
887
|
-
if (this.encoder.copyBuffers) return this.encoder.encode(value,
|
|
889
|
+
if (this.encoder.copyBuffers) return this.encoder.encode(value, REUSE_BUFFER_MODE | RESET_BUFFER_MODE);
|
|
888
890
|
const valueBuffer = this.encoder.encode(value);
|
|
889
891
|
if (typeof valueBuffer === "string") return Buffer.from(valueBuffer);
|
|
890
892
|
return valueBuffer;
|
|
@@ -1858,12 +1860,14 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
1858
1860
|
//#endregion
|
|
1859
1861
|
//#region src/parse-transaction-log.ts
|
|
1860
1862
|
const { TRANSACTION_LOG_TOKEN } = constants;
|
|
1863
|
+
const MIN_VALID_TIMESTAMP = Date.UTC(2026, 0, 27);
|
|
1864
|
+
const VALID_FLAGS_MASK = 1;
|
|
1861
1865
|
/**
|
|
1862
1866
|
* Loads an entire transaction log file into memory.
|
|
1863
1867
|
* @param path - The path to the transaction log file.
|
|
1864
1868
|
* @returns The transaction log.
|
|
1865
1869
|
*/
|
|
1866
|
-
function parseTransactionLog(path) {
|
|
1870
|
+
function parseTransactionLog(path, options = {}) {
|
|
1867
1871
|
let stats;
|
|
1868
1872
|
try {
|
|
1869
1873
|
stats = (0, node_fs.statSync)(path);
|
|
@@ -1887,7 +1891,10 @@ function parseTransactionLog(path) {
|
|
|
1887
1891
|
const version = read(1).readUInt8(0);
|
|
1888
1892
|
if (version !== 1) throw new Error(`Unsupported transaction log file version: ${version}`);
|
|
1889
1893
|
const timestamp = read(8).readDoubleBE(0);
|
|
1894
|
+
const anomalies = [];
|
|
1895
|
+
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) anomalies.push(`Header timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
1890
1896
|
const entries = [];
|
|
1897
|
+
let entryAnomalyCount = 0;
|
|
1891
1898
|
while (fileOffset < size) {
|
|
1892
1899
|
const timestamp = read(8).readDoubleBE(0);
|
|
1893
1900
|
if (timestamp === 0) {
|
|
@@ -1897,15 +1904,25 @@ function parseTransactionLog(path) {
|
|
|
1897
1904
|
const length = read(4).readUInt32BE(0);
|
|
1898
1905
|
const flags = read(1).readUInt8(0);
|
|
1899
1906
|
const data = read(length);
|
|
1900
|
-
|
|
1907
|
+
const entryAnomalies = [];
|
|
1908
|
+
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) entryAnomalies.push(`timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
1909
|
+
if ((flags & ~VALID_FLAGS_MASK) !== 0) entryAnomalies.push(`flags 0x${flags.toString(16).padStart(2, "0")} contains undefined bits (expected 0x00 or 0x01)`);
|
|
1910
|
+
const entry = {
|
|
1901
1911
|
timestamp,
|
|
1902
1912
|
length,
|
|
1903
1913
|
flags,
|
|
1904
|
-
data
|
|
1905
|
-
}
|
|
1914
|
+
data: options.skipData ? void 0 : Buffer.from(data)
|
|
1915
|
+
};
|
|
1916
|
+
if (entryAnomalies.length > 0) {
|
|
1917
|
+
entry.anomalies = entryAnomalies;
|
|
1918
|
+
entryAnomalyCount += entryAnomalies.length;
|
|
1919
|
+
}
|
|
1920
|
+
entries.push(entry);
|
|
1906
1921
|
}
|
|
1907
1922
|
return {
|
|
1923
|
+
anomalies,
|
|
1908
1924
|
entries,
|
|
1925
|
+
entryAnomalyCount,
|
|
1909
1926
|
timestamp,
|
|
1910
1927
|
size,
|
|
1911
1928
|
version
|
|
@@ -2098,7 +2115,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
2098
2115
|
//#region src/index.ts
|
|
2099
2116
|
const versions = {
|
|
2100
2117
|
rocksdb: version,
|
|
2101
|
-
"rocksdb-js": "1.
|
|
2118
|
+
"rocksdb-js": "1.4.0"
|
|
2102
2119
|
};
|
|
2103
2120
|
|
|
2104
2121
|
//#endregion
|