feat(db): add key-value db

This commit is contained in:
Julien Oculi 2024-05-13 17:57:43 +02:00
parent 0fc2da45ab
commit 03aff06804

128
src/db/mod.ts Normal file
View file

@ -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', Credential),
group: this.#ressourceStorage<Group>('group', Group),
machine: this.#ressourceStorage<Machine>('machine', Machine),
service: this.#ressourceStorage<Service>('service', Service),
user: this.#ressourceStorage<User>('user', User),
}
}
#ressourceStorage<T extends Ressource>(
type: RessourceType<T>,
Builder: RessourceBuilder<T>,
) {
return {
get: (ressource: Pick<T, 'uuid'>) =>
this.#get<T>(type, Builder, ressource),
set: (ressources: T[]) => this.#set<T>(type, ressources),
delete: (ressources: Pick<T, 'uuid'>[]) =>
this.#delete<T>(type, ressources),
list: (filter: (ressource: T) => boolean = () => true) =>
this.#list<T>(type, Builder, filter),
}
}
async #get<T extends Ressource>(
type: RessourceType<T>,
Builder: RessourceBuilder<T>,
entry: Pick<T, 'uuid'>,
): Promise<T> {
const json = await this.#kv.get<RessourceJson<T>>([
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<T extends Ressource>(type: RessourceType<T>, 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<T extends Ressource>(
type: RessourceType<T>,
entries: Pick<T, 'uuid'>[],
): Promise<Deno.KvCommitResult | Deno.KvCommitError> {
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<T extends Ressource>(
type: RessourceType<T>,
Builder: RessourceBuilder<T>,
filter: (entry: T) => boolean,
): AsyncGenerator<T, void, void> {
const list = this.#kv.list<RessourceJson<T>>({
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 Ressource> = 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 Ressource> = 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<T extends Ressource> = ReturnType<T['toJSON']>