jsonc-data
v0.0.1
Published
JSONC (JSON Lines with Comments) file storage for Bun. Append-only log format that supports logical deletes and updates via `asMap()`.
Readme
jsonc-data
JSONC (JSON Lines with Comments) file storage for Bun. Append-only log format that supports logical deletes and updates via asMap().
Install
bun add jsonc-dataUsage
import { JsoncData } from "jsonc-data";
const db = new JsoncData("data.jsonl", "My data file");
// Save records
await db.save({ id: 1, name: "Alice" });
await db.save({ id: 2, name: "Bob" });
// Get all records as array
const all = await db.asArray();
// [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
// Get records as Map (for easy lookups and updates)
const map = await db.asMap("id");
// Map { 1 => { id: 1, name: "Alice" }, 2 => { id: 2, name: "Bob" } }How It Works
Append-Only Log
jsonc-data uses an append-only log format (JSON Lines). Records are never overwritten or deleted from the file itself. Instead:
- Save: Appends a new JSON line to the file
- Delete: Appends a special comment marking a record as deleted
- Update: Saving a new record with the same key, then using
asMap()returns only the latest version
Example: Updates via asMap
const db = new JsoncData("users.jsonl", "User database");
// Original record
await db.save({ userId: "u1", name: "Alice", age: 25 });
// Update - this doesn't modify the old record in the file
await db.save({ userId: "u1", name: "Alice", age: 26 });
// asMap returns only the latest version for each key
const users = await db.asMap("userId");
// Map { "u1" => { userId: "u1", name: "Alice", age: 26 } }Deleting Records
const db = new JsoncData("users.jsonl", "User database");
await db.save({ userId: "u1", name: "Alice" });
await db.save({ userId: "u2", name: "Bob" });
// Logically delete a record by key
await db.deleteRecord("userId", "u1");
// asArray excludes deleted records
const users = await db.asArray();
// [{ userId: "u2", name: "Bob" }]
// asMap also excludes deleted records
const userMap = await db.asMap("userId");
// Map { "u2" => { userId: "u2", name: "Bob" } }Important: deleteRecord() appends a marker comment to the file (e.g., # DELETE userId=u1). The original data remains in the file but is filtered out when reading via asArray() or asMap().
File Format
# My data file
{"id":1,"name":"Alice"}
{"id":2,"name":"Bob"}
# DELETE id=1
{"id":1,"name":"Alice Updated"}Lines starting with # are comments. Delete markers use the format # DELETE key=value.
API
new JsoncData(filename: string, headerComment?: string)
Creates a new JsoncData instance.
filename: Path to the JSONL fileheaderComment: Optional comment to write at the top of new files
save(data: object): Promise<void>
Appends a JSON object as a new line in the file.
deleteRecord(key: string, value: any): Promise<void>
Appends a delete marker comment. Records matching key=value will be filtered out when reading.
asArray(): Promise<object[]>
Returns all non-deleted records as an array, in order of insertion.
asMap(keyName: string): Promise<Map>
Returns a Map where keys are the values of keyName from each record. Useful for lookups and automatically handles updates (latest record wins).
comment(text: string): Promise<void>
Appends a comment line to the file.
License
MIT
