polystore 0.15.13 → 0.16.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/index.js ADDED
@@ -0,0 +1,809 @@
1
+ // src/clients/Client.ts
2
+ var Client = class {
3
+ EXPIRES = false;
4
+ client;
5
+ encode = (val) => JSON.stringify(val, null, 2);
6
+ decode = (val) => val ? JSON.parse(val) : null;
7
+ constructor(client) {
8
+ this.client = client;
9
+ }
10
+ };
11
+
12
+ // src/clients/api.ts
13
+ var Api = class extends Client {
14
+ // Indicate that the file handler DOES handle expirations
15
+ EXPIRES = true;
16
+ static test = (client) => typeof client === "string" && /^https?:\/\//.test(client);
17
+ #api = async (key, opts = "", method = "GET", body) => {
18
+ const url = `${this.client.replace(/\/$/, "")}/${encodeURIComponent(key)}${opts}`;
19
+ const headers = {
20
+ accept: "application/json",
21
+ "content-type": "application/json"
22
+ };
23
+ const res = await fetch(url, { method, headers, body });
24
+ if (!res.ok) return null;
25
+ return this.decode(await res.text());
26
+ };
27
+ get = (key) => this.#api(key);
28
+ set = async (key, value, { expires } = {}) => {
29
+ const exp = typeof expires === "number" ? `?expires=${expires}` : "";
30
+ await this.#api(key, exp, "PUT", this.encode(value));
31
+ };
32
+ del = async (key) => {
33
+ await this.#api(key, "", "DELETE");
34
+ };
35
+ async *iterate(prefix = "") {
36
+ const data = await this.#api("", `?prefix=${encodeURIComponent(prefix)}`);
37
+ for (let [key, value] of Object.entries(data || {})) {
38
+ if (value !== null && value !== void 0) {
39
+ yield [prefix + key, value];
40
+ }
41
+ }
42
+ }
43
+ };
44
+
45
+ // src/clients/cloudflare.ts
46
+ var Cloudflare = class extends Client {
47
+ // It handles expirations natively
48
+ EXPIRES = true;
49
+ // Check whether the given store is a FILE-type
50
+ static test = (client) => client?.constructor?.name === "KvNamespace" || client?.constructor?.name === "EdgeKVNamespace";
51
+ get = async (key) => this.decode(await this.client.get(key));
52
+ set = (key, data, opts) => {
53
+ const expirationTtl = opts.expires ? Math.round(opts.expires) : void 0;
54
+ if (expirationTtl && expirationTtl < 60) {
55
+ throw new Error("Cloudflare's min expiration is '60s'");
56
+ }
57
+ return this.client.put(key, this.encode(data), { expirationTtl });
58
+ };
59
+ del = (key) => this.client.delete(key);
60
+ // Since we have pagination, we don't want to get all of the
61
+ // keys at once if we can avoid it
62
+ async *iterate(prefix = "") {
63
+ let cursor;
64
+ do {
65
+ const raw = await this.client.list({ prefix, cursor });
66
+ const keys = raw.keys.map((k) => k.name);
67
+ for (let key of keys) {
68
+ const value = await this.get(key);
69
+ if (value !== null && value !== void 0) yield [key, value];
70
+ }
71
+ cursor = raw.list_complete ? void 0 : raw.cursor;
72
+ } while (cursor);
73
+ }
74
+ keys = async (prefix = "") => {
75
+ const keys = [];
76
+ let cursor;
77
+ do {
78
+ const raw = await this.client.list({ prefix, cursor });
79
+ keys.push(...raw.keys.map((k) => k.name));
80
+ cursor = raw.list_complete ? void 0 : raw.cursor;
81
+ } while (cursor);
82
+ return keys;
83
+ };
84
+ entries = async (prefix = "") => {
85
+ const keys = await this.keys(prefix);
86
+ const values = await Promise.all(keys.map((k) => this.get(k)));
87
+ return keys.map((k, i) => [k, values[i]]);
88
+ };
89
+ };
90
+
91
+ // src/clients/cookie.ts
92
+ var Cookie = class extends Client {
93
+ // It handles expirations natively
94
+ EXPIRES = true;
95
+ // Check if this is the right class for the given client
96
+ static test = (client) => {
97
+ return client === "cookie" || client === "cookies";
98
+ };
99
+ // Group methods
100
+ #read = () => {
101
+ const all = {};
102
+ for (let entry of document.cookie.split(";")) {
103
+ try {
104
+ const [rawKey, rawValue] = entry.split("=");
105
+ const key = decodeURIComponent(rawKey.trim());
106
+ const value = this.decode(decodeURIComponent(rawValue.trim()));
107
+ all[key] = value;
108
+ } catch (error) {
109
+ }
110
+ }
111
+ return all;
112
+ };
113
+ // For cookies, an empty value is the same as null, even `""`
114
+ get = (key) => {
115
+ const all = this.#read();
116
+ return key in all ? all[key] : null;
117
+ };
118
+ set = (key, data, { expires }) => {
119
+ const k = encodeURIComponent(key);
120
+ const value = encodeURIComponent(this.encode(data ?? ""));
121
+ let exp = "";
122
+ if (typeof expires === "number") {
123
+ const when = expires <= 0 ? 0 : Date.now() + expires * 1e3;
124
+ exp = `; expires=${new Date(when).toUTCString()}`;
125
+ }
126
+ document.cookie = `${k}=${value}${exp}`;
127
+ };
128
+ del = (key) => this.set(key, "", { expires: -100 });
129
+ async *iterate(prefix = "") {
130
+ for (let [key, value] of Object.entries(this.#read())) {
131
+ if (!key.startsWith(prefix)) continue;
132
+ yield [key, value];
133
+ }
134
+ }
135
+ };
136
+
137
+ // src/clients/etcd.ts
138
+ var Etcd = class extends Client {
139
+ // It desn't handle expirations natively
140
+ EXPIRES = false;
141
+ // Check if this is the right class for the given client
142
+ static test = (client) => client?.constructor?.name === "Etcd3";
143
+ get = (key) => this.client.get(key).json();
144
+ set = (key, value) => this.client.put(key).value(this.encode(value));
145
+ del = (key) => this.client.delete().key(key).exec();
146
+ async *iterate(prefix = "") {
147
+ const keys = await this.client.getAll().prefix(prefix).keys();
148
+ for (const key of keys) {
149
+ yield [key, await this.get(key)];
150
+ }
151
+ }
152
+ keys = (prefix = "") => this.client.getAll().prefix(prefix).keys();
153
+ entries = async (prefix = "") => {
154
+ const keys = await this.keys(prefix);
155
+ const values = await Promise.all(keys.map((k) => this.get(k)));
156
+ return keys.map((k, i) => [k, values[i]]);
157
+ };
158
+ clear = async (prefix = "") => {
159
+ if (!prefix) return this.client.delete().all();
160
+ return this.client.delete().prefix(prefix);
161
+ };
162
+ };
163
+
164
+ // src/clients/file.ts
165
+ var File = class extends Client {
166
+ // It desn't handle expirations natively
167
+ EXPIRES = false;
168
+ fsp;
169
+ file = "";
170
+ #lock = Promise.resolve();
171
+ // Check if this is the right class for the given client
172
+ static test = (client) => {
173
+ if (client instanceof URL) client = client.href;
174
+ return typeof client === "string" && client.startsWith("file://") && client.includes(".");
175
+ };
176
+ // We want to make sure the file already exists, so attempt to
177
+ // create the folders and the file (but not OVERWRITE it, that's why the x flag)
178
+ // It fails if it already exists, hence the catch case
179
+ promise = (async () => {
180
+ this.fsp = await import("fs/promises");
181
+ this.file = (this.client?.href || this.client).replace(/^file:\/\//, "");
182
+ const folder = this.file.split("/").slice(0, -1).join("/");
183
+ await this.fsp.mkdir(folder, { recursive: true }).catch(() => {
184
+ });
185
+ await this.fsp.writeFile(this.file, "{}", { flag: "wx" }).catch(() => {
186
+ });
187
+ })();
188
+ // Internal - acquire lock before operations
189
+ #withLock = async (fn) => {
190
+ const previousLock = this.#lock;
191
+ let releaseLock;
192
+ this.#lock = new Promise((resolve) => {
193
+ releaseLock = resolve;
194
+ });
195
+ try {
196
+ await previousLock;
197
+ return await fn();
198
+ } finally {
199
+ releaseLock();
200
+ }
201
+ };
202
+ #read = async () => {
203
+ try {
204
+ const text = await this.fsp.readFile(this.file, "utf8");
205
+ return text ? JSON.parse(text) : {};
206
+ } catch (error) {
207
+ if (error.code === "ENOENT") return {};
208
+ throw error;
209
+ }
210
+ };
211
+ #write = async (data) => {
212
+ return this.fsp.writeFile(this.file, this.encode(data));
213
+ };
214
+ get = async (key) => {
215
+ return this.#withLock(async () => {
216
+ const data = await this.#read();
217
+ return data[key] ?? null;
218
+ });
219
+ };
220
+ set = async (key, value) => {
221
+ return this.#withLock(async () => {
222
+ const data = await this.#read();
223
+ if (value === null) {
224
+ delete data[key];
225
+ } else {
226
+ data[key] = value;
227
+ }
228
+ await this.#write(data);
229
+ });
230
+ };
231
+ async *iterate(prefix = "") {
232
+ const data = await this.#read();
233
+ const entries = Object.entries(data).filter((p) => p[0].startsWith(prefix));
234
+ for (const entry of entries) {
235
+ yield entry;
236
+ }
237
+ }
238
+ // Bulk updates are worth creating a custom method here
239
+ clearAll = () => this.#withLock(() => this.#write({}));
240
+ clear = async (prefix = "") => {
241
+ return this.#withLock(async () => {
242
+ const data = await this.#read();
243
+ for (let key in data) {
244
+ if (key.startsWith(prefix)) {
245
+ delete data[key];
246
+ }
247
+ }
248
+ await this.#write(data);
249
+ });
250
+ };
251
+ };
252
+
253
+ // src/clients/folder.ts
254
+ var noFileOk = (error) => {
255
+ if (error.code === "ENOENT") return null;
256
+ throw error;
257
+ };
258
+ var Folder = class extends Client {
259
+ // It desn't handle expirations natively
260
+ EXPIRES = false;
261
+ fsp;
262
+ folder;
263
+ // Check if this is the right class for the given client
264
+ static test = (client) => {
265
+ if (client instanceof URL) client = client.href;
266
+ return typeof client === "string" && client.startsWith("file://") && client.endsWith("/");
267
+ };
268
+ // Make sure the folder already exists, so attempt to create it
269
+ // It fails if it already exists, hence the catch case
270
+ promise = (async () => {
271
+ this.fsp = await import("fs/promises");
272
+ this.folder = (this.client?.href || this.client).replace(/^file:\/\//, "");
273
+ await this.fsp.mkdir(this.folder, { recursive: true }).catch(() => {
274
+ });
275
+ })();
276
+ file = (key) => this.folder + key + ".json";
277
+ get = (key) => {
278
+ return this.fsp.readFile(this.file(key), "utf8").then(this.decode, noFileOk);
279
+ };
280
+ set = (key, value) => {
281
+ return this.fsp.writeFile(this.file(key), this.encode(value), "utf8");
282
+ };
283
+ del = (key) => this.fsp.unlink(this.file(key)).catch(noFileOk);
284
+ async *iterate(prefix = "") {
285
+ const all = await this.fsp.readdir(this.folder);
286
+ const keys = all.filter((f) => f.startsWith(prefix) && f.endsWith(".json"));
287
+ for (const name of keys) {
288
+ const key = name.slice(0, -".json".length);
289
+ try {
290
+ const data = await this.get(key);
291
+ yield [key, data];
292
+ } catch {
293
+ continue;
294
+ }
295
+ }
296
+ }
297
+ };
298
+
299
+ // src/clients/forage.ts
300
+ var Forage = class extends Client {
301
+ // It desn't handle expirations natively
302
+ EXPIRES = false;
303
+ // Check if this is the right class for the given client
304
+ static test = (client) => client?.defineDriver && client?.dropInstance && client?.INDEXEDDB;
305
+ get = (key) => this.client.getItem(key);
306
+ set = (key, value) => this.client.setItem(key, value);
307
+ del = (key) => this.client.removeItem(key);
308
+ async *iterate(prefix = "") {
309
+ const keys = await this.client.keys();
310
+ const list = keys.filter((k) => k.startsWith(prefix));
311
+ for (const key of list) {
312
+ const value = await this.get(key);
313
+ if (value !== null && value !== void 0) {
314
+ yield [key, value];
315
+ }
316
+ }
317
+ }
318
+ entries = async (prefix = "") => {
319
+ const all = await this.client.keys();
320
+ const keys = all.filter((k) => k.startsWith(prefix));
321
+ const values = await Promise.all(keys.map((key) => this.get(key)));
322
+ return keys.map((key, i) => [key, values[i]]);
323
+ };
324
+ clearAll = () => this.client.clear();
325
+ };
326
+
327
+ // src/clients/level.ts
328
+ var valueEncoding = "json";
329
+ var notFound = (error) => {
330
+ if (error?.code === "LEVEL_NOT_FOUND") return null;
331
+ throw error;
332
+ };
333
+ var Level = class extends Client {
334
+ // It desn't handle expirations natively
335
+ EXPIRES = false;
336
+ // Check if this is the right class for the given client
337
+ static test = (client) => client?.constructor?.name === "ClassicLevel";
338
+ get = (key) => this.client.get(key, { valueEncoding }).catch(notFound);
339
+ set = (key, value) => this.client.put(key, value, { valueEncoding });
340
+ del = (key) => this.client.del(key);
341
+ async *iterate(prefix = "") {
342
+ const keys = await this.client.keys().all();
343
+ const list = keys.filter((k) => k.startsWith(prefix));
344
+ for (const key of list) {
345
+ yield [key, await this.get(key)];
346
+ }
347
+ }
348
+ entries = async (prefix = "") => {
349
+ const keys = await this.client.keys().all();
350
+ const list = keys.filter((k) => k.startsWith(prefix));
351
+ return Promise.all(
352
+ list.map(async (k) => [k, await this.get(k)])
353
+ );
354
+ };
355
+ clearAll = () => this.client.clear();
356
+ clear = async (prefix = "") => {
357
+ const keys = await this.client.keys().all();
358
+ const list = keys.filter((k) => k.startsWith(prefix));
359
+ return this.client.batch(
360
+ list.map((key) => ({ type: "del", key }))
361
+ );
362
+ };
363
+ close = () => this.client.close();
364
+ };
365
+
366
+ // src/clients/memory.ts
367
+ var Memory = class extends Client {
368
+ // It desn't handle expirations natively
369
+ EXPIRES = false;
370
+ // Check if this is the right class for the given client
371
+ static test = (client) => client instanceof Map;
372
+ get = (key) => this.client.get(key) ?? null;
373
+ set = (key, data) => this.client.set(key, data);
374
+ del = (key) => this.client.delete(key);
375
+ *iterate(prefix = "") {
376
+ for (const entry of this.client.entries()) {
377
+ if (entry[0].startsWith(prefix)) yield entry;
378
+ }
379
+ }
380
+ clearAll = () => this.client.clear();
381
+ };
382
+
383
+ // src/clients/redis.ts
384
+ var Redis = class extends Client {
385
+ // Indicate if this client handles expirations (true = it does)
386
+ EXPIRES = true;
387
+ // Check if this is the right class for the given client
388
+ static test = (client) => client && client.pSubscribe && client.sSubscribe;
389
+ get = async (key) => this.decode(await this.client.get(key));
390
+ set = async (key, value, { expires } = {}) => {
391
+ const EX = expires ? Math.round(expires) : void 0;
392
+ return this.client.set(key, this.encode(value), { EX });
393
+ };
394
+ del = (key) => this.client.del(key);
395
+ has = async (key) => Boolean(await this.client.exists(key));
396
+ // Go through each of the [key, value] in the set
397
+ async *iterate(prefix = "") {
398
+ const MATCH = prefix + "*";
399
+ for await (const key of this.client.scanIterator({ MATCH })) {
400
+ const value = await this.get(key);
401
+ if (value !== null && value !== void 0) {
402
+ yield [key, value];
403
+ }
404
+ }
405
+ }
406
+ // Optimizing the retrieval of them by not getting their values
407
+ keys = async (prefix = "") => {
408
+ const MATCH = prefix + "*";
409
+ const keys = [];
410
+ for await (const key of this.client.scanIterator({ MATCH })) {
411
+ keys.push(key);
412
+ }
413
+ return keys;
414
+ };
415
+ // Optimizing the retrieval of them all in bulk by loading the values
416
+ // in parallel
417
+ entries = async (prefix = "") => {
418
+ const keys = await this.keys(prefix);
419
+ const values = await Promise.all(keys.map((k) => this.get(k)));
420
+ return keys.map((k, i) => [k, values[i]]);
421
+ };
422
+ clearAll = () => this.client.flushAll();
423
+ close = () => this.client.quit();
424
+ };
425
+
426
+ // src/clients/sqlite.ts
427
+ var SQLite = class extends Client {
428
+ // This one is doing manual time management internally even though
429
+ // sqlite does not natively support expirations. This is because it does
430
+ // support creating a `expires_at:Date` column that makes managing
431
+ // expirations much easier, so it's really "somewhere in between"
432
+ EXPIRES = true;
433
+ static test = (client) => typeof client?.prepare === "function";
434
+ constructor(c) {
435
+ if (typeof c?.prepare("SELECT 1").get === "function") {
436
+ super({
437
+ run: (sql, ...args) => c.prepare(sql).run(...args),
438
+ get: (sql, ...args) => c.prepare(sql).get(...args),
439
+ all: (sql, ...args) => c.prepare(sql).all(...args)
440
+ });
441
+ return;
442
+ }
443
+ super(c);
444
+ }
445
+ get = (id) => {
446
+ const row = this.client.get(
447
+ `SELECT value, expires_at FROM kv WHERE id = ?`,
448
+ id
449
+ );
450
+ if (!row) return null;
451
+ if (row.expires_at && row.expires_at < Date.now()) {
452
+ this.del(id);
453
+ return null;
454
+ }
455
+ return this.decode(row.value);
456
+ };
457
+ set = (id, data, { expires } = {}) => {
458
+ const value = this.encode(data);
459
+ const expires_at = expires ? Date.now() + expires * 1e3 : null;
460
+ this.client.run(
461
+ `INSERT INTO kv (id, value, expires_at)
462
+ VALUES (?, ?, ?)
463
+ ON CONFLICT(id)
464
+ DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at`,
465
+ id,
466
+ value,
467
+ expires_at
468
+ );
469
+ };
470
+ del = (id) => {
471
+ this.client.run(`DELETE FROM kv WHERE id = ?`, id);
472
+ };
473
+ has = (id) => {
474
+ const row = this.client.get(`SELECT expires_at FROM kv WHERE id = ?`, id);
475
+ if (!row) return false;
476
+ if (row.expires_at && row.expires_at < Date.now()) {
477
+ this.del(id);
478
+ return false;
479
+ }
480
+ return true;
481
+ };
482
+ *iterate(prefix = "") {
483
+ this.#clearExpired();
484
+ const sql = `
485
+ SELECT id, value FROM kv
486
+ WHERE (expires_at IS NULL OR expires_at > ?)
487
+ ${prefix ? "AND id LIKE ?" : ""}
488
+ `;
489
+ const params = prefix ? [Date.now(), `${prefix}%`] : [Date.now()];
490
+ for (const row of this.client.all(sql, ...params)) {
491
+ yield [row.id, this.decode(row.value)];
492
+ }
493
+ }
494
+ keys = (prefix = "") => {
495
+ this.#clearExpired();
496
+ const sql = `
497
+ SELECT id FROM kv
498
+ WHERE (expires_at IS NULL OR expires_at > ?)
499
+ ${prefix ? "AND id LIKE ?" : ""}
500
+ `;
501
+ const params = prefix ? [Date.now(), `${prefix}%`] : [Date.now()];
502
+ const rows = this.client.all(sql, ...params);
503
+ return rows.map((r) => r.id);
504
+ };
505
+ #clearExpired = () => {
506
+ this.client.run(`DELETE FROM kv WHERE expires_at < ?`, Date.now());
507
+ };
508
+ clearAll = () => {
509
+ this.client.run(`DELETE FROM kv`);
510
+ };
511
+ close = () => {
512
+ this.client.close?.();
513
+ };
514
+ };
515
+
516
+ // src/clients/storage.ts
517
+ var WebStorage = class extends Client {
518
+ // It desn't handle expirations natively
519
+ EXPIRES = false;
520
+ // Check if this is the right class for the given client
521
+ static test(client) {
522
+ if (typeof Storage === "undefined") return false;
523
+ return client instanceof Storage;
524
+ }
525
+ // Item methods
526
+ get = (key) => this.decode(this.client.getItem(key));
527
+ set = (key, data) => this.client.setItem(key, this.encode(data));
528
+ del = (key) => this.client.removeItem(key);
529
+ *iterate(prefix = "") {
530
+ for (let i = 0; i < this.client.length; i++) {
531
+ const key = this.client.key(i);
532
+ if (!key || !key.startsWith(prefix)) continue;
533
+ const value = this.get(key);
534
+ if (value !== null && value !== void 0) {
535
+ yield [key, value];
536
+ }
537
+ }
538
+ }
539
+ clearAll = () => this.client.clear();
540
+ };
541
+
542
+ // src/clients/index.ts
543
+ var clients_default = {
544
+ api: Api,
545
+ cloudflare: Cloudflare,
546
+ cookie: Cookie,
547
+ etcd: Etcd,
548
+ file: File,
549
+ folder: Folder,
550
+ forage: Forage,
551
+ level: Level,
552
+ memory: Memory,
553
+ // postgres,
554
+ // prisma,
555
+ redis: Redis,
556
+ storage: WebStorage,
557
+ sqlite: SQLite
558
+ };
559
+
560
+ // src/utils.ts
561
+ var times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
562
+ var parse = function(str) {
563
+ if (str === null || str === void 0) return null;
564
+ if (typeof str === "number") return str;
565
+ const cleaned = str.toLowerCase().replace(/[,_]/g, "");
566
+ let [_, value, units] = times.exec(cleaned) || [];
567
+ if (!units) return null;
568
+ const unitValue = parse[units] || parse[units.replace(/s$/, "")];
569
+ if (!unitValue) return null;
570
+ const result = unitValue * parseFloat(value);
571
+ return Math.abs(Math.round(result * 1e3) / 1e3);
572
+ };
573
+ parse.millisecond = parse.ms = 1e-3;
574
+ parse.second = parse.sec = parse.s = parse[""] = 1;
575
+ parse.minute = parse.min = parse.m = parse.s * 60;
576
+ parse.hour = parse.hr = parse.h = parse.m * 60;
577
+ parse.day = parse.d = parse.h * 24;
578
+ parse.week = parse.wk = parse.w = parse.d * 7;
579
+ parse.year = parse.yr = parse.y = parse.d * 365.25;
580
+ parse.month = parse.b = parse.y / 12;
581
+ var urlAlphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
582
+ function createId() {
583
+ let size = 24;
584
+ let id = "";
585
+ let bytes = crypto.getRandomValues(new Uint8Array(size));
586
+ while (size--) {
587
+ id += urlAlphabet[bytes[size] & 61];
588
+ }
589
+ return id;
590
+ }
591
+ function unix(expires) {
592
+ const now = (/* @__PURE__ */ new Date()).getTime();
593
+ return expires === null ? null : now + expires * 1e3;
594
+ }
595
+
596
+ // src/index.ts
597
+ var Store = class _Store {
598
+ PREFIX = "";
599
+ promise;
600
+ client;
601
+ constructor(clientPromise = /* @__PURE__ */ new Map()) {
602
+ this.promise = Promise.resolve(clientPromise).then(async (client) => {
603
+ this.client = this.#find(client);
604
+ this.#validate(this.client);
605
+ this.promise = null;
606
+ await this.client.promise;
607
+ return client;
608
+ });
609
+ }
610
+ #find(store) {
611
+ if (store instanceof _Store) return store.client;
612
+ for (let client of Object.values(clients_default)) {
613
+ if (client.test && client.test(store)) {
614
+ return new client(store);
615
+ }
616
+ }
617
+ if (typeof store === "function" && /^class\s/.test(Function.prototype.toString.call(store))) {
618
+ return new store();
619
+ }
620
+ return store;
621
+ }
622
+ #validate(client) {
623
+ if (!client) throw new Error("No client received");
624
+ if (!client.set || !client.get || !client.iterate) {
625
+ throw new Error("Client should have .get(), .set() and .iterate()");
626
+ }
627
+ if (client.EXPIRES) return;
628
+ for (let method of ["has", "keys", "values"]) {
629
+ if (client[method]) {
630
+ const msg = `You can only define client.${method}() when the client manages the expiration.`;
631
+ throw new Error(msg);
632
+ }
633
+ }
634
+ }
635
+ // Check if the given data is fresh or not; if
636
+ #isFresh(data, key) {
637
+ if (!data || typeof data !== "object" || !("value" in data)) {
638
+ if (key) this.del(key);
639
+ return false;
640
+ }
641
+ if (data.expires === null) return true;
642
+ if (data.expires > Date.now()) return true;
643
+ if (key) this.del(key);
644
+ return false;
645
+ }
646
+ async add(value, options = {}) {
647
+ await this.promise;
648
+ let expires = parse(options.expires);
649
+ if (this.client.add) {
650
+ if (this.client.EXPIRES) {
651
+ return await this.client.add(this.PREFIX, value, { expires });
652
+ }
653
+ expires = unix(expires);
654
+ const key2 = await this.client.add(this.PREFIX, { expires, value });
655
+ return key2;
656
+ }
657
+ const key = createId();
658
+ return this.set(key, value, { expires });
659
+ }
660
+ async set(key, value, options = {}) {
661
+ await this.promise;
662
+ const id = this.PREFIX + key;
663
+ let expires = parse(options.expires);
664
+ if (value === null || typeof expires === "number" && expires <= 0) {
665
+ return this.del(key);
666
+ }
667
+ if (this.client.EXPIRES) {
668
+ await this.client.set(id, value, { expires });
669
+ return key;
670
+ }
671
+ expires = unix(expires);
672
+ await this.client.set(id, { expires, value });
673
+ return key;
674
+ }
675
+ async get(key) {
676
+ await this.promise;
677
+ const id = this.PREFIX + key;
678
+ if (this.client.EXPIRES) {
679
+ const data = await this.client.get(id) ?? null;
680
+ if (data === null) return null;
681
+ return data;
682
+ } else {
683
+ const data = await this.client.get(id) ?? null;
684
+ if (data === null) return null;
685
+ if (!this.#isFresh(data, key)) return null;
686
+ return data.value;
687
+ }
688
+ }
689
+ async has(key) {
690
+ await this.promise;
691
+ const id = this.PREFIX + key;
692
+ if (this.client.has) {
693
+ return this.client.has(id);
694
+ }
695
+ return await this.get(key) !== null;
696
+ }
697
+ async del(key) {
698
+ await this.promise;
699
+ const id = this.PREFIX + key;
700
+ if (this.client.del) {
701
+ await this.client.del(id);
702
+ return key;
703
+ }
704
+ if (this.client.EXPIRES) {
705
+ await this.client.set(id, null, { expires: 0 });
706
+ } else {
707
+ await this.client.set(id, null);
708
+ }
709
+ return key;
710
+ }
711
+ async *[Symbol.asyncIterator]() {
712
+ await this.promise;
713
+ if (this.client.EXPIRES) {
714
+ for await (const [name, data] of this.client.iterate(this.PREFIX)) {
715
+ const key = name.slice(this.PREFIX.length);
716
+ yield [key, data];
717
+ }
718
+ return;
719
+ }
720
+ for await (const [name, data] of this.client.iterate(this.PREFIX)) {
721
+ const key = name.slice(this.PREFIX.length);
722
+ if (this.#isFresh(data, key)) {
723
+ yield [key, data.value];
724
+ }
725
+ }
726
+ }
727
+ async entries() {
728
+ await this.promise;
729
+ const trim = (key) => key.slice(this.PREFIX.length);
730
+ if (this.client.entries) {
731
+ if (this.client.EXPIRES) {
732
+ const entries = await this.client.entries(this.PREFIX);
733
+ return entries.map(([k, v]) => [trim(k), v]);
734
+ } else {
735
+ const entries = await this.client.entries(this.PREFIX);
736
+ return entries.map(([k, v]) => [trim(k), v]).filter(([key, data]) => this.#isFresh(data, key)).map(([key, data]) => [key, data.value]);
737
+ }
738
+ }
739
+ if (this.client.EXPIRES) {
740
+ const list = [];
741
+ for await (const [k, v] of this.client.iterate(this.PREFIX)) {
742
+ list.push([trim(k), v]);
743
+ }
744
+ return list;
745
+ } else {
746
+ const list = [];
747
+ for await (const [k, data] of this.client.iterate(this.PREFIX)) {
748
+ if (this.#isFresh(data, trim(k))) {
749
+ list.push([trim(k), data.value]);
750
+ }
751
+ }
752
+ return list;
753
+ }
754
+ }
755
+ async keys() {
756
+ await this.promise;
757
+ if (this.client.keys) {
758
+ const list = await this.client.keys(this.PREFIX);
759
+ if (!this.PREFIX) return list;
760
+ return list.map((k) => k.slice(this.PREFIX.length));
761
+ }
762
+ const entries = await this.entries();
763
+ return entries.map((e) => e[0]);
764
+ }
765
+ async values() {
766
+ await this.promise;
767
+ if (this.client.values) {
768
+ if (this.client.EXPIRES) return this.client.values(this.PREFIX);
769
+ const list = await this.client.values(this.PREFIX);
770
+ return list.filter((data) => this.#isFresh(data)).map((data) => data.value);
771
+ }
772
+ const entries = await this.entries();
773
+ return entries.map((e) => e[1]);
774
+ }
775
+ async all() {
776
+ const entries = await this.entries();
777
+ return Object.fromEntries(entries);
778
+ }
779
+ async clear() {
780
+ await this.promise;
781
+ if (!this.PREFIX && this.client.clearAll) {
782
+ return this.client.clearAll();
783
+ }
784
+ if (this.client.clear) {
785
+ return this.client.clear(this.PREFIX);
786
+ }
787
+ const keys = await this.keys();
788
+ await Promise.all(keys.map((key) => this.del(key)));
789
+ }
790
+ prefix(prefix = "") {
791
+ const store = new _Store(
792
+ Promise.resolve(this.promise).then(() => this.client)
793
+ );
794
+ store.PREFIX = this.PREFIX + prefix;
795
+ return store;
796
+ }
797
+ async close() {
798
+ await this.promise;
799
+ if (this.client.close) {
800
+ return this.client.close();
801
+ }
802
+ }
803
+ };
804
+ function createStore(client) {
805
+ return new Store(client);
806
+ }
807
+ export {
808
+ createStore as default
809
+ };