import { Credential, Group, Machine, type Ressource, Service, User, } from '@models' import type { CredentialCategory } from '@models/credential.ts' //!TODO link ressources (get, list) //!TODO Purge unused ressources (delete) export class Db { #kv: Deno.Kv static async init(path?: string) { const kv = await Deno.openKv(path ?? Deno.env.get('DB_PATH')) return new Db(kv) } private constructor(kv: Deno.Kv) { this.#kv = kv } get storage() { return this.#kv } get prefix() { return { ressource: 'ressource', } } get ressource() { return { credential: this.#ressourceStorage>( 'credential', Credential, ), group: this.#ressourceStorage('group', Group), machine: this.#ressourceStorage('machine', Machine), service: this.#ressourceStorage('service', Service), user: this.#ressourceStorage('user', User), } } #ressourceStorage( type: RessourceType, Builder: RessourceBuilder, ) { return { get: (ressource: Pick) => this.#get(type, Builder, ressource), set: (ressources: T[]) => this.#set(type, ressources), delete: (ressources: Pick[]) => this.#delete(type, ressources), list: ( filter: (ressource: T) => boolean | Promise = () => true, ) => this.#list(type, Builder, filter), } } async #get( type: RessourceType, Builder: RessourceBuilder, entry: Pick, ): Promise { const json = await this.#kv.get>([ this.prefix.ressource, type, entry.uuid, ]) if (json.value) { //@ts-expect-error Type union of Ressource types for Builder return Builder.fromJSON(json.value) } throw new ReferenceError( `no ressource.${type} was found with uuid: "${entry.uuid}"`, ) } #set(type: RessourceType, entries: T[]) { const atomic = this.#kv.atomic() for (const entry of entries) { //! TODO check if `refs` exists const key = [this.prefix.ressource, type, entry.uuid] atomic.set(key, entry.toJSON()) } return atomic.commit() } #delete( type: RessourceType, entries: Pick[], ): Promise { const atomic = this.#kv.atomic() for (const entry of entries) { //! TODO check if `refs` exists atomic.delete([this.prefix.ressource, type, entry.uuid]) } return atomic.commit() } async *#list( type: RessourceType, Builder: RessourceBuilder, filter: (entry: T) => boolean | Promise, ): AsyncGenerator { const list = this.#kv.list>({ prefix: [this.prefix.ressource, type], }) for await (const entry of list) { const value = entry.value //@ts-expect-error Type union of Ressource types for Builder const ressource = Builder.fromJSON(value) as T if (await filter(ressource)) { yield ressource } } } } type RessourceType = T extends Credential ? 'credential' : T extends Group ? 'group' : T extends Machine ? 'machine' : T extends Service ? 'service' : T extends User ? 'user' : never type RessourceBuilder = T extends Credential ? typeof Credential : T extends Group ? typeof Group : T extends Machine ? typeof Machine : T extends Service ? typeof Service : T extends User ? typeof User : never type RessourceJson = ReturnType