import State from "@infinity-beyond/modules/state.ts";
export type MetaRow = { key: string, value: string }
export type EntityMetaTypeNames = 'text' | 'int' | 'real' | 'boolean';
export type EntityMetaTypes = string | number | boolean;
export type EntityMetaConfig = Record<string, EntityMetaTypeNames>;
type InferMetaType<TypeName extends EntityMetaTypeNames> =
TypeName extends 'text' ? string :
TypeName extends 'int' ? number :
TypeName extends 'real' ? number :
TypeName extends 'boolean' ? boolean :
never;
type InferFromMetaType<
EMC extends EntityMetaConfig,
PEMC extends EntityMetaConfig,
ValidField,
F extends ValidField
> =
F extends keyof EMC ? InferMetaType<EMC[F]> :
F extends keyof PEMC ? InferMetaType<PEMC[F]> :
never;
export class EntityMeta<
EMC extends EntityMetaConfig,
// deno-lint-ignore ban-types
PEMC extends EntityMetaConfig = {},
ValidField = Extract<keyof EMC | keyof PEMC, string>
> {
private table: string;
private schema: EMC & PEMC;
constructor(table: string, default_schema: EMC, passed_schema?: PEMC) {
this.table = table;
this.schema = { ...passed_schema, ...default_schema } as EMC & PEMC;
}
async get<Field extends ValidField, Value = InferFromMetaType<EMC, PEMC, ValidField, Field>, Out = Field extends keyof EMC ? Value : Value | null>(field: Field): Promise<Out> {
const { rows: [ row ] } = (await State.PostgresClient.query<MetaRow>(
`SELECT * FROM ${this.table} WHERE key = $1 LIMIT 1`,
[ field ]
));
if(!row) return null as Out;
switch(this.schema[row.key]) {
case 'boolean': return (row.value == 'true') as Out;
case 'int': return parseInt(row.value) as Out;
case 'real': return parseFloat(row.value) as Out;
default: return row.value as Out;
}
}
async set<Field extends ValidField>(
field: Field,
value: InferFromMetaType<EMC, PEMC, ValidField, Field>
): Promise<InferFromMetaType<EMC, PEMC, ValidField, Field>> {
await State.PostgresClient.query(`UPDATE ${this.table} SET value=$2 WHERE key=$1`, [field, (value as EntityMetaTypes).toString()]);
return value;
}
async increment<
Field extends ExtractNumeric<EMC, PEMC, ValidField>
>(field: Field, amount: number): Promise<number | null> {
if (!['int', 'real'].includes(this.schema[field as keyof (EMC & PEMC)])) {
throw new Error(`Field ${String(field)} is not a number type`);
}
const cast = this.schema[field];
const { rows: [{ value } = {}] } = await State.PostgresClient.query<{ value: string }>(`UPDATE ${this.table} SET value = (value::${cast} + ${amount})::varchar WHERE key='${String(field)}' returning value`);
if(!value) return null;
if(cast == 'real') return parseFloat(value);
return parseInt(value);
}
}
type ExtractNumeric<EMC, PEMC, ValidField> =
Extract<ValidField, {[F in keyof EMC]: EMC[F] extends 'int' ? F : EMC[F] extends 'real' ? F : never}[keyof EMC]> |
Extract<ValidField, {[F in keyof PEMC]: PEMC[F] extends 'int' ? F : PEMC[F] extends 'real' ? F : never}[keyof PEMC]>;