polystore 0.16.0 → 0.16.1

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 +161 -0
  2. package/index.js +88 -38
  3. package/package.json +1 -1
  4. package/readme.md +74 -0
package/index.d.ts CHANGED
@@ -52,28 +52,189 @@ declare class Store<TDefault extends Serializable = Serializable> {
52
52
  promise: Promise<Client> | null;
53
53
  client: Client;
54
54
  constructor(clientPromise?: any);
55
+ /**
56
+ * Save the data on an autogenerated key, can add expiration as well:
57
+ *
58
+ * ```js
59
+ * const key1 = await store.add("value1");
60
+ * const key2 = await store.add({ hello: "world" });
61
+ * const key3 = await store.add("value3", { expires: "1h" });
62
+ * ```
63
+ *
64
+ * **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
65
+ */
55
66
  add(value: TDefault, options?: Options): Promise<string>;
56
67
  add<T extends TDefault>(value: T, options?: Options): Promise<string>;
68
+ /**
69
+ * Save the data on the given key, can add expiration as well:
70
+ *
71
+ * ```js
72
+ * const key = await store.set("key1", "value1");
73
+ * await store.set("key2", { hello: "world" });
74
+ * await store.set("key3", "value3", { expires: "1h" });
75
+ * ```
76
+ *
77
+ * **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
78
+ */
57
79
  set(key: string, value: TDefault, options?: Options): Promise<string>;
58
80
  set<T extends TDefault>(key: string, value: T, options?: Options): Promise<string>;
81
+ /**
82
+ * Read a single value from the KV store:
83
+ *
84
+ * ```js
85
+ * const value1 = await store.get("key1");
86
+ * // null (doesn't exist or has expired)
87
+ * const value2 = await store.get("key2");
88
+ * // "value2"
89
+ * const value3 = await store.get("key3");
90
+ * // { hello: "world" }
91
+ * ```
92
+ *
93
+ * **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
94
+ */
59
95
  get(key: string): Promise<TDefault | null>;
60
96
  get<T extends TDefault>(key: string): Promise<T | null>;
97
+ /**
98
+ * Check whether a key exists or not:
99
+ *
100
+ * ```js
101
+ * if (await store.has("key1")) { ... }
102
+ * ```
103
+ *
104
+ * If you are going to use the value, it's better to just read it:
105
+ *
106
+ * ```js
107
+ * const val = await store.get("key1");
108
+ * if (val) { ... }
109
+ * ```
110
+ *
111
+ * **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
112
+ */
61
113
  has(key: string): Promise<boolean>;
114
+ /**
115
+ * Remove a single key and its value from the store:
116
+ *
117
+ * ```js
118
+ * const key = await store.del("key1");
119
+ * ```
120
+ *
121
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
122
+ */
62
123
  del(key: string): Promise<string>;
124
+ /**
125
+ * An iterator that goes through all of the key:value pairs in the client
126
+ *
127
+ * ```js
128
+ * for await (const [key, value] of store) {
129
+ * console.log(key, value);
130
+ * }
131
+ * ```
132
+ *
133
+ * **[→ Full Iterator Docs](https://polystore.dev/documentation#iterator)**
134
+ */
63
135
  [Symbol.asyncIterator](): AsyncGenerator<[string, TDefault], void, unknown>;
64
136
  [Symbol.asyncIterator]<T extends TDefault>(): AsyncGenerator<[
65
137
  string,
66
138
  T
67
139
  ], void, unknown>;
140
+ /**
141
+ * Return an array of the entries, in the [key, value] format:
142
+ *
143
+ * ```js
144
+ * const entries = await store.entries();
145
+ * // [["key1", "value1"], ["key2", { hello: "world" }], ...]
146
+ *
147
+ * // To limit it to a given prefix, use `.prefix()`:
148
+ * const sessions = await store.prefix("session:").entries();
149
+ * ```
150
+ *
151
+ * **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
152
+ */
68
153
  entries(): Promise<[string, TDefault][]>;
69
154
  entries<T extends TDefault>(): Promise<[string, T][]>;
155
+ /**
156
+ * Return an array of the keys in the store:
157
+ *
158
+ * ```js
159
+ * const keys = await store.keys();
160
+ * // ["key1", "key2", ...]
161
+ *
162
+ * // To limit it to a given prefix, use `.prefix()`:
163
+ * const sessions = await store.prefix("session:").keys();
164
+ * ```
165
+ *
166
+ * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
167
+ */
70
168
  keys(): Promise<string[]>;
169
+ /**
170
+ * Return an array of the values in the store:
171
+ *
172
+ * ```js
173
+ * const values = await store.values();
174
+ * // ["value1", { hello: "world" }, ...]
175
+ *
176
+ * // To limit it to a given prefix, use `.prefix()`:
177
+ * const sessions = await store.prefix("session:").values();
178
+ * ```
179
+ *
180
+ * **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
181
+ */
71
182
  values(): Promise<TDefault[]>;
72
183
  values<T extends TDefault>(): Promise<T[]>;
184
+ /**
185
+ * Return an object with the keys:values in the store:
186
+ *
187
+ * ```js
188
+ * const obj = await store.all();
189
+ * // { key1: "value1", key2: { hello: "world" }, ... }
190
+ *
191
+ * // To limit it to a given prefix, use `.prefix()`:
192
+ * const sessions = await store.prefix("session:").all();
193
+ * ```
194
+ *
195
+ * **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
196
+ */
73
197
  all(): Promise<Record<string, TDefault>>;
74
198
  all<T extends TDefault>(): Promise<Record<string, T>>;
199
+ /**
200
+ * Delete all of the records of the store:
201
+ *
202
+ * ```js
203
+ * await store.clear();
204
+ * ```
205
+ *
206
+ * It's useful for cache invalidation, clearing the data, and testing.
207
+ *
208
+ * **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
209
+ */
75
210
  clear(): Promise<void>;
211
+ /**
212
+ * Create a substore where all the keys are stored with
213
+ * the given prefix:
214
+ *
215
+ * ```js
216
+ * const session = store.prefix("session:");
217
+ * await session.set("key1", "value1");
218
+ * console.log(await session.entries()); // session.
219
+ * // [["key1", "value1"]]
220
+ * console.log(await store.entries()); // store.
221
+ * // [["session:key1", "value1"]]
222
+ * ```
223
+ *
224
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
225
+ */
76
226
  prefix(prefix?: string): Store<TDefault>;
227
+ /**
228
+ * Stop the connection to the store, if any:
229
+ *
230
+ * ```js
231
+ * await session.set("key1", "value1");
232
+ * await store.close();
233
+ * await session.set("key2", "value2"); // error
234
+ * ```
235
+ *
236
+ * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
237
+ */
77
238
  close(): Promise<void>;
78
239
  }
