polystore 0.20.0 → 0.21.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.
package/index.js CHANGED
@@ -179,7 +179,7 @@ var File = class extends Client {
179
179
  // Check if this is the right class for the given client
180
180
  static test = (client) => {
181
181
  if (client instanceof URL) client = client.href;
182
- return typeof client === "string" && client.startsWith("file://") && client.includes(".");
182
+ return typeof client === "string" && client.startsWith("file://") && client.endsWith(".json");
183
183
  };
184
184
  // We want to make sure the file already exists, so attempt to
185
185
  // create the folders and the file (but not OVERWRITE it, that's why the x flag)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polystore",
3
- "version": "0.20.0",
3
+ "version": "0.21.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",
@@ -16,25 +16,36 @@
16
16
  "types": "./index.d.ts",
17
17
  "import": "./index.js"
18
18
  },
19
- "./express": "./src/express.js"
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
+ }
20
27
  },
21
28
  "files": [
22
29
  "index.js",
23
30
  "index.d.ts",
24
- "src/express.js"
31
+ "src/integrations/express.js",
32
+ "src/integrations/express.d.ts",
33
+ "src/integrations/hono-sessions.js",
34
+ "src/integrations/hono-sessions.d.ts"
25
35
  ],
26
36
  "scripts": {
27
37
  "analyze": "npm run build && esbuild src/index.ts --bundle --packages=external --format=esm --minify --outfile=index.min.js && echo 'Final size:' && gzip-size index.min.js && rm index.min.js",
28
- "build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24",
38
+ "build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24 && bunx tsup src/integrations/express.ts src/integrations/hono-sessions.ts --format esm --dts --out-dir src/integrations --target node24 --external polystore --external express-session --external hono-sessions",
29
39
  "lint": "npx tsc --noEmit",
30
40
  "start": "bun test --watch",
41
+ "service:db": "etcd",
42
+ "service:redis": "brew services start redis",
43
+ "service:postgres": "brew services start postgresql",
44
+ "service:server": "bun ./src/server.ts",
45
+ "services": "concurrently \"npm run service:db\" \"npm run service:redis\" \"npm run service:postgres\" \"npm run service:server\"",
31
46
  "test": "npm run test:bun && npm run test:jest",
32
- "test:bun": "bun test ./test/index.test.ts",
33
- "test:jest": "jest ./test/index.test.ts --detectOpenHandles --forceExit",
34
- "run:db": "etcd",
35
- "run:redis": "brew services start redis",
36
- "run:postgres": "brew services start postgresql",
37
- "run:server": "bun ./src/server.ts"
47
+ "test:bun": "bun test ./test/index.test.ts ./src/integrations/",
48
+ "test:jest": "jest ./test/index.test.ts --detectOpenHandles --forceExit"
38
49
  },
