feat: add SQLite database and persistent task schema
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,6 +17,10 @@
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# local database
|
||||
/data/*.sqlite
|
||||
/data/*.sqlite-*
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
|
||||
17
db/schema.sql
Normal file
17
db/schema.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL CHECK (length(trim(title)) > 0),
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
due_date TEXT NOT NULL CHECK (due_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'),
|
||||
topic TEXT NOT NULL CHECK (length(trim(topic)) > 0),
|
||||
status TEXT NOT NULL DEFAULT 'Todo' CHECK (status IN ('Todo', 'In-Progress', 'Complete')),
|
||||
archived_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_active_status
|
||||
ON tasks (archived_at, status, due_date);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_topic
|
||||
ON tasks (topic COLLATE NOCASE);
|
||||
36
lib/db.ts
36
lib/db.ts
@@ -0,0 +1,36 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
|
||||
type DatabaseGlobal = typeof globalThis & {
|
||||
plannerDatabase?: DatabaseSync;
|
||||
plannerDatabasePath?: string;
|
||||
};
|
||||
|
||||
const schemaPath = path.join(process.cwd(), "db", "schema.sql");
|
||||
const defaultDatabasePath = path.join(process.cwd(), "data", "planner.sqlite");
|
||||
|
||||
export function getDatabase() {
|
||||
const databasePath = process.env.TASKS_DB_PATH ?? defaultDatabasePath;
|
||||
const globalDatabase = globalThis as DatabaseGlobal;
|
||||
|
||||
if (
|
||||
globalDatabase.plannerDatabase &&
|
||||
globalDatabase.plannerDatabasePath === databasePath
|
||||
) {
|
||||
return globalDatabase.plannerDatabase;
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(databasePath), { recursive: true });
|
||||
|
||||
const database = new DatabaseSync(databasePath);
|
||||
const schema = fs.readFileSync(schemaPath, "utf8");
|
||||
|
||||
database.exec("PRAGMA foreign_keys = ON;");
|
||||
database.exec(schema);
|
||||
|
||||
globalDatabase.plannerDatabase = database;
|
||||
globalDatabase.plannerDatabasePath = databasePath;
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
19
types/node-sqlite.d.ts
vendored
Normal file
19
types/node-sqlite.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
declare module "node:sqlite" {
|
||||
export type RunResult = {
|
||||
changes: number;
|
||||
lastInsertRowid: number | bigint;
|
||||
};
|
||||
|
||||
export class StatementSync {
|
||||
all(...params: unknown[]): unknown[];
|
||||
get(...params: unknown[]): unknown;
|
||||
run(...params: unknown[]): RunResult;
|
||||
}
|
||||
|
||||
export class DatabaseSync {
|
||||
constructor(location?: string | URL, options?: Record<string, unknown>);
|
||||
close(): void;
|
||||
exec(sql: string): void;
|
||||
prepare(sql: string): StatementSync;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user