@harperfast/rocksdb-js 2.2.0 → 2.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/README.md CHANGED
@@ -996,6 +996,40 @@ Note: If the `callback` throws an error, Node.js suppress the error. Node.js 18.
996
996
  `--force-node-api-uncaught-exceptions-policy` flag which will cause errors to emit the
997
997
  `'uncaughtException'` event. Future Node.js releases will enable this flag by default.
998
998
 
999
+ ## Exclusive File Locking
1000
+
1001
+ `rocksdb-js` includes helper functions for creating lock files and releasing them using native APIs.
1002
+ This can be used to prevent multiple processes from concurrently accessing a resource. The lock is
1003
+ automatically released when the process exits.
1004
+
1005
+ ### `tryFileLock(file: string): number`
1006
+
1007
+ Attempts to acquire an exclusive lock on the given file, creating it if it doesn't exist. Returns a
1008
+ non-zero token to pass to `fileLockRelease` if the lock was acquired, or `0` if another holder — in
1009
+ any process, container, or worker thread — currently has it. Throws if the file's parent directory
1010
+ is missing or on a hard error.
1011
+
1012
+ ```typescript
1013
+ import { tryFileLock } from '@harperfast/rocksdb-js';
1014
+
1015
+ const token = tryFileLock('/path/to/lock');
1016
+ if (token) {
1017
+ console.log('lock acquired');
1018
+ } else {
1019
+ console.log('lock not available, another process is holding it');
1020
+ }
1021
+ ```
1022
+
1023
+ ### `fileLockRelease(token: number): void`
1024
+
1025
+ Releases the file lock for the given token.
1026
+
1027
+ ```typescript
1028
+ import { fileLockRelease } from '@harperfast/rocksdb-js';
1029
+
1030
+ fileLockRelease(token);
1031
+ ```
1032
+
999
1033
  ## Verification Table
1000
1034
 
1001
1035
  The verification table is a process-global, fixed-size structure that lets an application cheaply
@@ -1385,16 +1419,57 @@ import { versions } from '@harperfast/rocksdb-js';
1385
1419
  console.log(versions); // { "rocksdb": "10.10.1", "rocksdb-js": "0.1.2" }
1386
1420
  ```
1387
1421
 
1422
+ ## Checkpoints
1423
+
1424
+ ### `db.createCheckpoint(targetPath: string): Promise<void>`
1425
+
1426
+ Creates a [checkpoint](https://github.com/facebook/rocksdb/wiki/Checkpoints) — a point-in-time,
1427
+ fully independent copy of the entire database (all column families) at `targetPath` — and resolves
1428
+ once written. Unlike a backup, a checkpoint is a normal, writable database: open it as a new
1429
+ `RocksDatabase` and it diverges independently from the source.
1430
+
1431
+ SST and blob files are **hard-linked** when `targetPath` is on the **same filesystem** as the
1432
+ database, and **copied** otherwise; other files (such as the `MANIFEST`) are always copied. As a
1433
+ result the operation is near-instant on the same filesystem and as costly as a full copy across
1434
+ filesystems. The memtable is flushed so the checkpoint includes the latest writes even when the
1435
+ WAL is disabled.
1436
+
1437
+ Parent directories are created as needed. `targetPath` itself must not already exist — RocksDB
1438
+ creates the checkpoint directory — and the call rejects with `Create checkpoint failed: target
1439
+ path exists` if it does (other failures, such as a full disk, surface the RocksDB status message).
1440
+ The caller is responsible for opening the checkpoint and for eventually deleting the directory.
1441
+
1442
+ ```typescript
1443
+ const db = RocksDatabase.open('/path/to/database');
1444
+ await db.createCheckpoint('/path/to/checkpoint');
1445
+
1446
+ // The checkpoint is a normal, writable database.
1447
+ const branch = RocksDatabase.open('/path/to/checkpoint');
1448
+ ```
1449
+
1388
1450
  ## Backups
1389
1451
 
1390
1452
  Backups use RocksDB's `BackupEngine` to capture consistent, incremental, checksum-verified
1391
1453
  snapshots of a database. A backup covers the **entire database** — every column family, the
1392
1454
  manifest, and (by default) the write-ahead log — so it is not scoped to an individual `Store`.
1393
1455
 
1456
+ A backup can be written to a local **directory** (incremental, with a management API) or streamed to
1457
+ a **`WritableStream`** as a tar archive with no intermediate copy on disk. See
1458
+ [docs/backups.md](docs/backups.md) for a full guide covering both modes, restore, checkpoints, and
1459
+ caveats.
1460
+
1394
1461
  Creating a backup is an instance method (`db.backup()`) because it needs a live database. The
1395
1462
  remaining operations act on a backup directory and do not require an open database, so they are
1396
1463
  grouped under the `backups` namespace export.
1397
1464
 
1465
+ > **Only one backup per directory may be in-flight at a time.** RocksDB has no cross-engine lock on
1466
+ > a backup directory, so the writing operations — `db.backup()`, `backups.delete()`, and
1467
+ > `backups.purge()` — take an on-disk lock (a `.backup.lock` file) for the directory. A second
1468
+ > writing operation on the same directory, whether from the same process, a `worker_thread`, or a
1469
+ > separate process, **rejects** with a "locked" error rather than corrupting the backup; retry once
1470
+ > the in-flight operation finishes. Operations on _different_ directories run in parallel, and the
1471
+ > read-only operations (`list`, `verify`, `restore`) are not locked.
1472
+
1398
1473
  ```typescript
