polystore 0.6.0 → 0.8.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 +1 -1
- package/readme.md +10 -3
- package/src/index.js +38 -1
- package/src/index.test.js +89 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
|
|
5
5
|
"homepage": "https://github.com/franciscop/polystore",
|
|
6
6
|
"repository": "https://github.com/franciscop/polystore.git",
|
package/readme.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Polystore [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [](https://github.com/franciscop/polystore/blob/master/src/index.js)
|
|
2
2
|
|
|
3
3
|
A small compatibility layer for many KV stores like localStorage, Redis, FileSystem, etc:
|
|
4
4
|
|
|
@@ -22,6 +22,7 @@ This is the [API](#api) with all of the methods (they are all `async`):
|
|
|
22
22
|
- `.entries(prefix?): [string, any][]`: get a list of all the key-value pairs in the store.
|
|
23
23
|
- `.clear()`: delete ALL of the data in the store, effectively resetting it.
|
|
24
24
|
- `.close()`: (only _some_ stores) ends the connection to the store.
|
|
25
|
+
- `.prefix(prefix): store`: create a new sub-instance of the store where all the keys have this prefix.
|
|
25
26
|
|
|
26
27
|
Available stores:
|
|
27
28
|
|
|
@@ -224,9 +225,9 @@ Remove all of the data from the store:
|
|
|
224
225
|
await store.clear();
|
|
225
226
|
```
|
|
226
227
|
|
|
227
|
-
### .prefix() (
|
|
228
|
+
### .prefix() (unstable)
|
|
228
229
|
|
|
229
|
-
Create a sub-store where all the operations use the given prefix:
|
|
230
|
+
Create a sub-store where all the operations use the given prefix. This is **the only method** of the store that is sync and you don't need to await:
|
|
230
231
|
|
|
231
232
|
```js
|
|
232
233
|
const store = kv(new Map());
|
|
@@ -248,8 +249,13 @@ await session.clear(); // delete only keys with the prefix
|
|
|
248
249
|
This will probably never be stable given the nature of some engines, so as an alternative please consider using two stores instead of prefixes:
|
|
249
250
|
|
|
250
251
|
```js
|
|
252
|
+
// Two in-memory stores
|
|
251
253
|
const store = kv(new Map());
|
|
252
254
|
const session = kv(new Map());
|
|
255
|
+
|
|
256
|
+
// Two file-stores
|
|
257
|
+
const users = kv(new URL(`file://${import.meta.dirname}/users.json`));
|
|
258
|
+
const books = kv(new URL(`file://${import.meta.dirname}/books.json`));
|
|
253
259
|
```
|
|
254
260
|
|
|
255
261
|
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_.
|
|
@@ -359,6 +365,7 @@ const store = kv(new URL("file:///Users/me/project/cache.json"));
|
|
|
359
365
|
// Paths need to be absolute, but you can use process.cwd() to make
|
|
360
366
|
// it relative to the current process:
|
|
361
367
|
const store = kv(new URL(`file://${process.cwd()}/cache.json`));
|
|
368
|
+
const store2 = kv(new URL(`file://${import.meta.dirname}/data.json`));
|
|
362
369
|
```
|
|
363
370
|
|
|
364
371
|
### Cloudflare KV
|
package/src/index.js
CHANGED
|
@@ -49,6 +49,10 @@ layers.extra = (store) => {
|
|
|
49
49
|
const add = async (value, options) => store.set(generateId(), value, options);
|
|
50
50
|
const has = async (key) => (await store.get(key)) !== null;
|
|
51
51
|
const del = async (key) => store.set(key, null);
|
|
52
|
+
const all = async (prefix = "") => {
|
|
53
|
+
const entries = await store.entries(prefix);
|
|
54
|
+
return Object.fromEntries(entries);
|
|
55
|
+
};
|
|
52
56
|
const keys = async (prefix = "") => {
|
|
53
57
|
const all = await store.entries(prefix);
|
|
54
58
|
return all.map((p) => p[0]);
|
|
@@ -57,7 +61,7 @@ layers.extra = (store) => {
|
|
|
57
61
|
const all = await store.entries(prefix);
|
|
58
62
|
return all.map((p) => p[1]);
|
|
59
63
|
};
|
|
60
|
-
return { add, has, del, keys, values, ...store };
|
|
64
|
+
return { add, has, del, keys, values, all, ...store };
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
// Adds an expiration layer to those stores that don't have it;
|
|
@@ -381,6 +385,39 @@ export default function compat(storeClient = new Map()) {
|
|
|
381
385
|
{},
|
|
382
386
|
{
|
|
383
387
|
get: (instance, key) => {
|
|
388
|
+
if (key === "prefix") {
|
|
389
|
+
const store = compat(storeClient);
|
|
390
|
+
return (prefix) => ({
|
|
391
|
+
get: (key) => store.get(prefix + key),
|
|
392
|
+
set: async (key, value, opts) => {
|
|
393
|
+
const id = await store.set(prefix + key, value, opts);
|
|
394
|
+
return id.slice(prefix.length);
|
|
395
|
+
},
|
|
396
|
+
add: async (value, opts) => {
|
|
397
|
+
const id = await store.set(prefix + generateId(), value, opts);
|
|
398
|
+
return id.slice(prefix.length);
|
|
399
|
+
},
|
|
400
|
+
has: (key) => store.has(prefix + key),
|
|
401
|
+
del: (key) => store.del(prefix + key),
|
|
402
|
+
|
|
403
|
+
keys: async (next = "") => {
|
|
404
|
+
const keys = await store.keys(prefix + next);
|
|
405
|
+
return keys.map((k) => k.slice(prefix.length));
|
|
406
|
+
},
|
|
407
|
+
values: (next = "") => store.values(prefix + next),
|
|
408
|
+
entries: async (next = "") => {
|
|
409
|
+
const entries = await store.entries(prefix + next);
|
|
410
|
+
return entries.map(([k, v]) => [k.slice(prefix.length), v]);
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
clear: async () => {
|
|
414
|
+
const keys = await store.keys(prefix);
|
|
415
|
+
await Promise.all(keys.map((key) => store.del(key)));
|
|
416
|
+
},
|
|
417
|
+
close: () => store.close(),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
384
421
|
return async (...args) => {
|
|
385
422
|
// Only once, even if called twice in succession, since the
|
|
386
423
|
// second time will go straight to the await
|
package/src/index.test.js
CHANGED
|
@@ -104,6 +104,15 @@ for (let [name, store] of stores) {
|
|
|
104
104
|
]);
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it("can get the all object", async () => {
|
|
108
|
+
await store.set("a", "b");
|
|
109
|
+
await store.set("c", "d");
|
|
110
|
+
expect(await store.all()).toEqual({
|
|
111
|
+
a: "b",
|
|
112
|
+
c: "d",
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
107
116
|
it("can get the keys with a colon prefix", async () => {
|
|
108
117
|
await store.set("a:0", "a0");
|
|
109
118
|
await store.set("a:1", "a1");
|
|
@@ -132,6 +141,18 @@ for (let [name, store] of stores) {
|
|
|
132
141
|
]);
|
|
133
142
|
});
|
|
134
143
|
|
|
144
|
+
it("can get the all object with a colon prefix", async () => {
|
|
145
|
+
await store.set("a:0", "a0");
|
|
146
|
+
await store.set("a:1", "a1");
|
|
147
|
+
await store.set("b:0", "b0");
|
|
148
|
+
await store.set("a:2", "a2");
|
|
149
|
+
expect(await store.all("a:")).toEqual({
|
|
150
|
+
"a:0": "a0",
|
|
151
|
+
"a:1": "a1",
|
|
152
|
+
"a:2": "a2",
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
135
156
|
it("can get the keys with a dash prefix", async () => {
|
|
136
157
|
await store.set("a-0", "a0");
|
|
137
158
|
await store.set("a-1", "a1");
|
|
@@ -160,6 +181,18 @@ for (let [name, store] of stores) {
|
|
|
160
181
|
]);
|
|
161
182
|
});
|
|
162
183
|
|
|
184
|
+
it("can get the all object with a dash prefix", async () => {
|
|
185
|
+
await store.set("a-0", "a0");
|
|
186
|
+
await store.set("a-1", "a1");
|
|
187
|
+
await store.set("b-0", "b0");
|
|
188
|
+
await store.set("a-2", "a2");
|
|
189
|
+
expect(await store.all("a-")).toEqual({
|
|
190
|
+
"a-0": "a0",
|
|
191
|
+
"a-1": "a1",
|
|
192
|
+
"a-2": "a2",
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
163
196
|
it("can delete the data", async () => {
|
|
164
197
|
await store.set("a", "b");
|
|
165
198
|
expect(await store.get("a")).toBe("b");
|
|
@@ -259,5 +292,61 @@ for (let [name, store] of stores) {
|
|
|
259
292
|
});
|
|
260
293
|
}
|
|
261
294
|
});
|
|
295
|
+
|
|
296
|
+
describe(".prefix()", () => {
|
|
297
|
+
const session = store.prefix("session:");
|
|
298
|
+
|
|
299
|
+
it("has all the methods", () => {
|
|
300
|
+
expect(Object.keys(session)).toEqual([
|
|
301
|
+
"get",
|
|
302
|
+
"set",
|
|
303
|
+
"add",
|
|
304
|
+
"has",
|
|
305
|
+
"del",
|
|
306
|
+
"keys",
|
|
307
|
+
"values",
|
|
308
|
+
"entries",
|
|
309
|
+
"clear",
|
|
310
|
+
"close",
|
|
311
|
+
]);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("can write/read one", async () => {
|
|
315
|
+
const id = await session.set("a", "b");
|
|
316
|
+
expect(id).toBe("a");
|
|
317
|
+
expect(await session.get("a")).toBe("b");
|
|
318
|
+
expect(await store.get("session:a")).toBe("b");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("can add with the prefix", async () => {
|
|
322
|
+
const id = await session.add("b");
|
|
323
|
+
expect(id.length).toBe(24);
|
|
324
|
+
expect(id).not.toMatch(/^session\:/);
|
|
325
|
+
|
|
326
|
+
const keys = await store.keys();
|
|
327
|
+
expect(keys[0]).toMatch(/^session\:/);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("the group operations return the proper values", async () => {
|
|
331
|
+
await session.set("a", "b");
|
|
332
|
+
|
|
333
|
+
expect(await session.keys()).toEqual(["a"]);
|
|
334
|
+
expect(await session.values()).toEqual(["b"]);
|
|
335
|
+
expect(await session.entries()).toEqual([["a", "b"]]);
|
|
336
|
+
|
|
337
|
+
expect(await store.keys()).toEqual(["session:a"]);
|
|
338
|
+
expect(await store.values()).toEqual(["b"]);
|
|
339
|
+
expect(await store.entries()).toEqual([["session:a", "b"]]);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("clears only the substore", async () => {
|
|
343
|
+
await store.set("a", "b");
|
|
344
|
+
await session.set("c", "d");
|
|
345
|
+
|
|
346
|
+
expect((await store.keys()).sort()).toEqual(["a", "session:c"]);
|
|
347
|
+
await session.clear();
|
|
348
|
+
expect(await store.keys()).toEqual(["a"]);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
262
351
|
});
|
|
263
352
|
}
|