resources_manager/src/models/utils/ref.ts

110 lines
2.6 KiB
TypeScript

import type { UUID } from '../../../types.ts'
import type { Db } from '../../db/mod.ts'
import {
Credential,
Group,
Machine,
type Resource,
Service,
User,
} from '../mod.ts'
export type RefString<T extends Resource> =
| `@ref/${T['type']}#${UUID}`
| Ref<T>
export type RefResolver<T extends Resource> = (
ref: RefString<T>,
) => T | Promise<T>
export class Ref<T extends Resource> extends String {
static #toString<T extends Resource>(
{ uuid, type }: { uuid: UUID; type: T['type'] },
) {
return `@ref/${type}#${uuid}` as const
}
static parse<T extends Resource>(
string: RefString<T>,
): { type: T['type']; uuid: UUID } {
const [_, value] = string.split('/')
const [type, uuid] = value.split('#') as [T['type'], UUID]
return { type, uuid } as const
}
static fromResource<T extends Resource>(resource: T): Ref<T> {
return new Ref<T>(resource)
}
static fromString<T extends Resource>(string: RefString<T>): Ref<T> {
return new Ref<T>(Ref.parse(string))
}
static dbResolver(
db: Db,
): <T extends Resource>(ref: RefString<T>) => Promise<T> {
return <T extends Resource>(ref: RefString<T>): Promise<T> => {
const { type, uuid } = Ref.parse(ref)
//@ts-expect-error force type casting to fix
return db.resource[type].get({ uuid })
}
}
static restResolver(
endpoint: string | URL,
): <T extends Resource>(ref: RefString<T>) => Promise<T> {
return async <T extends Resource>(ref: RefString<T>): Promise<T> => {
const { type, uuid } = Ref.parse(ref)
const url = new URL(`${type}s/${uuid}`, endpoint)
const response = await fetch(url)
const json = await response.json()
if (type === 'user') {
return User.fromJSON(json) as unknown as T
}
if (type === 'machine') {
return Machine.fromJSON(json) as unknown as T
}
if (type === 'service') {
return Service.fromJSON(json) as unknown as T
}
if (type === 'group') {
return Group.fromJSON(json) as unknown as T
}
if (type === 'credential') {
return Credential.fromJSON(json) as unknown as T
}
throw new TypeError(`unknown ref type "${type}"`)
}
}
private constructor({ uuid, type }: { uuid: UUID; type: T['type'] }) {
super(Ref.#toString({ uuid, type }))
this.#type = type
this.#uuid = uuid
}
#type: T['type']
#uuid: UUID
get type(): T['type'] {
return this.#type
}
get uuid(): UUID {
return this.#uuid
}
ref(resolver: RefResolver<T>): T | Promise<T> {
return resolver(this.toString())
}
toString(): `@ref/${T['type']}#${UUID}` {
return Ref.#toString<T>({ uuid: this.uuid, type: this.type })
}
toJSON(): `@ref/${T['type']}#${UUID}` {
return this.toString()
}
}