polystore 0.14.0 → 0.15.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/package.json +4 -4
- package/readme.md +172 -7
- package/src/clients/api.js +71 -0
- package/src/clients/folder.js +6 -11
- package/src/clients/index.js +2 -0
- package/src/server.js +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
|
|
5
5
|
"homepage": "https://polystore.dev/",
|
|
6
6
|
"repository": "https://github.com/franciscop/polystore.git",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
"src/"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"size": "echo $(gzip -c src/index.js | wc -c) bytes",
|
|
18
17
|
"lint": "check-dts test/index.types.ts",
|
|
19
18
|
"start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles",
|
|
20
19
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --ci --watchAll=false --detectOpenHandles",
|
|
21
|
-
"db": "etcd"
|
|
20
|
+
"db": "etcd",
|
|
21
|
+
"server": "bun ./src/server.js"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"kv",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"value"
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT",
|
|
32
|
-
"dependencies": {},
|
|
33
32
|
"devDependencies": {
|
|
34
33
|
"@deno/kv": "^0.8.1",
|
|
35
34
|
"check-dts": "^0.8.0",
|
|
35
|
+
"cross-fetch": "^4.0.0",
|
|
36
36
|
"dotenv": "^16.3.1",
|
|
37
37
|
"edge-mock": "^0.0.15",
|
|
38
38
|
"etcd3": "^1.1.2",
|
package/readme.md
CHANGED
|
@@ -35,6 +35,7 @@ Available clients for the KV store:
|
|
|
35
35
|
- [**Session Storage** `sessionStorage`](#session-storage) (fe): persist the data in the browser's sessionStorage.
|
|
36
36
|
- [**Cookies** `"cookie"`](#cookies) (fe): persist the data using cookies
|
|
37
37
|
- [**LocalForage** `localForage`](#local-forage) (fe): persist the data on IndexedDB
|
|
38
|
+
- [**Fetch API** `"https://..."`](#fetch-api) (fe+be): call an API to save/retrieve the data
|
|
38
39
|
- [**File** `new URL('file:///...')`](#file) (be): store the data in a single JSON file in your FS
|
|
39
40
|
- [**Folder** `new URL('file:///...')`](#folder) (be): store each key in a folder as json files
|
|
40
41
|
- [**Redis Client** `redisClient`](#redis-client) (be): use the Redis instance that you connect to
|
|
@@ -462,13 +463,23 @@ console.log(await store.get("key1"));
|
|
|
462
463
|
|
|
463
464
|
<details>
|
|
464
465
|
<summary>Why use polystore with <code>new Map()</code>?</summary>
|
|
465
|
-
<p>
|
|
466
|
+
<p>These benefits are for wrapping Map() with polystore:</p>
|
|
466
467
|
<ul>
|
|
467
468
|
<li><strong>Expiration</strong>: you can now set lifetime to your values so that they are automatically evicted when the time passes. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
468
469
|
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
469
470
|
</ul>
|
|
470
471
|
</details>
|
|
471
472
|
|
|
473
|
+
```js
|
|
474
|
+
// GOOD - with polystore
|
|
475
|
+
await store.set("key1", { name: "Francisco" }, { expires: "2days" });
|
|
476
|
+
|
|
477
|
+
// COMPLEX - With sessionStorage
|
|
478
|
+
const data = new Map();
|
|
479
|
+
data.set("key1", { name: "Francisco" });
|
|
480
|
+
// Expiration not supported
|
|
481
|
+
```
|
|
482
|
+
|
|
472
483
|
### Local Storage
|
|
473
484
|
|
|
474
485
|
The traditional localStorage that we all know and love, this time with a unified API, and promises:
|
|
@@ -485,6 +496,26 @@ console.log(await store.get("key1"));
|
|
|
485
496
|
|
|
486
497
|
Same limitations as always apply to localStorage, if you think you are going to use too much storage try instead our integration with [Local Forage](#local-forage)!
|
|
487
498
|
|
|
499
|
+
<details>
|
|
500
|
+
<summary>Why use polystore with <code>localStorage</code>?</summary>
|
|
501
|
+
<p>These benefits are for wrapping localStorage with polystore:</p>
|
|
502
|
+
<ul>
|
|
503
|
+
<li><strong>Data structures</strong>: with Polystore you can pass more complex data structures and we'll handle the serialization/deserialization.</li>
|
|
504
|
+
<li><strong>Expiration</strong>: you can now set lifetime to your values so that they are automatically evicted when the time passes. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
505
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
506
|
+
</ul>
|
|
507
|
+
</details>
|
|
508
|
+
|
|
509
|
+
```js
|
|
510
|
+
// GOOD - with polystore
|
|
511
|
+
await store.set("key1", { name: "Francisco" }, { expires: "2days" });
|
|
512
|
+
|
|
513
|
+
// COMPLEX - With localStorage
|
|
514
|
+
const serialValue = JSON.stringify({ name: "Francisco" });
|
|
515
|
+
localStorage.set("key1", serialValue);
|
|
516
|
+
// Expiration not supported
|
|
517
|
+
```
|
|
518
|
+
|
|
488
519
|
### Session Storage
|
|
489
520
|
|
|
490
521
|
Same as localStorage, but now for the session only:
|
|
@@ -499,6 +530,26 @@ console.log(await store.get("key1"));
|
|
|
499
530
|
// "Hello world"
|
|
500
531
|
```
|
|
501
532
|
|
|
533
|
+
<details>
|
|
534
|
+
<summary>Why use polystore with <code>sessionStorage</code>?</summary>
|
|
535
|
+
<p>These benefits are for wrapping sessionStorage with polystore:</p>
|
|
536
|
+
<ul>
|
|
537
|
+
<li><strong>Data structures</strong>: with Polystore you can pass more complex data structures and we'll handle the serialization/deserialization.</li>
|
|
538
|
+
<li><strong>Expiration</strong>: you can now set lifetime to your values so that they are automatically evicted when the time passes. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
539
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
540
|
+
</ul>
|
|
541
|
+
</details>
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
// GOOD - with polystore
|
|
545
|
+
await store.set("key1", { name: "Francisco" }, { expires: "2days" });
|
|
546
|
+
|
|
547
|
+
// COMPLEX - With sessionStorage
|
|
548
|
+
const serialValue = JSON.stringify({ name: "Francisco" });
|
|
549
|
+
sessionStorage.set("key1", serialValue);
|
|
550
|
+
// Expiration not supported
|
|
551
|
+
```
|
|
552
|
+
|
|
502
553
|
### Cookies
|
|
503
554
|
|
|
504
555
|
Supports native browser cookies, including setting the expire time:
|
|
@@ -517,6 +568,16 @@ It is fairly limited for how powerful cookies are, but in exchange it has the sa
|
|
|
517
568
|
|
|
518
569
|
> Note: the cookie expire resolution is in the seconds, so times shorter than 1 second like `expires: 0.02` (20 ms) don't make sense for this storage method and won't properly save them.
|
|
519
570
|
|
|
571
|
+
<details>
|
|
572
|
+
<summary>Why use polystore with <code>cookies</code>?</summary>
|
|
573
|
+
<p>These benefits are for wrapping cookies with polystore:</p>
|
|
574
|
+
<ul>
|
|
575
|
+
<li><strong>Data structures</strong>: with Polystore you can pass more complex data structures and we'll handle the serialization/deserialization.</li>
|
|
576
|
+
<li><strong>Intuitive expirations</strong>: use plain English to specify the expiration time like <code>10min</code>. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
577
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
578
|
+
</ul>
|
|
579
|
+
</details>
|
|
580
|
+
|
|
520
581
|
### Local Forage
|
|
521
582
|
|
|
522
583
|
Supports localForage (with any driver it uses) so that you have a unified API. It also _adds_ the `expires` option to the setters!
|
|
@@ -532,6 +593,15 @@ console.log(await store.get("key1"));
|
|
|
532
593
|
// "Hello world"
|
|
533
594
|
```
|
|
534
595
|
|
|
596
|
+
<details>
|
|
597
|
+
<summary>Why use polystore with <code>localStorage</code>?</summary>
|
|
598
|
+
<p>These benefits are for wrapping localStorage with polystore:</p>
|
|
599
|
+
<ul>
|
|
600
|
+
<li><strong>Intuitive expirations</strong>: use plain English to specify the expiration time like <code>10min</code>. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
601
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
602
|
+
</ul>
|
|
603
|
+
</details>
|
|
604
|
+
|
|
535
605
|
### Redis Client
|
|
536
606
|
|
|
537
607
|
Supports the official Node Redis Client. You can pass either the client or the promise:
|
|
@@ -551,6 +621,34 @@ You don't need to `await` for the connect or similar, this will process it prope
|
|
|
551
621
|
|
|
552
622
|
> Note: the Redis client expire resolution is in the seconds, so times shorter than 1 second like `expires: 0.02` (20 ms) don't make sense for this storage method and won't properly save them.
|
|
553
623
|
|
|
624
|
+
<details>
|
|
625
|
+
<summary>Why use polystore with <code>Redis</code>?</summary>
|
|
626
|
+
<p>These benefits are for wrapping Redis with polystore:</p>
|
|
627
|
+
<ul>
|
|
628
|
+
<li><strong>Intuitive expirations</strong>: use plain English to specify the expiration time like <code>10min</code>. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
629
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
630
|
+
</ul>
|
|
631
|
+
</details>
|
|
632
|
+
|
|
633
|
+
### Fetch API
|
|
634
|
+
|
|
635
|
+
Calls an API to get/put the data:
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
import kv from "polystore";
|
|
639
|
+
|
|
640
|
+
const store = kv("https://kv.example.com/");
|
|
641
|
+
|
|
642
|
+
await store.set("key1", "Hello world", { expires: "1h" });
|
|
643
|
+
console.log(await store.get("key1"));
|
|
644
|
+
// "Hello world"
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
> Note: the API client expire resolution is in the seconds, so times shorter than 1 second like `expires: 0.02` (20 ms) don't make sense for this storage method and won't properly save them.
|
|
648
|
+
|
|
649
|
+
> Note: see the reference implementation in src/server.js
|
|
650
|
+
|
|
651
|
+
|
|
554
652
|
### File
|
|
555
653
|
|
|
556
654
|
Treat a JSON file in your filesystem as the source for the KV store:
|
|
@@ -565,7 +663,7 @@ console.log(await store.get("key1"));
|
|
|
565
663
|
// "Hello world"
|
|
566
664
|
```
|
|
567
665
|
|
|
568
|
-
> Note: an extension is needed, to
|
|
666
|
+
> Note: an extension is needed, to disambiguate with "folder"
|
|
569
667
|
|
|
570
668
|
You can also create multiple stores:
|
|
571
669
|
|
|
@@ -576,6 +674,30 @@ const store1 = kv(new URL(`file://${process.cwd()}/cache.json`));
|
|
|
576
674
|
const store2 = kv(new URL(`file://${import.meta.dirname}/data.json`));
|
|
577
675
|
```
|
|
578
676
|
|
|
677
|
+
<details>
|
|
678
|
+
<summary>Why use polystore with a file?</summary>
|
|
679
|
+
<p>These benefits are for wrapping a file with polystore:</p>
|
|
680
|
+
<ul>
|
|
681
|
+
<li><strong>Data structures</strong>: with Polystore you can pass more complex data structures and we'll handle the serialization/deserialization.</li>
|
|
682
|
+
<li><strong>Expiration</strong>: you can now set lifetime to your values so that they are automatically evicted when the time passes. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
683
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
684
|
+
</ul>
|
|
685
|
+
</details>
|
|
686
|
+
|
|
687
|
+
```js
|
|
688
|
+
// GOOD - with polystore
|
|
689
|
+
await store.set("key1", { name: "Francisco" }, { expires: "2days" });
|
|
690
|
+
|
|
691
|
+
// COMPLEX - With native file managing
|
|
692
|
+
const file = './data/users.json';
|
|
693
|
+
const str = await fsp.readFile(file, "utf-8");
|
|
694
|
+
const data = JSON.parse(str);
|
|
695
|
+
data["key1"] = { name: "Francisco" };
|
|
696
|
+
const serialValue = JSON.stringify(data);
|
|
697
|
+
await fsp.writeFile(file, serialValue);
|
|
698
|
+
// Expiration not supported (and error handling not shown)
|
|
699
|
+
```
|
|
700
|
+
|
|
579
701
|
### Folder
|
|
580
702
|
|
|
581
703
|
Treat a single folder in your filesystem as the source for the KV store, with each key being within a file:
|
|
@@ -590,7 +712,7 @@ console.log(await store.get("key1"));
|
|
|
590
712
|
// "Hello world"
|
|
591
713
|
```
|
|
592
714
|
|
|
593
|
-
> Note: the ending slash `/` is needed, to
|
|
715
|
+
> Note: the ending slash `/` is needed, to disambiguate with "file"
|
|
594
716
|
|
|
595
717
|
You can also create multiple stores:
|
|
596
718
|
|
|
@@ -601,6 +723,27 @@ const store1 = kv(new URL(`file://${process.cwd()}/cache/`));
|
|
|
601
723
|
const store2 = kv(new URL(`file://${import.meta.dirname}/data/`));
|
|
602
724
|
```
|
|
603
725
|
|
|
726
|
+
<details>
|
|
727
|
+
<summary>Why use polystore with a folder?</summary>
|
|
728
|
+
<p>These benefits are for wrapping a folder with polystore:</p>
|
|
729
|
+
<ul>
|
|
730
|
+
<li><strong>Data structures</strong>: with Polystore you can pass more complex data structures and we'll handle the serialization/deserialization.</li>
|
|
731
|
+
<li><strong>Expiration</strong>: you can now set lifetime to your values so that they are automatically evicted when the time passes. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
732
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
733
|
+
</ul>
|
|
734
|
+
</details>
|
|
735
|
+
|
|
736
|
+
```js
|
|
737
|
+
// GOOD - with polystore
|
|
738
|
+
await store.set("key1", { name: "Francisco" }, { expires: "2days" });
|
|
739
|
+
|
|
740
|
+
// COMPLEX - With native folder
|
|
741
|
+
const file = './data/user/key1.json';
|
|
742
|
+
const serialValue = JSON.stringify({ name: "Francisco" });
|
|
743
|
+
await fsp.writeFile(file, serialValue);
|
|
744
|
+
// Expiration not supported (and error handling not shown)
|
|
745
|
+
```
|
|
746
|
+
|
|
604
747
|
### Cloudflare KV
|
|
605
748
|
|
|
606
749
|
Supports the official Cloudflare's KV stores. Follow [the official guide](https://developers.cloudflare.com/kv/get-started/), then load it like this:
|
|
@@ -621,7 +764,15 @@ export default {
|
|
|
621
764
|
};
|
|
622
765
|
```
|
|
623
766
|
|
|
624
|
-
|
|
767
|
+
<details>
|
|
768
|
+
<summary>Why use polystore with Cloudflare's KV?</summary>
|
|
769
|
+
<p>These benefits are for wrapping Cloudflare's KV with polystore:</p>
|
|
770
|
+
<ul>
|
|
771
|
+
<li><strong>Data structures</strong>: with Polystore you can pass more complex data structures and we'll handle the serialization/deserialization.</li>
|
|
772
|
+
<li><strong>Intuitive expirations</strong>: use plain English to specify the expiration time like <code>10min</code>. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
773
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
774
|
+
</ul>
|
|
775
|
+
</details>
|
|
625
776
|
|
|
626
777
|
```js
|
|
627
778
|
// GOOD - with polystore
|
|
@@ -650,7 +801,13 @@ console.log(await store.get("key1"));
|
|
|
650
801
|
// "Hello world"
|
|
651
802
|
```
|
|
652
803
|
|
|
653
|
-
|
|
804
|
+
<details>
|
|
805
|
+
<summary>Why use polystore with Level?</summary>
|
|
806
|
+
<p>These benefits are for wrapping Level with polystore:</p>
|
|
807
|
+
<ul>
|
|
808
|
+
<li><strong>Intuitive expirations</strong>: use plain English to specify the expiration time like <code>10min</code>. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
809
|
+
</ul>
|
|
810
|
+
</details>
|
|
654
811
|
|
|
655
812
|
```js
|
|
656
813
|
// GOOD - with polystore
|
|
@@ -675,6 +832,15 @@ console.log(await store.get("key1"));
|
|
|
675
832
|
// "Hello world"
|
|
676
833
|
```
|
|
677
834
|
|
|
835
|
+
<details>
|
|
836
|
+
<summary>Why use polystore with Etcd?</summary>
|
|
837
|
+
<p>These benefits are for wrapping Etcd with polystore:</p>
|
|
838
|
+
<ul>
|
|
839
|
+
<li><strong>Intuitive expirations</strong>: use plain English to specify the expiration time like <code>10min</code>. <a href="#expiration-explained">Expiration explained</a>.</li>
|
|
840
|
+
<li><strong>Substores</strong>: you can also create substores and manage partial data with ease. <a href="#prefix">Details about substores</a>.</li>
|
|
841
|
+
</ul>
|
|
842
|
+
</details>
|
|
843
|
+
|
|
678
844
|
### Custom store
|
|
679
845
|
|
|
680
846
|
Please see the [creating a store](#creating-a-store) section for all the details!
|
|
@@ -782,8 +948,7 @@ const value = await store.get("a");
|
|
|
782
948
|
// client.get("hello:world:a");
|
|
783
949
|
|
|
784
950
|
// User calls this, then the client is called with that:
|
|
785
|
-
for await (const
|
|
786
|
-
}
|
|
951
|
+
for await (const [key, value] of store) {}
|
|
787
952
|
// client.iterate("hello:world:");
|
|
788
953
|
```
|
|
789
954
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Use fetch()
|
|
2
|
+
export default class Api {
|
|
3
|
+
// Indicate that the file handler does NOT handle expirations
|
|
4
|
+
EXPIRES = true;
|
|
5
|
+
|
|
6
|
+
// Check whether the given store is a FILE-type
|
|
7
|
+
static test(client) {
|
|
8
|
+
return (
|
|
9
|
+
typeof client === "string" &&
|
|
10
|
+
(client.startsWith("https://") || client.startsWith("http://"))
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(client) {
|
|
15
|
+
client = client.replace(/\/$/, "") + "/";
|
|
16
|
+
this.client = async (path, opts = {}) => {
|
|
17
|
+
const query = Object.entries(opts.query || {})
|
|
18
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
19
|
+
.join("&");
|
|
20
|
+
let url = client + path.replace(/^\//, "") + "?" + query;
|
|
21
|
+
opts.headers = opts.headers || {};
|
|
22
|
+
opts.headers.accept = "application/json";
|
|
23
|
+
if (opts.body) opts.headers["content-type"] = "application/json";
|
|
24
|
+
const res = await fetch(url, opts);
|
|
25
|
+
if (!res.ok) return null;
|
|
26
|
+
if (!res.headers["content-type"] !== "application/json") {
|
|
27
|
+
console.warn("Not a JSON API");
|
|
28
|
+
}
|
|
29
|
+
return res.json();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async get(key) {
|
|
34
|
+
return await this.client(`/${key}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async set(key, value, { expires } = {}) {
|
|
38
|
+
return await this.client(`/${encodeURIComponent(key)}`, {
|
|
39
|
+
query: { expires },
|
|
40
|
+
method: "put",
|
|
41
|
+
body: JSON.stringify(value),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async del(key) {
|
|
46
|
+
return await this.client(`/${encodeURIComponent(key)}`, {
|
|
47
|
+
method: "delete",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Since we have pagination, we don't want to get all of the
|
|
52
|
+
// keys at once if we can avoid it
|
|
53
|
+
async *iterate(prefix = "") {
|
|
54
|
+
const data = await this.client("/", { query: { prefix } });
|
|
55
|
+
if (!data) return [];
|
|
56
|
+
for (let [key, value] of Object.entries(data)) {
|
|
57
|
+
yield [prefix + key, value];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async keys(prefix = "") {
|
|
62
|
+
const data = await this.client(`/`, { query: { prefix } });
|
|
63
|
+
if (!data) return [];
|
|
64
|
+
return Object.keys(data).map((k) => prefix + k);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async clear(prefix = "") {
|
|
68
|
+
const list = await this.keys(prefix);
|
|
69
|
+
return Promise.all(list.map((k) => this.del(k)));
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/clients/folder.js
CHANGED
|
@@ -23,7 +23,7 @@ export default class Folder {
|
|
|
23
23
|
constructor(folder) {
|
|
24
24
|
this.folder =
|
|
25
25
|
typeof folder === "string"
|
|
26
|
-
? folder.slice("
|
|
26
|
+
? folder.slice("file://".length).replace(/\/$/, "") + "/"
|
|
27
27
|
: folder.pathname.replace(/\/$/, "") + "/";
|
|
28
28
|
|
|
29
29
|
// Run this once on launch; import the FS module and reset the file
|
|
@@ -48,7 +48,7 @@ export default class Folder {
|
|
|
48
48
|
async set(key, value) {
|
|
49
49
|
const fsp = await this.promise;
|
|
50
50
|
const file = this.folder + key + ".json";
|
|
51
|
-
await fsp.writeFile(file, JSON.stringify(value), "utf8");
|
|
51
|
+
await fsp.writeFile(file, JSON.stringify(value, null, 2), "utf8");
|
|
52
52
|
return file;
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -61,15 +61,10 @@ export default class Folder {
|
|
|
61
61
|
|
|
62
62
|
async *iterate(prefix = "") {
|
|
63
63
|
const fsp = await this.promise;
|
|
64
|
-
const all = await fsp.readdir(this.folder
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
.map((
|
|
68
|
-
(file.path.replace(/\/$/, "") + "/" + file.name)
|
|
69
|
-
.replace(this.folder, "")
|
|
70
|
-
.replace(".json", ""),
|
|
71
|
-
)
|
|
72
|
-
.filter((k) => k.startsWith(prefix));
|
|
64
|
+
const all = await fsp.readdir(this.folder);
|
|
65
|
+
const keys = all
|
|
66
|
+
.filter((f) => f.startsWith(prefix) && f.endsWith(".json"))
|
|
67
|
+
.map((name) => name.slice(0, -".json".length));
|
|
73
68
|
for (const key of keys) {
|
|
74
69
|
const data = await this.get(key);
|
|
75
70
|
yield [key, data];
|
package/src/clients/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import api from "./api.js";
|
|
1
2
|
import cloudflare from "./cloudflare.js";
|
|
2
3
|
import cookie from "./cookie.js";
|
|
3
4
|
import etcd from "./etcd.js";
|
|
@@ -10,6 +11,7 @@ import redis from "./redis.js";
|
|
|
10
11
|
import storage from "./storage.js";
|
|
11
12
|
|
|
12
13
|
export default {
|
|
14
|
+
api,
|
|
13
15
|
cloudflare,
|
|
14
16
|
cookie,
|
|
15
17
|
etcd,
|
package/src/server.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// This is an example server implementation of the HTTP library!
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import kv from "./index.js";
|
|
4
|
+
import { parse } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
// Modify this to use any sub-store as desired. It's nice
|
|
7
|
+
// to use polystore itself for the polystore server library!'
|
|
8
|
+
const store = kv(new Map());
|
|
9
|
+
|
|
10
|
+
// Some reply helpers
|
|
11
|
+
const notFound = () => new Response(null, { status: 404 });
|
|
12
|
+
const sendJson = (data, status = 200) => {
|
|
13
|
+
const body = JSON.stringify(data);
|
|
14
|
+
const headers = { "content-type": "application/json" };
|
|
15
|
+
return new Response(body, { status, headers });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function fetch({ method, url, body }) {
|
|
19
|
+
method = method.toLowerCase();
|
|
20
|
+
url = new URL(url);
|
|
21
|
+
let [, id] = url.pathname.split("/");
|
|
22
|
+
id = decodeURIComponent(id);
|
|
23
|
+
const expires = Number(url.searchParams.get("expires")) || null;
|
|
24
|
+
const prefix = url.searchParams.get("prefix") || null;
|
|
25
|
+
|
|
26
|
+
let local = store;
|
|
27
|
+
if (prefix) local = store.prefix(prefix);
|
|
28
|
+
|
|
29
|
+
if (method === "get") {
|
|
30
|
+
if (id === "ping") return new Response(null, { status: 200 });
|
|
31
|
+
if (!id) return sendJson(await local.all());
|
|
32
|
+
const data = await local.get(id);
|
|
33
|
+
if (data === null) return notFound();
|
|
34
|
+
return sendJson(data);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (method === "put") {
|
|
38
|
+
if (!id) return notFound();
|
|
39
|
+
const data = await new Response(body).json();
|
|
40
|
+
if (!data) return notFound();
|
|
41
|
+
await local.set(id, data, { expires });
|
|
42
|
+
return sendJson(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (method === "delete" && id) {
|
|
46
|
+
await local.del(id);
|
|
47
|
+
return sendJson(id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return notFound();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// http or express server-like handler:
|
|
54
|
+
async function server(req, res) {
|
|
55
|
+
const url = new URL(req.url, "http://localhost:3000/").href;
|
|
56
|
+
const reply = await fetch({ ...req, url });
|
|
57
|
+
res.writeHead(reply.status, null, reply.headers || {});
|
|
58
|
+
if (reply.body) res.write(reply.body);
|
|
59
|
+
res.end();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function start(port = 3000) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const server = http.createServer(server);
|
|
65
|
+
server.on("clientError", (error, socket) => {
|
|
66
|
+
reject(error);
|
|
67
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
68
|
+
});
|
|
69
|
+
server.listen(port, resolve);
|
|
70
|
+
return () => server.close();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { fetch, server, start };
|
|
75
|
+
export default { fetch, server, start };
|