1399
1474
  import { RocksDatabase, backups } from '@harperfast/rocksdb-js';
1400
1475
 
@@ -1422,15 +1497,53 @@ const id = await db.backup('/path/to/backups', { metadata: 'nightly-2026-06-04'
1422
1497
 
1423
1498
  `BackupOptions`:
1424
1499
 
1425
- | Option | Type | Default | Description |
1426
- | ------------------------- | --------- | ---------------------- | --------------------------------------------------------------------- |
1427
- | `flushBeforeBackup` | `boolean` | `true` if WAL disabled | Flush the memtable before backing up. |
1428
- | `metadata` | `string` | `''` | Application metadata stored with the backup, returned by `list()`. |
1429
- | `shareTableFiles` | `boolean` | `true` | Share files between backups to enable incremental backups. |
1430
- | `shareFilesWithChecksum` | `boolean` | `true` | Distinguish shared files by checksum to avoid cross-database clashes. |
1431
- | `backupLogFiles` | `boolean` | `true` | Include write-ahead log files in the backup. |
1432
- | `sync` | `boolean` | `true` | `fsync` backup files for crash consistency. |
1433
- | `maxBackgroundOperations` | `number` | `1` | Number of background threads used to copy files. |
1500
+ | Option | Type | Default | Description |
1501
+ | ------------------------- | --------- | ---------------------- | ------------------------------------------------------------------------------------ |
1502
+ | `backupLogFiles` | `boolean` | `true` | Include write-ahead log files in the backup. |
1503
+ | `flushBeforeBackup` | `boolean` | `true` if WAL disabled | Flush the memtable before backing up. |
1504
+ | `maxBackgroundOperations` | `number` | `1` | Number of background threads used to copy files. |
1505
+ | `metadata` | `string` | `''` | Application metadata stored with the backup, returned by `list()`. |
1506
+ | `shareFilesWithChecksum` | `boolean` | `true` | Distinguish shared files by checksum to avoid cross-database clashes. |
1507
+ | `shareTableFiles` | `boolean` | `true` | Share files between backups to enable incremental backups. |
1508
+ | `sync` | `boolean` | `true` | `fsync` backup files (including the transaction log snapshot) for crash consistency. |
1509
+ | `transactionLogs` | `boolean` | `false` | Snapshot the transaction log store into `<backupDir>/transaction_logs/<backupId>/`. |
1510
+
1511
+ When `transactionLogs` is enabled, the log snapshot is staged and atomically renamed into
1512
+ `<backupDir>/transaction_logs/<backupId>/` only after every file has been copied (and fsynced, per
1513
+ `sync`), so a crash mid-backup can never leave a partial log snapshot for a listed backup id — a
1514
+ backup either has its complete snapshot or none. The snapshot is captured just after the RocksDB
1515
+ engine snapshot, so restored logs may run slightly ahead of the restored key-value data (never
1516
+ behind it), which is safe for redo-style logs replayed against the restored data.
1517
+
1518
+ ### `db.backup(stream: WritableStream<Uint8Array>, options?: BackupStreamOptions): Promise<void>`
1519
+
1520
+ Streams a consistent snapshot of the entire database to `stream` as a tar archive, with **no
1521
+ intermediate copy written to disk**, and resolves once the stream has been fully written and closed.
1522
+ Backpressure is honored end to end, so a slow consumer (e.g. an upload) paces the backup rather than
1523
+ buffering it in memory. The archive unpacks with any tar tool into a directory that opens as a
1524
+ RocksDB database.
1525
+
1526
+ ```typescript
1527
+ import { createWriteStream } from 'node:fs';
1528
+ import { Writable } from 'node:stream';
1529
+
1530
+ await db.backup(Writable.toWeb(createWriteStream('/path/to/backup.tar')));
1531
+ // Restore: `tar -xf backup.tar -C /restored`, then open '/restored'.
1532
+
1533
+ // Or gzip it (`tar -xzf backup.tar.gz` to restore):
1534
+ await db.backup(Writable.toWeb(createWriteStream('/path/to/backup.tar.gz')), { gzip: true });
1535
+ ```
1536
+
1537
+ `BackupStreamOptions`:
1538
+
1539
+ | Option | Type | Default | Description |
1540
+ | ------------------- | --------- | ---------------------- | ------------------------------------------------------------------- |
1541
+ | `flushBeforeBackup` | `boolean` | `true` if WAL disabled | Flush the memtable before streaming. |
1542
+ | `gzip` | `boolean` | `false` | Gzip-compress the archive, producing a `.tar.gz` instead of `.tar`. |
1543
+
1544
+ Stream backups are always full snapshots (no incremental sharing), have no `backups.*` management
1545
+ API, and **cannot be resumed** — a failed transfer must be restarted from the beginning. See
1546
+ [docs/backups.md](docs/backups.md#stream-backups) for details.
1434
1547
 
1435
1548
  ### `backups.restore(backupDir: string, dbDir: string, options?: RestoreOptions): Promise<void>`
1436
1549