diff --git a/package-lock.json b/package-lock.json index 30da447..b1cefe2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.13.0", "@sqlite.org/sqlite-wasm": "^3.46.1-build2", "framer-motion": "^11.3.31", + "neverchange": "^0.0.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -4307,6 +4308,15 @@ "dev": true, "license": "MIT" }, + "node_modules/neverchange": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/neverchange/-/neverchange-0.0.1.tgz", + "integrity": "sha512-KyxnQUknDnDbwWZV8niWW90P+lRkGQfWCG9R7Jo4EVlMVVZeCT02aeReBcLBhJE6kldQptlb7FchTKZH4yi8rg==", + "license": "MIT", + "dependencies": { + "@sqlite.org/sqlite-wasm": "^3.46.1-build2" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", diff --git a/package.json b/package.json index d2b9243..1dfef52 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.13.0", "@sqlite.org/sqlite-wasm": "^3.46.1-build2", "framer-motion": "^11.3.31", + "neverchange": "^0.0.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/src/db.ts b/src/db.ts index 46febb9..2dccf69 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,106 +1,44 @@ -import { sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm'; - -let dbPromise: Promise<(command: string, params: any) => Promise> | null = null; -let dbId: string | null = null; - -async function createInitialSchema(promiser: (command: string, params: any) => Promise) { - await promiser('exec', { - sql: ` - CREATE TABLE IF NOT EXISTS todos ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - text TEXT NOT NULL, - completed BOOLEAN NOT NULL DEFAULT 0 - ) - `, - dbId, - }); -} - -async function migrateDatabase(promiser: (command: string, params: any) => Promise) { - try { - const tableInfo = await promiser('exec', { - sql: "PRAGMA table_info(todos)", - rowMode: 'object', - dbId, - }); - - // migration check - const deletedColumnExists = tableInfo.result.resultRows.some( - (row: any) => row.name === 'deleted' - ); - - if (!deletedColumnExists) { - await promiser('exec', { - sql: "ALTER TABLE todos ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT 0", - dbId, - }); - console.log('Migration completed: Added deleted column to todos table'); - } else { - console.log('Migration not needed: deleted column already exists'); - } - } catch (error) { - console.error('Migration failed:', error); - throw error; - } -} +// @ts-ignore +import { NeverChangeDB } from 'neverchange'; +let db: any = null; +const DB_NAME = 'todo'; export async function initDb() { - if (dbPromise) return dbPromise; - - dbPromise = new Promise(async (resolve, reject) => { - try { - console.log('Loading and initializing SQLite3 module...'); - - const promiser = await new Promise((resolve) => { - const _promiser = sqlite3Worker1Promiser({ - onready: () => resolve(_promiser), - }); - }) as (command: string, params: any) => Promise; - - console.log('Done initializing. Opening database...'); - - // OPFS - let openResponse; - try { - openResponse = await promiser('open', { - filename: 'file:todo.sqlite3?vfs=opfs', - }); - console.log('OPFS database opened:', openResponse.result.filename); - } catch (opfsError) { - console.warn('OPFS is not available, falling back to in-memory database:', opfsError); - openResponse = await promiser('open', { - filename: ':memory:', - }); - console.log('In-memory database opened'); - } - - dbId = openResponse.result.dbId; - - // create schema - await createInitialSchema(promiser); - - // migration - await migrateDatabase(promiser); - - console.log('Database initialized and migrated successfully'); - resolve(promiser); - } catch (err) { - console.error('Failed to initialize or migrate database:', err); - reject(err); - } - }); - - return dbPromise; + if(db) db; + + db = new NeverChangeDB(DB_NAME); + + db.addMigrations([ + { + version: 1, + up: async (db: any) => { + await db.execute(` + CREATE TABLE IF NOT EXISTS todos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + text TEXT NOT NULL, + completed BOOLEAN NOT NULL DEFAULT 0 + ) + `); + }, + }, + { + version: 2, + up: async (db: any) => { + await db.execute(` + ALTER TABLE todos ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT 0 + `); + }, + }, + ]); + await db.init(); } export async function addTodo(text: string) { - const promiser = await initDb(); try { - await promiser('exec', { - sql: 'INSERT INTO todos (text) VALUES (?)', - bind: [text], - dbId, - }); + await db.execute( + 'INSERT INTO todos (text) VALUES (?)', + [text], + ); console.log('Todo added successfully'); } catch (error) { console.error('Failed to add todo:', error); @@ -108,33 +46,20 @@ export async function addTodo(text: string) { } export async function getTodos() { - const promiser = await initDb(); - const result = await promiser('exec', { - sql: 'SELECT * FROM todos WHERE deleted = 0 ORDER BY id DESC', - rowMode: 'object', - dbId, - }); - const { resultRows: rows } = result.result; + const rows = await db.query('SELECT * FROM todos WHERE deleted = 0 ORDER BY id DESC'); return rows || []; } export async function toggleTodo(id: number) { - const promiser = await initDb(); - await promiser('exec', { - sql: 'UPDATE todos SET completed = NOT completed WHERE id = ?', - bind: [id], - dbId, - }); + await db.execute( + 'UPDATE todos SET completed = NOT completed WHERE id = ?', + [id]); } export async function updateTodo(id: number, text: string) { - const promiser = await initDb(); try { - await promiser('exec', { - sql: 'UPDATE todos SET text = ? WHERE id = ?', - bind: [text, id], - dbId, - }); + await db.execute('UPDATE todos SET text = ? WHERE id = ?', + [text, id]); console.log('Todo updated successfully'); } catch (error) { console.error('Failed to update todo:', error); @@ -143,13 +68,11 @@ export async function updateTodo(id: number, text: string) { } export async function deleteTodo(id: number) { - const promiser = await initDb(); try { - await promiser('exec', { - sql: 'UPDATE todos SET deleted = 1 WHERE id = ?', - bind: [id], - dbId, - }); + await db.execute( + 'UPDATE todos SET deleted = 1 WHERE id = ?', + [id], + ); console.log('Todo marked as deleted successfully'); } catch (error) { console.error('Failed to mark todo as deleted:', error);