-
Notifications
You must be signed in to change notification settings - Fork 0
/
kv-storage-adapter.ts
130 lines (112 loc) · 3.85 KB
/
kv-storage-adapter.ts
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
import type {
Chunk,
StorageAdapterInterface,
StorageKey
} from 'npm:@automerge/[email protected]'
import { getLexicalIndexAt } from './lexicalIndex.ts'
type Data = Uint8Array
// Values have a maximum length of 64 KiB after serialization.
// https://docs.deno.com/api/deno/~/Deno.Kv
const VALUE_MAX_LEN = 65536
export class DenoKVStorageAdapter implements StorageAdapterInterface {
constructor(private kv: Deno.Kv) {}
async load(key: StorageKey): Promise<Data | undefined> {
const entry = await this.kv.get<Data>(key)
if (entry.value) return entry.value
const list = this.kv.list<Data>({
prefix: key
})
const returnData: number[] = []
for await (const entry of list) {
returnData.push(...entry.value)
}
if (returnData.length === 0) return undefined
return new Uint8Array(returnData)
}
async save(key: StorageKey, data: Data): Promise<void> {
if (data.length > VALUE_MAX_LEN) {
/**
* Threre might be a "single" value for this key,
* so clear it out
*/
await this.kv.delete(key)
/**
* Split the value into chunks and save them with a `chunk key`
*
* The `chunk key` is constructed by suffixing the original key
* with the lexically ordered index by chunk number:
*
* chunk 0 -> ['original', 'key', 'a']
* chunk 1 -> ['original', 'key', 'b']
* ...
* chunk 25 -> ['original', 'key', 'z']
* chunk 26 -> ['original', 'key', 'za']
* chunk 27 -> ['original', 'key', 'zb']
* ...
* chunk 51 -> ['original', 'key', 'zz']
* chunk 52 -> ['original', 'key', 'zza']
* chunk 53 -> ['original', 'key', 'zzb']
* ...
* chunk 77 -> ['original', 'key', 'zzz']
* ...
*/
const promises: Promise<void>[] = []
let chunkNumber = 0
for (let i = 0; i < data.length; i = i + VALUE_MAX_LEN) {
const chunkKey = key.concat(getLexicalIndexAt(chunkNumber++))
const sliced = data.slice(
i,
Math.min(i + VALUE_MAX_LEN, data.length)
)
this.kv.set(chunkKey, sliced)
}
await Promise.all(promises)
} else {
/**
* There might be chunked values for this key, so clear them out
*/
const list = await this.kv.list<Data>({
prefix: key
})
const promises = []
for await (const entry of list) {
promises.push(this.kv.delete(entry.key))
}
await Promise.all(promises)
//
await this.kv.set(key, data)
}
}
async remove(key: StorageKey) {
const list = await this.kv.list<Data>({
prefix: key
})
const promises = []
for await (const entry of list) {
promises.push(this.kv.delete(entry.key))
}
await Promise.all(promises)
await this.kv.delete(key)
}
async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
const list = await this.kv.list<Data>({
prefix: keyPrefix
})
const range: Chunk[] = []
for await (const entry of list) {
range.push({
key: entry.key.map(String),
data: entry.value
})
}
return range
}
async removeRange(keyPrefix: StorageKey): Promise<void> {
const list = await this.kv.list<Data>({
prefix: keyPrefix
})
for await (const entry of list) {
this.kv.delete(entry.key)
}
}
}