npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ltx-format

v0.1.0

Published

Reader and writer for the LTX (Lite Transaction) file format v3, byte-compatible with Litestream v0.5.

Downloads

21

Readme

ltx-format

npm CI license

A TypeScript reader and writer for the LTX (Lite Transaction) file format, version 3 — the replica format used by Litestream v0.5 and LiteFS.

An LTX file describes one SQLite transaction as the set of pages it changed. That makes it a general-purpose container: anything that knows which pages a transaction touched can produce one, without going through a WAL and without linking SQLite.

This package is pure. It performs no I/O and has no knowledge of SQLite, object storage, or any particular runtime — you hand it pages and get bytes back. It has zero runtime dependencies and touches nothing beyond Uint8Array, DataView, and ArrayBuffer, so it runs in any modern JavaScript runtime. CI exercises Node.js 22 and 24; Deno, Bun, and browsers are expected to work but are not covered by tests.

Install

npm install ltx-format

The package is ESM-only. It requires Node.js 22.12 or newer — that is the release where require() of an ES module became available unflagged, so CommonJS callers can require('ltx-format') too.

Usage

Writing a snapshot

A snapshot (minTXID === 1n) carries every page of the database, so it can be restored standalone. The encoder enforces that: pages must run 1..commit with no gaps, skipping only the lock page. A sparse snapshot is rejected here rather than failing later inside litestream restore.

import { writeFile } from 'node:fs/promises';
import { encodeLtx, formatFilename } from 'ltx-format';

const pageSize = 4096;
const file = encodeLtx({
	pageSize,
	commit: 12, // database size, in pages, after this transaction
	minTXID: 1n,
	maxTXID: 1n,
	pages: [
		{ pgno: 1, data: page1 }, // each `data` is exactly `pageSize` bytes
		{ pgno: 2, data: page2 },
		// ...
	],
});

// Litestream looks for files named by their transaction range.
await writeFile(formatFilename(1n, 1n), file); // 0000000000000001-...ltx

Writing an incremental

An incremental (minTXID > 1n) carries only the pages the transaction dirtied.

const file = encodeLtx({
	pageSize,
	commit: 14,
	minTXID: 2n,
	maxTXID: 2n,
	pages: dirtyPages,
});

Reading

import { readFile, writeFile } from 'node:fs/promises';
import { applyLtxToImage, decodeLtx, decodeToSnapshotImage } from 'ltx-format';

const snapshot = decodeLtx(await readFile('0000000000000001-0000000000000001.ltx'));

// Rebuild the full database image a snapshot describes.
let image = decodeToSnapshotImage(snapshot);

// Then replay incrementals on top of it, in transaction order.
for (const path of incrementals) {
	image = applyLtxToImage(image, decodeLtx(await readFile(path)));
}

await writeFile('restored.db', image);

decodeLtx verifies the file checksum and cross-checks the page index against the page block, throwing on any mismatch. Pass { verifyChecksums: false } to skip checksum verification when you are inspecting a file you are still assembling.

Checksums

LTX defines a rolling post-apply checksum over the whole database, which lets a reader detect a replica that has diverged from its source. Tracking it costs a CRC-64 over every page of the database on every transaction, and Litestream v0.5.11 does not do it — it sets HeaderFlagNoChecksum and writes zeros.

This library follows Litestream by default. Opt in with trackChecksums: true:

const file = encodeLtx({
	pageSize,
	commit,
	minTXID: 5n,
	maxTXID: 5n,
	pages: dirtyPages,
	trackChecksums: true,
	preApplyChecksum: previous.trailer.postApplyChecksum,
	postApplyChecksum: rolling, // you maintain this across transactions
});

For a snapshot the post-apply checksum is computed for you, since a snapshot contains every page. For an incremental it depends on pages the file does not carry, so you have to supply it — by folding each change into the value you carried forward from the previous transaction:

import { CHECKSUM_FLAG, foldPageChecksum } from 'ltx-format';

// CHECKSUM_FLAG is the value for an empty database, so it is only the right
// starting point for the very first transaction.
let rolling = previous?.trailer.postApplyChecksum ?? CHECKSUM_FLAG;