39
50
  "keywords": [
40
51
  "kv",
@@ -49,23 +60,32 @@
49
60
  "@deno/kv": "^0.8.1",
50
61
  "@types/better-sqlite3": "^7.6.13",
51
62
  "@types/bun": "^1.3.3",
63
+ "@types/express": "^5.0.6",
64
+ "@types/express-session": "^1.18.2",
52
65
  "@types/jest": "^30.0.0",
53
66
  "@types/jsdom": "^27.0.0",
54
67
  "@types/pg": "^8.11.10",
68
+ "@types/supertest": "^7.2.0",
55
69
  "better-sqlite3": "^12.6.0",
56
70
  "check-dts": "^0.8.0",
71
+ "concurrently": "^9.2.1",
57
72
  "cross-fetch": "^4.1.0",
58
73
  "dotenv": "^16.3.1",
59
74
  "edge-mock": "^0.0.15",
60
75
  "esbuild": "^0.27.0",
61
76
  "etcd3": "^1.1.2",
77
+ "express": "^5.2.1",
78
+ "express-session": "^1.19.0",
62
79
  "gzip-size-cli": "^5.1.0",
80
+ "hono": "^4.12.10",
81
+ "hono-sessions": "^0.8.1",
63
82
  "jest": "^30.2.0",
64
83
  "jsdom": "^27.2.0",
65
84
  "level": "^8.0.1",
66
85
  "localforage": "^1.10.0",
67
86
  "pg": "^8.13.1",
68
87
  "redis": "^4.6.10",
88
+ "supertest": "^7.2.2",
69
89
  "ts-jest": "^29.4.6",
70
90
  "ts-node": "^10.9.2",
71
91
  "tsup": "^8.5.1",
package/readme.md CHANGED
@@ -63,7 +63,7 @@ MyApi({ cache: env.KV_NAMESPACE }); // OR
63
63
 
64
64
  First, install `polystore` and whatever [supported client](#clients) that you prefer. Let's see Redis as an example here:
65
65
 
66
- ```
66
+ ```sh
67
67
  npm i polystore redis
68
68
  ```
69
69
 
@@ -93,7 +93,7 @@ await store.del(key);
93
93
 
94
94
  ## API
95
95
 
96
- The base `kv()` initialization is shared across clients ([see full clients list](#clients)); single argument that receives the client or a string representing the client:
96
+ The base `kv()` initialization is shared across clients ([see full clients list](#clients)); an argument that receives the client or a string representing the client and then the options:
97
97
 
98
98
  ```js
99
99
  import kv from "polystore";
@@ -107,7 +107,7 @@ const store = kv(MyClientInstance, { expires: null, prefix: "" });
107
107
  > [!IMPORTANT]
108
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
109
 
110
- You can enforce **types** for store values either at store creation or at the method level:
110
+ You can enforce **types** for the values either at store creation or at the method level:
111
111
 
112
112
  ```ts
113
113
  const store = kv<number>(new Map());
@@ -1225,6 +1225,155 @@ This keeps a single table while preserving namespace-style grouping through pref
1225
1225
 
1226
1226
  Please see the [creating a store](#creating-a-store) section for all the details!
1227
1227
 
1228
+ ## Integrations
1229
+
1230
+ Polystore has some easy integrations for you to use it as a simple connector.
1231
+
1232
+ ### Express
1233
+
1234
+ Use any Polystore-compatible store as an [express-session](https://github.com/expressjs/session) store:
1235
+
1236
+ ```js
1237
+ import session from "express-session";
1238
+ import expressStore from "polystore/express";
1239
+
1240
+ app.use(session({
1241
+ secret: "my-secret",
1242
+ store: expressStore(),
1243
+ }));
1244
+ ```
1245
+
1246
+ By default it uses an in-memory `Map`, which is fine for development. For production, pass any Polystore client:
1247
+
1248
+ ```js
1249
+ import { createClient } from "redis";
1250
+ // `npm install polystore`
1251
+ import expressStore from "polystore/express";
1252
+
1253
+ const store = expressStore(createClient().connect());
1254
+
1255
+ app.use(session({ secret: "my-secret", store }));
1256
+ ```
1257
+
1258
+ Any client 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
+
1260
+ Use `.prefix()` to namespace sessions, for example in a multi-tenant app:
1261
+
1262
+ ```js
1263
+ const store = expressStore(createClient().connect());
1264
+
1265
+ app.use((req, res, next) => {
1266
+ req.sessionStore = store.prefix(`tenant:${req.params.tenant}:`);
1267
+ next();
1268
+ });
1269
+ ```
1270
+
1271
+ ### Hono Sessions
1272
+
1273
+ Use any Polystore-compatible store as a [hono-sessions](https://github.com/jcs224/hono_sessions) store:
1274
+
1275
+ ```js
1276
+ import { Hono } from "hono";
1277
+ import { sessionMiddleware } from "hono-sessions";
1278
+ import honoStore from "polystore/hono-sessions";
1279
+
1280
+ const app = new Hono();
1281
+
1282
+ app.use("*", sessionMiddleware({
1283
+ store: honoStore(),
1284
+ encryptionKey: process.env.SESSION_KEY,
1285
+ expireAfterSeconds: 3600,
1286
+ }));
1287
+ ```
1288
+
1289
+ By default it uses an in-memory `Map`. For production, pass any Polystore client:
1290
+
1291
+ ```js
1292
+ import { createClient } from "redis";
1293
+ import honoStore from "polystore/hono-sessions";
1294
+
1295
+ app.use("*", sessionMiddleware({
1296
+ store: honoStore(createClient().connect()),
1297
+ encryptionKey: process.env.SESSION_KEY,
1298
+ expireAfterSeconds: 3600,
1299
+ }));
1300
+ ```
1301
+
1302
+ Session TTL is derived automatically from `expireAfterSeconds` — hono-sessions writes it to `_expire` on the session data, and Polystore uses it to set the underlying store TTL for automatic cleanup.
1303
+
1304
+ Use `.prefix()` to namespace sessions per tenant:
1305
+
1306
+ ```js
1307
+ const store = honoStore(createClient().connect());
1308
+
1309
+ app.use("*", (c, next) => {
1310
+ const tenant = c.req.param("tenant");
1311
+ return sessionMiddleware({
1312
+ store: store.prefix(`tenant:${tenant}:`),
1313
+ encryptionKey: process.env.SESSION_KEY,
1314
+ })(c, next);
1315
+ });
1316
+ ```
1317
+
1318
+
1319
+ ### fch
1320
+
1321
+ [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:
1322
+
1323
+ ```js
1324
+ import fch from "fch";
1325
+ import kv from "polystore";
1326
+
1327
+ const api = fch.create({
1328
+ baseUrl: "https://api.example.com",
1329
+ cache: kv(new Map(), { expires: "1h" }),
1330
+ });
1331
+
1332
+ await api.get("/users"); // fetched from network, stored in cache
1333
+ await api.get("/users"); // served from cache
1334
+ ```
1335
+
1336
+ Swap the backend without changing anything else:
1337
+
1338
+ ```js
1339
+ import { createClient } from "redis";
1340
+
1341
+ const api = fch.create({
1342
+ cache: kv(createClient().connect(), { expires: "10min" }),
1343
+ });
1344
+ ```
1345
+
1346
+ You can override or skip the cache per request:
1347
+
1348
+ ```js
1349
+ const shortCache = kv(new Map(), { expires: "30s" });
1350
+
1351
+ api.get("/realtime", { cache: null }); // skip cache
1352
+ api.get("/prices", { cache: shortCache }); // use a different cache
1353
+ ```
1354
+
1355
+
1356
+ ### @server/next
1357
+
1358
+ > [!WARNING]
1359
+ > @server/next is still experimental, but it's the main reason I created Polystore and so I wanted to document it as well
1360
+
1361
+ Server.js supports Polystore directly:
1362
+
1363
+ ```ts
1364
+ import kv from "polystore";
1365
+ import server from "@server/next";
1366
+
1367
+ const session = kv(new Map());
1368
+
1369
+ export default server({ session }).get("/", (ctx) => {
1370
+ if (!ctx.session.counter) ctx.session.counter = 0;
1371
+ ctx.session.counter++;
1372
+ return `User visited ${ctx.session.counter} times`;
1373
+ });
1374
+ ```
1375
+
1376
+
1228
1377
  ## Guides
1229
1378
 
1230
1379
  ### Performance
@@ -1610,23 +1759,3 @@ if (process.env.REDIS_URL) {
1610
1759
 
1611
1760
  export default store;
1612
1761
  ```
1613
-
1614
- ### @server/next
1615
-
1616
- > [!info]
1617
- > @server/next is still experimental, but it's the main reason I created Polystore and so I wanted to document it as well
1618
-
1619
- Server.js supports Polystore directly:
1620
-
1621
- ```ts
1622
- import kv from "polystore";
1623
- import server from "../../";
1624
-
1625
- const session = kv(new Map());
1626
-
1627
- export default server({ session }).get("/", (ctx) => {
1628
- if (!ctx.session.counter) ctx.session.counter = 0;
1629
- ctx.session.counter++;
1630
- return `User visited ${ctx.session.counter} times`;
1631
- });
1632
- ```
@@ -0,0 +1,307 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ type Prefix = string;
4
+ type Expires = number | null | string;
5
+ type Options = {
6
+ prefix?: Prefix;
7
+ expires?: Expires;
8
+ };
9
+ type StoreData<T extends Serializable = Serializable> = {
10
+ value: T;
11
+ expires: number | null;
12
+ };
13
+ type Serializable = string | number | boolean | null | (Serializable | null)[] | {
14
+ [key: string]: Serializable | null;
15
+ };
16
+ interface ClientExpires {
17
+ TYPE: string;
18
+ HAS_EXPIRATION: true;
19
+ promise?: Promise<any>;
20
+ test?: (client: any) => boolean;
21
+ get<T extends Serializable>(key: string): Promise<T | null> | T | null;
22
+ set<T extends Serializable>(key: string, value: T, expires?: Expires): Promise<any> | any;
23
+ iterate<T extends Serializable>(prefix: string): AsyncGenerator<[string, T], void, unknown> | Generator<[string, T], void, unknown>;
24
+ add?<T extends Serializable>(prefix: string, value: T, expires?: Expires): Promise<string>;
25
+ has?(key: string): Promise<boolean> | boolean;
26
+ del?(key: string): Promise<any> | any;
27
+ keys?(prefix: string): Promise<string[]> | string[];
28
+ values?<T extends Serializable>(prefix: string): Promise<T[]> | T[];
29
+ entries?<T extends Serializable>(prefix: string): Promise<[string, T][]> | [string, T][];
30
+ all?<T extends Serializable>(prefix: string): Promise<Record<string, T>> | Record<string, T>;
31
+ clear?(prefix: string): Promise<any> | any;
32
+ clearAll?(): Promise<any> | any;
33
+ close?(): Promise<any> | any;
34
+ }
35
+ interface ClientNonExpires {
36
+ TYPE: string;
37
+ HAS_EXPIRATION: false;
38
+ promise?: Promise<any>;
39
+ test?: (client: any) => boolean;
40
+ get<T extends Serializable>(key: string): Promise<StoreData<T> | null> | StoreData<T> | null;
41
+ set<T extends Serializable>(key: string, value: StoreData<T> | null, ttl?: Expires): Promise<any> | any;
42
+ iterate<T extends Serializable>(prefix: string): AsyncGenerator<[string, StoreData<T>], void, unknown> | Generator<[string, StoreData<T>], void, unknown>;
43
+ add?<T extends Serializable>(prefix: string, value: StoreData<T>, ttl?: Expires): Promise<string>;
44
+ has?(key: string): Promise<boolean> | boolean;
45
+ del?(key: string): Promise<any> | any;
46
+ keys?(prefix: string): Promise<string[]> | string[];
47
+ values?<T extends Serializable>(prefix: string): Promise<StoreData<T>[]> | StoreData<T>[];
48
+ entries?<T extends Serializable>(prefix: string): Promise<[string, StoreData<T>][]> | [string, StoreData<T>][];
49
+ all?<T extends Serializable>(prefix: string): Promise<Record<string, StoreData<T>>> | Record<string, StoreData<T>>;
50
+ prune?(): Promise<any> | any;
51
+ clear?(prefix: string): Promise<any> | any;
52
+ clearAll?(): Promise<any> | any;
53
+ close?(): Promise<any> | any;
54
+ }
55
+ type Client = ClientExpires | ClientNonExpires;
56
+
57
+ declare class Store<TD extends Serializable = Serializable> {
58
+ #private;
59
+ PREFIX: Prefix;
60
+ EXPIRES: Expires;
61
+ promise: Promise<Client> | null;
62
+ client: Client;
63
+ type: string;
64
+ constructor(clientPromise?: any, options?: Options);
65
+ /**
66
+ * Save the data on an autogenerated key, can add expiration as well:
67
+ *
68
+ * ```js
69
+ * const key1 = await store.add("value1");
70
+ * const key2 = await store.add({ hello: "world" });
71
+ * const key3 = await store.add("value3", { expires: "1h" });
72
+ * ```
73
+ *
74
+ * **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
75
+ */
76
+ add(value: TD, options?: Options): Promise<string>;
77
+ add<T extends TD>(value: T, options?: Options): Promise<string>;
78
+ /**
79
+ * Save the data on the given key, can add expiration as well:
80
+ *
81
+ * ```js
82
+ * const key = await store.set("key1", "value1");
83
+ * await store.set("key2", { hello: "world" });
84
+ * await store.set("key3", "value3", { expires: "1h" });
85
+ * ```
86
+ *
87
+ * **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
88
+ */
89
+ set(key: string, value: TD, options?: Options): Promise<string>;
90
+ set<T extends TD>(key: string, value: T, options?: Options): Promise<string>;
91
+ /**
92
+ * Read a single value from the KV store:
93
+ *
94
+ * ```js
95
+ * const value1 = await store.get("key1");
96
+ * // null (doesn't exist or has expired)
97
+ * const value2 = await store.get("key2");
98
+ * // "value2"
99
+ * const value3 = await store.get("key3");
100
+ * // { hello: "world" }
101
+ * ```
102
+ *
103
+ * **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
104
+ */
105
+ get(key: string): Promise<TD | null>;
106
+ get<T extends TD>(key: string): Promise<T | null>;
107
+ /**
108
+ * Check whether a key exists or not:
109
+ *
110
+ * ```js
111
+ * if (await store.has("key1")) { ... }
112
+ * ```
113
+ *
114
+ * If you are going to use the value, it's better to just read it:
115
+ *
116
+ * ```js
117
+ * const val = await store.get("key1");
118
+ * if (val) { ... }
119
+ * ```
120
+ *
121
+ * **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
122
+ */
123
+ has(key: string): Promise<boolean>;
124
+ /**
125
+ * Remove a single key and its value from the store:
126
+ *
127
+ * ```js
128
+ * const key = await store.del("key1");
129
+ * ```
130
+ *
131
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
132
+ */
133
+ del(key: string): Promise<string>;
134
+ /**
135
+ * @alias of .del(key: string)
136
+ * Remove a single key and its value from the store:
137
+ *
138
+ * ```js
139
+ * const key = await store.delete("key1");
140
+ * ```
141
+ *
142
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
143
+ */
144
+ delete(key: string): Promise<string>;
145
+ /**
146
+ * An iterator that goes through all of the key:value pairs in the client
147
+ *
148
+ * ```js
149
+ * for await (const [key, value] of store) {
150
+ * console.log(key, value);
151
+ * }
152
+ * ```
153
+ *
154
+ * **[→ Full Iterator Docs](https://polystore.dev/documentation#iterator)**
155
+ */
156
+ [Symbol.asyncIterator](): AsyncGenerator<[string, TD], void, unknown>;
157
+ [Symbol.asyncIterator]<T extends TD>(): AsyncGenerator<[
158
+ string,
159
+ T
160
+ ], void, unknown>;
161
+ /**
162
+ * Return an array of the entries, in the [key, value] format:
163
+ *
164
+ * ```js
165
+ * const entries = await store.entries();
166
+ * // [["key1", "value1"], ["key2", { hello: "world" }], ...]
167
+ *
168
+ * // To limit it to a given prefix, use `.prefix()`:
169
+ * const sessions = await store.prefix("session:").entries();
170
+ * ```
171
+ *
172
+ * **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
173
+ */
174
+ entries(): Promise<[string, TD][]>;
175
+ entries<T extends TD>(): Promise<[string, T][]>;
176
+ /**
177
+ * Return an array of the keys in the store:
178
+ *
179
+ * ```js
180
+ * const keys = await store.keys();
181
+ * // ["key1", "key2", ...]
182
+ *
183
+ * // To limit it to a given prefix, use `.prefix()`:
184
+ * const sessions = await store.prefix("session:").keys();
185
+ * ```
186
+ *
187
+ * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
188
+ */
189
+ keys(): Promise<string[]>;
190
+ /**
191
+ * Return an array of the values in the store:
192
+ *
193
+ * ```js
194
+ * const values = await store.values();
195
+ * // ["value1", { hello: "world" }, ...]
196
+ *
197
+ * // To limit it to a given prefix, use `.prefix()`:
198
+ * const sessions = await store.prefix("session:").values();
199
+ * ```
200
+ *
201
+ * **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
202
+ */
203
+ values(): Promise<TD[]>;
204
+ values<T extends TD>(): Promise<T[]>;
205
+ /**
206
+ * Return an object with the keys:values in the store:
207
+ *
208
+ * ```js
209
+ * const obj = await store.all();
210
+ * // { key1: "value1", key2: { hello: "world" }, ... }
211
+ *
212
+ * // To limit it to a given prefix, use `.prefix()`:
213
+ * const sessions = await store.prefix("session:").all();
214
+ * ```
215
+ *
216
+ * **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
217
+ */
218
+ all(): Promise<Record<string, TD>>;
219
+ all<T extends TD>(): Promise<Record<string, T>>;
220
+ /**
221
+ * Create a substore where all the keys are stored with
222
+ * the given prefix:
223
+ *
224
+ * ```js
225
+ * const session = store.prefix("session:");
226
+ * await session.set("key1", "value1");
227
+ * console.log(await session.entries()); // session.
228
+ * // [["key1", "value1"]]
229
+ * console.log(await store.entries()); // store.
230
+ * // [["session:key1", "value1"]]
231
+ * ```
232
+ *
233
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
234
+ */
235
+ prefix(prefix?: Prefix): Store<TD>;
236
+ /**
237
+ * Create a substore where all the keys are stored with
238
+ * the given prefix:
239
+ *
240
+ * ```js
241
+ * const session = store.prefix("session:");
242
+ * await session.set("key1", "value1");
243
+ * console.log(await session.entries()); // session.
244
+ * // [["key1", "value1"]]
245
+ * console.log(await store.entries()); // store.
246
+ * // [["session:key1", "value1"]]
247
+ * ```
248
+ *
249
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
250
+ */
251
+ expires(expires?: Expires): Store<TD>;
252
+ /**
253
+ * Delete all of the records of the store:
254
+ *
255
+ * ```js
256
+ * await store.clear();
257
+ * ```
258
+ *
259
+ * It's useful for cache invalidation, clearing the data, and testing.
260
+ *
261
+ * **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
262
+ */
263
+ clear(): Promise<void>;
264
+ /**
265
+ * Remove all expired records from the store.
266
+ *
267
+ * ```js
268
+ * await store.prune();
269
+ * ```
270
+ *
271
+ * Only affects stores where expiration is managed by this wrapper.
272
+ */
273
+ prune(): Promise<void>;
274
+ /**
275
+ * Stop the connection to the store, if any:
276
+ *
277
+ * ```js
278
+ * await session.set("key1", "value1");
279
+ * await store.close();
280
+ * await session.set("key2", "value2"); // error
281
+ * ```
282
+ *
283
+ * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
284
+ */
285
+ close(): Promise<void>;
286
+ }
287
+
288
+ interface SessionData {
289
+ cookie?: {
290
+ originalMaxAge?: number | null;
291
+ [key: string]: any;
292
+ };
293
+ [key: string]: any;
294
+ }
295
+ type Callback = (err?: any) => void;
296
+ declare class PolystoreSessionStore extends EventEmitter {
297
+ private store;
298
+ constructor(store: Store);
299
+ prefix(prefix?: string): PolystoreSessionStore;
300
+ get(sid: string, cb: (err: any, session?: SessionData | null) => void): void;
301
+ set(sid: string, data: SessionData, cb?: Callback): void;
302
+ destroy(sid: string, cb?: Callback): void;
303
+ touch(sid: string, data: SessionData, cb?: Callback): void;
304
+ }
305
+ declare function expressStore(client?: Map<any, any>): PolystoreSessionStore;
306
+
307
+ export { PolystoreSessionStore, expressStore as default };
@@ -0,0 +1,42 @@
1
+ // src/integrations/express.ts
2
+ import session from "express-session";
3
+ import kv from "polystore";
4
+ var ttlFromSession = (data) => {
5
+ const maxAge = data?.cookie?.originalMaxAge;
6
+ return typeof maxAge === "number" ? { expires: Math.ceil(maxAge / 1e3) } : void 0;
7
+ };
8
+ var PolystoreSessionStore = class _PolystoreSessionStore extends session.Store {
9
+ store;
10
+ constructor(store) {
11
+ super();
12
+ this.store = store;
13
+ }
14
+ prefix(prefix = "") {
15
+ return new _PolystoreSessionStore(this.store.prefix(prefix));
16
+ }
17
+ get(sid, cb) {
18
+ this.store.get(sid).then((data) => cb(null, data)).catch((err) => err?.code === "ENOENT" ? cb(null, null) : cb(err));
19
+ }
20
+ set(sid, data, cb) {
21
+ this.store.set(sid, data, ttlFromSession(data)).then(() => cb?.()).catch((err) => cb?.(err));
22
+ }
23
+ destroy(sid, cb) {
24
+ this.store.del(sid).then(() => cb?.()).catch((err) => cb?.(err));
25
+ }
26
+ touch(sid, data, cb) {
27
+ this.store.set(sid, data, ttlFromSession(data)).then(() => cb?.()).catch((err) => cb?.(err));
28
+ }
29
+ all(cb) {
30
+ this.store.values().then((vals) => cb(null, vals)).catch(cb);
31
+ }
32
+ clear(cb) {
33
+ this.store.clear().then(() => cb?.()).catch((err) => cb?.(err));
34
+ }
35
+ };
36
+ function expressStore(client = /* @__PURE__ */ new Map()) {
37
+ return new PolystoreSessionStore(kv(client));
38
+ }
39
+ export {
40
+ PolystoreSessionStore,
41
+ expressStore as default
42
+ };
@@ -0,0 +1,299 @@
1
+ import { Store as Store$1, SessionData } from 'hono-sessions';
2
+
3
+ type Prefix = string;
4
+ type Expires = number | null | string;
5
+ type Options = {
6
+ prefix?: Prefix;
7
+ expires?: Expires;
8
+ };
9
+ type StoreData<T extends Serializable = Serializable> = {
10
+ value: T;
11
+ expires: number | null;
12
+ };
13
+ type Serializable = string | number | boolean | null | (Serializable | null)[] | {
14
+ [key: string]: Serializable | null;
15
+ };
16
+ interface ClientExpires {
17
+ TYPE: string;
18
+ HAS_EXPIRATION: true;
19
+ promise?: Promise<any>;
20
+ test?: (client: any) => boolean;
21
+ get<T extends Serializable>(key: string): Promise<T | null> | T | null;
22
+ set<T extends Serializable>(key: string, value: T, expires?: Expires): Promise<any> | any;
23
+ iterate<T extends Serializable>(prefix: string): AsyncGenerator<[string, T], void, unknown> | Generator<[string, T], void, unknown>;
24
+ add?<T extends Serializable>(prefix: string, value: T, expires?: Expires): Promise<string>;
25
+ has?(key: string): Promise<boolean> | boolean;
26
+ del?(key: string): Promise<any> | any;
27
+ keys?(prefix: string): Promise<string[]> | string[];
28
+ values?<T extends Serializable>(prefix: string): Promise<T[]> | T[];
29
+ entries?<T extends Serializable>(prefix: string): Promise<[string, T][]> | [string, T][];
30
+ all?<T extends Serializable>(prefix: string): Promise<Record<string, T>> | Record<string, T>;
31
+ clear?(prefix: string): Promise<any> | any;
32
+ clearAll?(): Promise<any> | any;
33
+ close?(): Promise<any> | any;
34
+ }
35
+ interface ClientNonExpires {
36
+ TYPE: string;
37
+ HAS_EXPIRATION: false;
38
+ promise?: Promise<any>;
39
+ test?: (client: any) => boolean;
40
+ get<T extends Serializable>(key: string): Promise<StoreData<T> | null> | StoreData<T> | null;
41
+ set<T extends Serializable>(key: string, value: StoreData<T> | null, ttl?: Expires): Promise<any> | any;
42
+ iterate<T extends Serializable>(prefix: string): AsyncGenerator<[string, StoreData<T>], void, unknown> | Generator<[string, StoreData<T>], void, unknown>;
43
+ add?<T extends Serializable>(prefix: string, value: StoreData<T>, ttl?: Expires): Promise<string>;
44
+ has?(key: string): Promise<boolean> | boolean;
45
+ del?(key: string): Promise<any> | any;
46
+ keys?(prefix: string): Promise<string[]> | string[];
47
+ values?<T extends Serializable>(prefix: string): Promise<StoreData<T>[]> | StoreData<T>[];
48
+ entries?<T extends Serializable>(prefix: string): Promise<[string, StoreData<T>][]> | [string, StoreData<T>][];
49
+ all?<T extends Serializable>(prefix: string): Promise<Record<string, StoreData<T>>> | Record<string, StoreData<T>>;
50
+ prune?(): Promise<any> | any;
51
+ clear?(prefix: string): Promise<any> | any;
52
+ clearAll?(): Promise<any> | any;
53
+ close?(): Promise<any> | any;
54
+ }
55
+ type Client = ClientExpires | ClientNonExpires;
56
+
57
+ declare class Store<TD extends Serializable = Serializable> {
58
+ #private;
59
+ PREFIX: Prefix;
60
+ EXPIRES: Expires;
61
+ promise: Promise<Client> | null;
62
+ client: Client;
63
+ type: string;
64
+ constructor(clientPromise?: any, options?: Options);
65
+ /**
66
+ * Save the data on an autogenerated key, can add expiration as well:
67
+ *
68
+ * ```js
69
+ * const key1 = await store.add("value1");
70
+ * const key2 = await store.add({ hello: "world" });
71
+ * const key3 = await store.add("value3", { expires: "1h" });
72
+ * ```
73
+ *
74
+ * **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
75
+ */
76
+ add(value: TD, options?: Options): Promise<string>;
77
+ add<T extends TD>(value: T, options?: Options): Promise<string>;
78
+ /**
79
+ * Save the data on the given key, can add expiration as well:
80
+ *
81
+ * ```js
82
+ * const key = await store.set("key1", "value1");
83
+ * await store.set("key2", { hello: "world" });
84
+ * await store.set("key3", "value3", { expires: "1h" });
85
+ * ```
86
+ *
87
+ * **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
88
+ */
89
+ set(key: string, value: TD, options?: Options): Promise<string>;
90
+ set<T extends TD>(key: string, value: T, options?: Options): Promise<string>;
91
+ /**
92
+ * Read a single value from the KV store:
93
+ *
94
+ * ```js
95
+ * const value1 = await store.get("key1");
96
+ * // null (doesn't exist or has expired)
97
+ * const value2 = await store.get("key2");
98
+ * // "value2"
99
+ * const value3 = await store.get("key3");
100
+ * // { hello: "world" }
101
+ * ```
102
+ *
103
+ * **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
104
+ */
105
+ get(key: string): Promise<TD | null>;
106
+ get<T extends TD>(key: string): Promise<T | null>;
107
+ /**
108
+ * Check whether a key exists or not:
109
+ *
110
+ * ```js
111
+ * if (await store.has("key1")) { ... }
112
+ * ```
113
+ *
114
+ * If you are going to use the value, it's better to just read it:
115
+ *
116
+ * ```js
117
+ * const val = await store.get("key1");
118
+ * if (val) { ... }
119
+ * ```
120
+ *
121
+ * **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
122
+ */
123
+ has(key: string): Promise<boolean>;
124
+ /**
125
+ * Remove a single key and its value from the store:
126
+ *
127
+ * ```js
128
+ * const key = await store.del("key1");
129
+ * ```
130
+ *
131
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
132
+ */
133
+ del(key: string): Promise<string>;
134
+ /**
135
+ * @alias of .del(key: string)
136
+ * Remove a single key and its value from the store:
137
+ *
138
+ * ```js
139
+ * const key = await store.delete("key1");
140
+ * ```
141
+ *
142
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
143
+ */
144
+ delete(key: string): Promise<string>;
145
+ /**
146
+ * An iterator that goes through all of the key:value pairs in the client
147
+ *
148
+ * ```js
149
+ * for await (const [key, value] of store) {
150
+ * console.log(key, value);
151
+ * }
152
+ * ```
153
+ *
154
+ * **[→ Full Iterator Docs](https://polystore.dev/documentation#iterator)**
155
+ */
156
+ [Symbol.asyncIterator](): AsyncGenerator<[string, TD], void, unknown>;
157
+ [Symbol.asyncIterator]<T extends TD>(): AsyncGenerator<[
158
+ string,
159
+ T
160
+ ], void, unknown>;
161
+ /**
162
+ * Return an array of the entries, in the [key, value] format:
163
+ *
164
+ * ```js
165
+ * const entries = await store.entries();
166
+ * // [["key1", "value1"], ["key2", { hello: "world" }], ...]
167
+ *
168
+ * // To limit it to a given prefix, use `.prefix()`:
169
+ * const sessions = await store.prefix("session:").entries();
170
+ * ```
171
+ *
172
+ * **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
173
+ */
174
+ entries(): Promise<[string, TD][]>;
175
+ entries<T extends TD>(): Promise<[string, T][]>;
176
+ /**
177
+ * Return an array of the keys in the store:
178
+ *
179
+ * ```js
180
+ * const keys = await store.keys();
181
+ * // ["key1", "key2", ...]
182
+ *
183
+ * // To limit it to a given prefix, use `.prefix()`:
184
+ * const sessions = await store.prefix("session:").keys();
185
+ * ```
186
+ *
187
+ * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
188
+ */
189
+ keys(): Promise<string[]>;
190
+ /**
191
+ * Return an array of the values in the store:
192
+ *
193
+ * ```js
194
+ * const values = await store.values();
195
+ * // ["value1", { hello: "world" }, ...]
196
+ *
197
+ * // To limit it to a given prefix, use `.prefix()`:
198
+ * const sessions = await store.prefix("session:").values();
199
+ * ```
200
+ *
201
+ * **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
202
+ */
203
+ values(): Promise<TD[]>;
204
+ values<T extends TD>(): Promise<T[]>;
205
+ /**
206
+ * Return an object with the keys:values in the store:
207
+ *
208
+ * ```js
209
+ * const obj = await store.all();
210
+ * // { key1: "value1", key2: { hello: "world" }, ... }
211
+ *
212
+ * // To limit it to a given prefix, use `.prefix()`:
213
+ * const sessions = await store.prefix("session:").all();
214
+ * ```
215
+ *
216
+ * **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
217
+ */
218
+ all(): Promise<Record<string, TD>>;
219
+ all<T extends TD>(): Promise<Record<string, T>>;
220
+ /**
221
+ * Create a substore where all the keys are stored with
222
+ * the given prefix:
223
+ *
224
+ * ```js
225
+ * const session = store.prefix("session:");
226
+ * await session.set("key1", "value1");
227
+ * console.log(await session.entries()); // session.
228
+ * // [["key1", "value1"]]
229
+ * console.log(await store.entries()); // store.
230
+ * // [["session:key1", "value1"]]
231
+ * ```
232
+ *
233
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
234
+ */
235
+ prefix(prefix?: Prefix): Store<TD>;
236
+ /**
237
+ * Create a substore where all the keys are stored with
238
+ * the given prefix:
239
+ *
240
+ * ```js
241
+ * const session = store.prefix("session:");
242
+ * await session.set("key1", "value1");
243
+ * console.log(await session.entries()); // session.
244
+ * // [["key1", "value1"]]
245
+ * console.log(await store.entries()); // store.
246
+ * // [["session:key1", "value1"]]
247
+ * ```
248
+ *
249
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
250
+ */
251
+ expires(expires?: Expires): Store<TD>;
252
+ /**
253
+ * Delete all of the records of the store:
254
+ *
255
+ * ```js
256
+ * await store.clear();
257
+ * ```
258
+ *
259
+ * It's useful for cache invalidation, clearing the data, and testing.
260
+ *
261
+ * **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
262
+ */
263
+ clear(): Promise<void>;
264
+ /**
265
+ * Remove all expired records from the store.
266
+ *
267
+ * ```js
268
+ * await store.prune();
269
+ * ```
270
+ *
271
+ * Only affects stores where expiration is managed by this wrapper.
272
+ */
273
+ prune(): Promise<void>;
274
+ /**
275
+ * Stop the connection to the store, if any:
276
+ *
277
+ * ```js
278
+ * await session.set("key1", "value1");
279
+ * await store.close();
280
+ * await session.set("key2", "value2"); // error
281
+ * ```
282
+ *
283
+ * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
284
+ */
285
+ close(): Promise<void>;
286
+ }
287
+
288
+ declare class PolystoreHonoStore implements Store$1 {
289
+ private store;
290
+ constructor(store: Store);
291
+ prefix(prefix?: string): PolystoreHonoStore;
292
+ getSessionById(sessionId?: string): Promise<SessionData | null | undefined>;
293
+ createSession(sessionId: string, initialData: SessionData): Promise<void>;
294
+ persistSessionData(sessionId: string, sessionData: SessionData): Promise<void>;
295
+ deleteSession(sessionId: string): Promise<void>;
296
+ }
297
+ declare function honoStore(client?: Map<any, any>): PolystoreHonoStore;
298
+
299
+ export { PolystoreHonoStore, honoStore as default };
@@ -0,0 +1,36 @@
1
+ // src/integrations/hono-sessions.ts
2
+ import kv from "polystore";
3
+ var ttlFromSession = (data) => {
4
+ if (!data._expire) return void 0;
5
+ const secs = Math.ceil((new Date(data._expire).getTime() - Date.now()) / 1e3);
6
+ return secs > 0 ? { expires: secs } : void 0;
7
+ };
8
+ var PolystoreHonoStore = class _PolystoreHonoStore {
9
+ store;
10
+ constructor(store) {
11
+ this.store = store;
12
+ }
13
+ prefix(prefix = "") {
14
+ return new _PolystoreHonoStore(this.store.prefix(prefix));
15
+ }
16
+ async getSessionById(sessionId) {
17
+ if (!sessionId) return null;
18
+ return this.store.get(sessionId);
19
+ }
20
+ async createSession(sessionId, initialData) {
21
+ await this.store.set(sessionId, initialData, ttlFromSession(initialData));
22
+ }
23
+ async persistSessionData(sessionId, sessionData) {
24
+ await this.store.set(sessionId, sessionData, ttlFromSession(sessionData));
25
+ }
26
+ async deleteSession(sessionId) {
27
+ await this.store.del(sessionId);
28
+ }
29
+ };
30
+ function honoStore(client = /* @__PURE__ */ new Map()) {
31
+ return new PolystoreHonoStore(kv(client));
32
+ }
33
+ export {
34
+ PolystoreHonoStore,
35
+ honoStore as default
36
+ };
package/src/express.js DELETED
@@ -1,47 +0,0 @@
1
- import session from "express-session";
2
- import kv from "../index.js";
3
-
4
- const ttlFromSession = (data) => {
5
- const maxAge = data?.cookie?.originalMaxAge;
6
- return typeof maxAge === "number" ? Math.ceil(maxAge / 1000) : null;
7
- };
8
-
9
- export class PolystoreSessionStore extends session.Store {
10
- constructor(store) {
11
- super();
12
- this.store = store;
13
- }
14
-
15
- prefix(prefix = "") {
16
- return new PolystoreSessionStore(this.store.prefix(prefix));
17
- }
18
-
19
- get(sid, cb) {
20
- this.store.get(sid).then((data) => cb(null, data)).catch(cb);
21
- }
22
-
23
- set(sid, data, cb) {
24
- this.store
25
- .set(sid, data, ttlFromSession(data))
26
- .then(() => cb && cb())
27
- .catch((error) => cb && cb(error));
28
- }
29
-
30
- destroy(sid, cb) {
31
- this.store
32
- .del(sid)
33
- .then(() => cb && cb())
34
- .catch((error) => cb && cb(error));
35
- }
36
-
37
- touch(sid, data, cb) {
38
- this.store
39
- .set(sid, data, ttlFromSession(data))
40
- .then(() => cb && cb())
41
- .catch((error) => cb && cb(error));
42
- }
43
- }
44
-
45
- export default function expressStore(client = new Map()) {
46
- return new PolystoreSessionStore(kv(client));
47
- }