@harperfast/rocksdb-js 1.3.0 → 1.4.1

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.
@@ -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
- console.log(`Size = ${hl(`${log.size.toLocaleString()} bytes`)}`);
336
- console.log(`Version = ${hl(log.version)}`);
337
- console.log(`Timestamp = ${hl(new Date(log.timestamp).toISOString())}`);
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(new Date(entry.timestamp).toISOString())} ${entry.length.toLocaleString()} bytes ${entry.flags & 0x01 ? '' : note('(continued in next file)')}`
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 log of logs) {
356
- console.log(hl(log));
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, 1536);
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
- entries.push({
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.3.0"
2118
+ "rocksdb-js": "1.4.1"
2102
2119
  };
2103
2120
 
2104
2121
  //#endregion