🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// SQLite database client (sql.js — pure WASM, no native deps)
|
||||
import initSqlJs, { type Database, type BindParams } from "sql.js";
|
||||
import { createTables } from "./schema.js";
|
||||
|
||||
let db: Database | null = null;
|
||||
let initPromise: Promise<Database> | null = null;
|
||||
|
||||
/** Initialize the database (call once at startup). */
|
||||
export async function initDb(dbPath?: string): Promise<Database> {
|
||||
if (db) return db;
|
||||
if (initPromise) return initPromise;
|
||||
|
||||
initPromise = (async () => {
|
||||
const SQL = await initSqlJs();
|
||||
const path = dbPath || process.env.DATABASE_URL || ":memory:";
|
||||
|
||||
// Try to load existing database from file
|
||||
let buffer: ArrayLike<number> | undefined;
|
||||
if (path !== ":memory:") {
|
||||
try {
|
||||
const fs = await import("node:fs/promises");
|
||||
const data = await fs.readFile(path);
|
||||
buffer = new Uint8Array(data);
|
||||
} catch {
|
||||
// File doesn't exist yet — start fresh
|
||||
}
|
||||
}
|
||||
|
||||
db = new SQL.Database(buffer);
|
||||
db.run("PRAGMA foreign_keys = ON");
|
||||
createTables(db);
|
||||
return db;
|
||||
})();
|
||||
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
/** Get the initialized database (must call initDb first). */
|
||||
export function getDb(): Database {
|
||||
if (!db) throw new Error("Database not initialized. Call initDb() first.");
|
||||
return db;
|
||||
}
|
||||
|
||||
/** Save database to disk. */
|
||||
export async function saveDb(dbPath?: string): Promise<void> {
|
||||
if (!db) return;
|
||||
const path = dbPath || process.env.DATABASE_URL || "./data/app.db";
|
||||
if (path === ":memory:") return;
|
||||
const fs = await import("node:fs/promises");
|
||||
const { dirname } = await import("node:path");
|
||||
await fs.mkdir(dirname(path), { recursive: true });
|
||||
const data = db.export();
|
||||
await fs.writeFile(path, Buffer.from(data));
|
||||
}
|
||||
|
||||
export async function closeDb(): Promise<void> {
|
||||
if (db) {
|
||||
await saveDb();
|
||||
db.close();
|
||||
db = null;
|
||||
initPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: run a query and return all rows as objects
|
||||
export function queryAll<T = Record<string, unknown>>(sql: string, params: BindParams = []): T[] {
|
||||
const d = getDb();
|
||||
const stmt = d.prepare(sql);
|
||||
if (params) stmt.bind(params);
|
||||
const results: T[] = [];
|
||||
while (stmt.step()) {
|
||||
const row = stmt.getAsObject();
|
||||
results.push(row as unknown as T);
|
||||
}
|
||||
stmt.free();
|
||||
return results;
|
||||
}
|
||||
|
||||
// Helper: run a query and return the first row
|
||||
export function queryOne<T = Record<string, unknown>>(sql: string, params: BindParams = []): T | undefined {
|
||||
const rows = queryAll<T>(sql, params);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
// Helper: run a mutation and return { changes, lastInsertRowid }
|
||||
export function execute(sql: string, params: BindParams = []): { changes: number; lastInsertRowid: number } {
|
||||
const d = getDb();
|
||||
d.run(sql, params);
|
||||
return {
|
||||
changes: d.getRowsModified(),
|
||||
lastInsertRowid: 0,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Auto-generated SQLite schema
|
||||
import type { Database } from "sql.js";
|
||||
|
||||
export function createTables(db: Database): void {
|
||||
const statements = [
|
||||
"CREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n username TEXT NOT NULL UNIQUE,\n password_hash TEXT NOT NULL,\n nickname TEXT,\n role TEXT DEFAULT 'user',\n phone TEXT,\n avatar_url TEXT,\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS contracts (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT NOT NULL REFERENCES users(id),\n title TEXT NOT NULL,\n party_a TEXT,\n party_b TEXT,\n amount REAL,\n signed_at TEXT,\n expires_at TEXT,\n status TEXT DEFAULT 'draft',\n file_url TEXT,\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS approvals (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT NOT NULL REFERENCES users(id),\n entity_type TEXT NOT NULL,\n entity_id TEXT,\n applicant_id TEXT NOT NULL REFERENCES users(id),\n status TEXT DEFAULT 'pending',\n form_data TEXT,\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS customers (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT NOT NULL REFERENCES users(id),\n name TEXT NOT NULL,\n email TEXT,\n phone TEXT,\n company TEXT,\n source TEXT,\n tags TEXT DEFAULT '[]',\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS reminders (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT NOT NULL REFERENCES users(id),\n entity_type TEXT NOT NULL,\n entity_id TEXT,\n remind_at TEXT NOT NULL,\n message TEXT,\n sent INTEGER DEFAULT 'false',\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS employees (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT REFERENCES users(id),\n name TEXT NOT NULL,\n email TEXT,\n phone TEXT,\n department TEXT,\n position TEXT,\n hire_date TEXT,\n salary REAL,\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS attendance (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT REFERENCES users(id),\n title TEXT NOT NULL,\n description TEXT,\n status TEXT,\n data TEXT,\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS items (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT REFERENCES users(id),\n title TEXT NOT NULL,\n description TEXT,\n status TEXT,\n data TEXT,\n created_at TEXT,\n updated_at TEXT\n);",
|
||||
"CREATE TABLE IF NOT EXISTS performance (\n id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n user_id TEXT REFERENCES users(id),\n title TEXT NOT NULL,\n description TEXT,\n status TEXT,\n data TEXT,\n created_at TEXT,\n updated_at TEXT\n);"
|
||||
];
|
||||
for (const sql of statements) {
|
||||
const trimmed = sql.trim();
|
||||
if (trimmed) db.run(trimmed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user