79
240
  declare function createStore(): Store<Serializable>;
package/index.js CHANGED
@@ -431,22 +431,8 @@ var SQLite = class extends Client {
431
431
  // expirations much easier, so it's really "somewhere in between"
432
432
  EXPIRES = true;
433
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
434
  get = (id) => {
446
- const row = this.client.get(
447
- `SELECT value, expires_at FROM kv WHERE id = ?`,
448
- id
449
- );
435
+ const row = this.client.prepare(`SELECT value, expires_at FROM kv WHERE id = ?`).get(id);
450
436
  if (!row) return null;
451
437
  if (row.expires_at && row.expires_at < Date.now()) {
452
438
  this.del(id);
@@ -457,21 +443,15 @@ var SQLite = class extends Client {
457
443
  set = (id, data, { expires } = {}) => {
458
444
  const value = this.encode(data);
459
445
  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
- );
446
+ this.client.prepare(
447
+ `INSERT INTO kv (id, value, expires_at) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at`
448
+ ).run(id, value, expires_at);
469
449
  };
470
- del = (id) => {
471
- this.client.run(`DELETE FROM kv WHERE id = ?`, id);
450
+ del = async (id) => {
451
+ await this.client.prepare(`DELETE FROM kv WHERE id = ?`).run(id);
472
452
  };
473
453
  has = (id) => {
474
- const row = this.client.get(`SELECT expires_at FROM kv WHERE id = ?`, id);
454
+ const row = this.client.prepare(`SELECT expires_at FROM kv WHERE id = ?`).get(id);
475
455
  if (!row) return false;
476
456
  if (row.expires_at && row.expires_at < Date.now()) {
477
457
  this.del(id);
@@ -481,29 +461,24 @@ var SQLite = class extends Client {
481
461
  };
482
462
  *iterate(prefix = "") {
483
463
  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 ?" : ""}
464
+ const sql = `SELECT id, value FROM kv WHERE (expires_at IS NULL OR expires_at > ?) ${prefix ? "AND id LIKE ?" : ""}
488
465
  `;
489
466
  const params = prefix ? [Date.now(), `${prefix}%`] : [Date.now()];
490
- for (const row of this.client.all(sql, ...params)) {
467
+ for (const row of this.client.prepare(sql).all(...params)) {
491
468
  yield [row.id, this.decode(row.value)];
492
469
  }
493
470
  }
494
471
  keys = (prefix = "") => {
495
472
  this.#clearExpired();
496
- const sql = `
497
- SELECT id FROM kv
498
- WHERE (expires_at IS NULL OR expires_at > ?)
499
- ${prefix ? "AND id LIKE ?" : ""}
473
+ const sql = `SELECT id FROM kv WHERE (expires_at IS NULL OR expires_at > ?)
474
+ ${prefix ? "AND id LIKE ?" : ""}
500
475
  `;
501
476
  const params = prefix ? [Date.now(), `${prefix}%`] : [Date.now()];
502
- const rows = this.client.all(sql, ...params);
477
+ const rows = this.client.prepare(sql).all(...params);
503
478
  return rows.map((r) => r.id);
504
479
  };
505
480
  #clearExpired = () => {
506
- this.client.run(`DELETE FROM kv WHERE expires_at < ?`, Date.now());
481
+ this.client.prepare(`DELETE FROM kv WHERE expires_at < ?`).run(Date.now());
507
482
  };
508
483
  clearAll = () => {
509
484
  this.client.run(`DELETE FROM kv`);
@@ -686,6 +661,22 @@ var Store = class _Store {
686
661
  return data.value;
687
662
  }
688
663
  }
664
+ /**
665
+ * Check whether a key exists or not:
666
+ *
667
+ * ```js
668
+ * if (await store.has("key1")) { ... }
669
+ * ```
670
+ *
671
+ * If you are going to use the value, it's better to just read it:
672
+ *
673
+ * ```js
674
+ * const val = await store.get("key1");
675
+ * if (val) { ... }
676
+ * ```
677
+ *
678
+ * **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
679
+ */
689
680
  async has(key) {
690
681
  await this.promise;
691
682
  const id = this.PREFIX + key;
@@ -694,6 +685,15 @@ var Store = class _Store {
694
685
  }
695
686
  return await this.get(key) !== null;
696
687
  }
688
+ /**
689
+ * Remove a single key and its value from the store:
690
+ *
691
+ * ```js
692
+ * const key = await store.del("key1");
693
+ * ```
694
+ *
695
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
696
+ */
697
697
  async del(key) {
698
698
  await this.promise;
699
699
  const id = this.PREFIX + key;
@@ -752,6 +752,19 @@ var Store = class _Store {
752
752
  return list;
753
753
  }
754
754
  }
755
+ /**
756
+ * Return an array of the keys in the store:
757
+ *
758
+ * ```js
759
+ * const keys = await store.keys();
760
+ * // ["key1", "key2", ...]
761
+ *
762
+ * // To limit it to a given prefix, use `.prefix()`:
763
+ * const sessions = await store.prefix("session:").keys();
764
+ * ```
765
+ *
766
+ * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
767
+ */
755
768
  async keys() {
756
769
  await this.promise;
757
770
  if (this.client.keys) {
@@ -776,6 +789,17 @@ var Store = class _Store {
776
789
  const entries = await this.entries();
777
790
  return Object.fromEntries(entries);
778
791
  }
792
+ /**
793
+ * Delete all of the records of the store:
794
+ *
795
+ * ```js
796
+ * await store.clear();
797
+ * ```
798
+ *
799
+ * It's useful for cache invalidation, clearing the data, and testing.
800
+ *
801
+ * **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
802
+ */
779
803
  async clear() {
780
804
  await this.promise;
781
805
  if (!this.PREFIX && this.client.clearAll) {
@@ -787,6 +811,21 @@ var Store = class _Store {
787
811
  const keys = await this.keys();
788
812
  await Promise.all(keys.map((key) => this.del(key)));
789
813
  }
814
+ /**
815
+ * Create a substore where all the keys are stored with
816
+ * the given prefix:
817
+ *
818
+ * ```js
819
+ * const session = store.prefix("session:");
820
+ * await session.set("key1", "value1");
821
+ * console.log(await session.entries()); // session.
822
+ * // [["key1", "value1"]]
823
+ * console.log(await store.entries()); // store.
824
+ * // [["session:key1", "value1"]]
825
+ * ```
826
+ *
827
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
828
+ */
790
829
  prefix(prefix = "") {
791
830
  const store = new _Store(
792
831
  Promise.resolve(this.promise).then(() => this.client)
@@ -794,6 +833,17 @@ var Store = class _Store {
794
833
  store.PREFIX = this.PREFIX + prefix;
795
834
  return store;
796
835
  }
836
+ /**
837
+ * Stop the connection to the store, if any:
838
+ *
839
+ * ```js
840
+ * await session.set("key1", "value1");
841
+ * await store.close();
842
+ * await session.set("key2", "value2"); // error
843
+ * ```
844
+ *
845
+ * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
846
+ */
797
847
  async close() {
798
848
  await this.promise;
799
849
  if (this.client.close) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polystore",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
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",
package/readme.md CHANGED
@@ -910,6 +910,80 @@ You'll need to be running the etcd store for this to work as expected.
910
910
  </ul>
911
911
  </details>
912
912
 
913
+
914
+ ## SQLite
915
+
916
+ Supports both **`bun:sqlite`** and **`better-sqlite3`** directly. Pass an already-opened database instance to `kv()` and Polystore will use the `kv` table to store keys, values, and expirations:
917
+
918
+ > [!IMPORTANT]
919
+ > The table `kv` must already exist in the Database
920
+
921
+ ```js
922
+ import kv from "polystore";
923
+ import Database from "better-sqlite3";
924
+ // Or: import Database from "bun:sqlite";
925
+
926
+ const db = new Database("data.db")
927
+ const store = kv(db);
928
+
929
+ await store.set("key1", "Hello world", { expires: "1h" });
930
+ console.log(await store.get("key1"));
931
+ // "Hello world"
932
+ ```
933
+
934
+ ### SQLite schema
935
+
936
+ This is the required schema:
937
+
938
+ ```sql
939
+ CREATE TABLE kv (
940
+ id TEXT PRIMARY KEY,
941
+ value TEXT,
942
+ expires_at INTEGER
943
+ );
944
+ ```
945
+
946
+ You can create these with failsafes to make it much easier to initialize:
947
+
948
+ ```js
949
+ db.run(`
950
+ CREATE TABLE IF NOT EXISTS kv (
951
+ id TEXT PRIMARY KEY,
952
+ value TEXT NOT NULL,
953
+ expires_at INTEGER
954
+ )
955
+ `);
956
+ db.run(
957
+ `CREATE INDEX IF NOT EXISTS idx_kv_expires_at ON kv (expires_at)`,
958
+ );
959
+ ````
960
+
961
+ ### SQLite expirations
962
+
963
+ If `expires` is provided, Polystore will convert it to a timestamp and persist it in `expires_at`. We handle reading/writing rows and expiration checks.
964
+
965
+ However, these are not auto-evicted since SQLite doesn't have a native expiration. To avoid having stale data that is not used anymore, it's recommended you set a periodic check and clear expired records manually:
966
+
967
+ ```js
968
+ // Clear expired keys once every 10 minutes
969
+ setInterval(() => {
970
+ db.prepare(`DELETE FROM kv WHERE expires_at < ?`).run(Date.now());
971
+ }, 10 * 60 * 1000);
972
+ ````
973
+
974
+ Note that Polystore is self-reliant and won't have any problem even if you don't set that script, it will never render a stale record. It's just for both your convenience and privacy reasons.
975
+
976
+ <details>
977
+ <summary>Why use polystore with <code>SQLite</code>?</summary>
978
+ <p>These benefits apply when wrapping a SQLite DB with polystore:</p>
979
+ <ul>
980
+ <li><strong>Intuitive expirations</strong>: specify expiration times like <code>10min</code> or <code>2h</code> without manual date handling.</li>
981
+ <li><strong>Substores</strong>: use prefixes to isolate sets of keys cleanly.</li>
982
+ <li><strong>Simple persistence</strong>: full on-disk durability with a minimal driver.</li>
983
+ </ul>
984
+ </details>
985
+
986
+
913
987
  ### Postgres
914
988
 
915
989
  Use PostgreSQL with the `pg` library as a key-value store: