polystore 0.21.3 → 0.22.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.d.ts +11 -11
- package/index.js +189 -189
- package/package.json +61 -53
- package/readme.md +92 -58
- package/src/plugins/better-auth/index.d.ts +18 -0
- package/src/plugins/better-auth/index.js +27 -0
- package/src/plugins/express/index.d.ts +20 -0
- package/src/{integrations/express.js → plugins/express/index.js} +3 -3
- package/src/plugins/hono-sessions/index.d.ts +15 -0
- package/src/{integrations/hono-sessions.js → plugins/hono-sessions/index.js} +3 -3
- package/src/integrations/express.d.ts +0 -307
- package/src/integrations/hono-sessions.d.ts +0 -299
package/package.json
CHANGED
|
@@ -1,41 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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
|
-
"repository": "
|
|
7
|
-
"bugs": "https://github.com/franciscop/polystore/issues",
|
|
8
|
-
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
6
|
+
"repository": "github:franciscop/polystore",
|
|
9
7
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"./express": {
|
|
20
|
-
"types": "./src/integrations/express.d.ts",
|
|
21
|
-
"import": "./src/integrations/express.js"
|
|
22
|
-
},
|
|
23
|
-
"./hono-sessions": {
|
|
24
|
-
"types": "./src/integrations/hono-sessions.d.ts",
|
|
25
|
-
"import": "./src/integrations/hono-sessions.js"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"files": [
|
|
29
|
-
"index.js",
|
|
30
|
-
"index.d.ts",
|
|
31
|
-
"src/integrations/express.js",
|
|
32
|
-
"src/integrations/express.d.ts",
|
|
33
|
-
"src/integrations/hono-sessions.js",
|
|
34
|
-
"src/integrations/hono-sessions.d.ts"
|
|
8
|
+
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"kv",
|
|
12
|
+
"store",
|
|
13
|
+
"polystore",
|
|
14
|
+
"key-value",
|
|
15
|
+
"key",
|
|
16
|
+
"value"
|
|
35
17
|
],
|
|
36
18
|
"scripts": {
|
|
37
|
-
"
|
|
38
|
-
"build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24 && bunx tsup src/
|
|
19
|
+
"size": "echo $(gzip -c index.js | wc -c) bytes",
|
|
20
|
+
"build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24 && bunx tsup src/plugins/express/index.ts --format esm --dts --out-dir src/plugins/express --target node24 --external polystore --external express-session && bunx tsup src/plugins/hono-sessions/index.ts --format esm --dts --out-dir src/plugins/hono-sessions --target node24 --external polystore --external hono-sessions && bunx tsup src/plugins/better-auth/index.ts --format esm --dts --out-dir src/plugins/better-auth --target node24 --external polystore",
|
|
39
21
|
"lint": "tsc",
|
|
40
22
|
"start": "bun test --watch",
|
|
41
23
|
"service:db": "etcd",
|
|
@@ -44,22 +26,58 @@
|
|
|
44
26
|
"service:server": "bun ./src/server.ts",
|
|
45
27
|
"services": "concurrently \"npm run service:db\" \"npm run service:redis\" \"npm run service:postgres\" \"npm run service:server\"",
|
|
46
28
|
"test": "bun run test:bun && bun run test:jest",
|
|
47
|
-
"test:bun": "bun test ./test/index.test.ts ./src/
|
|
29
|
+
"test:bun": "bun test ./test/index.test.ts ./src/plugins/",
|
|
48
30
|
"test:jest": "jest ./test/index.test.ts --detectOpenHandles --forceExit"
|
|
49
31
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"store",
|
|
53
|
-
"polystore",
|
|
54
|
-
"key-value",
|
|
55
|
-
"key",
|
|
56
|
-
"value"
|
|
32
|
+
"workspaces": [
|
|
33
|
+
"examples/*"
|
|
57
34
|
],
|
|
58
|
-
"
|
|
35
|
+
"main": "index.js",
|
|
36
|
+
"type": "module",
|
|
37
|
+
"types": "index.d.ts",
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"index.d.ts",
|
|
41
|
+
"src/plugins/express/index.js",
|
|
42
|
+
"src/plugins/express/index.d.ts",
|
|
43
|
+
"src/plugins/hono-sessions/index.js",
|
|
44
|
+
"src/plugins/hono-sessions/index.d.ts",
|
|
45
|
+
"src/plugins/better-auth/index.js",
|
|
46
|
+
"src/plugins/better-auth/index.d.ts"
|
|
47
|
+
],
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./index.d.ts",
|
|
51
|
+
"import": "./index.js"
|
|
52
|
+
},
|
|
53
|
+
"./express": {
|
|
54
|
+
"types": "./src/plugins/express/index.d.ts",
|
|
55
|
+
"import": "./src/plugins/express/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./hono-sessions": {
|
|
58
|
+
"types": "./src/plugins/hono-sessions/index.d.ts",
|
|
59
|
+
"import": "./src/plugins/hono-sessions/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./better-auth": {
|
|
62
|
+
"types": "./src/plugins/better-auth/index.d.ts",
|
|
63
|
+
"import": "./src/plugins/better-auth/index.js"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"documentation.page": {
|
|
67
|
+
"title": "🏬 Polystore - A universal library for standardizing any KV-store",
|
|
68
|
+
"home": "assets/home.html",
|
|
69
|
+
"homepage": "https://polystore.dev/",
|
|
70
|
+
"menu": {
|
|
71
|
+
"Documentation": "/documentation",
|
|
72
|
+
"Issues": "https://github.com/franciscop/polystore/issues",
|
|
73
|
+
"Get help": "https://superpeer.com/francisco/-/javascript-and-react-help",
|
|
74
|
+
"Github": "https://github.com/franciscop/polystore"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
59
77
|
"devDependencies": {
|
|
60
78
|
"@deno/kv": "^0.8.1",
|
|
61
79
|
"@types/better-sqlite3": "^7.6.13",
|
|
62
|
-
"@types/bun": "^1.3.
|
|
80
|
+
"@types/bun": "^1.3.0",
|
|
63
81
|
"@types/express": "^5.0.6",
|
|
64
82
|
"@types/express-session": "^1.18.2",
|
|
65
83
|
"@types/jest": "^30.0.0",
|
|
@@ -86,17 +104,7 @@
|
|
|
86
104
|
"supertest": "^7.2.2",
|
|
87
105
|
"ts-jest": "^29.4.6",
|
|
88
106
|
"tsup": "^8.5.1",
|
|
89
|
-
"typescript": "^
|
|
107
|
+
"typescript": "^6.0.2"
|
|
90
108
|
},
|
|
91
|
-
"
|
|
92
|
-
"title": "🏬 Polystore - A universal library for standardizing any KV-store",
|
|
93
|
-
"home": "assets/home.html",
|
|
94
|
-
"homepage": "https://polystore.dev/",
|
|
95
|
-
"menu": {
|
|
96
|
-
"Documentation": "/documentation",
|
|
97
|
-
"Issues": "https://github.com/franciscop/polystore/issues",
|
|
98
|
-
"Get help": "https://superpeer.com/francisco/-/javascript-and-react-help",
|
|
99
|
-
"Github": "https://github.com/franciscop/polystore"
|
|
100
|
-
}
|
|
101
|
-
}
|
|
109
|
+
"sideEffects": false
|
|
102
110
|
}
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Polystore [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/actions) [](https://github.com/franciscop/polystore/blob/master/index.js) [](https://github.com/franciscop/polystore/blob/master/package.json)
|
|
2
2
|
|
|
3
|
-
A key-value library to unify the API of [many
|
|
3
|
+
A key-value library to unify the API of [many adapters](#adapters): localStorage, Redis, FileSystem, SQLite, etc.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
import kv from "polystore";
|
|
@@ -8,7 +8,7 @@ const store1 = kv(new Map()); // in-memory
|
|
|
8
8
|
const store2 = kv(localStorage); // Persist in the browser
|
|
9
9
|
const store3 = kv(redisClient); // Use a Redis client for backend persistence
|
|
10
10
|
const store4 = kv(yourOwnStore); // Create a store based on your code
|
|
11
|
-
// Many more
|
|
11
|
+
// Many more adapters available
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
These are all the methods of the [API](#api) (they are all `async`):
|
|
@@ -29,7 +29,7 @@ These are all the methods of the [API](#api) (they are all `async`):
|
|
|
29
29
|
- [`.prune()`](#prune): delete only the **expired** data from the store.
|
|
30
30
|
- [`.close()`](#close): (only _some_ stores) ends the connection to the store.
|
|
31
31
|
|
|
32
|
-
Available
|
|
32
|
+
Available adapters for the KV store:
|
|
33
33
|
|
|
34
34
|
- [**Memory** `new Map()`](#memory) (fe+be): an in-memory API to keep your KV store.
|
|
35
35
|
- [**Local Storage** `localStorage`](#local-storage) (fe): persist the data in the browser's localStorage.
|
|
@@ -42,7 +42,7 @@ Available clients for the KV store:
|
|
|
42
42
|
- [**File** `"file:///[...].json"`](#file) (be): store the data in a single JSON file in your FS.
|
|
43
43
|
- [**Folder** `"file:///[...]/"`](#folder) (be): store each key in a folder as json files.
|
|
44
44
|
- [**Cloudflare KV** `env.KV_NAMESPACE`](#cloudflare-kv) (be): use Cloudflare's KV store.
|
|
45
|
-
- [**Postgres** `
|
|
45
|
+
- [**Postgres** `pg`](#postgres) (be): use PostgreSQL with the pg library.
|
|
46
46
|
- [**Level** `new Level('example', { valueEncoding: 'json' })`](#level) (fe+be): support the whole Level ecosystem.
|
|
47
47
|
- [**Etcd** `new Etcd3()`](#etcd) (be): the Microsoft's high performance KV store.
|
|
48
48
|
- [**_Custom_** `{}`](#creating-a-store) (fe+be): create your own store with just 3 methods!
|
|
@@ -61,7 +61,7 @@ MyApi({ cache: env.KV_NAMESPACE }); // OR
|
|
|
61
61
|
|
|
62
62
|
## Getting started
|
|
63
63
|
|
|
64
|
-
First, install `polystore` and whatever [supported
|
|
64
|
+
First, install `polystore` and whatever [supported adapter](#adapters) that you prefer. Let's see Redis as an example here:
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
67
|
npm i polystore redis
|
|
@@ -93,7 +93,7 @@ await store.del(key);
|
|
|
93
93
|
|
|
94
94
|
## API
|
|
95
95
|
|
|
96
|
-
The base `kv()` initialization is shared across
|
|
96
|
+
The base `kv()` initialization is shared across adapters ([see full adapters list](#adapters)); an argument that receives the adapter or a string representing the adapter and then the options:
|
|
97
97
|
|
|
98
98
|
```js
|
|
99
99
|
import kv from "polystore";
|
|
@@ -104,9 +104,6 @@ const store = kv(MyClientInstance, { expires: null, prefix: "" });
|
|
|
104
104
|
// use the store
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
> [!IMPORTANT]
|
|
108
|
-
> The library delivers excellent performance for item-level operations (GET, SET, ADD, HAS, DEL). For other methods or detailed guidance, check the performance considerations and consult your specific client’s documentation.
|
|
109
|
-
|
|
110
107
|
You can enforce **types** for the values either at store creation or at the method level:
|
|
111
108
|
|
|
112
109
|
```ts
|
|
@@ -129,14 +126,14 @@ store.set<number>("abc", "hello"); // FAILS
|
|
|
129
126
|
|
|
130
127
|
Store values must be JSON-like data. The Serializable type represents values composed of `string`, `number`, `boolean`, `null`, and `arrays` and plain `objects` whose values are serializable. Class instances or non-plain objects will lose their prototypes and methods when stored.
|
|
131
128
|
|
|
132
|
-
These are the exported types, `
|
|
129
|
+
These are the exported types, `Adapter`, `Serializable`, `Store` and `Options`:
|
|
133
130
|
|
|
134
131
|
```ts
|
|
135
132
|
import kv from "polystore";
|
|
136
|
-
import type {
|
|
133
|
+
import type { Adapter, Serializable, Store, Options } from "polystore";
|
|
137
134
|
|
|
138
|
-
const
|
|
139
|
-
const store: Store = kv(
|
|
135
|
+
const adapter: Adapter = ...; // See #creating-a-store
|
|
136
|
+
const store: Store = kv(adapter, opts as Options);
|
|
140
137
|
const key = await store.set('hello', 'b', opts as Options)
|
|
141
138
|
const value: Serializable = await store.get('hello');
|
|
142
139
|
```
|
|
@@ -308,9 +305,9 @@ const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60 });
|
|
|
308
305
|
|
|
309
306
|
The value and options are similar to [`.set()`](#set), except for the lack of the first argument, since `.add()` automatically generates the key.
|
|
310
307
|
|
|
311
|
-
The default key is 24 AlphaNumeric characters (upper+lower case), however this can change if you are using a `.prefix()` or some
|
|
308
|
+
The default key is 24 AlphaNumeric characters (upper+lower case), however this can change if you are using a `.prefix()` or some adapters might generate it differently (only custom adapters can do that right now).
|
|
312
309
|
|
|
313
|
-
Some
|
|
310
|
+
Some adapters will generate their own key, e.g. you can connect to a SQL client that does auto-incremental integers (always casted to `string` since a `key` is always a string in Polystore).
|
|
314
311
|
|
|
315
312
|
<details>
|
|
316
313
|
<summary>Key Generation details</summary>
|
|
@@ -553,7 +550,7 @@ This operation is O(n) and should typically be run in a scheduled job or mainten
|
|
|
553
550
|
|
|
554
551
|
### .close()
|
|
555
552
|
|
|
556
|
-
Close the connection (if any) from the
|
|
553
|
+
Close the connection (if any) from the adapter:
|
|
557
554
|
|
|
558
555
|
```js
|
|
559
556
|
await store.close();
|
|
@@ -561,7 +558,7 @@ await store.close();
|
|
|
561
558
|
|
|
562
559
|
### .prefix()
|
|
563
560
|
|
|
564
|
-
Creates **a new instance** of the Store, _with the same
|
|
561
|
+
Creates **a new instance** of the Store, _with the same adapter_ as you provided, but now any key you read, write, etc. will be passed with the given prefix to the adapter. You only write `.prefix()` once and then don't need to worry about any prefix for any method anymore, it's all automatic. You don't need to await for it:
|
|
565
562
|
|
|
566
563
|
```js
|
|
567
564
|
const store = kv(new Map());
|
|
@@ -586,7 +583,7 @@ for await (const [key, value] of session) {
|
|
|
586
583
|
}
|
|
587
584
|
```
|
|
588
585
|
|
|
589
|
-
Different
|
|
586
|
+
Different adapters have better/worse support for substores, and in some cases some operations might be slower. This should be documented on each adapter's documentation (see below). As an alternative, you can always create two different stores instead of a substore:
|
|
590
587
|
|
|
591
588
|
```js
|
|
592
589
|
// Two in-memory stores
|
|
@@ -625,15 +622,15 @@ You can combine it with .prefix():
|
|
|
625
622
|
const sessions = store.prefix("session:").expires("1day");
|
|
626
623
|
```
|
|
627
624
|
|
|
628
|
-
##
|
|
625
|
+
## Adapters
|
|
629
626
|
|
|
630
|
-
|
|
627
|
+
An adapter is the library that manages the low-level store operations. For example, the Redis adapter, or the browser's `localStorage` API. In some exceptions it's just a string and we do a bit more work on Polystore, like with `"cookie"` or `"file:///users/me/data.json"`.
|
|
631
628
|
|
|
632
629
|
Polystore provides a unified API you can use `Promises`, `expires` and `.prefix()` even with those stores that do not support these operations natively.
|
|
633
630
|
|
|
634
631
|
Quick overview:
|
|
635
632
|
|
|
636
|
-
|
|
|
633
|
+
| Adapter | Runtime | Persistence | Native expiration | Notes |
|
|
637
634
|
|---|---|---|---|---|
|
|
638
635
|
| [Memory](#memory) | Node.js + Browser | ❌ | ❌ | Great for tests and ephemeral caches |
|
|
639
636
|
| [Local Storage](#local-storage) | Browser | ✅ | ❌ | Persistent browser storage |
|
|
@@ -650,7 +647,7 @@ Quick overview:
|
|
|
650
647
|
| [Etcd](#etcd) | Node.js | ✅ | ✅ | Distributed KV |
|
|
651
648
|
| [Postgres](#postgres) | Node.js | ✅ | ❌ | Table-backed KV |
|
|
652
649
|
|
|
653
|
-
While you can keep a reference to the
|
|
650
|
+
While you can keep a reference to the underlying adapter and access it directly, we strongly recommend to only access it through `polystore`, since we might add custom serialization and extra properties for e.g. expiration time:
|
|
654
651
|
|
|
655
652
|
```js
|
|
656
653
|
const map = new Map();
|
|
@@ -1225,12 +1222,14 @@ This keeps a single table while preserving namespace-style grouping through pref
|
|
|
1225
1222
|
|
|
1226
1223
|
Please see the [creating a store](#creating-a-store) section for all the details!
|
|
1227
1224
|
|
|
1228
|
-
##
|
|
1225
|
+
## Plugins
|
|
1229
1226
|
|
|
1230
|
-
Polystore has
|
|
1227
|
+
Polystore has easy plugins to drop into popular frameworks and auth libraries. Each one has a runnable example in the [`examples/`](https://github.com/franciscop/polystore/tree/master/examples) folder.
|
|
1231
1228
|
|
|
1232
1229
|
### Express
|
|
1233
1230
|
|
|
1231
|
+
> [Full example →](https://github.com/franciscop/polystore/tree/master/examples/express)
|
|
1232
|
+
|
|
1234
1233
|
Use any Polystore-compatible store as an [express-session](https://github.com/expressjs/session) store:
|
|
1235
1234
|
|
|
1236
1235
|
```js
|
|
@@ -1243,7 +1242,7 @@ app.use(session({
|
|
|
1243
1242
|
}));
|
|
1244
1243
|
```
|
|
1245
1244
|
|
|
1246
|
-
By default it uses an in-memory `Map`, which is fine for development. For production, pass any Polystore
|
|
1245
|
+
By default it uses an in-memory `Map`, which is fine for development. For production, pass any Polystore adapter:
|
|
1247
1246
|
|
|
1248
1247
|
```js
|
|
1249
1248
|
import { createClient } from "redis";
|
|
@@ -1255,7 +1254,7 @@ const store = expressStore(createClient().connect());
|
|
|
1255
1254
|
app.use(session({ secret: "my-secret", store }));
|
|
1256
1255
|
```
|
|
1257
1256
|
|
|
1258
|
-
Any
|
|
1257
|
+
Any adapter works — Redis, Postgres, SQLite, file-based, etc. Session TTL is read automatically from `cookie.originalMaxAge` so you don't need to configure it separately.
|
|
1259
1258
|
|
|
1260
1259
|
Use `.prefix()` to namespace sessions, for example in a multi-tenant app:
|
|
1261
1260
|
|
|
@@ -1270,30 +1269,35 @@ app.use((req, res, next) => {
|
|
|
1270
1269
|
|
|
1271
1270
|
### Hono Sessions
|
|
1272
1271
|
|
|
1272
|
+
> [Full example →](https://github.com/franciscop/polystore/tree/master/examples/hono-sessions)
|
|
1273
|
+
|
|
1273
1274
|
Use any Polystore-compatible store as a [hono-sessions](https://github.com/jcs224/hono_sessions) store:
|
|
1274
1275
|
|
|
1275
1276
|
```js
|
|
1276
1277
|
import { Hono } from "hono";
|
|
1277
1278
|
import { sessionMiddleware } from "hono-sessions";
|
|
1278
|
-
import
|
|
1279
|
+
import kv from "polystore/hono-sessions";
|
|
1279
1280
|
|
|
1280
|
-
const
|
|
1281
|
+
const store = kv();
|
|
1281
1282
|
|
|
1283
|
+
const app = new Hono();
|
|
1282
1284
|
app.use("*", sessionMiddleware({
|
|
1283
|
-
store
|
|
1285
|
+
store,
|
|
1284
1286
|
encryptionKey: process.env.SESSION_KEY,
|
|
1285
1287
|
expireAfterSeconds: 3600,
|
|
1286
1288
|
}));
|
|
1287
1289
|
```
|
|
1288
1290
|
|
|
1289
|
-
By default it uses an in-memory `Map`. For production, pass any Polystore
|
|
1291
|
+
By default it uses an in-memory `Map`. For production, pass any Polystore adapter:
|
|
1290
1292
|
|
|
1291
1293
|
```js
|
|
1292
1294
|
import { createClient } from "redis";
|
|
1293
|
-
import
|
|
1295
|
+
import kv from "polystore/hono-sessions";
|
|
1296
|
+
|
|
1297
|
+
const store = kv(createClient().connect());
|
|
1294
1298
|
|
|
1295
1299
|
app.use("*", sessionMiddleware({
|
|
1296
|
-
store
|
|
1300
|
+
store,
|
|
1297
1301
|
encryptionKey: process.env.SESSION_KEY,
|
|
1298
1302
|
expireAfterSeconds: 3600,
|
|
1299
1303
|
}));
|
|
@@ -1316,6 +1320,36 @@ app.use("*", (c, next) => {
|
|
|
1316
1320
|
```
|
|
1317
1321
|
|
|
1318
1322
|
|
|
1323
|
+
### Better Auth
|
|
1324
|
+
|
|
1325
|
+
> [Full example →](https://github.com/franciscop/polystore/tree/master/examples/better-auth)
|
|
1326
|
+
|
|
1327
|
+
Use any Polystore-compatible store as the [`secondaryStorage`](https://www.better-auth.com/docs/concepts/database#secondary-storage) for [Better Auth](https://better-auth.com). No database required — Polystore handles session caching and token storage:
|
|
1328
|
+
|
|
1329
|
+
```js
|
|
1330
|
+
import { betterAuth } from "better-auth";
|
|
1331
|
+
import betterAuthStorage from "polystore/better-auth";
|
|
1332
|
+
|
|
1333
|
+
export const auth = betterAuth({
|
|
1334
|
+
secondaryStorage: betterAuthStorage(), // in-memory by default
|
|
1335
|
+
emailAndPassword: { enabled: true },
|
|
1336
|
+
});
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
For production, swap in any Polystore adapter:
|
|
1340
|
+
|
|
1341
|
+
```js
|
|
1342
|
+
import { createClient } from "redis";
|
|
1343
|
+
|
|
1344
|
+
secondaryStorage: betterAuthStorage(createClient().connect())
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
Use `.prefix()` to namespace keys in a shared store:
|
|
1348
|
+
|
|
1349
|
+
```js
|
|
1350
|
+
secondaryStorage: betterAuthStorage(createClient().connect()).prefix("auth:")
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1319
1353
|
### fch
|
|
1320
1354
|
|
|
1321
1355
|
[Fch](https://www.npmjs.com/package/fch) is a lightweight fetch wrapper that uses Polystore natively for caching. Pass any Polystore store as the `cache` option and GET responses are cached automatically:
|
|
@@ -1378,7 +1412,7 @@ export default server({ session }).get("/", (ctx) => {
|
|
|
1378
1412
|
|
|
1379
1413
|
### Performance
|
|
1380
1414
|
|
|
1381
|
-
> TL;DR: if you only use the item operations (add, set, get, has, del) and your
|
|
1415
|
+
> TL;DR: if you only use the item operations (add, set, get, has, del) and your adapter supports expiration natively, you have nothing to worry about! Otherwise, please read on.
|
|
1382
1416
|
|
|
1383
1417
|
While all of our stores support `expires`, `.prefix()` and group operations, the nature of those makes them to have different performance characteristics.
|
|
1384
1418
|
|
|
@@ -1390,9 +1424,9 @@ While all of our stores support `expires`, `.prefix()` and group operations, the
|
|
|
1390
1424
|
|
|
1391
1425
|
### Expirations
|
|
1392
1426
|
|
|
1393
|
-
> Warning: if
|
|
1427
|
+
> Warning: if an adapter doesn't support expiration natively, we will hide expired keys on the API calls for a nice DX, but _old data might not be evicted automatically_. See [the notes in Performance](#performance) for details on how to work around this.
|
|
1394
1428
|
|
|
1395
|
-
We unify all of the
|
|
1429
|
+
We unify all of the adapters' diverse expiration methods into a single, easy one with `expires` (**seconds** | string):
|
|
1396
1430
|
|
|
1397
1431
|
```js
|
|
1398
1432
|
// in-memory store
|
|
@@ -1438,13 +1472,13 @@ However, in some stores this does come with some potential performance disadvant
|
|
|
1438
1472
|
|
|
1439
1473
|
For other stores like Redis this is not a problem, because the low-level operations already do them natively, so we don't need to worry about this for performance at the user-level. Instead, Redis and cookies have the problem that they only have expiration resolution at the second level. Meaning that 800ms is not a valid Redis expiration time, it has to be 1s, 2s, etc.
|
|
1440
1474
|
|
|
1441
|
-
These details are explained in the respective
|
|
1475
|
+
These details are explained in the respective adapter information.
|
|
1442
1476
|
|
|
1443
1477
|
### Substores
|
|
1444
1478
|
|
|
1445
1479
|
> There's some [basic `.prefix()` API info](#prefix) for everyday usage, this section is the in-depth explanation.
|
|
1446
1480
|
|
|
1447
|
-
What `.prefix()` does is it creates **a new instance** of the Store, _with the same
|
|
1481
|
+
What `.prefix()` does is it creates **a new instance** of the Store, _with the same adapter_ as you provided, but now any key you read, write, etc. will be passed with the given prefix to the adapter. The issue is that support from the underlying adapters is inconsistent.
|
|
1448
1482
|
|
|
1449
1483
|
When dealing with large or complex amounts of data in a KV store, sometimes it's useful to divide them by categories. Some examples might be:
|
|
1450
1484
|
|
|
@@ -1465,15 +1499,15 @@ Polystore methods return promises and surface errors from the underlying client.
|
|
|
1465
1499
|
Invalid JSON payloads, invalid value encoding, or data that was written outside Polystore and cannot be decoded with its metadata expectations.
|
|
1466
1500
|
|
|
1467
1501
|
3. **Usage/configuration errors**
|
|
1468
|
-
Invalid
|
|
1502
|
+
Invalid adapter setup, invalid URLs/paths, or unsupported operations in a specific runtime.
|
|
1469
1503
|
|
|
1470
1504
|
Recommended patterns:
|
|
1471
1505
|
|
|
1472
1506
|
- Use `try/catch` around all write/read operations in production paths.
|
|
1473
1507
|
- Prefer returning safe fallbacks for cache-like usage (`null`, stale response, or refetch).
|
|
1474
|
-
- Log enough context (`
|
|
1475
|
-
- For remote
|
|
1476
|
-
- Call `.close()` during shutdown when the
|
|
1508
|
+
- Log enough context (`adapter type`, `key`, operation name) without logging sensitive values.
|
|
1509
|
+
- For remote adapters, consider retry/backoff only for transient failures.
|
|
1510
|
+
- Call `.close()` during shutdown when the adapter supports it.
|
|
1477
1511
|
|
|
1478
1512
|
Example:
|
|
1479
1513
|
|
|
@@ -1524,43 +1558,43 @@ class MyClient {
|
|
|
1524
1558
|
}
|
|
1525
1559
|
```
|
|
1526
1560
|
|
|
1527
|
-
Note that this is NOT the public API, it's the internal **
|
|
1561
|
+
Note that this is NOT the public API, it's the internal **adapter** API. It's simpler than the public API since we do some of the heavy lifting as an intermediate layer (e.g. for the adapter, the `expires` will always be a `null` or `number`, never `undefined` or a `string`), but also it differs from polystore's public API, like `.add()` has a different signature, and the group methods all take a explicit prefix.
|
|
1528
1562
|
|
|
1529
|
-
**Expires**: if you set the `HAS_EXPIRATION = true`, then you are indicating that the
|
|
1563
|
+
**Expires**: if you set the `HAS_EXPIRATION = true`, then you are indicating that the adapter WILL manage the lifecycle of the data. This includes all methods, for example if an item is expired, then its key should not be returned in `.keys()`, it's value should not be returned in `.values()`, and the method `.has()` will return `false`. The good news is that you will always receive the option `expires`, which is either `null` (no expiration) or a `number` indicating the **seconds** for the key/value to will expire.
|
|
1530
1564
|
|
|
1531
1565
|
**Prefix**: we manage the `prefix` as an invisible layer on top, you only need to be aware of it in the `.add()` method, as well as in the group methods:
|
|
1532
1566
|
|
|
1533
1567
|
```js
|
|
1534
1568
|
// What the user of polystore does:
|
|
1535
|
-
const store = await kv(
|
|
1569
|
+
const store = await kv(adapter).prefix("hello:").prefix("world:");
|
|
1536
1570
|
|
|
1537
|
-
// User calls this, then the
|
|
1571
|
+
// User calls this, then the adapter is called with that:
|
|
1538
1572
|
const value = await store.get("a");
|
|
1539
|
-
//
|
|
1573
|
+
// adapter.get("hello:world:a");
|
|
1540
1574
|
|
|
1541
|
-
// User calls this, then the
|
|
1575
|
+
// User calls this, then the adapter is called with that:
|
|
1542
1576
|
for await (const [key, value] of store) {}
|
|
1543
|
-
//
|
|
1577
|
+
// adapter.iterate("hello:world:");
|
|
1544
1578
|
```
|
|
1545
1579
|
|
|
1546
1580
|
> Note: all of the _group methods_ that return keys, should return them **with the prefix**:
|
|
1547
1581
|
|
|
1548
1582
|
```js
|
|
1549
|
-
|
|
1583
|
+
adapter.keys = (prefix) => {
|
|
1550
1584
|
// Filter the keys, and return them INCLUDING the prefix!
|
|
1551
1585
|
return Object.keys(subStore).filter((key) => key.startsWith(prefix));
|
|
1552
1586
|
};
|
|
1553
1587
|
```
|
|
1554
1588
|
|
|
1555
|
-
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
|
|
1589
|
+
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 adapter to do, e.g. `.clear()` will remove all of the items that match the prefix (or everything if there's no prefix).
|
|
1556
1590
|
|
|
1557
1591
|
|
|
1558
1592
|
|
|
1559
1593
|
## Examples
|
|
1560
1594
|
|
|
1561
|
-
### Plain Object
|
|
1595
|
+
### Plain Object adapter
|
|
1562
1596
|
|
|
1563
|
-
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
|
|
1597
|
+
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 adapters:
|
|
1564
1598
|
|
|
1565
1599
|
```js
|
|
1566
1600
|
const dataSource = {};
|
|
@@ -1590,10 +1624,10 @@ We don't set `HAS_EXPIRATION` to true since plain objects do NOT support expirat
|
|
|
1590
1624
|
|
|
1591
1625
|
### Custom ID generation
|
|
1592
1626
|
|
|
1593
|
-
You might want to provide your custom key generation algorithm, which I'm going to call `customId()` for example purposes. The only place where `polystore` generates IDs is in `add`, so you can provide your
|
|
1627
|
+
You might want to provide your custom key generation algorithm, which I'm going to call `customId()` for example purposes. The only place where `polystore` generates IDs is in `add`, so you can provide your adapter with a custom generator:
|
|
1594
1628
|
|
|
1595
1629
|
```js
|
|
1596
|
-
class
|
|
1630
|
+
class MyAdapter {
|
|
1597
1631
|
|
|
1598
1632
|
// Add the opt method .add() to have more control over the ID generation
|
|
1599
1633
|
async add (prefix, data, expires) {
|
|
@@ -1623,10 +1657,10 @@ const id2 = await store.prefix("hello:").add({ hello: "world" });
|
|
|
1623
1657
|
|
|
1624
1658
|
### Serializing the data
|
|
1625
1659
|
|
|
1626
|
-
If you need to serialize the data before storing it, you can do it within your custom
|
|
1660
|
+
If you need to serialize the data before storing it, you can do it within your custom adapter. Here's an example of how you can handle data serialization when setting values:
|
|
1627
1661
|
|
|
1628
1662
|
```js
|
|
1629
|
-
class
|
|
1663
|
+
class MyAdapter {
|
|
1630
1664
|
get(key) {
|
|
1631
1665
|
const data = dataSource[key];
|
|
1632
1666
|
return data ? JSON.parse(data) : null;
|
|
@@ -1741,7 +1775,7 @@ You can store either the raw data, or the processed data. Depending on whether t
|
|
|
1741
1775
|
|
|
1742
1776
|
### Dev vs Prod
|
|
1743
1777
|
|
|
1744
|
-
With Polystore it's easy to configure your KV solution to use a different
|
|
1778
|
+
With Polystore it's easy to configure your KV solution to use a different adapter in dev vs production. We've found particularly useful to use an easy-to-debug adapter in dev like [Folder](#folder) and a high-performance adapter in production like [Redis](#redis):
|
|
1745
1779
|
|
|
1746
1780
|
```ts
|
|
1747
1781
|
// store.ts
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Store } from 'polystore';
|
|
2
|
+
|
|
3
|
+
type SecondaryStorage = {
|
|
4
|
+
get(key: string): Promise<string | null>;
|
|
5
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
6
|
+
delete(key: string): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
declare class PolystoreBetterAuthStorage implements SecondaryStorage {
|
|
9
|
+
private store;
|
|
10
|
+
constructor(store: Store);
|
|
11
|
+
prefix(prefix?: string): PolystoreBetterAuthStorage;
|
|
12
|
+
get(key: string): Promise<string | null>;
|
|
13
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
14
|
+
delete(key: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
declare function betterAuthStorage(store?: any): PolystoreBetterAuthStorage;
|
|
17
|
+
|
|
18
|
+
export { PolystoreBetterAuthStorage, betterAuthStorage as default };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/plugins/better-auth/index.ts
|
|
2
|
+
import kv from "polystore";
|
|
3
|
+
var PolystoreBetterAuthStorage = class _PolystoreBetterAuthStorage {
|
|
4
|
+
store;
|
|
5
|
+
constructor(store) {
|
|
6
|
+
this.store = store;
|
|
7
|
+
}
|
|
8
|
+
prefix(prefix = "") {
|
|
9
|
+
return new _PolystoreBetterAuthStorage(this.store.prefix(prefix));
|
|
10
|
+
}
|
|
11
|
+
get(key) {
|
|
12
|
+
return this.store.get(key);
|
|
13
|
+
}
|
|
14
|
+
async set(key, value, ttl) {
|
|
15
|
+
await this.store.set(key, value, ttl ? { expires: ttl } : void 0);
|
|
16
|
+
}
|
|
17
|
+
async delete(key) {
|
|
18
|
+
await this.store.del(key);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function betterAuthStorage(store = /* @__PURE__ */ new Map()) {
|
|
22
|
+
return new PolystoreBetterAuthStorage(kv(store));
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
PolystoreBetterAuthStorage,
|
|
26
|
+
betterAuthStorage as default
|
|
27
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import session, { SessionData } from 'express-session';
|
|
2
|
+
import { Store } from 'polystore';
|
|
3
|
+
|
|
4
|
+
type Callback = (err?: any) => void;
|
|
5
|
+
declare class PolystoreSessionStore extends session.Store {
|
|
6
|
+
private store;
|
|
7
|
+
constructor(store: Store);
|
|
8
|
+
prefix(prefix?: string): PolystoreSessionStore;
|
|
9
|
+
get(sid: string, cb: (err: any, session?: SessionData | null) => void): void;
|
|
10
|
+
set(sid: string, data: SessionData, cb?: Callback): void;
|
|
11
|
+
destroy(sid: string, cb?: Callback): void;
|
|
12
|
+
touch(sid: string, data: SessionData, cb?: Callback): void;
|
|
13
|
+
all(cb: (err: any, sessions?: SessionData[] | {
|
|
14
|
+
[sid: string]: SessionData;
|
|
15
|
+
} | null) => void): void;
|
|
16
|
+
clear(cb?: Callback): void;
|
|
17
|
+
}
|
|
18
|
+
declare function expressStore(store?: Map<any, any>): PolystoreSessionStore;
|
|
19
|
+
|
|
20
|
+
export { PolystoreSessionStore, expressStore as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/plugins/express/index.ts
|
|
2
2
|
import session from "express-session";
|
|
3
3
|
import kv from "polystore";
|
|
4
4
|
var ttlFromSession = (data) => {
|
|
@@ -33,8 +33,8 @@ var PolystoreSessionStore = class _PolystoreSessionStore extends session.Store {
|
|
|
33
33
|
this.store.clear().then(() => cb?.()).catch((err) => cb?.(err));
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
|
-
function expressStore(
|
|
37
|
-
return new PolystoreSessionStore(kv(
|
|
36
|
+
function expressStore(store = /* @__PURE__ */ new Map()) {
|
|
37
|
+
return new PolystoreSessionStore(kv(store));
|
|
38
38
|
}
|
|
39
39
|
export {
|
|
40
40
|
PolystoreSessionStore,
|