-
Notifications
You must be signed in to change notification settings - Fork 0
/
db.ts
328 lines (263 loc) · 9.77 KB
/
db.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// Florian Crafter (ClashCrafter#0001) - 02-05.2021 - Version 2.5.1
// Modified by Kile to only use what is necessary. Find the full code here: https://github.com/FlorianStrobl/Discord-Pylon-Bot/blob/master/Scripts/BetterKV/betterKV.ts
// this namespace will be used, if you don't specify a namespace
const defaultNamespace: string = 'database';
// pylons byte limit per KV key. you shoudn't change it!! If you bought BYOB and have higher byte limits, change the value here.
const maxByteSize: number = 8196;
// This is the default namespace.
export const Default_KV: pylon.KVNamespace = new pylon.KVNamespace(
defaultNamespace
);
type worked = boolean;
type key = string | number;
// #region extern functions
// save a value to a key
export async function save(
key: key,
value: pylon.Json,
namespace?: string,
overwriteIfExist: boolean = true
): Promise<worked> {
// validate inputs
if (
value === undefined ||
(await getSize(value)) > maxByteSize ||
key === null ||
key === undefined ||
key === 'databaseKeySize' ||
(typeof key === 'string' && key.startsWith('database_'))
)
return false;
key = key.toString();
const KV: pylon.KVNamespace = await getKV(namespace);
let size: number = await getDBKeySize(KV.namespace);
let savedData: pylon.JsonObject;
try {
// check if key is already in some db key and change the value. return true if so
for (let i: number = 0; i <= size; ++i) {
savedData = await getInternalObject(i, KV.namespace);
// search data in current db key
const cvalue: pylon.Json | undefined = savedData[key];
if (cvalue !== undefined) {
// value does exist
if (overwriteIfExist === false) return false;
savedData[key] = value; // change value of existing data in local array
if ((await saveInternalObject(savedData, i, KV.namespace)) === false) {
// too many bytes for current key => delete object from current key and saving it as new
delete savedData[key];
await saveInternalObject(savedData, i, KV.namespace);
//await dbKeyOrder(KV.namespace);
await save(key, cvalue, KV.namespace);
} else return true;
}
}
// key is not in current database => try to save in an existing db key
for (let i: number = 0; i <= size; ++i) {
savedData = await getInternalObject(i, KV.namespace);
// saving the data
savedData[key] = value;
if ((await saveInternalObject(savedData, i, KV.namespace)) === true)
return true; // current key has space if true => data is saved in this db key
}
// no db key had space and key didn't exist yet => new db key is cerated and object saved there
++size;
if (
(await saveInternalObject({ [key]: value }, size, KV.namespace)) === true
) {
await KV.put(`databaseKeySize`, size);
return true;
} else return false;
} catch (error) {
console.log(error);
return false;
}
}
export async function transact(
key: key,
edit: (value: pylon.Json | undefined) => pylon.Json,
namespace?: string,
replaceUndefined?: boolean
): Promise<worked>;
export async function transact(
key: key[],
edit: (value: pylon.Json | undefined) => pylon.Json,
namespace?: string,
replaceUndefined?: boolean
): Promise<worked[]>;
// modify values on the fly
export async function transact(
key: key | key[],
edit: (value: pylon.Json | undefined) => pylon.Json,
namespace?: string,
replaceUndefined: boolean = false
): Promise<worked | worked[]> {
if (Array.isArray(key)) {
// array so just do this function recursively
let workedForAll: worked[] = [];
for await (const k of key)
workedForAll.push(await transact(k, edit, namespace, replaceUndefined));
return workedForAll;
//return !workedForAll.includes(false);
}
key = key.toString();
const KV: pylon.KVNamespace = await getKV(namespace);
// try get current data
const oldValue: pylon.Json | undefined = await get(key, KV.namespace);
if (oldValue === undefined && replaceUndefined === false) return false;
let newValue: pylon.Json = await edit(oldValue); // updated data locally
if (newValue === undefined) return false;
// object is too big
if ((await getSize(newValue)) <= maxByteSize)
return await save(key, newValue, KV.namespace);
// updated object
else return false;
}
export async function del(key: key, namespace?: string): Promise<worked>;
export async function del(key: key[], namespace?: string): Promise<worked[]>;
// delete the key(s) and it's value(s)
export async function del(
key: key | key[],
namespace?: string
): Promise<worked | worked[]> {
if (Array.isArray(key)) {
// array so just do this function recursively
let workedForAll: worked[] = [];
for await (const k of key) workedForAll.push(await del(k, namespace));
return workedForAll;
// if you just want to know if it was for every single one succesfull do:
// return !workedForAll.includes(false);
}
key = key.toString();
const KV: pylon.KVNamespace = await getKV(namespace);
const size: number = await getDBKeySize(KV.namespace);
// go through every db key and search for the key
for (let i: number = 0; i <= size; ++i) {
let savedData: pylon.JsonObject = await getInternalObject(i, namespace);
// object is in current key
if (savedData[key] !== undefined) {
// found data and deleting it localy
delete savedData[key];
// update db key
await saveInternalObject(savedData, i, KV.namespace);
if (Object.keys(savedData).length === 0) await dbKeyOrder(KV.namespace); // db keys have to be sorted, because one of the db keys is now empty
return true;
}
}
// no key+data was deleted
return false;
}
export async function exist(key: key, namespace?: string): Promise<boolean>;
export async function exist(key: key[], namespace?: string): Promise<boolean[]>;
// check if an key exist
export async function exist(
key: key | key[],
namespace?: string
): Promise<boolean | boolean[]> {
if (Array.isArray(key)) {
// array so just do this function recursively
let exists: boolean[] = [];
for await (const k of key)
exists.push((await get(k, namespace)) !== undefined);
return exists;
}
return (await get(key, namespace)) !== undefined;
}
export async function get<T extends pylon.Json>(
key: key,
namespace?: string
): Promise<T | undefined>;
export async function get<T extends pylon.Json>(
key: key[],
namespace?: string
): Promise<T[] | undefined>;
// get the values from the key(s)
export async function get<T extends pylon.Json>(
key: key | key[],
namespace?: string
): Promise<T | T[] | undefined> {
if (Array.isArray(key)) {
// array so just do this function recursively
let values: (pylon.Json | undefined)[] = [];
for await (const k of key) values.push(await get(k, namespace));
return values as any;
}
key = key.toString();
const KV: pylon.KVNamespace = await getKV(namespace);
const size: number = await getDBKeySize(KV.namespace);
// it is more optimized to go manually through the keys, than just doing GetAllValues() and searching there for the data
for (let i: number = 0; i <= size; ++i) {
// search for key in the db key and return the value if it exists
const savedData: pylon.JsonObject = await getInternalObject(i, namespace);
if (savedData[key] !== undefined) return (savedData as any)[key];
}
// key doesn't exist
return undefined;
}
// get the KV for the namespace
async function getKV(namespace?: string): Promise<pylon.KVNamespace> {
if (namespace !== undefined && namespace !== null)
return new pylon.KVNamespace(namespace);
else return Default_KV;
}
// get number of db keys in this namespace
async function getDBKeySize(namespace: string): Promise<number> {
return (await (await getKV(namespace)).get<number>(`databaseKeySize`)) ?? 0;
}
async function getInternalObject(
index: number,
namespace?: string
): Promise<pylon.JsonObject> {
const KV: pylon.KVNamespace = await getKV(namespace);
return (await KV.get(`database_${index}`)) ?? {};
}
async function saveInternalObject(
value: pylon.Json,
index: number,
namespace?: string
): Promise<boolean> {
const KV: pylon.KVNamespace = await getKV(namespace);
if ((await getSize(value)) <= maxByteSize) {
await KV.put(`database_${index}`, value);
return true;
} else return false;
}
// correct empty db keys
async function dbKeyOrder(namespace: string): Promise<boolean> {
const KV: pylon.KVNamespace = await getKV(namespace);
let size: number =
(await getDBKeySize(namespace)) === 0 ? -1 : await getDBKeySize(namespace);
for (let i: number = 0; i <= size; ++i) {
const data: pylon.JsonObject | undefined = await getInternalObject(
i,
namespace
);
if (data === undefined || Object.keys(data).length === 0) {
// current key is empty
// puts data from key x+1 in key x
for (let y: number = i; y < size; ++y)
await KV.put(
`database_${y}`,
(await KV.get(`database_${y + 1}`)) ?? {}
);
try {
// deletes empty key which is now the last one
await KV.delete(`database_${size}`);
} catch (_) {}
// decreases the size
--size;
// In theory one more key if database is empty, but doesn't work right now. TODO
if (size === 0 || size === -1)
try {
await KV.delete(`databaseKeySize`);
} catch (_) {}
else await KV.put(`databaseKeySize`, size); // update size
await dbKeyOrder(KV.namespace); // restart the whole process to check for a second empty key
return true;
}
}
return false; // changed nothing
}
// get the size in bytes of an object saved as JSON string
async function getSize(data: any): Promise<number> {
return new TextEncoder().encode(JSON.stringify(data)).length;
}