polystore 0.11.4 → 0.12.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/package.json +7 -3
- package/readme.md +65 -30
- package/src/clients/cloudflare.js +3 -0
- package/src/index.d.ts +25 -10
- package/src/index.js +3 -4
- package/src/utils.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
|
|
5
5
|
"homepage": "https://polystore.dev/",
|
|
6
6
|
"repository": "https://github.com/franciscop/polystore.git",
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"size": "echo $(gzip -c src/index.js | wc -c) bytes",
|
|
18
|
+
"lint": "check-dts test/index.types.ts",
|
|
18
19
|
"start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles",
|
|
19
|
-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --ci --watchAll=false --detectOpenHandles
|
|
20
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --ci --watchAll=false --detectOpenHandles"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"kv",
|
|
@@ -56,6 +57,9 @@
|
|
|
56
57
|
"setupFiles": [
|
|
57
58
|
"./test/setup.js"
|
|
58
59
|
],
|
|
59
|
-
"transform": {}
|
|
60
|
+
"transform": {},
|
|
61
|
+
"modulePathIgnorePatterns": [
|
|
62
|
+
"test/cloudflare"
|
|
63
|
+
]
|
|
60
64
|
}
|
|
61
65
|
}
|
package/readme.md
CHANGED
|
@@ -99,18 +99,6 @@ const store = kv(MyClientOrStoreInstance);
|
|
|
99
99
|
// use the store
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
If the instance you pass contains a `connect()` or `open()` method, polystore **will** call that without any argument:
|
|
103
|
-
|
|
104
|
-
```js
|
|
105
|
-
// Simpler
|
|
106
|
-
const store = kv(createClient());
|
|
107
|
-
|
|
108
|
-
// NO NEED
|
|
109
|
-
const client = createClient();
|
|
110
|
-
client.connect();
|
|
111
|
-
const store = kv(client);
|
|
112
|
-
```
|
|
113
|
-
|
|
114
102
|
While you can keep a reference to the store and access it directly, we strongly recommend if you are going to use a store, to only access it through `polystore`, since we might add custom serialization and extra properties for e.g. expiration time:
|
|
115
103
|
|
|
116
104
|
```js
|
|
@@ -141,7 +129,7 @@ console.log(await store.get("key3")); // { name: "Francisco" }
|
|
|
141
129
|
|
|
142
130
|
If the value is returned, it can be a simple type like `boolean`, `string` or `number`, or it can be a plain Object or Array, or a combination of those.
|
|
143
131
|
|
|
144
|
-
> The value cannot be more complex or non-serializable values like a `Date()`, `Infinity`, `undefined
|
|
132
|
+
> The value cannot be more complex or non-serializable values like a `Date()`, `Infinity`, `undefined`, a `Symbol`, etc.
|
|
145
133
|
|
|
146
134
|
### .set()
|
|
147
135
|
|
|
@@ -152,7 +140,7 @@ await store.set(key: string, value: any, options?: { expires: number|string });
|
|
|
152
140
|
|
|
153
141
|
await store.set("key1", "Hello World");
|
|
154
142
|
await store.set("key2", ["my", "grocery", "list"], { expires: "1h" });
|
|
155
|
-
await store.set("key3", { name: "Francisco" }, { expires: 60 * 60
|
|
143
|
+
await store.set("key3", { name: "Francisco" }, { expires: 60 * 60 });
|
|
156
144
|
```
|
|
157
145
|
|
|
158
146
|
The value can be a simple type like `boolean`, `string` or `number`, or it can be a plain Object or Array, or a combination of those. It **cannot** be a more complex or non-serializable values like a `Date()`, `Infinity`, `undefined` (casted to `null`), a `Symbol`, etc.
|
|
@@ -176,7 +164,7 @@ When the `expires` option is set, it can be a number (**seconds**) or a string r
|
|
|
176
164
|
"5d" - expire after 5 days
|
|
177
165
|
```
|
|
178
166
|
|
|
179
|
-
\* not all stores support sub-second expirations, notably Redis and Cookies don't, so it's safer to always use an integer or an amount larger than 1s
|
|
167
|
+
\* not all stores support sub-second expirations, notably Redis and Cookies don't, so it's safer to always use an integer or an amount larger than 1s. There will be a note in each store for this.
|
|
180
168
|
|
|
181
169
|
These are all the units available:
|
|
182
170
|
|
|
@@ -191,7 +179,7 @@ const key:string = await store.add(value: any, options?: { expires: number|strin
|
|
|
191
179
|
|
|
192
180
|
const key1 = await store.add("Hello World");
|
|
193
181
|
const key2 = await store.add(["my", "grocery", "list"], { expires: "1h" });
|
|
194
|
-
const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60
|
|
182
|
+
const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60 });
|
|
195
183
|
```
|
|
196
184
|
|
|
197
185
|
The generated key is 24 AlphaNumeric characters (including upper and lower case) generated with random cryptography to make sure it's unguessable, high entropy and safe to use in most contexts like URLs, queries, etc. We use [`nanoid`](https://github.com/ai/nanoid/) with a custom dictionary, so you can check the entropy [in this dictionary](https://zelark.github.io/nano-id-cc/) by removing the "\_" and "-", and setting it to 24 characters.
|
|
@@ -200,9 +188,26 @@ Here is the safety: "If you generate 1 million keys/second, it will take ~14 mil
|
|
|
200
188
|
|
|
201
189
|
> Note: please make sure to read the [`.set()`](#set) section for all the details, since `.set()` and `.add()` behave the same way except for the first argument.
|
|
202
190
|
|
|
191
|
+
The main reason why `.add()` exists is to allow it to work with the prefix seamlessly:
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
const session = store.prefix("session:");
|
|
195
|
+
|
|
196
|
+
// Creates a key with the prefix (returns only the key)
|
|
197
|
+
const key1 = await session.add("value1");
|
|
198
|
+
// "c4ONlvweshXPUEy76q3eFHPL"
|
|
199
|
+
|
|
200
|
+
console.log(await session.keys()); // on the "session" store
|
|
201
|
+
// ["c4ONlvweshXPUEy76q3eFHPL"]
|
|
202
|
+
console.log(await store.keys()); // on the root store
|
|
203
|
+
// ["session:c4ONlvweshXPUEy76q3eFHPL"]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Remember that [substores with `.prefix()`](#prefix) behave as if they were an independent store, so when adding, manipulating, etc. a value you should treat the key as if it had no prefix. This is explained in detail in the [.prefix()](#prefix) documentation.
|
|
207
|
+
|
|
203
208
|
### .has()
|
|
204
209
|
|
|
205
|
-
Check whether the key is available in the store and not expired:
|
|
210
|
+
Check whether the key:value is available in the store and not expired:
|
|
206
211
|
|
|
207
212
|
```js
|
|
208
213
|
await store.has(key: string);
|
|
@@ -233,6 +238,17 @@ async function fetchUser(id) {
|
|
|
233
238
|
}
|
|
234
239
|
```
|
|
235
240
|
|
|
241
|
+
An example with a prefix:
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
const session = store.prefix("session:");
|
|
245
|
+
|
|
246
|
+
// These three perform the same operation internally
|
|
247
|
+
const has1 = await session.has("key1");
|
|
248
|
+
const has2 = await store.prefix("session:").has("key1");
|
|
249
|
+
const has3 = await store.has("session:key1");
|
|
250
|
+
```
|
|
251
|
+
|
|
236
252
|
### .del()
|
|
237
253
|
|
|
238
254
|
Remove a single key from the store and return the key itself:
|
|
@@ -241,7 +257,24 @@ Remove a single key from the store and return the key itself:
|
|
|
241
257
|
await store.del(key: string);
|
|
242
258
|
```
|
|
243
259
|
|
|
244
|
-
It will ignore the operation if the key or value don't exist already (but won't thorw).
|
|
260
|
+
It will ignore the operation if the key or value don't exist already (but won't thorw). The API makes it easy to delete multiple keys at once:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
const keys = ["key1", "key2"];
|
|
264
|
+
await Promise.all(keys.map(store.del));
|
|
265
|
+
console.log(done);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
An example with a prefix:
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
const session = store.prefix("session:");
|
|
272
|
+
|
|
273
|
+
// These three perform the same operation internally
|
|
274
|
+
await session.del("key1");
|
|
275
|
+
await store.prefix("session:").del("key1");
|
|
276
|
+
await store.del("session:key1");
|
|
277
|
+
```
|
|
245
278
|
|
|
246
279
|
### _Iterator_
|
|
247
280
|
|
|
@@ -249,18 +282,29 @@ You can iterate over the whole store with an async iterator:
|
|
|
249
282
|
|
|
250
283
|
```js
|
|
251
284
|
for await (const [key, value] of store) {
|
|
252
|
-
|
|
285
|
+
console.log(key, value);
|
|
253
286
|
}
|
|
254
287
|
```
|
|
255
288
|
|
|
256
|
-
This is very useful for performance resons.
|
|
289
|
+
This is very useful for performance resons 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 while the async iterator is running.
|
|
290
|
+
|
|
291
|
+
You can also iterate on a subset of the entries with `.prefix()` (the prefix is stripped from the key here, see [.`prefix()`](#prefix)):
|
|
257
292
|
|
|
258
293
|
```js
|
|
294
|
+
const session = store.prefix("session:");
|
|
295
|
+
for await (const [key, value] of session) {
|
|
296
|
+
console.log(key, value);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Same as this (both have the prefix stripped):
|
|
300
|
+
|
|
259
301
|
for await (const [key, value] of store.prefix("session:")) {
|
|
260
|
-
|
|
302
|
+
console.log(key, value);
|
|
261
303
|
}
|
|
262
304
|
```
|
|
263
305
|
|
|
306
|
+
There are also methods to retrieve all of the keys, values, or entries at once below, but those [have worse performance](#performance).
|
|
307
|
+
|
|
264
308
|
### .keys()
|
|
265
309
|
|
|
266
310
|
Get all of the keys in the store, optionally filtered by a prefix:
|
|
@@ -374,15 +418,6 @@ console.log(await store.get("key1"));
|
|
|
374
418
|
// "Hello world"
|
|
375
419
|
```
|
|
376
420
|
|
|
377
|
-
It can also be initialized empty, then it'll use the in-memory store:
|
|
378
|
-
|
|
379
|
-
```js
|
|
380
|
-
import kv from "polystore";
|
|
381
|
-
|
|
382
|
-
const store = kv();
|
|
383
|
-
const store = kv(new Map());
|
|
384
|
-
```
|
|
385
|
-
|
|
386
421
|
<details>
|
|
387
422
|
<summary>Why use polystore with <code>new Map()</code>?</summary>
|
|
388
423
|
<p>Besides the other benefits already documented elsewhere, these are the ones specifically for wrapping Map() with polystore:</p>
|
|
@@ -23,6 +23,9 @@ export default class Cloudflare {
|
|
|
23
23
|
|
|
24
24
|
async set(key, value, { expires } = {}) {
|
|
25
25
|
const expirationTtl = expires ? Math.round(expires) : undefined;
|
|
26
|
+
if (expirationTtl && expirationTtl < 60) {
|
|
27
|
+
throw new Error("Cloudflare's min expiration is '60s'");
|
|
28
|
+
}
|
|
26
29
|
return this.client.put(key, JSON.stringify(value), { expirationTtl });
|
|
27
30
|
}
|
|
28
31
|
|
package/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
type Options = { expires?: number | string | null };
|
|
2
|
-
type Value =
|
|
1
|
+
export type Options = { expires?: number | string | null };
|
|
2
|
+
export type Value = string | { [key: string]: Value } | Value[];
|
|
3
3
|
|
|
4
|
-
interface Store {
|
|
4
|
+
export interface Store {
|
|
5
5
|
/**
|
|
6
6
|
* Save the data on an autogenerated key, can add expiration as well:
|
|
7
7
|
*
|
|
@@ -13,7 +13,7 @@ interface Store {
|
|
|
13
13
|
*
|
|
14
14
|
* **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
|
|
15
15
|
*/
|
|
16
|
-
add: (value:
|
|
16
|
+
add: <T = Value>(value: T, options?: Options) => Promise<string>;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Save the data on the given key, can add expiration as well:
|
|
@@ -26,7 +26,7 @@ interface Store {
|
|
|
26
26
|
*
|
|
27
27
|
* **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
|
|
28
28
|
*/
|
|
29
|
-
set: (key: string, value:
|
|
29
|
+
set: <T = Value>(key: string, value: T, options?: Options) => Promise<string>;
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Read a single value from the KV store:
|
|
@@ -42,7 +42,7 @@ interface Store {
|
|
|
42
42
|
*
|
|
43
43
|
* **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
|
|
44
44
|
*/
|
|
45
|
-
get: (key: string) => Promise<
|
|
45
|
+
get: <T = Value>(key: string) => Promise<T | null>;
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Check whether a key exists or not:
|
|
@@ -72,7 +72,7 @@ interface Store {
|
|
|
72
72
|
*
|
|
73
73
|
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
74
74
|
*/
|
|
75
|
-
del: (key: string) => Promise<
|
|
75
|
+
del: (key: string) => Promise<string>;
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Return an array of the entries, in the [key, value] format:
|
|
@@ -87,7 +87,7 @@ interface Store {
|
|
|
87
87
|
*
|
|
88
88
|
* **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
|
|
89
89
|
*/
|
|
90
|
-
entries: () => Promise<[key: string, value:
|
|
90
|
+
entries: <T = Value>() => Promise<[key: string, value: T][]>;
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* Return an array of the keys in the store:
|
|
@@ -117,7 +117,7 @@ interface Store {
|
|
|
117
117
|
*
|
|
118
118
|
* **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
|
|
119
119
|
*/
|
|
120
|
-
values: () => Promise<
|
|
120
|
+
values: <T = Value>() => Promise<T[]>;
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Return an object with the keys:values in the store:
|
|
@@ -132,7 +132,7 @@ interface Store {
|
|
|
132
132
|
*
|
|
133
133
|
* **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
|
|
134
134
|
*/
|
|
135
|
-
all: () => Promise<{ [key: string]:
|
|
135
|
+
all: <T = Value>() => Promise<{ [key: string]: T }>;
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
138
|
* Delete all of the records of the store:
|
|
@@ -176,6 +176,21 @@ interface Store {
|
|
|
176
176
|
* **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
|
|
177
177
|
*/
|
|
178
178
|
close?: () => Promise<null>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* An iterator that goes through all of the key:value pairs in the client
|
|
182
|
+
*
|
|
183
|
+
* ```js
|
|
184
|
+
* for await (const [key, value] of store) {
|
|
185
|
+
* console.log(key, value);
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* **[→ Full Iterator Docs](https://polystore.dev/documentation#iterator)**
|
|
190
|
+
*/
|
|
191
|
+
[Symbol.asyncIterator]: () => {
|
|
192
|
+
next: () => Promise<{ value: [string, Value] }>;
|
|
193
|
+
};
|
|
179
194
|
}
|
|
180
195
|
|
|
181
196
|
export default function (store?: any): Store;
|
package/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createId, isClass, parse } from "./utils.js";
|
|
|
4
4
|
class Store {
|
|
5
5
|
PREFIX = "";
|
|
6
6
|
|
|
7
|
-
constructor(clientPromise
|
|
7
|
+
constructor(clientPromise) {
|
|
8
8
|
this.promise = Promise.resolve(clientPromise).then(async (client) => {
|
|
9
9
|
this.client = this.#find(client);
|
|
10
10
|
this.#validate(this.client);
|
|
@@ -33,10 +33,9 @@ class Store {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
#validate(client) {
|
|
36
|
+
if (!client) throw new Error("No client received");
|
|
36
37
|
if (!client.set || !client.get || !client.iterate) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
"A client should have at least a .get(), .set() and .iterate()"
|
|
39
|
-
);
|
|
38
|
+
throw new Error("Client should have .get(), .set() and .iterate()");
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
if (!client.EXPIRES) {
|
package/src/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ parse.week = parse.wk = parse.w = parse.d * 7;
|
|
|
9
9
|
parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
10
10
|
parse.month = parse.b = parse.y / 12;
|
|
11
11
|
|
|
12
|
-
// Returns the time in
|
|
12
|
+
// Returns the time in seconds
|
|
13
13
|
export function parse(str) {
|
|
14
14
|
if (str === null || str === undefined) return null;
|
|
15
15
|
if (typeof str === "number") return str;
|