polystore 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/autocomplete.png +0 -0
- package/assets/autocomplete.webp +0 -0
- package/assets/favicon.png +0 -0
- package/assets/home.html +379 -0
- package/assets/splash.png +0 -0
- package/documentation.page.json +12 -0
- package/package.json +8 -3
- package/readme.md +297 -81
- package/src/clients/cloudflare.js +49 -0
- package/src/clients/cookie.js +55 -0
- package/src/clients/etcd.js +39 -0
- package/src/clients/file.js +75 -0
- package/src/clients/forage.js +42 -0
- package/src/clients/index.js +21 -0
- package/src/clients/level.js +45 -0
- package/src/clients/memory.js +38 -0
- package/src/clients/redis.js +56 -0
- package/src/clients/storage.js +43 -0
- package/src/index.js +269 -358
- package/src/index.test.js +314 -22
- package/src/index.types.ts +1 -0
- package/src/{index.d.ts → indexa.d.ts} +5 -3
- package/src/test/customFull.js +38 -0
- package/src/test/customSimple.js +23 -0
- package/src/test/setup.js +12 -0
- package/src/utils.js +44 -0
package/src/index.js
CHANGED
|
@@ -1,407 +1,318 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
parse.day = parse.d = parse.h * 24;
|
|
10
|
-
parse.week = parse.wk = parse.w = parse.d * 7;
|
|
11
|
-
parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
12
|
-
parse.month = parse.b = parse.y / 12;
|
|
13
|
-
|
|
14
|
-
// Returns the time in milliseconds
|
|
15
|
-
function parse(str) {
|
|
16
|
-
if (str === null || str === undefined) return null;
|
|
17
|
-
if (typeof str === "number") return str;
|
|
18
|
-
// ignore commas/placeholders
|
|
19
|
-
str = str.toLowerCase().replace(/[,_]/g, "");
|
|
20
|
-
let [_, value, units] = times.exec(str) || [];
|
|
21
|
-
if (!units) return null;
|
|
22
|
-
const unitValue = parse[units] || parse[units.replace(/s$/, "")];
|
|
23
|
-
if (!unitValue) return null;
|
|
24
|
-
const result = unitValue * parseFloat(value, 10);
|
|
25
|
-
return Math.abs(Math.round(result * 1000) / 1000);
|
|
1
|
+
import clients from "./clients/index.js";
|
|
2
|
+
import { createId, parse } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
function isClass(func) {
|
|
5
|
+
return (
|
|
6
|
+
typeof func === "function" &&
|
|
7
|
+
/^class\s/.test(Function.prototype.toString.call(func))
|
|
8
|
+
);
|
|
26
9
|
}
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
let bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
39
|
-
while (size--) {
|
|
40
|
-
// Using the bitwise AND operator to "cap" the value of
|
|
41
|
-
// the random byte from 255 to 63, in that way we can make sure
|
|
42
|
-
// that the value will be a valid index for the "chars" string.
|
|
43
|
-
id += urlAlphabet[bytes[size] & 61];
|
|
11
|
+
const getClient = (store) => {
|
|
12
|
+
// Already a fully compliant KV store
|
|
13
|
+
if (store instanceof Store) return store.client;
|
|
14
|
+
|
|
15
|
+
// One of the supported ones, so we receive an instance and
|
|
16
|
+
// wrap it with the client wrapper
|
|
17
|
+
for (let client of Object.values(clients)) {
|
|
18
|
+
if (client.test && client.test(store)) {
|
|
19
|
+
return new client(store);
|
|
20
|
+
}
|
|
44
21
|
}
|
|
45
|
-
return id;
|
|
46
|
-
}
|
|
47
22
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const entries = await store.entries(prefix);
|
|
54
|
-
return Object.fromEntries(entries);
|
|
55
|
-
};
|
|
56
|
-
const keys = async (prefix = "") => {
|
|
57
|
-
const all = await store.entries(prefix);
|
|
58
|
-
return all.map((p) => p[0]);
|
|
59
|
-
};
|
|
60
|
-
const values = async (prefix = "") => {
|
|
61
|
-
const all = await store.entries(prefix);
|
|
62
|
-
return all.map((p) => p[1]);
|
|
63
|
-
};
|
|
64
|
-
return { add, has, del, keys, values, all, ...store };
|
|
23
|
+
// A raw one, we just receive the single instance to use directly
|
|
24
|
+
if (isClass(store)) {
|
|
25
|
+
return new store();
|
|
26
|
+
}
|
|
27
|
+
return store;
|
|
65
28
|
};
|
|
66
29
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// hey it's better than nothing
|
|
70
|
-
layers.expire = (store) => {
|
|
71
|
-
// Item methods
|
|
72
|
-
const get = async (key) => {
|
|
73
|
-
const data = await store.get(key);
|
|
74
|
-
if (!data) return null;
|
|
75
|
-
const { value, expire } = data;
|
|
76
|
-
// It never expires
|
|
77
|
-
if (expire === null) return value;
|
|
78
|
-
const diff = expire - new Date().getTime();
|
|
79
|
-
if (diff <= 0) return null;
|
|
80
|
-
return value;
|
|
81
|
-
};
|
|
82
|
-
const set = async (key, value, { expire, expires } = {}) => {
|
|
83
|
-
const time = parse(expire || expires);
|
|
84
|
-
// Already expired, or do _not_ save it, then delete it
|
|
85
|
-
if (value === null || time === 0) return store.set(key, null);
|
|
86
|
-
const expDiff = time !== null ? new Date().getTime() + time * 1000 : null;
|
|
87
|
-
return store.set(key, { expire: expDiff, value });
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Group methods
|
|
91
|
-
const entries = async (prefix = "") => {
|
|
92
|
-
const all = await store.entries(prefix);
|
|
93
|
-
const now = new Date().getTime();
|
|
94
|
-
return all
|
|
95
|
-
.filter(([, data]) => {
|
|
96
|
-
// There's no data, so remove this
|
|
97
|
-
if (!data || data === null) return false;
|
|
30
|
+
class Store {
|
|
31
|
+
PREFIX = "";
|
|
98
32
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
33
|
+
constructor(clientPromise = new Map()) {
|
|
34
|
+
this.promise = Promise.resolve(clientPromise).then(async (client) => {
|
|
35
|
+
this.client = getClient(client);
|
|
36
|
+
this.#validate(this.client);
|
|
37
|
+
if (this.client.open) {
|
|
38
|
+
await this.client.open();
|
|
39
|
+
}
|
|
40
|
+
if (this.client.connect) {
|
|
41
|
+
await this.client.connect();
|
|
42
|
+
}
|
|
43
|
+
this.promise = null;
|
|
44
|
+
return client;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
102
47
|
|
|
103
|
-
|
|
104
|
-
|
|
48
|
+
#validate(client) {
|
|
49
|
+
if (!client.set || !client.get || !client.entries) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"A client should have at least a .get(), .set() and .entries()"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
105
54
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
55
|
+
if (!client.EXPIRES) {
|
|
56
|
+
if (client.has) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`You can only define client.has() when the client manages the expiration; otherwise please do NOT define .has() and let us manage it`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (client.keys) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`You can only define client.keys() when the client manages the expiration; otherwise please do NOT define .keys() and let us manage them`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (client.values) {
|
|
67
|
+
console.warn(
|
|
68
|
+
`Since this KV client does not manage expiration, it's better not to define client.values() since it doesn't allow us to evict expired keys`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
111
73
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
74
|
+
async add(data, options = {}) {
|
|
75
|
+
await this.promise;
|
|
76
|
+
const expires = parse(options.expire ?? options.expires);
|
|
115
77
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
78
|
+
// Use the underlying one from the client if found
|
|
79
|
+
if (this.client.add) {
|
|
80
|
+
if (this.client.EXPIRES) {
|
|
81
|
+
return this.client.add(this.PREFIX, data, { expires });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// In the data we need the timestamp since we need it "absolute":
|
|
85
|
+
const now = new Date().getTime();
|
|
86
|
+
const expDiff = expires === null ? null : now + expires * 1000;
|
|
87
|
+
return this.client.add(this.PREFIX, { expires: expDiff, value: data });
|
|
124
88
|
}
|
|
125
|
-
return key;
|
|
126
|
-
};
|
|
127
89
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
133
|
-
const clear = () => store.clear();
|
|
90
|
+
const id = createId();
|
|
91
|
+
await this.set(id, data, { expires });
|
|
92
|
+
return id; // The plain one without the prefix
|
|
93
|
+
}
|
|
134
94
|
|
|
135
|
-
|
|
136
|
-
|
|
95
|
+
async set(id, data, options = {}) {
|
|
96
|
+
await this.promise;
|
|
97
|
+
const key = this.PREFIX + id;
|
|
98
|
+
const expires = parse(options.expire ?? options.expires);
|
|
137
99
|
|
|
138
|
-
|
|
139
|
-
// Item methods
|
|
140
|
-
const get = async (key) => (store[key] ? JSON.parse(store[key]) : null);
|
|
141
|
-
const set = async (key, data) => {
|
|
100
|
+
// Quick delete
|
|
142
101
|
if (data === null) {
|
|
143
|
-
await
|
|
144
|
-
|
|
145
|
-
await store.setItem(key, JSON.stringify(data));
|
|
102
|
+
await this.del(key, null);
|
|
103
|
+
return id;
|
|
146
104
|
}
|
|
147
|
-
return key;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Group methods
|
|
151
|
-
const entries = async (prefix = "") => {
|
|
152
|
-
const entries = Object.entries(store);
|
|
153
|
-
return entries
|
|
154
|
-
.map((p) => [p[0], p[1] ? JSON.parse(p[1]) : null])
|
|
155
|
-
.filter((p) => p[0].startsWith(prefix));
|
|
156
|
-
};
|
|
157
|
-
const clear = () => store.clear();
|
|
158
|
-
|
|
159
|
-
return { get, set, entries, clear };
|
|
160
|
-
};
|
|
161
105
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for (let entry of document.cookie
|
|
167
|
-
.split(";")
|
|
168
|
-
.map((k) => k.trim())
|
|
169
|
-
.filter(Boolean)) {
|
|
170
|
-
const [key, data] = entry.split("=");
|
|
171
|
-
try {
|
|
172
|
-
all[key.trim()] = JSON.parse(decodeURIComponent(data.trim()));
|
|
173
|
-
} catch (error) {
|
|
174
|
-
// no-op (some 3rd party can set cookies independently)
|
|
175
|
-
}
|
|
106
|
+
// The client manages the expiration, so let it manage it
|
|
107
|
+
if (this.client.EXPIRES) {
|
|
108
|
+
await this.client.set(key, data, { expires });
|
|
109
|
+
return id;
|
|
176
110
|
}
|
|
177
|
-
return all;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const get = async (key) => getAll()[key] ?? null;
|
|
181
111
|
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
await
|
|
185
|
-
|
|
186
|
-
const time = parse(expire || expires);
|
|
187
|
-
const now = new Date().getTime();
|
|
188
|
-
// NOTE: 0 is already considered here!
|
|
189
|
-
const expireStr =
|
|
190
|
-
time !== null
|
|
191
|
-
? `; expires=${new Date(now + time * 1000).toUTCString()}`
|
|
192
|
-
: "";
|
|
193
|
-
const value = encodeURIComponent(JSON.stringify(data));
|
|
194
|
-
document.cookie = key + "=" + value + expireStr;
|
|
112
|
+
// Already expired, then delete it
|
|
113
|
+
if (expires === 0) {
|
|
114
|
+
await this.del(id);
|
|
115
|
+
return id;
|
|
195
116
|
}
|
|
196
|
-
return key;
|
|
197
|
-
};
|
|
198
117
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
118
|
+
// In the data we need the timestamp since we need it "absolute":
|
|
119
|
+
const now = new Date().getTime();
|
|
120
|
+
const expDiff = expires === null ? null : now + expires * 1000;
|
|
121
|
+
await this.client.set(key, { expires: expDiff, value: data });
|
|
122
|
+
return id;
|
|
123
|
+
}
|
|
204
124
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Read a single value from the KV store:
|
|
127
|
+
*
|
|
128
|
+
* ```js
|
|
129
|
+
* const key = await store.set("key1", "value1");
|
|
130
|
+
* const value = await store.get("key1");
|
|
131
|
+
* // "value1"
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
|
|
135
|
+
* @param {(string)} key
|
|
136
|
+
* @returns {(any)} value
|
|
137
|
+
*/
|
|
138
|
+
async get(key) {
|
|
139
|
+
await this.promise;
|
|
140
|
+
const id = this.PREFIX + key;
|
|
141
|
+
|
|
142
|
+
const data = (await this.client.get(id)) ?? null;
|
|
143
|
+
|
|
144
|
+
// No value; nothing to do/check
|
|
145
|
+
if (data === null) return null;
|
|
146
|
+
|
|
147
|
+
// The client already managed expiration and there's STILL some data,
|
|
148
|
+
// so we can assume it's the raw user data
|
|
149
|
+
if (this.client.EXPIRES) return data;
|
|
150
|
+
|
|
151
|
+
// Make sure that if there's no data by now, empty is returned
|
|
152
|
+
if (!data) return null;
|
|
209
153
|
|
|
210
|
-
|
|
211
|
-
|
|
154
|
+
// We manage expiration manually, so we know it should have this structure
|
|
155
|
+
// TODO: ADD A CHECK HERE
|
|
156
|
+
const { expires, value } = data;
|
|
212
157
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
const get = async (key) => {
|
|
216
|
-
const value = await store.get(key);
|
|
217
|
-
if (!value) return null;
|
|
218
|
-
return JSON.parse(value);
|
|
219
|
-
};
|
|
220
|
-
const set = async (key, value, { expire, expires } = {}) => {
|
|
221
|
-
const time = parse(expire || expires);
|
|
222
|
-
if (value === null || time === 0) return del(key);
|
|
223
|
-
const EX = time ? Math.round(time) : undefined;
|
|
224
|
-
await store.set(key, JSON.stringify(value), { EX });
|
|
225
|
-
return key;
|
|
226
|
-
};
|
|
227
|
-
const has = async (key) => Boolean(await store.exists(key));
|
|
228
|
-
const del = async (key) => store.del(key);
|
|
229
|
-
|
|
230
|
-
const keys = async (prefix = "") => store.keys(prefix + "*");
|
|
231
|
-
const entries = async (prefix = "") => {
|
|
232
|
-
const keys = await store.keys(prefix + "*");
|
|
233
|
-
const values = await Promise.all(keys.map((k) => get(k)));
|
|
234
|
-
return keys.map((k, i) => [k, values[i]]);
|
|
235
|
-
};
|
|
236
|
-
const clear = async () => store.flushAll();
|
|
237
|
-
const close = async () => store.quit();
|
|
238
|
-
|
|
239
|
-
return { get, set, has, del, keys, entries, clear, close };
|
|
240
|
-
};
|
|
158
|
+
// It never expires
|
|
159
|
+
if (expires === null) return value ?? null;
|
|
241
160
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
await store.removeItem(key);
|
|
247
|
-
} else {
|
|
248
|
-
await store.setItem(key, value);
|
|
161
|
+
// Already expired! Return nothing, and remove the whole key
|
|
162
|
+
if (expires <= new Date().getTime()) {
|
|
163
|
+
await this.del(key);
|
|
164
|
+
return null;
|
|
249
165
|
}
|
|
250
|
-
return key;
|
|
251
|
-
};
|
|
252
|
-
const entries = async (prefix = "") => {
|
|
253
|
-
const all = await store.keys();
|
|
254
|
-
const keys = all.filter((k) => k.startsWith(prefix));
|
|
255
|
-
const values = await Promise.all(keys.map((key) => store.getItem(key)));
|
|
256
|
-
return keys.map((key, i) => [key, values[i]]);
|
|
257
|
-
};
|
|
258
|
-
const clear = async () => store.clear();
|
|
259
|
-
|
|
260
|
-
return { get, set, entries, clear };
|
|
261
|
-
};
|
|
262
166
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const data = await store.get(key);
|
|
266
|
-
if (!data) return null;
|
|
267
|
-
return JSON.parse(data);
|
|
268
|
-
};
|
|
269
|
-
const set = async (key, value, { expire, expires } = {}) => {
|
|
270
|
-
const time = parse(expire || expires);
|
|
271
|
-
if (value === null || time === 0) return del(key);
|
|
272
|
-
const client = await store;
|
|
273
|
-
const expirationTtl = time ? Math.round(time) : undefined;
|
|
274
|
-
client.put(key, JSON.stringify(value), { expirationTtl });
|
|
275
|
-
return key;
|
|
276
|
-
};
|
|
277
|
-
const has = async (key) => Boolean(await store.get(key));
|
|
278
|
-
const del = (key) => store.delete(key);
|
|
279
|
-
|
|
280
|
-
// Group methods
|
|
281
|
-
const keys = async (prefix = "") => {
|
|
282
|
-
const raw = await store.list({ prefix });
|
|
283
|
-
return raw.keys;
|
|
284
|
-
};
|
|
285
|
-
const entries = async (prefix = "") => {
|
|
286
|
-
const all = await keys(prefix);
|
|
287
|
-
const values = await Promise.all(all.map((k) => get(k)));
|
|
288
|
-
return all.map((key, i) => [key, values[i]]);
|
|
289
|
-
};
|
|
290
|
-
const clear = () => {};
|
|
291
|
-
return { get, set, has, del, entries, keys, clear };
|
|
292
|
-
};
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
293
169
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// create it (but not OVERWRITE it, that's why the x flag) and
|
|
301
|
-
// it fails if it already exists
|
|
302
|
-
await fsp.writeFile(file.pathname, "{}", { flag: "wx" }).catch((err) => {
|
|
303
|
-
if (err.code !== "EEXIST") throw err;
|
|
304
|
-
});
|
|
305
|
-
return fsp;
|
|
306
|
-
})();
|
|
307
|
-
const getContent = async () => {
|
|
308
|
-
const fsp = await fsProm;
|
|
309
|
-
const text = await fsp.readFile(file.pathname, "utf8");
|
|
310
|
-
if (!text) return {};
|
|
311
|
-
return JSON.parse(text);
|
|
312
|
-
};
|
|
313
|
-
const setContent = async (data) => {
|
|
314
|
-
const fsp = await fsProm;
|
|
315
|
-
await fsp.writeFile(file.pathname, JSON.stringify(data, null, 2));
|
|
316
|
-
};
|
|
317
|
-
const get = async (key) => {
|
|
318
|
-
const data = await getContent();
|
|
319
|
-
return data[key] ?? null;
|
|
320
|
-
};
|
|
321
|
-
const set = async (key, value) => {
|
|
322
|
-
const data = await getContent();
|
|
323
|
-
if (value === null) {
|
|
324
|
-
delete data[key];
|
|
325
|
-
} else {
|
|
326
|
-
data[key] = value;
|
|
170
|
+
async has(id) {
|
|
171
|
+
await this.promise;
|
|
172
|
+
const key = this.PREFIX + id;
|
|
173
|
+
|
|
174
|
+
if (this.client.has) {
|
|
175
|
+
return this.client.has(key);
|
|
327
176
|
}
|
|
328
|
-
await setContent(data);
|
|
329
|
-
return key;
|
|
330
|
-
};
|
|
331
|
-
const has = async (key) => (await get(key)) !== null;
|
|
332
|
-
const del = async (key) => set(key, null);
|
|
333
|
-
|
|
334
|
-
// Group methods
|
|
335
|
-
const entries = async (prefix = "") => {
|
|
336
|
-
const data = await getContent();
|
|
337
|
-
return Object.entries(data).filter((p) => p[0].startsWith(prefix));
|
|
338
|
-
};
|
|
339
|
-
const clear = async () => {
|
|
340
|
-
await setContent({});
|
|
341
|
-
};
|
|
342
|
-
return { get, set, has, del, entries, clear };
|
|
343
|
-
};
|
|
344
177
|
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
if (store instanceof Map) {
|
|
348
|
-
return layers.extra(layers.expire(layers.memory(store)));
|
|
178
|
+
const value = await this.get(key);
|
|
179
|
+
return value !== null;
|
|
349
180
|
}
|
|
350
181
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
182
|
+
async del(id) {
|
|
183
|
+
await this.promise;
|
|
184
|
+
const key = this.PREFIX + id;
|
|
185
|
+
|
|
186
|
+
if (this.client.del) {
|
|
187
|
+
await this.client.del(key);
|
|
188
|
+
return id;
|
|
189
|
+
}
|
|
354
190
|
|
|
355
|
-
|
|
356
|
-
return
|
|
191
|
+
await this.client.set(key, null, { expires: 0 });
|
|
192
|
+
return id;
|
|
357
193
|
}
|
|
358
194
|
|
|
359
|
-
|
|
360
|
-
|
|
195
|
+
async entries() {
|
|
196
|
+
await this.promise;
|
|
197
|
+
|
|
198
|
+
const entries = await this.client.entries(this.PREFIX);
|
|
199
|
+
const list = entries.map(([key, data]) => [
|
|
200
|
+
key.slice(this.PREFIX.length),
|
|
201
|
+
data,
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
// The client already manages the expiration, so we can assume
|
|
205
|
+
// that at this point, all entries are not-expired
|
|
206
|
+
if (this.client.EXPIRES) return list;
|
|
207
|
+
|
|
208
|
+
// We need to do manual expiration checking
|
|
209
|
+
const now = new Date().getTime();
|
|
210
|
+
return list
|
|
211
|
+
.filter(([key, data]) => {
|
|
212
|
+
// Should never happen
|
|
213
|
+
if (!data || data.value === null) return false;
|
|
214
|
+
|
|
215
|
+
// It never expires, so keep it
|
|
216
|
+
const { expires } = data;
|
|
217
|
+
if (expires === null) return true;
|
|
218
|
+
|
|
219
|
+
// It's expired, so remove it
|
|
220
|
+
if (expires <= now) {
|
|
221
|
+
this.del(key);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// It's not expired, keep it
|
|
226
|
+
return true;
|
|
227
|
+
})
|
|
228
|
+
.map(([key, data]) => [key, data.value]);
|
|
361
229
|
}
|
|
362
230
|
|
|
363
|
-
|
|
364
|
-
|
|
231
|
+
async values() {
|
|
232
|
+
await this.promise;
|
|
233
|
+
|
|
234
|
+
if (this.client.values) {
|
|
235
|
+
const list = this.client.values(this.PREFIX);
|
|
236
|
+
if (this.client.EXPIRES) return list;
|
|
237
|
+
const now = new Date().getTime();
|
|
238
|
+
return list
|
|
239
|
+
.filter((data) => {
|
|
240
|
+
// There's no data, so remove this
|
|
241
|
+
if (!data || data.value === null) return false;
|
|
242
|
+
|
|
243
|
+
// It never expires, so keep it
|
|
244
|
+
const { expires } = data;
|
|
245
|
+
if (expires === null) return true;
|
|
246
|
+
|
|
247
|
+
// It's expired, so remove it
|
|
248
|
+
// We cannot unfortunately evict it since we don't know the key!
|
|
249
|
+
if (expires <= now) return false;
|
|
250
|
+
|
|
251
|
+
// It's not expired, keep it
|
|
252
|
+
return true;
|
|
253
|
+
})
|
|
254
|
+
.map((data) => data.value);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const entries = await this.entries();
|
|
258
|
+
return entries.map((e) => e[1]);
|
|
365
259
|
}
|
|
366
260
|
|
|
367
|
-
|
|
368
|
-
|
|
261
|
+
async keys() {
|
|
262
|
+
await this.promise;
|
|
263
|
+
|
|
264
|
+
if (this.client.keys) {
|
|
265
|
+
const list = await this.client.keys(this.PREFIX);
|
|
266
|
+
if (!this.PREFIX) return list;
|
|
267
|
+
return list.map((k) => k.slice(this.PREFIX.length));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const entries = await this.entries();
|
|
271
|
+
return entries.map((e) => e[0]);
|
|
369
272
|
}
|
|
370
273
|
|
|
371
|
-
|
|
372
|
-
|
|
274
|
+
async all() {
|
|
275
|
+
await this.promise;
|
|
276
|
+
|
|
277
|
+
if (this.client.all) {
|
|
278
|
+
const obj = await this.client.all(this.PREFIX);
|
|
279
|
+
if (!this.PREFIX) return obj;
|
|
280
|
+
const all = {};
|
|
281
|
+
for (let key in obj) {
|
|
282
|
+
all[key.slice(this.PREFIX.length)] = obj[key];
|
|
283
|
+
}
|
|
284
|
+
return all;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const entries = await this.entries();
|
|
288
|
+
return Object.fromEntries(entries);
|
|
373
289
|
}
|
|
374
290
|
|
|
375
|
-
|
|
376
|
-
|
|
291
|
+
async clear() {
|
|
292
|
+
await this.promise;
|
|
293
|
+
|
|
294
|
+
if (this.client.clear) {
|
|
295
|
+
return this.client.clear(this.PREFIX);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const keys = await this.keys();
|
|
299
|
+
// Note: this gives trouble of concurrent deletes in the FS
|
|
300
|
+
return await Promise.all(keys.map((key) => this.del(key)));
|
|
377
301
|
}
|
|
378
302
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
303
|
+
prefix(prefix = "") {
|
|
304
|
+
const store = new Store(
|
|
305
|
+
Promise.resolve(this.promise).then((client) => client || this.client)
|
|
306
|
+
);
|
|
307
|
+
store.PREFIX = this.PREFIX + prefix;
|
|
308
|
+
return store;
|
|
309
|
+
}
|
|
382
310
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
{
|
|
387
|
-
get: (instance, key) => {
|
|
388
|
-
return async (...args) => {
|
|
389
|
-
// Only once, even if called twice in succession, since the
|
|
390
|
-
// second time will go straight to the await
|
|
391
|
-
if (!instance.store && !instance.promise) {
|
|
392
|
-
instance.promise = getStore(await storeClient);
|
|
393
|
-
}
|
|
394
|
-
instance.store = await instance.promise;
|
|
395
|
-
// Throw at the first chance when the store failed to init:
|
|
396
|
-
if (!instance.store) {
|
|
397
|
-
throw new Error("Store is not valid");
|
|
398
|
-
}
|
|
399
|
-
// The store.close() is the only one allowed to be called even
|
|
400
|
-
// if it doesn't exist, since it's optional in some stores
|
|
401
|
-
if (!instance.store[key] && key === "close") return null;
|
|
402
|
-
return instance.store[key](...args);
|
|
403
|
-
};
|
|
404
|
-
},
|
|
311
|
+
async close() {
|
|
312
|
+
if (this.client.close) {
|
|
313
|
+
return this.client.close();
|
|
405
314
|
}
|
|
406
|
-
|
|
315
|
+
}
|
|
407
316
|
}
|
|
317
|
+
|
|
318
|
+
export default kv = (client) => new Store(client);
|