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
x6
x1
x6
x6
x6
x6
x16
x16
x16
x16
x16
x16
x6
x6
x16
x16
x6
x6
x6
x6
x16
x16
x25
x25
x16
x17
x17
x16
x6
x12
x10
x10
x10
x10
x6
x6
x67
x67
x67
x6
x6
x121
x121
x121
x680
x680
x170
x935
x748
x121
x6
x6
x46
x145
x46
x46
x46
x430
x86
x86
x84
x420
x336
x46
x6
x6
x16
x53
x16
x16
x85
x17
x17
x25
x16
x125
x25
x25
x120
x24
x16
x6
x6
x22
x88
x154
x22
x22
x6
x6
x6
x206
x214
x214
x398
x206 |
|
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(", ")}]`
}
|