for (const page of dirtyPages) {
	const before = imageBeforeTransaction.subarray(
		(page.pgno - 1) * pageSize,
		page.pgno * pageSize
	);
	// Pages appended by this transaction have nothing to remove.
	if (before.length === pageSize) {
		rolling = foldPageChecksum(rolling, page.pgno, before);
	}
	rolling = foldPageChecksum(rolling, page.pgno, page.data);
}

foldPageChecksum is an XOR fold, so it is its own inverse and independent of page order — folding a page in twice removes it again, which is what makes the "remove the old, add the new" pattern above work. If the transaction shrinks the database, fold out every page past the new commit as well.

API

Codec

| Export | Purpose | | --------------------------------- | ------------------------------------------------------------- | | encodeLtx(options) | Encode one LTX file. Returns Uint8Array. | | decodeLtx(bytes, options?) | Decode and verify a file. Returns DecodedLtx. | | decodeToSnapshotImage(decoded) | Rebuild the full database image a snapshot describes. | | applyLtxToImage(image, decoded) | Apply an incremental onto an image, truncating past commit. |

Format primitives

| Export | Purpose | | ----------------------------------------- | -------------------------------------------------- | | readHeader(bytes) / writeHeader(h) | The 100-byte header, with validation. | | formatFilename(min, max) | <minTXID>-<maxTXID>.ltx, 16 hex digits each. | | formatTxid(txid) / parseTxid(text) | Canonical zero-padded lowercase hex TXIDs. | | isSnapshot(header) | true when minTXID === 1n. | | isValidPageSize(size) | Powers of two from 512 to 65536. | | lockPgno(pageSize) | SQLite's lock page for a page size. | | MAGIC, VERSION, HEADER_SIZE, … | Format constants. |

Building blocks

Exported because they are independently useful, and because a codec you cannot poke at is a codec you cannot debug.

| Export | Purpose | | ----------------------------------------------- | ------------------------------------------- | | Crc64, crc64, checksumPage, foldPageChecksum | CRC-64/ISO, matching Go's hash/crc64. | | encodeLz4Frame / decodeLz4Frame | Minimal LZ4 frame writer and reader. | | writeUvarint / readUvarint / uvarintSize | LEB128 varints, matching Go's binary. | | xxhash32 | xxHash32, as the LZ4 frame format needs. |

All types (LtxHeader, LtxPage, LtxTrailer, DecodedLtx, DecodeOptions, EncodeLtxOptions) are exported too.

Compatibility

Byte-compatible with Litestream v0.5.11 / superfly ltx v0.5.1. Files written here are readable by litestream restore, and that is enforced by a differential test that shells out to the real binary rather than asserted in prose. See docs/format.md for the byte layout.

Two deliberate choices are worth knowing about:

  • The writer emits stored (uncompressed) LZ4 blocks. That is fully legal per the frame specification and keeps a compressor off the critical path. The reader handles compressed blocks, because Litestream writes them.
  • Timestamps default to 0, so encoding is deterministic: the same pages produce the same bytes. Pass timestamp if you want a real one.

One capability of the format is not implemented: the encoder cannot write deletion files (commit === 0), which the reference implementation uses to record that a database was dropped. The decoder reads them fine.

encodeLtx and writeHeader enforce every invariant Header.Validate() and Encoder.EncodePage() enforce in ltx v0.5.1, so a file this library accepts is one the reference implementation accepts. That parity is the point: a validation gap here shows up as an unreadable replica later.

Development

npm install
npm run verify   # lint, typecheck, test, build, package checks

Individual steps: npm run lint, npm run typecheck, npm test, npm run test:coverage, npm run build, npm run check:package.

The differential test needs litestream v0.5.x and sqlite3 on PATH. It skips itself when litestream is absent, so a clean checkout still passes — CI installs it so the check is never silently lost.

Prior art

The format is defined by superfly/ltx (Apache-2.0); the checksum construction and on-disk layout here follow that implementation so the two interoperate. The LZ4 code implements the published frame format specification and contains no code from the reference LZ4 implementation.

License

Apache-2.0