All files / store / kv.ts

100.00% Branches 13/13
100.00% Lines 71/71
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
x1
 
x5
 
 
x5
x5
x5
x5
x9
x9
x9
 
x5
x5
 
x5
x5
x9
x9
 
x5
x5
x8
x8
 
x5
x5
x115
x460
x115
 
x5
x5
x41
x41
x41
x41
x324
x81
x41
x164
x41
 
x5
x5
x12
x12
x12
x84
x21
x12
x36
x12
 
x5
x5
x19
x19
x19
x25
x175
x174
x19
x162
x27
x19
x33
x33
x34
x34
x33
x315
x63
x33
x36
x36
x19
x19
x19
x5














































































// Imports
import type { callback, Nullable, Promisable, record, rw } from "@libs/typing"
import { Store as _Store } from "./store.ts"
import type { key, version } from "./store.ts"

/** Key-Value store based upon {@link https://deno.land/api?s=Deno.Kv&unstable | Deno.KV}. */
export class Store extends _Store {
  /** Constructor. */
  constructor(options: { path?: string } & ConstructorParameters<typeof _Store>[0] = {}) {
    super(options)
    this.#kv = null as unknown as Deno.Kv
  }

  /** {@link Deno.Kv} store. */
  readonly #kv

  /** Open {@link Store}. */
  protected async _open() {
    ;(this as rw).#kv = await Deno.openKv(this.options.path as string)
  }

  /** Close {@link Store} instance. */
  protected _close() {
    this.#kv.close()
  }

  /** Get entry from {@link Store}. */
  protected async _get<T extends object>(key: key): Promise<{ value: Nullable<T>; version: Nullable<version> }> {
    const { value, versionstamp: version } = await this.#kv.get<T>(key)
    return { value, version }
  }

  /** Set entry in {@link Store}. */
  protected async _set<T extends object>(keys: key[], value: T, versionstamp: Nullable<version>): Promise<{ ok: boolean; version: version }> {
    let ok = false
    let version = null
    const transaction = this.#kv.atomic()
    for (const key of keys) {
      transaction.check({ key, versionstamp }).set(key, value)
    }
    ;({ ok, versionstamp: version } = await transaction.commit() as Deno.KvCommitResult)
    return { ok, version }
  }

  /** Delete entry from {@link Store}. */
  protected async _delete(keys: key[], versionstamp: Nullable<version>): Promise<{ ok: boolean }> {
    let ok = false
    const transaction = this.#kv.atomic()
    for (const key of keys) {
      transaction.check({ key, versionstamp }).delete(key)
    }
    ;({ ok } = await transaction.commit() as Deno.KvCommitResult)
    return { ok }
  }

  /** List entries from {@link Store}. */
  protected _list<T extends record>(key: key | [key, key], { limit, reverse }: { limit?: number; reverse?: boolean }, array: boolean): Promisable<Array<{ key: key; value: T; version: version }> | AsyncGenerator<{ key: key; value: T; version: version }>> {
    let list: Deno.KvListIterator<T>
    let last = null as Nullable<callback>
    if ((Array.isArray(key[0])) && (Array.isArray(key[1]))) {
      const [start, end] = key
      list = this.#kv.list<T>({ start, end }, { limit, reverse })
      last = async () => (await Array.fromAsync(this.#kv.list({ prefix: [], start: end }, { limit: 1 })))[0]
    } else {
      list = this.#kv.list<T>({ prefix: key as key }, { limit, reverse })
    }
    const iterator = (async function* () {
      const inclusive = await last?.()
      if (inclusive && reverse) {
        yield inclusive
      }
      for await (const { key, value, versionstamp: version } of list) {
        yield { key: key as key, value, version }
      }
      if ((inclusive && !reverse)) {
        yield inclusive
      }
    })()
    return array ? Array.fromAsync(iterator) : iterator
  }
}