diff --git a/src/db/mod.ts b/src/db/mod.ts new file mode 100644 index 0000000..148d1c2 --- /dev/null +++ b/src/db/mod.ts @@ -0,0 +1,128 @@ +import { Credential, Group, Machine, Ressource, Service, User } from '@models' + +//!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 = () => 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, + ): 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 (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