lowdb JSON store: working versions (tested 2026-10-16)
Seed default data, read and write nested collections, and verify a persistence roundtrip with lowdb's JSONFilePreset and MemoryPreset fully offline.
passed5 passed · 0 failed2026-10-16 02:18 UTC- Status
- passed
- Last passed
- 2026-10-16 02:18 UTC
- Tests
- 5 / 5
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
lowdb@7.0.1@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38839686104
The code
Exactly the files that passed. Your agent can pull them with get_recipe("lowdb-json-store") (install), or see them on GitHub.
package.json
{
"name": "lowdb-json-store",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"lowdb": "7.0.1"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/store.ts
import { Low, Memory } from "lowdb";
import { JSONFilePreset } from "lowdb/node";
export type User = { id: string; name: string };
export type Comment = { id: string; postId: string; body: string };
export type Post = {
id: string;
title: string;
authorId: string;
tags: string[];
comments: Comment[];
};
export type Data = {
users: User[];
posts: Post[];
};
export type Store = Low<Data>;
export const defaultData: Data = {
users: [],
posts: [],
};
export async function openStore(filename: string): Promise<Store> {
const store = await JSONFilePreset<Data>(filename, structuredClone(defaultData));
// Ensure the seed file exists for callers that read it immediately.
await store.write();
return store;
}
export async function openMemoryStore(): Promise<Store> {
const store = new Low<Data>(new Memory<Data>(), structuredClone(defaultData));
await store.read();
return store;
}
export async function addUser(store: Store, user: User): Promise<void> {
await store.update((data) => {
if (data.users.some((u) => u.id === user.id)) {
throw new Error(`user already exists: ${user.id}`);
}
data.users.push(user);
});
}
export async function addPost(store: Store, post: Post): Promise<void> {
await store.update((data) => {
const author = data.users.find((u) => u.id === post.authorId);
if (!author) {
throw new Error(`unknown author: ${post.authorId}`);
}
data.posts.push(post);
});
}
export async function addComment(
store: Store,
postId: string,
comment: Comment,
): Promise<void> {
await store.update((data) => {
const post = data.posts.find((p) => p.id === postId);
if (!post) {
throw new Error(`unknown post: ${postId}`);
}
post.comments.push(comment);
});
}
export function getPost(store: Store, postId: string): Post | undefined {
return store.data.posts.find((p) => p.id === postId);
}
export function getPostsByTag(store: Store, tag: string): Post[] {
return store.data.posts.filter((p) => p.tags.includes(tag));
}
export function getUserPosts(store: Store, userId: string): Post[] {
return store.data.posts.filter((p) => p.authorId === userId);
}
export function countComments(store: Store, postId: string): number {
return getPost(store, postId)?.comments.length ?? 0;
}
test/store.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
addComment,
addPost,
addUser,
countComments,
defaultData,
getPost,
getPostsByTag,
getUserPosts,
openMemoryStore,
openStore,
} from "../src/store.ts";
async function withTempFile(fn: (file: string) => Promise<void>): Promise<void> {
const dir = await mkdtemp(join(tmpdir(), "lowdb-"));
try {
await fn(join(dir, "db.json"));
} finally {
await rm(dir, { recursive: true, force: true });
}
}
test("seeds default data into a missing JSON file", async () => {
await withTempFile(async (file) => {
const store = await openStore(file);
assert.deepEqual(store.data, defaultData);
const raw = await readFile(file, "utf8");
assert.deepEqual(JSON.parse(raw), defaultData);
});
});
test("persists nested collections across reopen", async () => {
await withTempFile(async (file) => {
const store = await openStore(file);
await addUser(store, { id: "u1", name: "Ada" });
await addPost(store, {
id: "p1",
title: "Hello lowdb",
authorId: "u1",
tags: ["intro", "json"],
comments: [],
});
await addComment(store, "p1", { id: "c1", postId: "p1", body: "nice" });
await addComment(store, "p1", { id: "c2", postId: "p1", body: "agree" });
const reopened = await openStore(file);
const post = getPost(reopened, "p1");
assert.ok(post);
assert.deepEqual(post!.tags, ["intro", "json"]);
assert.equal(countComments(reopened, "p1"), 2);
assert.deepEqual(getUserPosts(reopened, "u1").map((p) => p.id), ["p1"]);
assert.deepEqual(getPostsByTag(reopened, "json").map((p) => p.id), ["p1"]);
});
});
test("supports direct data mutation followed by write()", async () => {
await withTempFile(async (file) => {
const store = await openStore(file);
await addUser(store, { id: "u9", name: "Linus" });
store.data.posts.push({
id: "p9",
title: "Manual write",
authorId: "u9",
tags: ["manual"],
comments: [],
});
await store.write();
const reopened = await openStore(file);
assert.equal(getPost(reopened, "p9")?.title, "Manual write");
});
});
test("Memory adapter keeps data in memory only", async () => {
const store = await openMemoryStore();
assert.deepEqual(store.data, defaultData);
await addUser(store, { id: "u2", name: "Grace" });
await addPost(store, {
id: "p2",
title: "Ephemeral",
authorId: "u2",
tags: ["memory"],
comments: [],
});
assert.deepEqual(getUserPosts(store, "u2").map((p) => p.id), ["p2"]);
});
test("rejects writes that violate referential integrity", async () => {
const store = await openMemoryStore();
await assert.rejects(
() =>
addPost(store, {
id: "p3",
title: "orphan",
authorId: "missing",
tags: [],
comments: [],
}),
/unknown author/,
);
assert.equal(store.data.posts.length, 0);
await assert.rejects(
() => addComment(store, "nope", { id: "c9", postId: "nope", body: "x" }),
/unknown post/,
);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.