Skip to content

Commit f2e9c7d

Browse files
committed
chore: release 0.2.0
1 parent 69f7161 commit f2e9c7d

6 files changed

Lines changed: 48 additions & 47 deletions

File tree

‎.changeset/pre-announcement-hardening.md‎

Lines changed: 0 additions & 43 deletions
This file was deleted.

‎CHANGELOG.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
11
# @libredb/libredb
22

3+
## 0.2.0
4+
5+
### Minor Changes
6+
7+
- 69f7161: Durability, safety, and API-contract hardening across the kernel, adapters, lenses, and CLI (the pre-announcement audit wave).
8+
9+
On-disk format: new databases now begin with an 8-byte `LRDB` magic/version header, and each record header carries a checksum of its own length field. Files written by earlier releases (headerless) keep opening through a legacy read path, and keep their legacy record framing on later appends. The header is what lets `open()` refuse a file that is not a LibreDB database with a clear error instead of destroying it; the record-header checksum is what lets recovery refuse a damaged length field instead of mistaking it for a torn tail.
10+
11+
DOWNGRADE WARNING: a file written by this release must never be opened by 0.1.3 or older — the old recovery cannot parse the header, classifies the whole file as a torn tail, and silently truncates it to zero bytes. Back up before any downgrade. Three smaller legacy-behavior changes: a headerless file whose only record is torn/incomplete now refuses to open as `NOT_A_DATABASE` (0.1.3 recovered it to an empty database; refusing is the safe reading, since such a file is indistinguishable from a foreign one); any file shorter than the 8-byte header is likewise refused untouched (a crash inside the first bytes of a brand-new database's first-ever commit therefore needs a manual delete — nothing in it was acknowledged); and a legacy length-field corruption still reads as a torn tail (the legacy format has no header checksum — the v1 format exists to close exactly that gap).
12+
13+
Kernel:
14+
15+
- `open({ path })` on a non-LibreDB file throws `NOT_A_DATABASE` and leaves the file byte-for-byte untouched (previously the file was silently truncated to zero).
16+
- Recovery classifies failures: a torn tail truncates (reported through the new `onRecovery` open option), while mid-log corruption throws `CORRUPT_WAL` and truncates nothing. Record payloads are structurally validated during replay.
17+
- A failed append/fsync latches the database: every later `transact()` throws `FAILED` until reopen, so an IO error can never lead recovery to silently drop later acknowledged commits.
18+
- `transact()` rejects async callbacks (`ASYNC_TRANSACTION`): writes after an `await` could never reach the log.
19+
- Keys and values are copied at the transaction boundary in both directions — caller buffer reuse and mutation of returned buffers can no longer corrupt the store.
20+
- `getRange` snapshots at first iteration, so delete-while-scanning visits every entry exactly once.
21+
- `close()` inside a transaction throws `CLOSE_IN_TRANSACTION` instead of surfacing a raw file error.
22+
- `open()` takes an exclusive per-file lock (`<path>.lock`, pid/host/nonce): a second writer throws `LOCKED` instead of silently diverging; locks from verifiably dead holders are reclaimed automatically. `FileSystem` gains an optional `lock()` seam method.
23+
- All kernel failures are now `LibreDbError` instances carrying a stable `code` (exported, with the `ErrorCode` and `RecoveryInfo` types).
24+
25+
Adapters:
26+
27+
- node-fs: creating a database fsyncs the parent directory (a fresh database can no longer vanish wholesale on power loss); a directory-fsync failure that is not a platform limitation (e.g. EIO) now surfaces as an error instead of being silently ignored; recovery truncation is fsync'd; reads are positional on the WAL's own file descriptor instead of re-reading the whole file per call.
28+
- OPFS: reads loop until filled, so a legal short read can no longer masquerade as a torn tail; recovery treats an incomplete read as an IO fault (`INCOMPLETE_READ`), never as license to truncate.
29+
30+
Lenses:
31+
32+
- Collection/table names may not be empty or contain `:` (both broke namespace isolation); ids keep full freedom.
33+
- Strings that are not well-formed UTF-16 (lone surrogates) are rejected wherever they would become keys, ids, names, or kv values — distinct strings can no longer silently collide on one key.
34+
- Relational `number` columns reject `NaN` and the infinities (JSON would store them as `null`).
35+
- `doc()` refuses a name cataloged as a relational table (it would bypass schema validation); `table()` refuses a document collection's name.
36+
- `find()`/`where()` reject a predicate field explicitly set to `undefined`, which previously matched documents _missing_ the field.
37+
38+
CLI:
39+
40+
- Write commands rely on the kernel's exclusive lock; `--force` removes a lock only when its holder is not verifiably alive, and never deletes a file that is not a libredb lock. Automatic reclaim is stricter still: only a lock whose holder is VERIFIABLY dead (same host, pid gone) is reclaimed without `--force` — anonymous locks (empty, or the sentinel-only 0.1.x format) carry no liveness information and now require `--force`.
41+
- `get`/`scan` escape control characters (including tab and newline) by default so untrusted values cannot inject terminal escape sequences — scripts that consumed values verbatim should pass `--raw`.
42+
43+
New exports: `LibreDbError`, `ErrorCode`, `RecoveryInfo`, `nodeFileSystem`, and `readonlyFileSystem` (open a database for inspection with no lock and no writes — the supported way to read a file a live writer holds).
44+
45+
Docker image now runs as a non-root user (distroless `:nonroot`, uid 65532): bind-mounted directories must be writable by that uid, or pass `--user "$(id -u):$(id -g)"`.
46+
347
## 0.1.3
448

549
### Patch Changes

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ bunx jsr add @libredb/libredb
102102
**CDN** — every release is served from the npm registry by the usual CDNs. Pin a version:
103103

104104
```ts
105-
import { open, kv } from "https://esm.sh/@libredb/libredb@0.1.3";
105+
import { open, kv } from "https://esm.sh/@libredb/libredb@0.2.0";
106106
```
107107

108108
**Browser** — a dedicated entry that imports nothing from `node:`, so it bundles for the browser

‎jsr.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@libredb/libredb",
3-
"version": "0.1.3",
3+
"version": "0.2.0",
44
"exports": {
55
".": "./src/index.ts",
66
"./browser": "./src/browser.ts"

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@libredb/libredb",
3-
"version": "0.1.3",
3+
"version": "0.2.0",
44
"description": "A small, readable, embeddable, multi-model database. One ordered key-value core, thin model lenses on top.",
55
"keywords": [
66
"database",

‎src/core.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323

2424
/** The LibreDB package version. Kept in sync with package.json. */
25-
export const version = "0.1.3";
25+
export const version = "0.2.0";
2626

2727
/**
2828
* The stable failure codes of the kernel. Every error the kernel throws is a

0 commit comments

Comments
 (0)