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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 |
x1
x5
x1
x5
x5
x5
x5
x15
x15
x15
x15
x15
x15
x5
x5
x15
x15
x5
x5
x5
x5
x15
x15
x24
x24
x15
x16
x16
x15
x5
x10
x9
x9
x9
x9
x5
x5
x66
x66
x66
x5
x5
x120
x120
x120
x676
x676
x169
x930
x744
x120
x5
x5
x45
x142
x45
x45
x45
x425
x85
x85
x83
x415
x332
x45
x5
x5
x15
x50
x15
x15
x80
x16
x16
x24
x15
x120
x24
x24
x115
x23
x15
x5
x5
x21
x84
x147
x21
x21
x5
x5
x5
x205
x213
x213
x397
x205 |
|
import { Logger } from "@libs/logger"
import type { Nullable, Promisable, record } from "@libs/typing"
export type { Promisable, record }
export abstract class Store {
constructor({ log = new Logger(), ...options }: { log?: Logger } = {}) {
const { promise, resolve, reject } = Promise.withResolvers<this>()
this.ready = promise
this.options = options as record
this.#log = log
this.#init(resolve, reject)
}
readonly ready: Promise<this>
protected readonly options: record
readonly #log
async #init(resolve: (value: this) => void, reject: (error: Error) => void) {
try {
await this._open()
this.#log.debug("opened")
resolve(this)
} catch (error) {
reject(error)
}
}
protected abstract _open(): Promisable<void>
async [Symbol.asyncDispose]() {
await this.ready
await this._close()
this.#log.debug("closed")
}
protected abstract _close(): Promisable<void>
async has(key: key): Promise<boolean> {
await this.ready
return (await this.get(key)).version !== null
}
async get<T extends object>(key: key): Promise<{ value: Nullable<T>; version: Nullable<version> }> {
await this.ready
const { value, version } = await this._get<T>(key)
if (version === null) {
this.#log.with({ op: "get", key: f(key) }).wdebug("no result")
return { value: null, version: null }
}
this.#log.with({ op: "get", key: f(key), version }).trace()
return { value, version }
}
protected abstract _get<T extends object>(key: key): Promisable<{ value: Nullable<T>; version: Nullable<version> }>
async set<T extends object>(keys: key | key[], value: T, version = null as Nullable<version>): Promise<{ value: T; version: version }> {
await this.ready
const indexes = Array.isArray(keys[0]) ? keys as Array<key> : [keys as key]
const primary = indexes[0]
const { ok, version: _version } = await this._set(indexes, value, version)
if (!ok) {
this.#log.with({ op: "set", key: f(primary), version }).error("failed")
throw new TypeError(`Failed to write: ${f(primary)}@${version}`)
}
version = _version
this.#log.with({ op: "set", key: f(primary), version }).debug()
return { value, version }
}
protected abstract _set<T extends object>(keys: key[], value: T, versionstamp: Nullable<version>): Promisable<{ ok: boolean; version: version }>
async delete(keys: key | key[], version = null as Nullable<version>): Promise<boolean> {
await this.ready
const indexes = Array.isArray(keys[0]) ? keys as Array<key> : [keys as key]
const primary = indexes[0] as key
if (!await this.has(primary)) {
this.#log.with({ op: "delete", key: f(primary), version }).wdebug("no result")
return false
}
const { ok } = await this._delete(indexes, version)
if (!ok) {
this.#log.with({ op: "delete", key: f(primary), version }).error("failed")
throw new TypeError(`Failed to delete: ${f(primary)}@${version}`)
}
this.#log.with({ op: "delete", key: f(primary), version }).debug()
return ok
}
protected abstract _delete(keys: key[], versionstamp: Nullable<version>): Promisable<{ ok: boolean }>
async list<T extends record>(key: key | [key, key], options?: { limit?: number; reverse?: boolean; array: true }): Promise<Array<{ key: key; value: T; version: version }>>
async list<T extends record>(key: key | [key, key], options?: { limit?: number; reverse?: boolean; array?: false }): Promise<AsyncGenerator<{ key: key; value: T; version: version }>>
async list<T extends record>(key: key | [key, key], { limit, reverse, array = true }: { limit?: number; reverse?: boolean; array?: boolean } = {}): Promise<Array<{ key: key; value: T; version: version }> | AsyncGenerator<{ key: key; value: T; version: version }>> {
await this.ready
const list = this._list<T>(key, { limit, reverse }, array)
this.#log.with({ op: "list", key: f(key), limit, reverse, array }).trace()
return list
}
protected abstract _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 }>>
}
export type key = Array<string | number | boolean | bigint>
export type version = string
function f(key: key | [key, key]): string {
if ((Array.isArray(key[0])) && (Array.isArray(key[1]))) {
return `[${f(key[0])}...${f(key[1])}]`
}
return `[${key.join(", ")}]`
}
|