// deno-lint-ignore-file no-explicit-any
import { EntityQuery } from "../modules/db/entity_query.ts";
import { EventEmitter } from "../modules/db/event_emitter.ts";
import { Postgres } from "https://viapak.xyz/@db/pg";
export interface Entity extends DB.Entity {}
export class Entity extends EventEmitter {
constructor(params: DB.Entity) {
super();
this.id = params.id;
this.created_at = params.created_at;
const t = this as any;
for(const [key, value] of Object.entries(params)) {
if(Object.hasOwn(params, key)) {
t[key] = value;
}
}
this.__parent_class = this.EntityClass.name;
delete t.deleted_at;
}
get EntityClass() {
return this.constructor as typeof Entity;
}
async save() {
this.emit('save');
if(!this.id) {
const [ id ] = await this.EntityClass.insert(this.__clean());
this.id = id;
this.created_at = new Date();
return this;
}
await this.EntityClass.update({ id: this.id! }, this.__clean());
return this;
}
__clean(keep_id?: boolean) {
const t = {} as Record<string, unknown>;
for(const key of Object.keys(this)) {
if(key.startsWith('__')) continue;
if(key == 'id' && !keep_id) continue;
if(['created_at', 'deleted_at'].includes(key)) {
continue;
}
t[key] = (this as Record<string, unknown>)[key];
}
return t;
}
static get table() {
return this.name.toLowerCase();
}
protected get table() {
return this.EntityClass.table;
}
static find<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T> = {}): EntityQuery<T> {
return new EntityQuery(this as unknown as typeof Entity, (this as unknown as typeof Entity).table, filter);
}
static async findOne<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T> = {}): Promise<T | null> {
return (await this.find(filter))[0];
}
static async count<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T> = {}) {
return await this.find(filter).count();
}
static async exists<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T> = {}) {
return await this.find(filter).exists();
}
static async delete<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T> = {}, permanent?: boolean) {
return await this.find(filter).delete(permanent);
}
static async restore<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T> = {}) {
return await this.find(filter).restore();
}
static async update<T extends Entity>(this: DB.Entity.Constructor<T>, filter: DB.Filterable<T>, values: Partial<DB.Entity.Clean<T>>) {
return await this.find(filter).update(values);
}
static async insert<T extends Entity>(this: DB.Entity.Constructor<T>, input: Omit<Partial<DB.Entity.Clean<T>>, 'id' | 'created_at'> | Omit<Partial<DB.Entity.Clean<T>>, 'id' | 'created_at'>[]): Promise<number[]> {
const items = [input].flat() as Partial<DB.Entity.Clean<T>>[];
if(items.length == 0) return [];
const keys = Object.entries(items[0]).filter(([k, v]) => !['id', 'created_at'].includes(k) && v !== undefined).map(([k]) => k);
const values: unknown[] = [];
const valueRows = items.map((item, rowIndex) => {
const rowValues = keys.map((key, colIndex) => {
const value = (item as any)[key];
if(value === undefined) return undefined;
values.push(value);
return `$${rowIndex * keys.length + colIndex + 1}`;
}).filter(v => v !== undefined);
return `(${rowValues.join(', ')})`;
});
const sql = `INSERT INTO "${this.table}" (${keys.join(', ')}) VALUES ${valueRows.join(', ')} RETURNING id`;
const { rows } = await Postgres.query<{ id: number }>(sql, values);
return rows.map(row => row.id) || [];
}
}