Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
167 changes: 45 additions & 122 deletions src/db.ts
Original file line number Diff line number Diff line change
@@ -1,140 +1,65 @@
import { sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm';

let dbPromise: Promise<(command: string, params: any) => Promise<any>> | null = null;
let dbId: string | null = null;

async function createInitialSchema(promiser: (command: string, params: any) => Promise<any>) {
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<any>) {
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<unknown>((resolve) => {
const _promiser = sqlite3Worker1Promiser({
onready: () => resolve(_promiser),
});
}) as (command: string, params: any) => Promise<any>;

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);
}
}

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);
Expand All @@ -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);
Expand Down