polystore 0.17.0 → 0.19.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 +76 -32
- package/index.js +156 -82
- package/package.json +11 -3
- package/readme.md +186 -60
- package/src/express.js +47 -0
package/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
type Prefix = string;
|
|
2
|
+
type Expires = number | null | string;
|
|
1
3
|
type Options = {
|
|
2
|
-
|
|
4
|
+
prefix?: Prefix;
|
|
5
|
+
expires?: Expires;
|
|
3
6
|
};
|
|
4
7
|
type StoreData<T extends Serializable = Serializable> = {
|
|
5
8
|
value: T;
|
|
@@ -9,13 +12,14 @@ type Serializable = string | number | boolean | null | (Serializable | null)[] |
|
|
|
9
12
|
[key: string]: Serializable | null;
|
|
10
13
|
};
|
|
11
14
|
interface ClientExpires {
|
|
12
|
-
|
|
15
|
+
TYPE: string;
|
|
16
|
+
HAS_EXPIRATION: true;
|
|
13
17
|
promise?: Promise<any>;
|
|
14
18
|
test?: (client: any) => boolean;
|
|
15
19
|
get<T extends Serializable>(key: string): Promise<T | null> | T | null;
|
|
16
|
-
set<T extends Serializable>(key: string, value: T,
|
|
20
|
+
set<T extends Serializable>(key: string, value: T, expires?: Expires): Promise<any> | any;
|
|
17
21
|
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,
|
|
22
|
+
add?<T extends Serializable>(prefix: string, value: T, expires?: Expires): Promise<string>;
|
|
19
23
|
has?(key: string): Promise<boolean> | boolean;
|
|
20
24
|
del?(key: string): Promise<any> | any;
|
|
21
25
|
keys?(prefix: string): Promise<string[]> | string[];
|
|
@@ -27,13 +31,14 @@ interface ClientExpires {
|
|
|
27
31
|
close?(): Promise<any> | any;
|
|
28
32
|
}
|
|
29
33
|
interface ClientNonExpires {
|
|
30
|
-
|
|
34
|
+
TYPE: string;
|
|
35
|
+
HAS_EXPIRATION: false;
|
|
31
36
|
promise?: Promise<any>;
|
|
32
37
|
test?: (client: any) => boolean;
|
|
33
38
|
get<T extends Serializable>(key: string): Promise<StoreData<T> | null> | StoreData<T> | null;
|
|
34
|
-
set<T extends Serializable>(key: string, value: StoreData<T> | null,
|
|
39
|
+
set<T extends Serializable>(key: string, value: StoreData<T> | null, ttl?: Expires): Promise<any> | any;
|
|
35
40
|
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>,
|
|
41
|
+
add?<T extends Serializable>(prefix: string, value: StoreData<T>, ttl?: Expires): Promise<string>;
|
|
37
42
|
has?(key: string): Promise<boolean> | boolean;
|
|
38
43
|
del?(key: string): Promise<any> | any;
|
|
39
44
|
keys?(prefix: string): Promise<string[]> | string[];
|
|
@@ -46,12 +51,14 @@ interface ClientNonExpires {
|
|
|
46
51
|
}
|
|
47
52
|
type Client = ClientExpires | ClientNonExpires;
|
|
48
53
|
|
|
49
|
-
declare class Store<
|
|
54
|
+
declare class Store<TD extends Serializable = Serializable> {
|
|
50
55
|
#private;
|
|
51
|
-
PREFIX:
|
|
56
|
+
PREFIX: Prefix;
|
|
57
|
+
EXPIRES: Expires;
|
|
52
58
|
promise: Promise<Client> | null;
|
|
53
59
|
client: Client;
|
|
54
|
-
|
|
60
|
+
type: string;
|
|
61
|
+
constructor(clientPromise?: any, options?: Options);
|
|
55
62
|
/**
|
|
56
63
|
* Save the data on an autogenerated key, can add expiration as well:
|
|
57
64
|
*
|
|
@@ -63,8 +70,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
63
70
|
*
|
|
64
71
|
* **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
|
|
65
72
|
*/
|
|
66
|
-
add(value:
|
|
67
|
-
add<T extends
|
|
73
|
+
add(value: TD, options?: Options): Promise<string>;
|
|
74
|
+
add<T extends TD>(value: T, options?: Options): Promise<string>;
|
|
68
75
|
/**
|
|
69
76
|
* Save the data on the given key, can add expiration as well:
|
|
70
77
|
*
|
|
@@ -76,8 +83,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
76
83
|
*
|
|
77
84
|
* **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
|
|
78
85
|
*/
|
|
79
|
-
set(key: string, value:
|
|
80
|
-
set<T extends
|
|
86
|
+
set(key: string, value: TD, options?: Options): Promise<string>;
|
|
87
|
+
set<T extends TD>(key: string, value: T, options?: Options): Promise<string>;
|
|
81
88
|
/**
|
|
82
89
|
* Read a single value from the KV store:
|
|
83
90
|
*
|
|
@@ -92,8 +99,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
92
99
|
*
|
|
93
100
|
* **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
|
|
94
101
|
*/
|
|
95
|
-
get(key: string): Promise<
|
|
96
|
-
get<T extends
|
|
102
|
+
get(key: string): Promise<TD | null>;
|
|
103
|
+
get<T extends TD>(key: string): Promise<T | null>;
|
|
97
104
|
/**
|
|
98
105
|
* Check whether a key exists or not:
|
|
99
106
|
*
|
|
@@ -121,6 +128,17 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
121
128
|
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
122
129
|
*/
|
|
123
130
|
del(key: string): Promise<string>;
|
|
131
|
+
/**
|
|
132
|
+
* @alias of .del(key: string)
|
|
133
|
+
* Remove a single key and its value from the store:
|
|
134
|
+
*
|
|
135
|
+
* ```js
|
|
136
|
+
* const key = await store.delete("key1");
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
140
|
+
*/
|
|
141
|
+
delete(key: string): Promise<string>;
|
|
124
142
|
/**
|
|
125
143
|
* An iterator that goes through all of the key:value pairs in the client
|
|
126
144
|
*
|
|
@@ -132,8 +150,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
132
150
|
*
|
|
133
151
|
* **[→ Full Iterator Docs](https://polystore.dev/documentation#iterator)**
|
|
134
152
|
*/
|
|
135
|
-
[Symbol.asyncIterator](): AsyncGenerator<[string,
|
|
136
|
-
[Symbol.asyncIterator]<T extends
|
|
153
|
+
[Symbol.asyncIterator](): AsyncGenerator<[string, TD], void, unknown>;
|
|
154
|
+
[Symbol.asyncIterator]<T extends TD>(): AsyncGenerator<[
|
|
137
155
|
string,
|
|
138
156
|
T
|
|
139
157
|
], void, unknown>;
|
|
@@ -150,8 +168,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
150
168
|
*
|
|
151
169
|
* **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
|
|
152
170
|
*/
|
|
153
|
-
entries(): Promise<[string,
|
|
154
|
-
entries<T extends
|
|
171
|
+
entries(): Promise<[string, TD][]>;
|
|
172
|
+
entries<T extends TD>(): Promise<[string, T][]>;
|
|
155
173
|
/**
|
|
156
174
|
* Return an array of the keys in the store:
|
|
157
175
|
*
|
|
@@ -179,8 +197,8 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
179
197
|
*
|
|
180
198
|
* **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
|
|
181
199
|
*/
|
|
182
|
-
values(): Promise<
|
|
183
|
-
values<T extends
|
|
200
|
+
values(): Promise<TD[]>;
|
|
201
|
+
values<T extends TD>(): Promise<T[]>;
|
|
184
202
|
/**
|
|
185
203
|
* Return an object with the keys:values in the store:
|
|
186
204
|
*
|
|
@@ -194,20 +212,24 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
194
212
|
*
|
|
195
213
|
* **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
|
|
196
214
|
*/
|
|
197
|
-
all(): Promise<Record<string,
|
|
198
|
-
all<T extends
|
|
215
|
+
all(): Promise<Record<string, TD>>;
|
|
216
|
+
all<T extends TD>(): Promise<Record<string, T>>;
|
|
199
217
|
/**
|
|
200
|
-
*
|
|
218
|
+
* Create a substore where all the keys are stored with
|
|
219
|
+
* the given prefix:
|
|
201
220
|
*
|
|
202
221
|
* ```js
|
|
203
|
-
*
|
|
222
|
+
* const session = store.prefix("session:");
|
|
223
|
+
* await session.set("key1", "value1");
|
|
224
|
+
* console.log(await session.entries()); // session.
|
|
225
|
+
* // [["key1", "value1"]]
|
|
226
|
+
* console.log(await store.entries()); // store.
|
|
227
|
+
* // [["session:key1", "value1"]]
|
|
204
228
|
* ```
|
|
205
229
|
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
|
|
230
|
+
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
209
231
|
*/
|
|
210
|
-
|
|
232
|
+
prefix(prefix?: Prefix): Store<TD>;
|
|
211
233
|
/**
|
|
212
234
|
* Create a substore where all the keys are stored with
|
|
213
235
|
* the given prefix:
|
|
@@ -223,7 +245,29 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
223
245
|
*
|
|
224
246
|
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
225
247
|
*/
|
|
226
|
-
|
|
248
|
+
expires(expires?: Expires): Store<TD>;
|
|
249
|
+
/**
|
|
250
|
+
* Delete all of the records of the store:
|
|
251
|
+
*
|
|
252
|
+
* ```js
|
|
253
|
+
* await store.clear();
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* It's useful for cache invalidation, clearing the data, and testing.
|
|
257
|
+
*
|
|
258
|
+
* **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
|
|
259
|
+
*/
|
|
260
|
+
clear(): Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Remove all expired records from the store.
|
|
263
|
+
*
|
|
264
|
+
* ```js
|
|
265
|
+
* await store.prune();
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* Only affects stores where expiration is managed by this wrapper.
|
|
269
|
+
*/
|
|
270
|
+
prune(): Promise<void>;
|
|
227
271
|
/**
|
|
228
272
|
* Stop the connection to the store, if any:
|
|
229
273
|
*
|
|
@@ -238,6 +282,6 @@ declare class Store<TDefault extends Serializable = Serializable> {
|
|
|
238
282
|
close(): Promise<void>;
|
|
239
283
|
}
|
|
240
284
|
declare function createStore(): Store<Serializable>;
|
|
241
|
-
declare function createStore<T extends Serializable = Serializable>(client?: any): Store<T>;
|
|
285
|
+
declare function createStore<T extends Serializable = Serializable>(client?: any, options?: Options): Store<T>;
|
|
242
286
|
|
|
243
287
|
export { type Client, type Serializable, Store, createStore as default };
|
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/clients/Client.ts
|
|
2
2
|
var Client = class {
|
|
3
|
-
|
|
3
|
+
TYPE;
|
|
4
|
+
HAS_EXPIRATION = false;
|
|
4
5
|
client;
|
|
5
6
|
encode = (val) => JSON.stringify(val, null, 2);
|
|
6
7
|
decode = (val) => val ? JSON.parse(val) : null;
|
|
@@ -11,8 +12,9 @@ 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
|
+
HAS_EXPIRATION = true;
|
|
16
18
|
static test = (client) => typeof client === "string" && /^https?:\/\//.test(client);
|
|
17
19
|
#api = async (key, opts = "", method = "GET", body) => {
|
|
18
20
|
const url = `${this.client.replace(/\/$/, "")}/${encodeURIComponent(key)}${opts}`;
|
|
@@ -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,
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
static test = (client) => client?.constructor?.name === "KvNamespace" || client?.constructor?.name === "EdgeKVNamespace";
|
|
52
|
+
HAS_EXPIRATION = true;
|
|
53
|
+
static testKeys = ["getWithMetadata", "get", "list", "delete"];
|
|
52
54
|
get = async (key) => {
|
|
53
|
-
|
|
55
|
+
const value = await this.client.get(key);
|
|
56
|
+
return this.decode(value);
|
|
54
57
|
};
|
|
55
|
-
set = async (key, data,
|
|
56
|
-
const expirationTtl =
|
|
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,8 +96,9 @@ 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
|
+
HAS_EXPIRATION = true;
|
|
98
102
|
// Check if this is the right class for the given client
|
|
99
103
|
static test = (client) => {
|
|
100
104
|
return client === "cookie" || client === "cookies";
|
|
@@ -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,
|
|
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, "",
|
|
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
|
+
HAS_EXPIRATION = false;
|
|
144
149
|
// Check if this is the right class for the given client
|
|
145
|
-
static
|
|
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,8 +170,9 @@ 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
|
+
HAS_EXPIRATION = false;
|
|
178
176
|
fsp;
|
|
179
177
|
file = "";
|
|
180
178
|
#lock = Promise.resolve();
|
|
@@ -266,8 +264,9 @@ 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
|
+
HAS_EXPIRATION = false;
|
|
271
270
|
fsp;
|
|
272
271
|
folder;
|
|
273
272
|
// Check if this is the right class for the given client
|
|
@@ -311,8 +310,9 @@ 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
|
+
HAS_EXPIRATION = false;
|
|
316
316
|
// Check if this is the right class for the given client
|
|
317
317
|
static test = (client) => client?.defineDriver && client?.dropInstance && client?.INDEXEDDB;
|
|
318
318
|
get = (key) => this.client.getItem(key);
|
|
@@ -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
|
+
HAS_EXPIRATION = false;
|
|
349
350
|
// Check if this is the right class for the given client
|
|
350
|
-
static
|
|
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,8 +379,9 @@ 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
|
+
HAS_EXPIRATION = false;
|
|
383
385
|
// Check if this is the right class for the given client
|
|
384
386
|
static test = (client) => client instanceof Map;
|
|
385
387
|
get = (key) => this.client.get(key) ?? null;
|
|
@@ -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
|
+
HAS_EXPIRATION = 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,
|
|
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,11 +441,12 @@ 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
|
|
444
448
|
// expirations much easier, so it's really "somewhere in between"
|
|
445
|
-
|
|
449
|
+
HAS_EXPIRATION = true;
|
|
446
450
|
// The table name to use
|
|
447
451
|
table = "kv";
|
|
448
452
|
// Make sure the folder already exists, so attempt to create it
|
|
@@ -474,7 +478,7 @@ var SQLite = class extends Client {
|
|
|
474
478
|
}
|
|
475
479
|
return this.decode(row.value);
|
|
476
480
|
};
|
|
477
|
-
set = (id, data,
|
|
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,8 +528,9 @@ ${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
|
+
HAS_EXPIRATION = false;
|
|
529
534
|
// Check if this is the right class for the given client
|
|
530
535
|
static test(client) {
|
|
531
536
|
if (typeof Storage === "undefined") return false;
|
|
@@ -605,23 +610,36 @@ function unix(expires) {
|
|
|
605
610
|
// src/index.ts
|
|
606
611
|
var Store = class _Store {
|
|
607
612
|
PREFIX = "";
|
|
613
|
+
EXPIRES = null;
|
|
608
614
|
promise;
|
|
609
615
|
client;
|
|
610
|
-
|
|
616
|
+
type = "UNKNOWN";
|
|
617
|
+
constructor(clientPromise = /* @__PURE__ */ new Map(), options = {
|
|
618
|
+
prefix: "",
|
|
619
|
+
expires: null
|
|
620
|
+
}) {
|
|
621
|
+
this.PREFIX = options.prefix || "";
|
|
622
|
+
this.EXPIRES = parse(options.expires || null);
|
|
611
623
|
this.promise = Promise.resolve(clientPromise).then(async (client) => {
|
|
612
624
|
this.client = this.#find(client);
|
|
613
625
|
this.#validate(this.client);
|
|
614
626
|
this.promise = null;
|
|
615
627
|
await this.client.promise;
|
|
628
|
+
this.type = this.client?.TYPE || this.type;
|
|
616
629
|
return client;
|
|
617
630
|
});
|
|
618
631
|
}
|
|
619
632
|
#find(store) {
|
|
620
633
|
if (store instanceof _Store) return store.client;
|
|
621
634
|
for (let client of Object.values(clients_default)) {
|
|
622
|
-
if (
|
|
635
|
+
if ("test" in client && client.test(store)) {
|
|
623
636
|
return new client(store);
|
|
624
637
|
}
|
|
638
|
+
if ("testKeys" in client && typeof store === "object") {
|
|
639
|
+
if (client.testKeys.every((key) => store[key])) {
|
|
640
|
+
return new client(store);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
625
643
|
}
|
|
626
644
|
if (typeof store === "function" && /^class\s/.test(Function.prototype.toString.call(store))) {
|
|
627
645
|
return new store();
|
|
@@ -633,7 +651,7 @@ var Store = class _Store {
|
|
|
633
651
|
if (!client.set || !client.get || !client.iterate) {
|
|
634
652
|
throw new Error("Client should have .get(), .set() and .iterate()");
|
|
635
653
|
}
|
|
636
|
-
if (client.
|
|
654
|
+
if (client.HAS_EXPIRATION) return;
|
|
637
655
|
for (let method of ["has", "keys", "values"]) {
|
|
638
656
|
if (client[method]) {
|
|
639
657
|
const msg = `You can only define client.${method}() when the client manages the expiration.`;
|
|
@@ -641,50 +659,48 @@ var Store = class _Store {
|
|
|
641
659
|
}
|
|
642
660
|
}
|
|
643
661
|
}
|
|
644
|
-
// Check if the given data is fresh or not
|
|
662
|
+
// Check if the given data is fresh or not
|
|
645
663
|
#isFresh(data, key) {
|
|
646
664
|
if (!data || typeof data !== "object" || !("value" in data)) {
|
|
647
|
-
if (key) this.del(key);
|
|
648
665
|
return false;
|
|
649
666
|
}
|
|
650
|
-
|
|
651
|
-
if (data.expires > Date.now()) return true;
|
|
652
|
-
if (key) this.del(key);
|
|
653
|
-
return false;
|
|
667
|
+
return data.expires === null || data.expires > Date.now();
|
|
654
668
|
}
|
|
655
|
-
|
|
669
|
+
// Normalize returns the instance's `prefix` and `expires`
|
|
670
|
+
#expiration(expires) {
|
|
671
|
+
return parse(expires !== void 0 ? expires : this.EXPIRES);
|
|
672
|
+
}
|
|
673
|
+
async add(value, options) {
|
|
656
674
|
await this.promise;
|
|
657
|
-
|
|
675
|
+
const expires = this.#expiration(options?.expires);
|
|
676
|
+
const prefix = options?.prefix || this.PREFIX;
|
|
658
677
|
if (this.client.add) {
|
|
659
|
-
if (this.client.
|
|
660
|
-
return
|
|
678
|
+
if (this.client.HAS_EXPIRATION) {
|
|
679
|
+
return this.client.add(prefix, value, expires);
|
|
661
680
|
}
|
|
662
|
-
expires
|
|
663
|
-
const key2 = await this.client.add(this.PREFIX, { expires, value });
|
|
664
|
-
return key2;
|
|
681
|
+
return this.client.add(prefix, { expires: unix(expires), value });
|
|
665
682
|
}
|
|
666
|
-
|
|
667
|
-
return this.set(key, value, { expires });
|
|
683
|
+
return this.set(createId(), value, { prefix, expires });
|
|
668
684
|
}
|
|
669
|
-
async set(key, value, options
|
|
685
|
+
async set(key, value, options) {
|
|
670
686
|
await this.promise;
|
|
671
|
-
const
|
|
672
|
-
|
|
687
|
+
const expires = this.#expiration(options?.expires);
|
|
688
|
+
const prefix = options?.prefix || this.PREFIX;
|
|
689
|
+
const id = prefix + key;
|
|
673
690
|
if (value === null || typeof expires === "number" && expires <= 0) {
|
|
674
691
|
return this.del(key);
|
|
675
692
|
}
|
|
676
|
-
if (this.client.
|
|
677
|
-
await this.client.set(id, value,
|
|
693
|
+
if (this.client.HAS_EXPIRATION) {
|
|
694
|
+
await this.client.set(id, value, expires);
|
|
678
695
|
return key;
|
|
679
696
|
}
|
|
680
|
-
expires
|
|
681
|
-
await this.client.set(id, { expires, value });
|
|
697
|
+
await this.client.set(id, { expires: unix(expires), value });
|
|
682
698
|
return key;
|
|
683
699
|
}
|
|
684
700
|
async get(key) {
|
|
685
701
|
await this.promise;
|
|
686
702
|
const id = this.PREFIX + key;
|
|
687
|
-
if (this.client.
|
|
703
|
+
if (this.client.HAS_EXPIRATION) {
|
|
688
704
|
const data = await this.client.get(id) ?? null;
|
|
689
705
|
if (data === null) return null;
|
|
690
706
|
return data;
|
|
@@ -735,16 +751,29 @@ var Store = class _Store {
|
|
|
735
751
|
await this.client.del(id);
|
|
736
752
|
return key;
|
|
737
753
|
}
|
|
738
|
-
if (this.client.
|
|
739
|
-
await this.client.set(id, null,
|
|
754
|
+
if (this.client.HAS_EXPIRATION) {
|
|
755
|
+
await this.client.set(id, null, 0);
|
|
740
756
|
} else {
|
|
741
757
|
await this.client.set(id, null);
|
|
742
758
|
}
|
|
743
759
|
return key;
|
|
744
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* @alias of .del(key: string)
|
|
763
|
+
* Remove a single key and its value from the store:
|
|
764
|
+
*
|
|
765
|
+
* ```js
|
|
766
|
+
* const key = await store.delete("key1");
|
|
767
|
+
* ```
|
|
768
|
+
*
|
|
769
|
+
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
770
|
+
*/
|
|
771
|
+
async delete(key) {
|
|
772
|
+
return this.del(key);
|
|
773
|
+
}
|
|
745
774
|
async *[Symbol.asyncIterator]() {
|
|
746
775
|
await this.promise;
|
|
747
|
-
if (this.client.
|
|
776
|
+
if (this.client.HAS_EXPIRATION) {
|
|
748
777
|
for await (const [name, data] of this.client.iterate(this.PREFIX)) {
|
|
749
778
|
const key = name.slice(this.PREFIX.length);
|
|
750
779
|
yield [key, data];
|
|
@@ -762,7 +791,7 @@ var Store = class _Store {
|
|
|
762
791
|
await this.promise;
|
|
763
792
|
const trim = (key) => key.slice(this.PREFIX.length);
|
|
764
793
|
if (this.client.entries) {
|
|
765
|
-
if (this.client.
|
|
794
|
+
if (this.client.HAS_EXPIRATION) {
|
|
766
795
|
const entries = await this.client.entries(this.PREFIX);
|
|
767
796
|
return entries.map(([k, v]) => [trim(k), v]);
|
|
768
797
|
} else {
|
|
@@ -770,7 +799,7 @@ var Store = class _Store {
|
|
|
770
799
|
return entries.map(([k, v]) => [trim(k), v]).filter(([key, data]) => this.#isFresh(data, key)).map(([key, data]) => [key, data.value]);
|
|
771
800
|
}
|
|
772
801
|
}
|
|
773
|
-
if (this.client.
|
|
802
|
+
if (this.client.HAS_EXPIRATION) {
|
|
774
803
|
const list = [];
|
|
775
804
|
for await (const [k, v] of this.client.iterate(this.PREFIX)) {
|
|
776
805
|
list.push([trim(k), v]);
|
|
@@ -812,7 +841,7 @@ var Store = class _Store {
|
|
|
812
841
|
async values() {
|
|
813
842
|
await this.promise;
|
|
814
843
|
if (this.client.values) {
|
|
815
|
-
if (this.client.
|
|
844
|
+
if (this.client.HAS_EXPIRATION) return this.client.values(this.PREFIX);
|
|
816
845
|
const list = await this.client.values(this.PREFIX);
|
|
817
846
|
return list.filter((data) => this.#isFresh(data)).map((data) => data.value);
|
|
818
847
|
}
|
|
@@ -823,6 +852,52 @@ var Store = class _Store {
|
|
|
823
852
|
const entries = await this.entries();
|
|
824
853
|
return Object.fromEntries(entries);
|
|
825
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Create a substore where all the keys are stored with
|
|
857
|
+
* the given prefix:
|
|
858
|
+
*
|
|
859
|
+
* ```js
|
|
860
|
+
* const session = store.prefix("session:");
|
|
861
|
+
* await session.set("key1", "value1");
|
|
862
|
+
* console.log(await session.entries()); // session.
|
|
863
|
+
* // [["key1", "value1"]]
|
|
864
|
+
* console.log(await store.entries()); // store.
|
|
865
|
+
* // [["session:key1", "value1"]]
|
|
866
|
+
* ```
|
|
867
|
+
*
|
|
868
|
+
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
869
|
+
*/
|
|
870
|
+
prefix(prefix = "") {
|
|
871
|
+
const store = new _Store(
|
|
872
|
+
Promise.resolve(this.promise).then(() => this.client)
|
|
873
|
+
);
|
|
874
|
+
store.PREFIX = this.PREFIX + prefix;
|
|
875
|
+
store.EXPIRES = this.EXPIRES;
|
|
876
|
+
return store;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Create a substore where all the keys are stored with
|
|
880
|
+
* the given prefix:
|
|
881
|
+
*
|
|
882
|
+
* ```js
|
|
883
|
+
* const session = store.prefix("session:");
|
|
884
|
+
* await session.set("key1", "value1");
|
|
885
|
+
* console.log(await session.entries()); // session.
|
|
886
|
+
* // [["key1", "value1"]]
|
|
887
|
+
* console.log(await store.entries()); // store.
|
|
888
|
+
* // [["session:key1", "value1"]]
|
|
889
|
+
* ```
|
|
890
|
+
*
|
|
891
|
+
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
892
|
+
*/
|
|
893
|
+
expires(expires = null) {
|
|
894
|
+
const store = new _Store(
|
|
895
|
+
Promise.resolve(this.promise).then(() => this.client)
|
|
896
|
+
);
|
|
897
|
+
store.EXPIRES = parse(expires);
|
|
898
|
+
store.PREFIX = this.PREFIX;
|
|
899
|
+
return store;
|
|
900
|
+
}
|
|
826
901
|
/**
|
|
827
902
|
* Delete all of the records of the store:
|
|
828
903
|
*
|
|
@@ -846,26 +921,25 @@ var Store = class _Store {
|
|
|
846
921
|
await Promise.all(keys.map((key) => this.del(key)));
|
|
847
922
|
}
|
|
848
923
|
/**
|
|
849
|
-
*
|
|
850
|
-
* the given prefix:
|
|
924
|
+
* Remove all expired records from the store.
|
|
851
925
|
*
|
|
852
926
|
* ```js
|
|
853
|
-
*
|
|
854
|
-
* await session.set("key1", "value1");
|
|
855
|
-
* console.log(await session.entries()); // session.
|
|
856
|
-
* // [["key1", "value1"]]
|
|
857
|
-
* console.log(await store.entries()); // store.
|
|
858
|
-
* // [["session:key1", "value1"]]
|
|
927
|
+
* await store.prune();
|
|
859
928
|
* ```
|
|
860
929
|
*
|
|
861
|
-
*
|
|
930
|
+
* Only affects stores where expiration is managed by this wrapper.
|
|
862
931
|
*/
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
932
|
+
async prune() {
|
|
933
|
+
await this.promise;
|
|
934
|
+
if (this.client.HAS_EXPIRATION) return;
|
|
935
|
+
for await (const [name, data] of this.client.iterate(
|
|
936
|
+
this.PREFIX
|
|
937
|
+
)) {
|
|
938
|
+
const key = name.slice(this.PREFIX.length);
|
|
939
|
+
if (!this.#isFresh(data, key)) {
|
|
940
|
+
await this.del(key);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
869
943
|
}
|
|
870
944
|
/**
|
|
871
945
|
* Stop the connection to the store, if any:
|
|
@@ -885,8 +959,8 @@ var Store = class _Store {
|
|
|
885
959
|
}
|
|
886
960
|
}
|
|
887
961
|
};
|
|
888
|
-
function createStore(client) {
|
|
889
|
-
return new Store(client);
|
|
962
|
+
function createStore(client, options) {
|
|
963
|
+
return new Store(client, options);
|
|
890
964
|
}
|
|
891
965
|
export {
|
|
892
966
|
createStore as default
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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",
|
|
@@ -11,9 +11,17 @@
|
|
|
11
11
|
"sideEffects": false,
|
|
12
12
|
"main": "index.js",
|
|
13
13
|
"types": "index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./index.d.ts",
|
|
17
|
+
"import": "./index.js"
|
|
18
|
+
},
|
|
19
|
+
"./express": "./src/express.js"
|
|
20
|
+
},
|
|
14
21
|
"files": [
|
|
15
22
|
"index.js",
|
|
16
|
-
"index.d.ts"
|
|
23
|
+
"index.d.ts",
|
|
24
|
+
"src/express.js"
|
|
17
25
|
],
|
|
18
26
|
"scripts": {
|
|
19
27
|
"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",
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Polystore [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [](https://bundlephobia.com/package/polystore)
|
|
2
2
|
|
|
3
|
-
A key-value library to unify the API of [many clients](#clients)
|
|
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";
|
|
@@ -18,14 +18,16 @@ These are all the methods of the [API](#api) (they are all `async`):
|
|
|
18
18
|
- [`.add(value, options?)`](#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
|
+
- [`.prefix(prefix)`](#prefix): create a sub-store that manages the keys with that prefix.
|
|
22
|
+
- [`.expires(expires)`](#expires): create a sub-store with a different default expiration.
|
|
21
23
|
- [Iterator](#iterator): go through all of the key/values one by one.
|
|
22
24
|
- [`.keys()`](#keys): get a list of all the available keys in the store.
|
|
23
25
|
- [`.values()`](#values): get a list of all the available values in the store.
|
|
24
26
|
- [`.entries()`](#entries): get a list of all the available key-value pairs.
|
|
25
27
|
- [`.all()`](#all): get an object of all the key:values mapped.
|
|
26
|
-
- [`.clear()`](#clear): delete
|
|
28
|
+
- [`.clear()`](#clear): delete **all** of the data in the store, effectively resetting it.
|
|
29
|
+
- [`.prune()`](#prune): delete only the **expired** data from the store.
|
|
27
30
|
- [`.close()`](#close): (only _some_ stores) ends the connection to the store.
|
|
28
|
-
- [`.prefix(prefix)`](#prefix): create a sub-store that manages the keys with that prefix.
|
|
29
31
|
|
|
30
32
|
Available clients for the KV store:
|
|
31
33
|
|
|
@@ -97,7 +99,7 @@ The base `kv()` initialization is shared across clients ([see full clients list]
|
|
|
97
99
|
import kv from "polystore";
|
|
98
100
|
|
|
99
101
|
// Initialize it; NO "new"; NO "await", just a plain function wrap:
|
|
100
|
-
const store = kv(
|
|
102
|
+
const store = kv(MyClientInstance, { expires: null, prefix: "" });
|
|
101
103
|
|
|
102
104
|
// use the store
|
|
103
105
|
```
|
|
@@ -120,23 +122,24 @@ store.get<number>("abc"); // number | null
|
|
|
120
122
|
store.set<number>("abc", 10);
|
|
121
123
|
|
|
122
124
|
store.set<number>("abc", "hello"); // FAILS
|
|
123
|
-
|
|
125
|
+
```
|
|
124
126
|
|
|
125
127
|
> [!WARNING]
|
|
126
|
-
> If you enforce types at _both_ the store level and method level, the method type must be a subset of the store type. For example, `kv<string | number>().get<string>("a")` works, but `kv<string>().get<number>("a")`
|
|
128
|
+
> If you enforce types at _both_ the store level and method level, the method type must be a subset of the store type. For example, `kv<string | number>().get<string>("a")` works, but `kv<string>().get<number>("a")` will show an error as expected.
|
|
127
129
|
|
|
128
130
|
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.
|
|
129
131
|
|
|
130
|
-
These are the exported types, `Client`, `Serializable` and `
|
|
132
|
+
These are the exported types, `Client`, `Serializable`, `Store` and `Options`:
|
|
131
133
|
|
|
132
134
|
```ts
|
|
133
135
|
import kv from "polystore";
|
|
134
|
-
import type { Client, Serializable, Store } from "polystore";
|
|
136
|
+
import type { Client, Serializable, Store, Options } from "polystore";
|
|
135
137
|
|
|
136
138
|
const client: Client = ...; // See #creating-a-store
|
|
137
|
-
const store: Store = kv(client);
|
|
138
|
-
const
|
|
139
|
-
|
|
139
|
+
const store: Store = kv(client, opts as Options);
|
|
140
|
+
const key = await store.set('hello', 'b', opts as Options)
|
|
141
|
+
const value: Serializable = await store.get('hello');
|
|
142
|
+
```
|
|
140
143
|
|
|
141
144
|
### .get()
|
|
142
145
|
|
|
@@ -150,7 +153,7 @@ console.log(await store.get("key2")); // ["my", "grocery", "list"]
|
|
|
150
153
|
console.log(await store.get("key3")); // { name: "Francisco" }
|
|
151
154
|
```
|
|
152
155
|
|
|
153
|
-
You can specify the type
|
|
156
|
+
You can specify the type of the return:
|
|
154
157
|
|
|
155
158
|
```ts
|
|
156
159
|
console.log(await store.get<string>("key1")); // "Hello World"
|
|
@@ -197,15 +200,12 @@ await store.del('key2');
|
|
|
197
200
|
console.log(await store.get("key2")); // null
|
|
198
201
|
|
|
199
202
|
// Expired
|
|
200
|
-
await store.set("key3", "Hello", { expires:
|
|
203
|
+
await store.set("key3", "Hello", { expires: "1s" });
|
|
201
204
|
console.log(await store.get("key3")); // "Hello"
|
|
202
205
|
await new Promise((done) => setTimeout(done, 2000)); // Wait 2 seconds
|
|
203
206
|
console.log(await store.get("key3")); // null (already expired)
|
|
204
207
|
```
|
|
205
208
|
|
|
206
|
-
> [!WARNING]
|
|
207
|
-
> Attempting to read an expired key that wasn't automatically evicted will trigger a delete internally. This should not affect you directly, but it's good to know since you might expect a `read` operation not to modify the underlying data. See the [Eviction](#eviction) section and your specific client for details.
|
|
208
|
-
|
|
209
209
|
If you are using a substore with `.prefix()`, `.get()` will respect it:
|
|
210
210
|
|
|
211
211
|
```ts
|
|
@@ -220,8 +220,12 @@ console.log(await session.get('key1'));
|
|
|
220
220
|
|
|
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
|
-
```
|
|
224
|
-
await store.set(
|
|
223
|
+
```ts
|
|
224
|
+
const key = await store.set(
|
|
225
|
+
key: string,
|
|
226
|
+
value: any,
|
|
227
|
+
options?: { expires?: number|string; prefix?: string }
|
|
228
|
+
);
|
|
225
229
|
|
|
226
230
|
await store.set("key1", "Hello World");
|
|
227
231
|
await store.set("key2", ["my", "grocery", "list"], { expires: "1h" });
|
|
@@ -292,14 +296,17 @@ These are all the units available:
|
|
|
292
296
|
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
297
|
|
|
294
298
|
```js
|
|
295
|
-
const key:string = await store.add(
|
|
299
|
+
const key:string = await store.add(
|
|
300
|
+
value: any,
|
|
301
|
+
options?: { expires?: number|string; prefix?: string }
|
|
302
|
+
);
|
|
296
303
|
|
|
297
304
|
const key1 = await store.add("Hello World");
|
|
298
305
|
const key2 = await store.add(["my", "grocery", "list"], { expires: "1h" });
|
|
299
|
-
const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60
|
|
306
|
+
const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60 });
|
|
300
307
|
```
|
|
301
308
|
|
|
302
|
-
The
|
|
309
|
+
The value and options are similar to [`.set()`](#set), except for the lack of the first argument, since `.add()` automatically generates the key.
|
|
303
310
|
|
|
304
311
|
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
312
|
|
|
@@ -346,7 +353,7 @@ In many cases, internally the check for `.has()` is the same as `.get()`, so if
|
|
|
346
353
|
|
|
347
354
|
```js
|
|
348
355
|
const val = await store.get("key1");
|
|
349
|
-
if (val) { ... }
|
|
356
|
+
if (val !== null) { ... }
|
|
350
357
|
```
|
|
351
358
|
|
|
352
359
|
An example of an exception of the above is when you use it as a cache, then you can write code like this:
|
|
@@ -379,7 +386,7 @@ const has3 = await store.has("session:key1");
|
|
|
379
386
|
Remove a single key from the store and return the key itself:
|
|
380
387
|
|
|
381
388
|
```js
|
|
382
|
-
await store.del(key: string);
|
|
389
|
+
const key = await store.del(key: string);
|
|
383
390
|
```
|
|
384
391
|
|
|
385
392
|
It will ignore the operation if the key or value don't exist already (but won't throw). The API makes it easy to delete multiple keys at once:
|
|
@@ -387,7 +394,7 @@ It will ignore the operation if the key or value don't exist already (but won't
|
|
|
387
394
|
```js
|
|
388
395
|
const keys = ["key1", "key2"];
|
|
389
396
|
await Promise.all(keys.map(store.del));
|
|
390
|
-
console.log(done);
|
|
397
|
+
console.log("done");
|
|
391
398
|
```
|
|
392
399
|
|
|
393
400
|
An example with a prefix:
|
|
@@ -411,7 +418,7 @@ for await (const [key, value] of store) {
|
|
|
411
418
|
}
|
|
412
419
|
```
|
|
413
420
|
|
|
414
|
-
This is very useful for performance
|
|
421
|
+
This is very useful for performance reasons since it will retrieve the data sequentially, avoiding blocking the client while retrieving it all at once. The main disadvantage is if you keep writing data asynchronously while the async iterator is running.
|
|
415
422
|
|
|
416
423
|
You can also iterate on a subset of the entries with `.prefix()` (the prefix is stripped from the key here, see [.`prefix()`](#prefix)):
|
|
417
424
|
|
|
@@ -428,11 +435,11 @@ for await (const [key, value] of store.prefix("session:")) {
|
|
|
428
435
|
}
|
|
429
436
|
```
|
|
430
437
|
|
|
431
|
-
There are also methods to retrieve all
|
|
438
|
+
There are also methods to retrieve all the keys, values, or entries at once below, but those [have worse performance](#performance).
|
|
432
439
|
|
|
433
440
|
### .keys()
|
|
434
441
|
|
|
435
|
-
Get all
|
|
442
|
+
Get all the keys in the store as a simple array of strings:
|
|
436
443
|
|
|
437
444
|
```js
|
|
438
445
|
await store.keys();
|
|
@@ -446,11 +453,11 @@ const sessions = await store.prefix("session:").keys();
|
|
|
446
453
|
// ["keyA", "keyB"]
|
|
447
454
|
```
|
|
448
455
|
|
|
449
|
-
> We ensure that all
|
|
456
|
+
> We ensure that all the keys returned by this method are _not_ expired, while discarding any potentially expired key. See [**expirations**](#expirations) for more details.
|
|
450
457
|
|
|
451
458
|
### .values()
|
|
452
459
|
|
|
453
|
-
Get all
|
|
460
|
+
Get all the values in the store as a simple array with all the values:
|
|
454
461
|
|
|
455
462
|
```js
|
|
456
463
|
await store.values();
|
|
@@ -467,11 +474,11 @@ const companies = await store.prefix("company:").values();
|
|
|
467
474
|
// A list of all the companies
|
|
468
475
|
```
|
|
469
476
|
|
|
470
|
-
> We ensure that all
|
|
477
|
+
> We ensure that all the values returned by this method are _not_ expired, while discarding any potentially expired key. See [**expirations**](#expirations) for more details.
|
|
471
478
|
|
|
472
479
|
### .entries()
|
|
473
480
|
|
|
474
|
-
Get all
|
|
481
|
+
Get all the entries (key:value tuples) in the store:
|
|
475
482
|
|
|
476
483
|
```js
|
|
477
484
|
const entries = await store.entries();
|
|
@@ -512,31 +519,57 @@ const sessionObj = await store.prefix('session:').all();
|
|
|
512
519
|
|
|
513
520
|
### .clear()
|
|
514
521
|
|
|
515
|
-
Remove all
|
|
522
|
+
Remove all data from the current store and resets it to the original state:
|
|
516
523
|
|
|
517
524
|
```js
|
|
518
525
|
await store.clear();
|
|
519
526
|
```
|
|
520
527
|
|
|
528
|
+
If called on a `.prefix()` substore, only keys with that prefix are removed:
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
const sessions = store.prefix("session:");
|
|
532
|
+
|
|
533
|
+
await sessions.clear();
|
|
534
|
+
// removes only session:* keys
|
|
535
|
+
````
|
|
536
|
+
|
|
537
|
+
### .prune()
|
|
538
|
+
|
|
539
|
+
> [!IMPORTANT]
|
|
540
|
+
> For stores with native expiration (Redis, Cloudflare KV, etc.), `.prune()` usually does nothing because the underlying client already removes expired keys automatically.
|
|
541
|
+
|
|
542
|
+
Remove only expired records from the store.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
await store.prune();
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
This method is only useful for stores that do not support native expiration (such as Map, localStorage, files, etc.). In those cases expired records are hidden by the API but may still exist internally until they are removed.
|
|
549
|
+
|
|
550
|
+
Calling `.prune()` scans the store and deletes all expired entries.
|
|
551
|
+
|
|
552
|
+
This operation is O(n) and should typically be run in a scheduled job or maintenance task rather than on every request.
|
|
553
|
+
|
|
521
554
|
### .close()
|
|
522
555
|
|
|
523
|
-
Close the
|
|
556
|
+
Close the connection (if any) from the client:
|
|
524
557
|
|
|
525
558
|
```js
|
|
526
559
|
await store.close();
|
|
527
|
-
|
|
560
|
+
```
|
|
528
561
|
|
|
529
562
|
### .prefix()
|
|
530
563
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
Creates **a new instance** of the Store, _with the same client_ as you provided, but now any key you read, write, etc. will be passed with the given prefix to the client. You only write `.prefix()` once and then don't need to worry about any prefix for any method anymore, it's all automatic. It's **the only method** that you don't need to await:
|
|
564
|
+
Creates **a new instance** of the Store, _with the same client_ as you provided, but now any key you read, write, etc. will be passed with the given prefix to the client. 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:
|
|
534
565
|
|
|
535
566
|
```js
|
|
536
567
|
const store = kv(new Map());
|
|
537
568
|
const session = store.prefix("session:");
|
|
538
569
|
```
|
|
539
570
|
|
|
571
|
+
> There's [an in-depth explanation about Substores](#substores) that is very informative for production usage.
|
|
572
|
+
|
|
540
573
|
Then all of the operations will be converted internally to add the prefix when reading, writing, etc:
|
|
541
574
|
|
|
542
575
|
```js
|
|
@@ -567,6 +600,31 @@ const books = kv(`file://${import.meta.dirname}/books.json`);
|
|
|
567
600
|
|
|
568
601
|
The main reason this is not stable is because [_some_ store engines don't allow for atomic deletion of keys given a prefix](https://stackoverflow.com/q/4006324/938236). While we do still clear them internally in those cases, that is a non-atomic operation and it could have some trouble if some other thread is reading/writing the data _at the same time_.
|
|
569
602
|
|
|
603
|
+
### .expires()
|
|
604
|
+
|
|
605
|
+
Create a substore with a different default expiration time.
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
const store = kv(new Map()); // No expiration
|
|
609
|
+
const cache = store.expires("10min"); // 10 min expiration
|
|
610
|
+
await cache.set("a", "b"); // 10 mins
|
|
611
|
+
await cache.set("c", "e", { expires: '5s' }); // 5 seconds, overwrites it
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
> There's [an in-depth explanation about Expirations](#expirations) that is very informative for production usage.
|
|
615
|
+
|
|
616
|
+
But applied automatically to all writes in the substore. Explicit expiration always overrides the default:
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
await cache.set("a", "b", { expires: "1h" });
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
You can combine it with .prefix():
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
const sessions = store.prefix("session:").expires("1day");
|
|
626
|
+
```
|
|
627
|
+
|
|
570
628
|
## Clients
|
|
571
629
|
|
|
572
630
|
A client is the library that manages the low-level store operations. For example, the Redis Client, 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"`.
|
|
@@ -617,7 +675,7 @@ console.log(await store.get("key1"));
|
|
|
617
675
|
// GOOD - with polystore
|
|
618
676
|
await store.set("key1", { name: "Francisco" }, { expires: "2days" });
|
|
619
677
|
|
|
620
|
-
// COMPLEX - With
|
|
678
|
+
// COMPLEX - With plain Map
|
|
621
679
|
const data = new Map();
|
|
622
680
|
data.set("key1", { name: "Francisco" });
|
|
623
681
|
// Expiration not supported
|
|
@@ -830,13 +888,13 @@ db.exec(`
|
|
|
830
888
|
db.exec(
|
|
831
889
|
`CREATE INDEX IF NOT EXISTS idx_kv_expires_at ON kv (expires_at)`,
|
|
832
890
|
);
|
|
833
|
-
|
|
891
|
+
```
|
|
834
892
|
|
|
835
893
|
#### SQLite expirations
|
|
836
894
|
|
|
837
895
|
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.
|
|
838
896
|
|
|
839
|
-
|
|
897
|
+
Expired rows remain in the table unless you delete them manually. To avoid having stale data that is not used anymore, it's recommended you set a periodic check and clear expired records manually.
|
|
840
898
|
|
|
841
899
|
There's many ways of doing this, but a simple/basic one is this:
|
|
842
900
|
|
|
@@ -845,7 +903,7 @@ There's many ways of doing this, but a simple/basic one is this:
|
|
|
845
903
|
setInterval(() => {
|
|
846
904
|
db.prepare(`DELETE FROM kv WHERE expires_at < ?`).run(Date.now());
|
|
847
905
|
}, 10 * 60 * 1000);
|
|
848
|
-
|
|
906
|
+
```
|
|
849
907
|
|
|
850
908
|
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.
|
|
851
909
|
|
|
@@ -867,14 +925,14 @@ console.log(await store.get("key1"));
|
|
|
867
925
|
// "Hello world"
|
|
868
926
|
```
|
|
869
927
|
|
|
870
|
-
> Note: the API client expire resolution is in
|
|
928
|
+
> Note: the API client expire resolution is in seconds, so times shorter than 1 second like `expires: 0.02` (20 ms) don't make sense for this storage method and won't properly save them.
|
|
871
929
|
|
|
872
930
|
> Note: see the [reference implementation in src/server.js](https://github.com/franciscop/polystore/blob/master/src/server.js)
|
|
873
931
|
|
|
874
932
|
|
|
875
933
|
### File
|
|
876
934
|
|
|
877
|
-
Treat a JSON file in your filesystem as the source for the KV store. Pass it an absolute `file://` url or a `new URL(
|
|
935
|
+
Treat a JSON file in your filesystem as the source for the KV store. Pass it an absolute `file://` url or a `new URL("file://...")` instance:
|
|
878
936
|
|
|
879
937
|
```js
|
|
880
938
|
import kv from "polystore";
|
|
@@ -1150,9 +1208,9 @@ Please see the [creating a store](#creating-a-store) section for all the details
|
|
|
1150
1208
|
|
|
1151
1209
|
While all of our stores support `expires`, `.prefix()` and group operations, the nature of those makes them to have different performance characteristics.
|
|
1152
1210
|
|
|
1153
|
-
**Expires** we polyfill expiration when the underlying client library does not support it. The impact on read/write operations and on data size of each key should be minimal. However, it can have a big impact in storage size, since the expired keys are not evicted automatically.
|
|
1211
|
+
**Expires** we polyfill expiration when the underlying client library does not support it. The impact on read/write operations and on data size of each key should be minimal. However, it can have a big impact in storage size, since the expired keys are not evicted automatically. Expired keys remain in the datastore and could create some old-data issues. This is **especially important where sensitive data is involved**! To fix this, the easiest way is calling `await store.prune();` on a cron job and that should evict all of the old keys (this operation is O(n) though, so not suitable for calling it on EVERY API call, see the next point).
|
|
1154
1212
|
|
|
1155
|
-
**Group operations**
|
|
1213
|
+
**Group operations** are mostly intended for small datasets, for one-off scripts or for dev purposes, since by their own nature they can _never_ be high performance in the general case. But this is normal if you think about traditional DBs, reading a single record by its ID is O(1), while reading all of the IDs in the DB into an array is going to be O(n). Same applies with polystore.
|
|
1156
1214
|
|
|
1157
1215
|
**Substores** when dealing with a `.prefix()` substore, the same applies. Item operations should see no performance degradation from `.prefix()`, but group operations follow the above performance considerations. Some engines might have native prefix support, so performance in those is better for group operations in a substore than the whole store. But in general you should consider `.prefix()` as a convenient way of classifying your keys and not as a performance fix for group operations.
|
|
1158
1216
|
|
|
@@ -1164,7 +1222,7 @@ We unify all of the clients diverse expiration methods into a single, easy one w
|
|
|
1164
1222
|
|
|
1165
1223
|
```js
|
|
1166
1224
|
// in-memory store
|
|
1167
|
-
const store =
|
|
1225
|
+
const store = kv(new Map());
|
|
1168
1226
|
await store.set("a", "b", { expires: "1s" });
|
|
1169
1227
|
|
|
1170
1228
|
// These checks of course work:
|
|
@@ -1214,7 +1272,7 @@ These details are explained in the respective client information.
|
|
|
1214
1272
|
|
|
1215
1273
|
What `.prefix()` does is it creates **a new instance** of the Store, _with the same client_ as you provided, but now any key you read, write, etc. will be passed with the given prefix to the client. The issue is that support from the underlying clients is inconsistent.
|
|
1216
1274
|
|
|
1217
|
-
When dealing with large or complex amounts of data in a KV store,
|
|
1275
|
+
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:
|
|
1218
1276
|
|
|
1219
1277
|
- You use KV as a cache, and have different categories of data.
|
|
1220
1278
|
- You use KV as a session store, and want to differentiate different kinds of sessions.
|
|
@@ -1230,15 +1288,15 @@ To create a store, you define a class with these properties and methods:
|
|
|
1230
1288
|
class MyClient {
|
|
1231
1289
|
// If this is set to `true`, the CLIENT (you) handle the expiration, so
|
|
1232
1290
|
// the `.set()` and `.add()` receive a `expires` that is a `null` or `number`:
|
|
1233
|
-
|
|
1291
|
+
HAS_EXPIRATION = false;
|
|
1234
1292
|
|
|
1235
1293
|
// Mandatory methods
|
|
1236
1294
|
get (key): Promise<any>;
|
|
1237
|
-
set (key, value,
|
|
1238
|
-
iterate(prefix):
|
|
1295
|
+
set (key, value, null|number): Promise<null>;
|
|
1296
|
+
iterate(prefix): AsyncIterator<[string, any]>
|
|
1239
1297
|
|
|
1240
1298
|
// Optional item methods (for optimization or customization)
|
|
1241
|
-
add (prefix, data,
|
|
1299
|
+
add (prefix, data, null|number): Promise<string>;
|
|
1242
1300
|
has (key): Promise<boolean>;
|
|
1243
1301
|
del (key): Promise<null>;
|
|
1244
1302
|
|
|
@@ -1255,7 +1313,7 @@ class MyClient {
|
|
|
1255
1313
|
|
|
1256
1314
|
Note that this is NOT the public API, it's the internal **client** API. It's simpler than the public API since we do some of the heavy lifting as an intermediate layer (e.g. for the client, 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.
|
|
1257
1315
|
|
|
1258
|
-
**Expires**: if you set the `
|
|
1316
|
+
**Expires**: if you set the `HAS_EXPIRATION = true`, then you are indicating that the client 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.
|
|
1259
1317
|
|
|
1260
1318
|
**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:
|
|
1261
1319
|
|
|
@@ -1283,7 +1341,7 @@ client.keys = (prefix) => {
|
|
|
1283
1341
|
|
|
1284
1342
|
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
1343
|
|
|
1286
|
-
###
|
|
1344
|
+
### Plain Object client
|
|
1287
1345
|
|
|
1288
1346
|
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
1347
|
|
|
@@ -1311,7 +1369,7 @@ class MyClient {
|
|
|
1311
1369
|
}
|
|
1312
1370
|
```
|
|
1313
1371
|
|
|
1314
|
-
We don't set `
|
|
1372
|
+
We don't set `HAS_EXPIRATION` to true since plain objects do NOT support expiration natively. So by not adding the `HAS_EXPIRATION` property, it's the same as setting it to `false`, and polystore will manage all the expirations as a layer on top of the data. We could be more explicit and set it to `HAS_EXPIRATION = false`, but it's not needed in this case.
|
|
1315
1373
|
|
|
1316
1374
|
### Example: custom ID generation
|
|
1317
1375
|
|
|
@@ -1321,10 +1379,10 @@ You might want to provide your custom key generation algorithm, which I'm going
|
|
|
1321
1379
|
class MyClient {
|
|
1322
1380
|
|
|
1323
1381
|
// Add the opt method .add() to have more control over the ID generation
|
|
1324
|
-
async add (prefix, data,
|
|
1382
|
+
async add (prefix, data, expires) {
|
|
1325
1383
|
const id = customId();
|
|
1326
1384
|
const key = prefix + id;
|
|
1327
|
-
return this.set(key, data,
|
|
1385
|
+
return this.set(key, data, expires);
|
|
1328
1386
|
}
|
|
1329
1387
|
|
|
1330
1388
|
//
|
|
@@ -1375,7 +1433,7 @@ class MyClient {
|
|
|
1375
1433
|
|
|
1376
1434
|
In this example on one of my projects, I needed to use Cloudflare's REST API since I didn't have access to any KV store I was happy with on Netlify's Edge Functions. So I created it like this:
|
|
1377
1435
|
|
|
1378
|
-
> Warning: this code snippet is an experimental example and hasn't gone through
|
|
1436
|
+
> Warning: this code snippet is an experimental example and hasn't gone through rigorous testing as the rest of the library, so please treat with caution.
|
|
1379
1437
|
|
|
1380
1438
|
```js
|
|
1381
1439
|
const {
|
|
@@ -1392,7 +1450,7 @@ const headers = {
|
|
|
1392
1450
|
};
|
|
1393
1451
|
|
|
1394
1452
|
class CloudflareCustom {
|
|
1395
|
-
|
|
1453
|
+
HAS_EXPIRATION = true;
|
|
1396
1454
|
|
|
1397
1455
|
async get(key) {
|
|
1398
1456
|
const res = await fetch(`${baseUrl}/values/${key}`, { headers });
|
|
@@ -1439,6 +1497,74 @@ class CloudflareCustom {
|
|
|
1439
1497
|
}
|
|
1440
1498
|
|
|
1441
1499
|
const store = kv(CloudflareCustom);
|
|
1442
|
-
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
It's lacking a few things, so make sure to adapt to your needs, but it worked for my very simple cache needs.
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
## Examples
|
|
1506
|
+
|
|
1507
|
+
### Simple cache
|
|
1508
|
+
|
|
1509
|
+
I've used Polystore in many projects as a simple cache. With `fetch()`, it's fairly easy:
|
|
1510
|
+
|
|
1511
|
+
```ts
|
|
1512
|
+
async function getProductInfo(id: string) {
|
|
1513
|
+
const data = await store.get(id);
|
|
1514
|
+
if (data) return data;
|
|
1515
|
+
|
|
1516
|
+
const res = await fetch(`https://some-url.com/products/${id}`);
|
|
1517
|
+
const raw = await res.json();
|
|
1518
|
+
|
|
1519
|
+
// Some processing here
|
|
1520
|
+
const clean = raw??;
|
|
1521
|
+
|
|
1522
|
+
await store.set(id, clean, { expires: "10days" });
|
|
1523
|
+
return clean;
|
|
1524
|
+
}
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
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.
|
|
1443
1528
|
|
|
1444
|
-
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
### Dev vs Prod
|
|
1532
|
+
|
|
1533
|
+
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):
|
|
1534
|
+
|
|
1535
|
+
```ts
|
|
1536
|
+
// store.ts
|
|
1537
|
+
import kv from "polystore";
|
|
1538
|
+
import { createClient } from "redis";
|
|
1539
|
+
|
|
1540
|
+
let store;
|
|
1541
|
+
if (process.env.REDIS_URL) {
|
|
1542
|
+
console.log('kv:redis using Redis for cache data');
|
|
1543
|
+
store = kv(createClient(process.env.REDIS_URL).connect());
|
|
1544
|
+
} else {
|
|
1545
|
+
console.log('kv:folder using a folder for cache data');
|
|
1546
|
+
store = kv(`file://${process.cwd()}/data/`);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
export default store;
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
### @server/next
|
|
1553
|
+
|
|
1554
|
+
> [!info]
|
|
1555
|
+
> @server/next is still experimental, but it's the main reason I created Polystore and so I wanted to document it as well
|
|
1556
|
+
|
|
1557
|
+
Server.js supports Polystore directly:
|
|
1558
|
+
|
|
1559
|
+
```ts
|
|
1560
|
+
import kv from "polystore";
|
|
1561
|
+
import server from "../../";
|
|
1562
|
+
|
|
1563
|
+
const session = kv(new Map());
|
|
1564
|
+
|
|
1565
|
+
export default server({ session }).get("/", (ctx) => {
|
|
1566
|
+
if (!ctx.session.counter) ctx.session.counter = 0;
|
|
1567
|
+
ctx.session.counter++;
|
|
1568
|
+
return `User visited ${ctx.session.counter} times`;
|
|
1569
|
+
});
|
|
1570
|
+
```
|
package/src/express.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
}
|