{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sqlite-db",
  "type": "registry:block",
  "title": "tneemo/sqlite-db",
  "description": "Zero-dependency SQLite for Node.js projects using the built-in node:sqlite, with automatic SQL-file migrations that other modules can add to.",
  "author": "tneemo",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "db/migrations/.gitkeep",
      "type": "registry:file",
      "target": "~/db/migrations/.gitkeep",
      "content": ""
    },
    {
      "path": "src/db/index.ts",
      "type": "registry:file",
      "target": "~/src/db/index.ts",
      "content": "import { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { migrate } from \"./migrate\";\n\nlet instance: DatabaseSync | undefined;\n\n/** DATABASE_PATH, or data/app.db; tests (NODE_ENV=test) get a fresh in-memory database. */\nexport function databasePath(): string {\n  return process.env.DATABASE_PATH ?? (process.env.NODE_ENV === \"test\" ? \":memory:\" : \"data/app.db\");\n}\n\n/** The shared connection. Opened on first use, with pending migrations applied. */\nexport function getDb(): DatabaseSync {\n  if (!instance) {\n    const path = databasePath();\n    if (path !== \":memory:\") mkdirSync(dirname(path), { recursive: true });\n    const db = new DatabaseSync(path);\n    db.exec(\"PRAGMA foreign_keys = ON;\");\n    if (path !== \":memory:\") db.exec(\"PRAGMA journal_mode = WAL;\");\n    migrate(db);\n    instance = db;\n  }\n  return instance;\n}\n\n/** Runs fn in a transaction; rolls back if it throws. */\nexport function transaction<T>(fn: (db: DatabaseSync) => T): T {\n  const db = getDb();\n  db.exec(\"BEGIN\");\n  try {\n    const result = fn(db);\n    db.exec(\"COMMIT\");\n    return result;\n  } catch (err) {\n    db.exec(\"ROLLBACK\");\n    throw err;\n  }\n}\n\nexport function closeDb(): void {\n  instance?.close();\n  instance = undefined;\n}\n\nexport { migrate };\n"
    },
    {
      "path": "src/db/migrate.ts",
      "type": "registry:file",
      "target": "~/src/db/migrate.ts",
      "content": "import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * Applies every *.sql file in `dir` that has not been applied yet, in file-name order.\n * Name files `<UTC timestamp>_<description>.sql`, e.g. `20261019120000_todos.sql`.\n */\nexport function migrate(db: DatabaseSync, dir = process.env.MIGRATIONS_DIR ?? \"db/migrations\"): string[] {\n  db.exec(\"CREATE TABLE IF NOT EXISTS _migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)\");\n  if (!existsSync(dir)) return [];\n  const applied = new Set(db.prepare(\"SELECT name FROM _migrations\").all().map((row) => String(row.name)));\n  const pending = readdirSync(dir).filter((f) => f.endsWith(\".sql\") && !applied.has(f)).sort();\n  for (const name of pending) {\n    db.exec(\"BEGIN\");\n    try {\n      db.exec(readFileSync(join(dir, name), \"utf8\"));\n      db.prepare(\"INSERT INTO _migrations (name, applied_at) VALUES (?, ?)\").run(name, new Date().toISOString());\n      db.exec(\"COMMIT\");\n    } catch (err) {\n      db.exec(\"ROLLBACK\");\n      throw new Error(`Migration ${name} failed: ${(err as Error).message}`);\n    }\n  }\n  return pending;\n}\n"
    },
    {
      "path": "test/db.test.ts",
      "type": "registry:file",
      "target": "~/test/db.test.ts",
      "content": "import { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { describe, expect, it } from \"vitest\";\nimport { getDb, migrate, transaction } from \"../src/db\";\n\ndescribe(\"db\", () => {\n  it(\"applies migrations once, in name order\", () => {\n    const dir = mkdtempSync(join(tmpdir(), \"migrations-\"));\n    writeFileSync(join(dir, \"20260102000000_b.sql\"), \"INSERT INTO t (v) VALUES ('b');\");\n    writeFileSync(join(dir, \"20260101000000_a.sql\"), \"CREATE TABLE t (v TEXT);\");\n    const db = new DatabaseSync(\":memory:\");\n    expect(migrate(db, dir)).toEqual([\"20260101000000_a.sql\", \"20260102000000_b.sql\"]);\n    expect(migrate(db, dir)).toEqual([]);\n    expect(db.prepare(\"SELECT v FROM t\").all()).toEqual([{ v: \"b\" }]);\n  });\n\n  it(\"rolls back failed transactions\", () => {\n    const db = getDb();\n    db.exec(\"CREATE TABLE IF NOT EXISTS items (name TEXT NOT NULL)\");\n    expect(() =>\n      transaction((tx) => {\n        tx.prepare(\"INSERT INTO items (name) VALUES (?)\").run(\"kept?\");\n        throw new Error(\"boom\");\n      }),\n    ).toThrow(\"boom\");\n    expect(db.prepare(\"SELECT COUNT(*) AS n FROM items\").get()).toEqual({ n: 0 });\n  });\n});\n"
    }
  ],
  "envVars": {},
  "docs": "1. Use the database anywhere on the server:\n   ```ts\n   import { getDb } from \"../db\"; // path relative to your file; the module lives in src/db\n   const rows = getDb().prepare(\"SELECT * FROM todos WHERE user_id = ?\").all(userId);\n   ```\n   `node:sqlite` statements are synchronous: `.run(...)`, `.get(...)`, `.all(...)`. Use `RETURNING *` with `.get()` to read back inserted rows.\n2. Add tables with a new file in `db/migrations/` named `<UTC timestamp>_<name>.sql`, e.g. `db/migrations/20261019120000_todos.sql`. It runs automatically on the next `getDb()` call.\n3. The database file is `data/app.db` by default (set `DATABASE_PATH` to change it). Make sure `data/` is in `.gitignore`. Tests use an in-memory database automatically.\n",
  "categories": [
    "database",
    "db",
    "sqlite",
    "sql",
    "migrations",
    "persistence",
    "storage"
  ],
  "meta": {
    "pkg": "https://pkg.tneemo.com/tneemo/sqlite-db",
    "version": "1.0.0",
    "license": "MIT"
  }
}