polystore 0.17.0 → 0.18.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.
Files changed (4) hide show
  1. package/index.d.ts +23 -11
  2. package/index.js +54 -29
  3. package/package.json +2 -2
  4. package/readme.md +169 -38
package/index.d.ts CHANGED
@@ -1,6 +1,4 @@
1
- type Options = {
2
- expires?: number | null | string;
3
- };
1
+ type Expires = number | null | string;
4
2
  type StoreData<T extends Serializable = Serializable> = {
5
3
  value: T;
6
4
  expires: number | null;
@@ -9,13 +7,14 @@ type Serializable = string | number | boolean | null | (Serializable | null)[] |
9
7
  [key: string]: Serializable | null;
10
8
  };
11
9
  interface ClientExpires {
10
+ TYPE: string;
12
11
  EXPIRES: true;
13
12
  promise?: Promise<any>;
14
13
  test?: (client: any) => boolean;
15
14
  get<T extends Serializable>(key: string): Promise<T | null> | T | null;
16
- set<T extends Serializable>(key: string, value: T, options?: Options): Promise<any> | any;
15
+ set<T extends Serializable>(key: string, value: T, expires?: Expires): Promise<any> | any;
17
16
  iterate<T extends Serializable>(prefix: string): AsyncGenerator<[string, T], void, unknown> | Generator<[string, T], void, unknown>;
18
- add?<T extends Serializable>(prefix: string, value: T, options?: Options): Promise<string>;
17
+ add?<T extends Serializable>(prefix: string, value: T, expires?: Expires): Promise<string>;
19
18
  has?(key: string): Promise<boolean> | boolean;
20
19
  del?(key: string): Promise<any> | any;
21
20
  keys?(prefix: string): Promise<string[]> | string[];
@@ -27,13 +26,14 @@ interface ClientExpires {
27
26
  close?(): Promise<any> | any;
28
27
  }
29
28
  interface ClientNonExpires {
29
+ TYPE: string;
30
30
  EXPIRES: false;
31
31
  promise?: Promise<any>;
32
32
  test?: (client: any) => boolean;
33
33
  get<T extends Serializable>(key: string): Promise<StoreData<T> | null> | StoreData<T> | null;
34
- set<T extends Serializable>(key: string, value: StoreData<T> | null, options?: Options): Promise<any> | any;
34
+ set<T extends Serializable>(key: string, value: StoreData<T> | null, ttl?: Expires): Promise<any> | any;
35
35
  iterate<T extends Serializable>(prefix: string): AsyncGenerator<[string, StoreData<T>], void, unknown> | Generator<[string, StoreData<T>], void, unknown>;
36
- add?<T extends Serializable>(prefix: string, value: StoreData<T>, options?: Options): Promise<string>;
36
+ add?<T extends Serializable>(prefix: string, value: StoreData<T>, ttl?: Expires): Promise<string>;
37
37
  has?(key: string): Promise<boolean> | boolean;
38
38
  del?(key: string): Promise<any> | any;
39
39
  keys?(prefix: string): Promise<string[]> | string[];
@@ -51,6 +51,7 @@ declare class Store<TDefault extends Serializable = Serializable> {
51
51
  PREFIX: string;
52
52
  promise: Promise<Client> | null;
53
53
  client: Client;
54
+ type: string;
54
55
  constructor(clientPromise?: any);
55
56
  /**
56
57
  * Save the data on an autogenerated key, can add expiration as well:
@@ -63,8 +64,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
63
64
  *
64
65
  * **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
65
66
  */
66
- add(value: TDefault, options?: Options): Promise<string>;
67
- add<T extends TDefault>(value: T, options?: Options): Promise<string>;
67
+ add(value: TDefault, ttl?: Expires): Promise<string>;
68
+ add<T extends TDefault>(value: T, ttl?: Expires): Promise<string>;
68
69
  /**
69
70
  * Save the data on the given key, can add expiration as well:
70
71
  *
@@ -76,8 +77,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
76
77
  *
77
78
  * **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
78
79
  */
79
- set(key: string, value: TDefault, options?: Options): Promise<string>;
80
- set<T extends TDefault>(key: string, value: T, options?: Options): Promise<string>;
80
+ set(key: string, value: TDefault, ttl?: Expires): Promise<string>;
81
+ set<T extends TDefault>(key: string, value: T, ttl?: Expires): Promise<string>;
81
82
  /**
82
83
  * Read a single value from the KV store:
83
84
  *
@@ -121,6 +122,17 @@ declare class Store<TDefault extends Serializable = Serializable> {
121
122
  * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
122
123
  */
123
124
  del(key: string): Promise<string>;
125
+ /**
126
+ * @alias of .del(key: string)
127
+ * Remove a single key and its value from the store:
128
+ *
129
+ * ```js
130
+ * const key = await store.delete("key1");
131
+ * ```
132
+ *
133
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
134
+ */
135
+ delete(key: string): Promise<string>;
124
136
  /**
125
137
  * An iterator that goes through all of the key:value pairs in the client
126
138
  *
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/clients/Client.ts
2
2
  var Client = class {
3
+ TYPE;
3
4
  EXPIRES = false;
4
5
  client;
5
6
  encode = (val) => JSON.stringify(val, null, 2);
@@ -11,6 +12,7 @@ var Client = class {
11
12
 
12
13
  // src/clients/api.ts
13
14
  var Api = class extends Client {
15
+ TYPE = "API";
14
16
  // Indicate that the file handler DOES handle expirations
15
17
  EXPIRES = true;
16
18
  static test = (client) => typeof client === "string" && /^https?:\/\//.test(client);
@@ -25,7 +27,7 @@ var Api = class extends Client {
25
27
  return this.decode(await res.text());
26
28
  };
27
29
  get = (key) => this.#api(key);
28
- set = async (key, value, { expires } = {}) => {
30
+ set = async (key, value, expires) => {
29
31
  const exp = typeof expires === "number" ? `?expires=${expires}` : "";
30
32
  await this.#api(key, exp, "PUT", this.encode(value));
31
33
  };
@@ -45,15 +47,16 @@ var Api = class extends Client {
45
47
 
46
48
  // src/clients/cloudflare.ts
47
49
  var Cloudflare = class extends Client {
50
+ TYPE = "CLOUDFLARE";
48
51
  // It handles expirations natively
49
52
  EXPIRES = true;
50
- // Check whether the given store is a FILE-type
51
- static test = (client) => client?.constructor?.name === "KvNamespace" || client?.constructor?.name === "EdgeKVNamespace";
53
+ static testKeys = ["getWithMetadata", "get", "list", "delete"];
52
54
  get = async (key) => {
53
- return this.decode(await this.client.get(key));
55
+ const value = await this.client.get(key);
56
+ return this.decode(value);
54
57
  };
55
- set = async (key, data, opts) => {
56
- const expirationTtl = opts.expires ? Math.round(opts.expires) : void 0;
58
+ set = async (key, data, expires) => {
59
+ const expirationTtl = expires ? Math.round(expires) : void 0;
57
60
  if (expirationTtl && expirationTtl < 60) {
58
61
  throw new Error("Cloudflare's min expiration is '60s'");
59
62
  }
@@ -93,6 +96,7 @@ var Cloudflare = class extends Client {
93
96
 
94
97
  // src/clients/cookie.ts
95
98
  var Cookie = class extends Client {
99
+ TYPE = "COOKIE";
96
100
  // It handles expirations natively
97
101
  EXPIRES = true;
98
102
  // Check if this is the right class for the given client
@@ -118,7 +122,7 @@ var Cookie = class extends Client {
118
122
  const all = this.#read();
119
123
  return key in all ? all[key] : null;
120
124
  };
121
- set = (key, data, { expires }) => {
125
+ set = (key, data, expires) => {
122
126
  const k = encodeURIComponent(key);
123
127
  const value = encodeURIComponent(this.encode(data ?? ""));
124
128
  let exp = "";
@@ -128,7 +132,7 @@ var Cookie = class extends Client {
128
132
  }
129
133
  document.cookie = `${k}=${value}${exp}`;
130
134
  };
131
- del = (key) => this.set(key, "", { expires: -100 });
135
+ del = (key) => this.set(key, "", -100);
132
136
  async *iterate(prefix = "") {
133
137
  for (let [key, value] of Object.entries(this.#read())) {
134
138
  if (!key.startsWith(prefix)) continue;
@@ -139,10 +143,11 @@ var Cookie = class extends Client {
139
143
 
140
144
  // src/clients/etcd.ts
141
145
  var Etcd = class extends Client {
146
+ TYPE = "ETCD3";
142
147
  // It desn't handle expirations natively
143
148
  EXPIRES = false;
144
149
  // Check if this is the right class for the given client
145
- static test = (client) => client?.constructor?.name === "Etcd3";
150
+ static testKeys = ["leaseClient", "watchClient", "watchManager"];
146
151
  get = async (key) => {
147
152
  const data = await this.client.get(key).json();
148
153
  return data;
@@ -157,14 +162,6 @@ var Etcd = class extends Client {
157
162
  yield [key, await this.get(key)];
158
163
  }
159
164
  }
160
- keys = (prefix = "") => {
161
- return this.client.getAll().prefix(prefix).keys();
162
- };
163
- entries = async (prefix = "") => {
164
- const keys = await this.keys(prefix);
165
- const values = await Promise.all(keys.map((k) => this.get(k)));
166
- return keys.map((k, i) => [k, values[i]]);
167
- };
168
165
  clear = async (prefix = "") => {
169
166
  if (!prefix) return this.client.delete().all();
170
167
  return this.client.delete().prefix(prefix);
@@ -173,6 +170,7 @@ var Etcd = class extends Client {
173
170
 
174
171
  // src/clients/file.ts
175
172
  var File = class extends Client {
173
+ TYPE = "FILE";
176
174
  // It desn't handle expirations natively
177
175
  EXPIRES = false;
178
176
  fsp;
@@ -266,6 +264,7 @@ var noFileOk = (error) => {
266
264
  throw error;
267
265
  };
268
266
  var Folder = class extends Client {
267
+ TYPE = "FOLDER";
269
268
  // It desn't handle expirations natively
270
269
  EXPIRES = false;
271
270
  fsp;
@@ -311,6 +310,7 @@ var Folder = class extends Client {
311
310
 
312
311
  // src/clients/forage.ts
313
312
  var Forage = class extends Client {
313
+ TYPE = "FORAGE";
314
314
  // It desn't handle expirations natively
315
315
  EXPIRES = false;
316
316
  // Check if this is the right class for the given client
@@ -344,10 +344,11 @@ var notFound = (error) => {
344
344
  throw error;
345
345
  };
346
346
  var Level = class extends Client {
347
+ TYPE = "LEVEL";
347
348
  // It desn't handle expirations natively
348
349
  EXPIRES = false;
349
350
  // Check if this is the right class for the given client
350
- static test = (client) => client?.constructor?.name === "ClassicLevel";
351
+ static testKeys = ["attachResource", "detachResource", "prependOnceListener"];
351
352
  get = (key) => this.client.get(key, { valueEncoding }).catch(notFound);
352
353
  set = (key, value) => this.client.put(key, value, { valueEncoding });
353
354
  del = (key) => this.client.del(key);
@@ -378,6 +379,7 @@ var Level = class extends Client {
378
379
 
379
380
  // src/clients/memory.ts
380
381
  var Memory = class extends Client {
382
+ TYPE = "MEMORY";
381
383
  // It desn't handle expirations natively
382
384
  EXPIRES = false;
383
385
  // Check if this is the right class for the given client
@@ -395,12 +397,13 @@ var Memory = class extends Client {
395
397
 
396
398
  // src/clients/redis.ts
397
399
  var Redis = class extends Client {
400
+ TYPE = "REDIS";
398
401
  // Indicate if this client handles expirations (true = it does)
399
402
  EXPIRES = true;
400
403
  // Check if this is the right class for the given client
401
404
  static test = (client) => client && client.pSubscribe && client.sSubscribe;
402
405
  get = async (key) => this.decode(await this.client.get(key));
403
- set = async (key, value, { expires } = {}) => {
406
+ set = async (key, value, expires) => {
404
407
  const EX = expires ? Math.round(expires) : void 0;
405
408
  return this.client.set(key, this.encode(value), { EX });
406
409
  };
@@ -438,6 +441,7 @@ var Redis = class extends Client {
438
441
 
439
442
  // src/clients/sqlite.ts
440
443
  var SQLite = class extends Client {
444
+ TYPE = "SQLITE";
441
445
  // This one is doing manual time management internally even though
442
446
  // sqlite does not natively support expirations. This is because it does
443
447
  // support creating a `expires_at:Date` column that makes managing
@@ -474,7 +478,7 @@ var SQLite = class extends Client {
474
478
  }
475
479
  return this.decode(row.value);
476
480
  };
477
- set = (id, data, { expires } = {}) => {
481
+ set = (id, data, expires) => {
478
482
  const value = this.encode(data);
479
483
  const expires_at = expires ? Date.now() + expires * 1e3 : null;
480
484
  this.client.prepare(
@@ -524,6 +528,7 @@ ${prefix ? "AND id LIKE ?" : ""}
524
528
 
525
529
  // src/clients/storage.ts
526
530
  var WebStorage = class extends Client {
531
+ TYPE = "STORAGE";
527
532
  // It desn't handle expirations natively
528
533
  EXPIRES = false;
529
534
  // Check if this is the right class for the given client
@@ -607,21 +612,28 @@ var Store = class _Store {
607
612
  PREFIX = "";
608
613
  promise;
609
614
  client;
615
+ type = "UNKNOWN";
610
616
  constructor(clientPromise = /* @__PURE__ */ new Map()) {
611
617
  this.promise = Promise.resolve(clientPromise).then(async (client) => {
612
618
  this.client = this.#find(client);
613
619
  this.#validate(this.client);
614
620
  this.promise = null;
615
621
  await this.client.promise;
622
+ this.type = this.client?.TYPE || this.type;
616
623
  return client;
617
624
  });
618
625
  }
619
626
  #find(store) {
620
627
  if (store instanceof _Store) return store.client;
621
628
  for (let client of Object.values(clients_default)) {
622
- if (client.test && client.test(store)) {
629
+ if ("test" in client && client.test(store)) {
623
630
  return new client(store);
624
631
  }
632
+ if ("testKeys" in client && typeof store === "object") {
633
+ if (client.testKeys.every((key) => store[key])) {
634
+ return new client(store);
635
+ }
636
+ }
625
637
  }
626
638
  if (typeof store === "function" && /^class\s/.test(Function.prototype.toString.call(store))) {
627
639
  return new store();
@@ -652,29 +664,29 @@ var Store = class _Store {
652
664
  if (key) this.del(key);
653
665
  return false;
654
666
  }
655
- async add(value, options = {}) {
667
+ async add(value, ttl) {
656
668
  await this.promise;
657
- let expires = parse(options.expires);
669
+ let expires = parse(ttl);
658
670
  if (this.client.add) {
659
671
  if (this.client.EXPIRES) {
660
- return await this.client.add(this.PREFIX, value, { expires });
672
+ return await this.client.add(this.PREFIX, value, expires);
661
673
  }
662
674
  expires = unix(expires);
663
675
  const key2 = await this.client.add(this.PREFIX, { expires, value });
664
676
  return key2;
665
677
  }
666
678
  const key = createId();
667
- return this.set(key, value, { expires });
679
+ return this.set(key, value, expires);
668
680
  }
669
- async set(key, value, options = {}) {
681
+ async set(key, value, ttl) {
670
682
  await this.promise;
671
683
  const id = this.PREFIX + key;
672
- let expires = parse(options.expires);
684
+ let expires = parse(ttl);
673
685
  if (value === null || typeof expires === "number" && expires <= 0) {
674
686
  return this.del(key);
675
687
  }
676
688
  if (this.client.EXPIRES) {
677
- await this.client.set(id, value, { expires });
689
+ await this.client.set(id, value, expires);
678
690
  return key;
679
691
  }
680
692
  expires = unix(expires);
@@ -736,12 +748,25 @@ var Store = class _Store {
736
748
  return key;
737
749
  }
738
750
  if (this.client.EXPIRES) {
739
- await this.client.set(id, null, { expires: 0 });
751
+ await this.client.set(id, null, 0);
740
752
  } else {
741
753
  await this.client.set(id, null);
742
754
  }
743
755
  return key;
744
756
  }
757
+ /**
758
+ * @alias of .del(key: string)
759
+ * Remove a single key and its value from the store:
760
+ *
761
+ * ```js
762
+ * const key = await store.delete("key1");
763
+ * ```
764
+ *
765
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
766
+ */
767
+ async delete(key) {
768
+ return this.del(key);
769
+ }
745
770
  async *[Symbol.asyncIterator]() {
746
771
  await this.promise;
747
772
  if (this.client.EXPIRES) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "polystore",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
5
- "homepage": "https://polystore.dev/",
5
+ "homepage": "https://polystore.dev",
6
6
  "repository": "https://github.com/franciscop/polystore.git",
7
7
  "bugs": "https://github.com/franciscop/polystore/issues",
8
8
  "funding": "https://www.paypal.me/franciscopresencia/19",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Polystore [![npm install polystore](https://img.shields.io/badge/npm%20install-polystore-blue.svg)](https://www.npmjs.com/package/polystore) [![test badge](https://github.com/franciscop/polystore/workflows/tests/badge.svg "test badge")](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [![gzip size](https://badgen.net/bundlephobia/minzip/polystore?label=gzip&color=green)](https://bundlephobia.com/package/polystore)
2
2
 
3
- A key-value library to unify the API of [many clients](#clients), like localStorage, Redis, FileSystem, etc:
3
+ A key-value library to unify the API of [many clients](#clients): localStorage, Redis, FileSystem, SQLite, etc.
4
4
 
5
5
  ```js
6
6
  import kv from "polystore";
@@ -14,8 +14,8 @@ const store4 = kv(yourOwnStore); // Create a store based on your code
14
14
  These are all the methods of the [API](#api) (they are all `async`):
15
15
 
16
16
  - [`.get(key)`](#get): read a single value, or `null` if it doesn't exist or is expired.
17
- - [`.set(key, value, options?)`](#set): save a single value that is serializable.
18
- - [`.add(value, options?)`](#add): save a single value with an auto-generated key.
17
+ - [`.set(key, value, ttl?)`](#set): save a single value that is serializable.
18
+ - [`.add(value, ttl?)`](#add): save a single value with an auto-generated key.
19
19
  - [`.has(key)`](#has): check whether a key exists and is not expired.
20
20
  - [`.del(key)`](#del): delete a single key/value from the store.
21
21
  - [Iterator](#iterator): go through all of the key/values one by one.
@@ -197,7 +197,7 @@ await store.del('key2');
197
197
  console.log(await store.get("key2")); // null
198
198
 
199
199
  // Expired
200
- await store.set("key3", "Hello", { expires: '1s' });
200
+ await store.set("key3", "Hello", "1s");
201
201
  console.log(await store.get("key3")); // "Hello"
202
202
  await new Promise((done) => setTimeout(done, 2000)); // Wait 2 seconds
203
203
  console.log(await store.get("key3")); // null (already expired)
@@ -221,11 +221,11 @@ console.log(await session.get('key1'));
221
221
  Create or update a value in the store. Will return a promise that resolves with the key when the value has been saved:
222
222
 
223
223
  ```js
224
- await store.set(key: string, value: any, options?: { expires: number|string });
224
+ await store.set(key: string, value: any, expires?: number|string);
225
225
 
226
226
  await store.set("key1", "Hello World");
227
- await store.set("key2", ["my", "grocery", "list"], { expires: "1h" });
228
- await store.set("key3", { name: "Francisco" }, { expires: 60 * 60 });
227
+ await store.set("key2", ["my", "grocery", "list"], "1h");
228
+ await store.set("key3", { name: "Francisco" }, 60 * 60);
229
229
  ```
230
230
 
231
231
  You can specify the type either at [the store level](#api) or at the method level:
@@ -268,7 +268,7 @@ In short, only JSON-serializable data is safe to store.
268
268
 
269
269
  #### Expires
270
270
 
271
- When the `expires` option is set, it can be a number (**seconds**) or a string representing some time:
271
+ When the `ttl` option is set, it can be a number (**seconds**) or a string representing some time:
272
272
 
273
273
  ```js
274
274
  // Valid "expire" values:
@@ -292,14 +292,14 @@ These are all the units available:
292
292
  Create a value in the store with an auto-generated key. Will return a promise that resolves with the key when the value has been saved. The value needs to be serializable:
293
293
 
294
294
  ```js
295
- const key:string = await store.add(value: any, options?: { expires: number|string });
295
+ const key:string = await store.add(value: any, expires?: number|string);
296
296
 
297
297
  const key1 = await store.add("Hello World");
298
- const key2 = await store.add(["my", "grocery", "list"], { expires: "1h" });
299
- const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60 });
298
+ const key2 = await store.add(["my", "grocery", "list"], "1h");
299
+ const key3 = await store.add({ name: "Francisco" }, 60 * 60);
300
300
  ```
301
301
 
302
- The options and details are similar to [`.set()`](#set), except for the lack of the first argument, since `.add()` will generate the key automatically.
302
+ The value and expires are similar to [`.set()`](#set), except for the lack of the first argument, since `.add()` will generate the key automatically.
303
303
 
304
304
  The default key is 24 AlphaNumeric characters (upper+lower case), however this can change if you are using a `.prefix()` or some clients might generate it differently (only custom clients can do that right now).
305
305
 
@@ -357,7 +357,7 @@ An example of an exception of the above is when you use it as a cache, then you
357
357
  async function fetchUser(id) {
358
358
  if (!(await store.has(id))) {
359
359
  const { data } = await axios.get(`/users/${id}`);
360
- await store.set(id, data, { expires: "1h" });
360
+ await store.set(id, data, "1h");
361
361
  }
362
362
  return store.get(id);
363
363
  }
@@ -599,7 +599,7 @@ import kv from "polystore";
599
599
 
600
600
  const store = kv(new Map());
601
601
 
602
- await store.set("key1", "Hello world", { expires: "1h" });
602
+ await store.set("key1", "Hello world", "1h");
603
603
  console.log(await store.get("key1"));
604
604
  // "Hello world"
605
605
  ```
@@ -615,7 +615,7 @@ console.log(await store.get("key1"));
615
615
 
616
616
  ```js
617
617
  // GOOD - with polystore
618
- await store.set("key1", { name: "Francisco" }, { expires: "2days" });
618
+ await store.set("key1", { name: "Francisco" }, "2days");
619
619
 
620
620
  // COMPLEX - With sessionStorage
621
621
  const data = new Map();
@@ -632,7 +632,7 @@ import kv from "polystore";
632
632
 
633
633
  const store = kv(localStorage);
634
634
 
635
- await store.set("key1", "Hello world", { expires: "1h" });
635
+ await store.set("key1", "Hello world", "1h");
636
636
  console.log(await store.get("key1"));
637
637
  // "Hello world"
638
638
  ```
@@ -651,7 +651,7 @@ Same limitations as always apply to localStorage, if you think you are going to
651
651
 
652
652
  ```js
653
653
  // GOOD - with polystore
654
- await store.set("key1", { name: "Francisco" }, { expires: "2days" });
654
+ await store.set("key1", { name: "Francisco" }, "2days");
655
655
 
656
656
  // COMPLEX - With localStorage
657
657
  const serialValue = JSON.stringify({ name: "Francisco" });
@@ -668,7 +668,7 @@ import kv from "polystore";
668
668
 
669
669
  const store = kv(sessionStorage);
670
670
 
671
- await store.set("key1", "Hello world", { expires: "1h" });
671
+ await store.set("key1", "Hello world", "1h");
672
672
  console.log(await store.get("key1"));
673
673
  // "Hello world"
674
674
  ```
@@ -685,7 +685,7 @@ console.log(await store.get("key1"));
685
685
 
686
686
  ```js
687
687
  // GOOD - with polystore
688
- await store.set("key1", { name: "Francisco" }, { expires: "2days" });
688
+ await store.set("key1", { name: "Francisco" }, "2days");
689
689
 
690
690
  // COMPLEX - With sessionStorage
691
691
  const serialValue = JSON.stringify({ name: "Francisco" });
@@ -702,7 +702,7 @@ import kv from "polystore";
702
702
 
703
703
  const store = kv("cookie"); // just a plain string
704
704
 
705
- await store.set("key1", "Hello world", { expires: "1h" });
705
+ await store.set("key1", "Hello world", "1h");
706
706
  console.log(await store.get("key1"));
707
707
  // "Hello world"
708
708
  ```
@@ -731,7 +731,7 @@ import localForage from "localforage";
731
731
 
732
732
  const store = kv(localForage);
733
733
 
734
- await store.set("key1", "Hello world", { expires: "1h" });
734
+ await store.set("key1", "Hello world", "1h");
735
735
  console.log(await store.get("key1"));
736
736
  // "Hello world"
737
737
  ```
@@ -755,7 +755,7 @@ import { createClient } from "redis";
755
755
 
756
756
  const store = kv(createClient().connect());
757
757
 
758
- await store.set("key1", "Hello world", { expires: "1h" });
758
+ await store.set("key1", "Hello world", "1h");
759
759
  console.log(await store.get("key1"));
760
760
  // "Hello world"
761
761
  ```
@@ -790,7 +790,7 @@ import Database from "better-sqlite3";
790
790
  const db = new Database("data.db")
791
791
  const store = kv(db);
792
792
 
793
- await store.set("key1", "Hello world", { expires: "1h" });
793
+ await store.set("key1", "Hello world", "1h");
794
794
  console.log(await store.get("key1"));
795
795
  // "Hello world"
796
796
  ```
@@ -862,7 +862,7 @@ import kv from "polystore";
862
862
 
863
863
  const store = kv("https://kv.example.com/");
864
864
 
865
- await store.set("key1", "Hello world", { expires: "1h" });
865
+ await store.set("key1", "Hello world", "1h");
866
866
  console.log(await store.get("key1"));
867
867
  // "Hello world"
868
868
  ```
@@ -882,7 +882,7 @@ import kv from "polystore";
882
882
  // Path is "/Users/me/project/cache.json"
883
883
  const store = kv("file:///Users/me/project/cache.json");
884
884
 
885
- await store.set("key1", "Hello world", { expires: "1h" });
885
+ await store.set("key1", "Hello world", "1h");
886
886
  console.log(await store.get("key1"));
887
887
  // "Hello world"
888
888
  ```
@@ -916,7 +916,7 @@ const store1 = kv(new URL(`file://${process.cwd()}/cache.json`));
916
916
 
917
917
  ```js
918
918
  // GOOD - with polystore
919
- await store.set("key1", { name: "Francisco" }, { expires: "2days" });
919
+ await store.set("key1", { name: "Francisco" }, "2days");
920
920
 
921
921
  // COMPLEX - With native file managing
922
922
  const file = './data/users.json';
@@ -937,7 +937,7 @@ import kv from "polystore";
937
937
 
938
938
  const store = kv("file:///Users/me/project/data/");
939
939
 
940
- await store.set("key1", "Hello world", { expires: "1h" });
940
+ await store.set("key1", "Hello world", "1h");
941
941
  // Writes "./data/key1.json"
942
942
  console.log(await store.get("key1"));
943
943
  // "Hello world"
@@ -975,7 +975,7 @@ const store1 = kv(new URL(`file://${process.cwd()}/cache/`));
975
975
 
976
976
  ```js
977
977
  // GOOD - with polystore
978
- await store.set("key1", { name: "Francisco" }, { expires: "2days" });
978
+ await store.set("key1", { name: "Francisco" }, "2days");
979
979
 
980
980
  // COMPLEX - With native folder
981
981
  const file = './data/user/key1.json';
@@ -995,7 +995,7 @@ export default {
995
995
  async fetch(request, env, ctx) {
996
996
  const store = kv(env.YOUR_KV_NAMESPACE);
997
997
 
998
- await store.set("key1", "Hello world", { expires: "1h" });
998
+ await store.set("key1", "Hello world", "1h");
999
999
  console.log(await store.get("key1"));
1000
1000
  // "Hello world"
1001
1001
 
@@ -1018,7 +1018,7 @@ It expects that you pass the namespace from Cloudflare straight as a `kv()` argu
1018
1018
 
1019
1019
  ```js
1020
1020
  // GOOD - with polystore
1021
- await store.set("user", { name: "Francisco" }, { expires: "2days" });
1021
+ await store.set("user", { name: "Francisco" }, "2days");
1022
1022
 
1023
1023
  // COMPLEX - With native Cloudflare KV
1024
1024
  const serialValue = JSON.stringify({ name: "Francisco" });
@@ -1038,7 +1038,7 @@ import { Level } from "level";
1038
1038
 
1039
1039
  const store = kv(new Level("example", { valueEncoding: "json" }));
1040
1040
 
1041
- await store.set("key1", "Hello world", { expires: "1h" });
1041
+ await store.set("key1", "Hello world", "1h");
1042
1042
  console.log(await store.get("key1"));
1043
1043
  // "Hello world"
1044
1044
  ```
@@ -1055,7 +1055,7 @@ You will need to set the `valueEncoding` to `"json"` for the store to work as ex
1055
1055
 
1056
1056
  ```js
1057
1057
  // GOOD - with polystore
1058
- await store.set("user", { hello: 'world' }, { expires: "2days" });
1058
+ await store.set("user", { hello: 'world' }, "2days");
1059
1059
 
1060
1060
  // With Level:
1061
1061
  ?? // Just not possible
@@ -1071,7 +1071,7 @@ import { Etcd3 } from "etcd3";
1071
1071
 
1072
1072
  const store = kv(new Etcd3());
1073
1073
 
1074
- await store.set("key1", "Hello world", { expires: "1h" });
1074
+ await store.set("key1", "Hello world", "1h");
1075
1075
  console.log(await store.get("key1"));
1076
1076
  // "Hello world"
1077
1077
  ```
@@ -1101,7 +1101,7 @@ await client.connect();
1101
1101
 
1102
1102
  const store = kv(client);
1103
1103
 
1104
- await store.set("key1", "Hello world", { expires: "1h" });
1104
+ await store.set("key1", "Hello world", "1h");
1105
1105
  console.log(await store.get("key1"));
1106
1106
  // "Hello world"
1107
1107
  ```
@@ -1165,7 +1165,7 @@ We unify all of the clients diverse expiration methods into a single, easy one w
1165
1165
  ```js
1166
1166
  // in-memory store
1167
1167
  const store = polystore(new Map());
1168
- await store.set("a", "b", { expires: "1s" });
1168
+ await store.set("a", "b", "1s");
1169
1169
 
1170
1170
  // These checks of course work:
1171
1171
  console.log(await store.keys()); // ['a']
@@ -1234,11 +1234,11 @@ class MyClient {
1234
1234
 
1235
1235
  // Mandatory methods
1236
1236
  get (key): Promise<any>;
1237
- set (key, value, { expires: null|number }): Promise<null>;
1237
+ set (key, value, null|number): Promise<null>;
1238
1238
  iterate(prefix): AyncIterator<[string, any]>
1239
1239
 
1240
1240
  // Optional item methods (for optimization or customization)
1241
- add (prefix, data, { expires: null|number }): Promise<string>;
1241
+ add (prefix, data, null|number): Promise<string>;
1242
1242
  has (key): Promise<boolean>;
1243
1243
  del (key): Promise<null>;
1244
1244
 
@@ -1283,7 +1283,7 @@ client.keys = (prefix) => {
1283
1283
 
1284
1284
  While the signatures are different, you can check each entries on the output of Polystore API to see what is expected for the methods of the client to do, e.g. `.clear()` will remove all of the items that match the prefix (or everything if there's no prefix).
1285
1285
 
1286
- ### Example: Plain Object client
1286
+ ### Plain Object client
1287
1287
 
1288
1288
  This is a good example of how simple a store can be, however do not use it literally since it behaves the same as the already-supported `new Map()`, only use it as the base for your own clients:
1289
1289
 
@@ -1442,3 +1442,134 @@ const store = kv(CloudflareCustom);
1442
1442
  ````
1443
1443
 
1444
1444
  It's lacking few things, so make sure to adapt to your needs, but it worked for my very simple cache needs.
1445
+
1446
+
1447
+ ## Examples
1448
+
1449
+ ### Simple cache
1450
+
1451
+ I've used Polystore in many projects as a simple cache. With `fetch()`, it's fairly easy:
1452
+
1453
+ ```ts
1454
+ async function getProductInfo(id: string) {
1455
+ const data = await store.get(id);
1456
+ if (data) return data;
1457
+
1458
+ const res = await fetch(`https://some-url.com/products/${id}`);
1459
+ const raw = await res.json();
1460
+
1461
+ // Some processing here
1462
+ const clean = raw??;
1463
+
1464
+ await store.set(id, clean, '10days');
1465
+ return clean;
1466
+ }
1467
+ ```
1468
+
1469
+ You can store either the raw data, or the processed data. Depending on whether the processing is sync or async, and the data size of the raw vs processing data, we pick one or the other.
1470
+
1471
+
1472
+
1473
+ ### Dev vs Prod
1474
+
1475
+ With Polystore it's easy to configure your KV solution to use a different client in dev vs production. We've found particularly useful to use an easy-to-debug client in dev like [Folder](#folder) and a high-performance client in production like [Redis](#redis):
1476
+
1477
+ ```ts
1478
+ // store.ts
1479
+ import kv from "polystore";
1480
+ import { createClient } from "redis";
1481
+
1482
+ let store;
1483
+ if (process.env.REDIS_URL) {
1484
+ console.log('kv:redis using Redis for cache data');
1485
+ store = kv(createClient(process.env.REDIS_URL).connect());
1486
+ } else {
1487
+ console.log('kv:folder using a folder for cache data');
1488
+ store = kv(`${process.cwd()}/data/`);
1489
+ }
1490
+
1491
+ export default store;
1492
+ ```
1493
+
1494
+
1495
+ ### Better Auth
1496
+
1497
+ Polystore is directly compatible with Better Auth, so no need for any wrapper. For example, for Redis:
1498
+
1499
+ ```ts
1500
+ import kv from "polystore";
1501
+ import { createClient } from "redis";
1502
+ import { betterAuth } from "better-auth";
1503
+
1504
+ const secondaryStorage = kv(createClient().connect());
1505
+
1506
+ export const auth = betterAuth({
1507
+ // ... other options
1508
+ secondaryStorage
1509
+ });
1510
+ ````
1511
+
1512
+ Compare that with their official documentation for using Redis:
1513
+
1514
+ ```ts
1515
+ import { createClient } from "redis";
1516
+ import { betterAuth } from "better-auth";
1517
+
1518
+ const redis = createClient();
1519
+ await redis.connect();
1520
+
1521
+ export const auth = betterAuth({
1522
+ // ... other options
1523
+ secondaryStorage: {
1524
+ get: async (key) => {
1525
+ return await redis.get(key);
1526
+ },
1527
+ set: async (key, value, ttl) => {
1528
+ if (ttl) await redis.set(key, value, { EX: ttl });
1529
+ // or for ioredis:
1530
+ // if (ttl) await redis.set(key, value, 'EX', ttl)
1531
+ else await redis.set(key, value);
1532
+ },
1533
+ delete: async (key) => {
1534
+ await redis.del(key);
1535
+ }
1536
+ }
1537
+ });
1538
+ ```
1539
+
1540
+ ### Express.js
1541
+
1542
+ ```ts
1543
+ import kv from 'polystore/express';
1544
+
1545
+ // This is a special, Express-only store
1546
+ const store = kv();
1547
+
1548
+ app.use(session({ store, ... }));
1549
+ ````
1550
+
1551
+ ### Hono
1552
+
1553
+ ### Elysia?
1554
+
1555
+ ### Axios Cache Interceptor
1556
+
1557
+ ### @server/next
1558
+
1559
+ > [!info]
1560
+ > @server/next is still experimental, but it's the main reason I created Polystore and so I wanted to document it as well
1561
+
1562
+ Server.js supports Polystore directly:
1563
+
1564
+ ```ts
1565
+ import kv from "polystore";
1566
+ import server from "../../";
1567
+
1568
+ const session = kv(new Map());
1569
+
1570
+ export default server({ session }).get("/", (ctx) => {
1571
+ if (!ctx.session.counter) ctx.session.counter = 0;
1572
+ ctx.session.counter++;
1573
+ return `User visited ${ctx.session.counter} times`;
1574
+ });
1575
+ ```