userdo
Version:
A Durable Object base class that provides user authentication, per-user data storage, and real-time updates for Cloudflare Workers applications.
77 lines (76 loc) • 3.4 kB
JavaScript
import { GenericQuery } from './query';
export class GenericTable {
constructor(tableName, schema, storage, userId, broadcast) {
this.tableName = tableName;
this.schema = schema;
this.storage = storage;
this.userId = userId;
this.broadcast = broadcast;
}
async create(data) {
const validated = this.schema.parse(data);
const id = crypto.randomUUID();
const now = Date.now();
const insertSQL = `INSERT INTO "${this.tableName}" (id, data, created_at, updated_at, user_id) VALUES (?, ?, ?, ?, ?)`;
this.storage.sql.exec(insertSQL, id, JSON.stringify(validated), now, now, this.userId);
const result = { ...validated, id, createdAt: new Date(now), updatedAt: new Date(now) };
// Broadcast table change
this.broadcast?.(`table:${this.tableName}`, { type: 'create', data: result });
return result;
}
async findById(id) {
const selectSQL = `SELECT * FROM "${this.tableName}" WHERE id = ? AND user_id = ? LIMIT 1`;
const cursor = this.storage.sql.exec(selectSQL, id, this.userId);
const row = cursor.one();
if (!row)
return null;
const data = JSON.parse(row.data);
return {
...data,
id: row.id,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at)
};
}
async update(id, updates) {
const existing = await this.findById(id);
if (!existing)
throw new Error('Record not found');
const merged = { ...existing, ...updates };
delete merged.id;
delete merged.createdAt;
delete merged.updatedAt;
const validated = this.schema.parse(merged);
const now = Date.now();
const updateSQL = `UPDATE "${this.tableName}" SET data = ?, updated_at = ? WHERE id = ? AND user_id = ?`;
this.storage.sql.exec(updateSQL, JSON.stringify(validated), now, id, this.userId);
const result = { ...validated, id, createdAt: existing.createdAt, updatedAt: new Date(now) };
// Broadcast table change
this.broadcast?.(`table:${this.tableName}`, { type: 'update', data: result });
return result;
}
async delete(id) {
const deleteSQL = `DELETE FROM "${this.tableName}" WHERE id = ? AND user_id = ?`;
this.storage.sql.exec(deleteSQL, id, this.userId);
// Broadcast table change
this.broadcast?.(`table:${this.tableName}`, { type: 'delete', data: { id } });
}
where(path, operator, value) {
return new GenericQuery(this.tableName, this.storage, this.schema, this.userId).where(path, operator, value);
}
orderBy(field, direction = 'asc') {
return new GenericQuery(this.tableName, this.storage, this.schema, this.userId).orderBy(field, direction);
}
limit(count) {
return new GenericQuery(this.tableName, this.storage, this.schema, this.userId).limit(count);
}
async getAll() {
return new GenericQuery(this.tableName, this.storage, this.schema, this.userId).get();
}
async count() {
const countSQL = `SELECT COUNT(*) as count FROM "${this.tableName}" WHERE user_id = ?`;
const cursor = this.storage.sql.exec(countSQL, this.userId);
const row = cursor.one();
return row ? Number(row.count) : 0;
}
}