polystore 0.11.1 → 0.11.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polystore",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
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,7 +16,7 @@
16
16
  "scripts": {
17
17
  "size": "echo $(gzip -c src/index.js | wc -c) bytes",
18
18
  "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 && check-dts src/index.types.ts"
19
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --ci --watchAll=false --detectOpenHandles && check-dts test/index.types.ts"
20
20
  },
21
21
  "keywords": [
22
22
  "kv",
@@ -54,11 +54,8 @@
54
54
  "jest": {
55
55
  "testEnvironment": "jsdom",
56
56
  "setupFiles": [
57
- "./src/test/setup.js"
57
+ "./test/setup.js"
58
58
  ],
59
- "transform": {},
60
- "modulePathIgnorePatterns": [
61
- "src/test/"
62
- ]
59
+ "transform": {}
63
60
  }
64
61
  }
package/src/index.js CHANGED
@@ -1,12 +1,6 @@
1
- /**
2
- * A number, or a string containing a number.
3
- * @typedef {(number|string|object|array)} Value
4
- */
5
-
6
1
  import clients from "./clients/index.js";
7
2
  import { createId, isClass, parse } from "./utils.js";
8
3
 
9
- // #region Store
10
4
  class Store {
11
5
  PREFIX = "";
12
6
 
@@ -21,7 +15,6 @@ class Store {
21
15
  });
22
16
  }
23
17
 
24
- // #region #client()
25
18
  #find(store) {
26
19
  // Already a fully compliant KV store
27
20
  if (store instanceof Store) return store.client;
@@ -41,7 +34,6 @@ class Store {
41
34
  return store;
42
35
  }
43
36
 
44
- // #region #validate()
45
37
  #validate(client) {
46
38
  if (!client.set || !client.get || !client.iterate) {
47
39
  throw new Error(
@@ -93,21 +85,6 @@ class Store {
93
85
  return false;
94
86
  }
95
87
 
96
- // #region .add()
97
- /**
98
- * Save the data on an autogenerated key, can add expiration as well:
99
- *
100
- * ```js
101
- * const key1 = await store.add("value1");
102
- * const key2 = await store.add({ hello: "world" });
103
- * const key3 = await store.add("value3", { expires: "1h" });
104
- * ```
105
- *
106
- * **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
107
- * @param {Value} value
108
- * @param {{ expires: string }} options
109
- * @returns {Promise<string>}
110
- */
111
88
  async add(value, options = {}) {
112
89
  await this.promise;
113
90
  const expires = parse(options.expire ?? options.expires);
@@ -130,22 +107,6 @@ class Store {
130
107
  return id; // The plain one without the prefix
131
108
  }
132
109
 
133
- // #region .set()
134
- /**
135
- * Save the data on the given key, can add expiration as well:
136
- *
137
- * ```js
138
- * const key = await store.set("key1", "value1");
139
- * await store.set("key2", { hello: "world" });
140
- * await store.set("key3", "value3", { expires: "1h" });
141
- * ```
142
- *
143
- * **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
144
- * @param {string} key
145
- * @param {Value} value
146
- * @param {{ expires: string }} options
147
- * @returns {Promise<string>}
148
- */
149
110
  async set(key, value, options = {}) {
150
111
  await this.promise;
151
112
  const id = this.PREFIX + key;
@@ -168,23 +129,6 @@ class Store {
168
129
  return key;
169
130
  }
170
131
 
171
- // #region .get()
172
- /**
173
- * Read a single value from the KV store:
174
- *
175
- * ```js
176
- * const value1 = await store.get("key1");
177
- * // null (doesn't exist or has expired)
178
- * const value2 = await store.get("key2");
179
- * // "value2"
180
- * const value3 = await store.get("key3");
181
- * // { hello: "world" }
182
- * ```
183
- *
184
- * **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
185
- * @param {string} key
186
- * @returns {Promise<Value>}
187
- */
188
132
  async get(key) {
189
133
  await this.promise;
190
134
  const id = this.PREFIX + key;
@@ -202,26 +146,6 @@ class Store {
202
146
  return data.value;
203
147
  }
204
148
 
205
- // #region .has()
206
- /**
207
- * Check whether a key exists or not:
208
- *
209
- * ```js
210
- * if (await store.has("key1")) { ... }
211
- * ```
212
- *
213
- * If you are going to use the value, it's better to just read it:
214
- *
215
- * ```js
216
- * const val = await store.get("key1");
217
- * if (val) { ... }
218
- * ```
219
- *
220
- *
221
- * **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
222
- * @param {string} key
223
- * @returns {Promise<boolean>}
224
- */
225
149
  async has(key) {
226
150
  await this.promise;
227
151
  const id = this.PREFIX + key;
@@ -234,18 +158,6 @@ class Store {
234
158
  return value !== null;
235
159
  }
236
160
 
237
- // #region .del()
238
- /**
239
- * Remove a single key and its value from the store:
240
- *
241
- * ```js
242
- * const key = await store.del("key1");
243
- * ```
244
- *
245
- * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
246
- * @param {string} key
247
- * @returns {Promise<string>}
248
- */
249
161
  async del(key) {
250
162
  await this.promise;
251
163
  const id = this.PREFIX + key;
@@ -272,33 +184,19 @@ class Store {
272
184
  }
273
185
  }
274
186
 
275
- // #region .entries()
276
- /**
277
- * Return an array of the entries, in the [key, value] format:
278
- *
279
- * ```js
280
- * const entries = await store.entries();
281
- * // [["key1", "value1"], ["key2", { hello: "world" }], ...]
282
- *
283
- * // To limit it to a given prefix, use `.prefix()`:
284
- * const sessions = await store.prefix("session:").entries();
285
- * ```
286
- *
287
- * **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
288
- * @returns {Promise<[string, Value][]>}
289
- */
290
187
  async entries() {
291
188
  await this.promise;
292
189
 
190
+ // Cut the key to size
191
+ const trim = (key) => key.slice(this.PREFIX.length);
192
+
293
193
  let list = [];
294
194
  if (this.client.entries) {
295
- list = (await this.client.entries(this.PREFIX)).map(([key, value]) => [
296
- key.slice(this.PREFIX.length),
297
- value,
298
- ]);
195
+ const entries = await this.client.entries(this.PREFIX);
196
+ list = entries.map(([key, value]) => [trim(key), value]);
299
197
  } else {
300
198
  for await (const [key, value] of this.client.iterate(this.PREFIX)) {
301
- list.push([key.slice(this.PREFIX.length), value]);
199
+ list.push([trim(key), value]);
302
200
  }
303
201
  }
304
202
 
@@ -312,21 +210,6 @@ class Store {
312
210
  .map(([key, data]) => [key, data.value]);
313
211
  }
314
212
 
315
- // #region .keys()
316
- /**
317
- * Return an array of the keys in the store:
318
- *
319
- * ```js
320
- * const keys = await store.keys();
321
- * // ["key1", "key2", ...]
322
- *
323
- * // To limit it to a given prefix, use `.prefix()`:
324
- * const sessions = await store.prefix("session:").keys();
325
- * ```
326
- *
327
- * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
328
- * @returns {Promise<string[]>}
329
- */
330
213
  async keys() {
331
214
  await this.promise;
332
215
 
@@ -340,21 +223,6 @@ class Store {
340
223
  return entries.map((e) => e[0]);
341
224
  }
342
225
 
343
- // #region .values()
344
- /**
345
- * Return an array of the values in the store:
346
- *
347
- * ```js
348
- * const values = await store.values();
349
- * // ["value1", { hello: "world" }, ...]
350
- *
351
- * // To limit it to a given prefix, use `.prefix()`:
352
- * const sessions = await store.prefix("session:").values();
353
- * ```
354
- *
355
- * **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
356
- * @returns {Promise<Value[]>}
357
- */
358
226
  async values() {
359
227
  await this.promise;
360
228
 
@@ -370,21 +238,6 @@ class Store {
370
238
  return entries.map((e) => e[1]);
371
239
  }
372
240
 
373
- // #region .all()
374
- /**
375
- * Return an object with the keys:values in the store:
376
- *
377
- * ```js
378
- * const obj = await store.all();
379
- * // { key1: "value1", key2: { hello: "world" }, ... }
380
- *
381
- * // To limit it to a given prefix, use `.prefix()`:
382
- * const sessions = await store.prefix("session:").all();
383
- * ```
384
- *
385
- * **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
386
- * @returns {Promise<{ [key:string]: Value }>}
387
- */
388
241
  async all() {
389
242
  await this.promise;
390
243
 
@@ -402,19 +255,6 @@ class Store {
402
255
  return Object.fromEntries(entries);
403
256
  }
404
257
 
405
- // #region .clear()
406
- /**
407
- * Delete all of the records of the store:
408
- *
409
- * ```js
410
- * await store.clear();
411
- * ```
412
- *
413
- * It's useful for cache invalidation, clearing the data, and testing.
414
- *
415
- * **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
416
- * @returns {Promise<null>}
417
- */
418
258
  async clear() {
419
259
  await this.promise;
420
260
 
@@ -427,23 +267,6 @@ class Store {
427
267
  await Promise.all(keys.map((key) => this.del(key)));
428
268
  }
429
269
 
430
- // #region .prefix()
431
- /**
432
- * Create a substore where all the keys are stored with
433
- * the given prefix:
434
- *
435
- * ```js
436
- * const session = store.prefix("session:");
437
- * await session.set("key1", "value1");
438
- * console.log(await session.entries()); // session.
439
- * // [["key1", "value1"]]
440
- * console.log(await store.entries()); // store.
441
- * // [["session:key1", "value1"]]
442
- * ```
443
- *
444
- * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
445
- * @returns {Store}
446
- */
447
270
  prefix(prefix = "") {
448
271
  const store = new Store(
449
272
  Promise.resolve(this.promise).then((client) => client || this.client)
@@ -452,19 +275,6 @@ class Store {
452
275
  return store;
453
276
  }
454
277
 
455
- // #region .close()
456
- /**
457
- * Stop the connection to the store, if any:
458
- *
459
- * ```js
460
- * await session.set("key1", "value1");
461
- * await store.close();
462
- * await session.set("key2", "value2"); // error
463
- * ```
464
- *
465
- * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
466
- * @returns {Store}
467
- */
468
278
  async close() {
469
279
  await this.promise;
470
280
 
package/src/index.test.js DELETED
@@ -1,631 +0,0 @@
1
- import "dotenv/config";
2
-
3
- import { jest } from "@jest/globals";
4
- import { EdgeKVNamespace as KVNamespace } from "edge-mock";
5
- import { Etcd3 } from "etcd3";
6
- import { Level } from "level";
7
- import localForage from "localforage";
8
- import { createClient } from "redis";
9
-
10
- import kv from "./index.js";
11
- import customFull from "./test/customFull.js";
12
- import customSimple from "./test/customSimple.js";
13
-
14
- const stores = [];
15
- stores.push(["kv()", kv()]);
16
- stores.push(["kv(new Map())", kv(new Map())]);
17
- stores.push(["kv(localStorage)", kv(localStorage)]);
18
- stores.push(["kv(sessionStorage)", kv(sessionStorage)]);
19
- stores.push(["kv(localForage)", kv(localForage)]);
20
- const path = `file://${process.cwd()}/src/test/data.json`;
21
- stores.push([`kv(new URL("${path}"))`, kv(new URL(path))]);
22
- const path2 = `file://${process.cwd()}/src/test/data.json`;
23
- stores.push([`kv("${path2}")`, kv(path2)]);
24
- stores.push([`kv("cookie")`, kv("cookie")]);
25
- stores.push(["kv(new KVNamespace())", kv(new KVNamespace())]);
26
- stores.push([`kv(new Level("data"))`, kv(new Level("data"))]);
27
- if (process.env.REDIS) {
28
- stores.push(["kv(redis)", kv(createClient())]);
29
- }
30
- if (process.env.ETCD) {
31
- // Note: need to add to .env "ETCD=true" and run `etcd` in the terminal
32
- stores.push(["kv(new Etcd3())", kv(new Etcd3())]);
33
- }
34
-
35
- stores.push(["kv(customSimple)", kv(customSimple)]);
36
- stores.push(["kv(customFull)", kv(customFull)]);
37
-
38
- const delay = (t) => new Promise((done) => setTimeout(done, t));
39
-
40
- class Base {
41
- get() {}
42
- set() {}
43
- *iterate() {}
44
- }
45
-
46
- global.console = {
47
- ...console,
48
- warn: jest.fn(),
49
- };
50
-
51
- describe("potato", () => {
52
- it("a potato is not a valid store", async () => {
53
- await expect(() => kv("potato").get("any")).rejects.toThrow();
54
- });
55
-
56
- it("an empty object is not a valid store", async () => {
57
- await expect(() => kv({}).get("any")).rejects.toThrow({
58
- message: "A client should have at least a .get(), .set() and .iterate()",
59
- });
60
- });
61
-
62
- it("cannot handle no EXPIRES + keys", async () => {
63
- await expect(() =>
64
- kv(
65
- class extends Base {
66
- has() {}
67
- }
68
- ).get("any")
69
- ).rejects.toThrow({
70
- message:
71
- "You can only define client.has() when the client manages the expiration; otherwise please do NOT define .has() and let us manage it",
72
- });
73
- });
74
-
75
- it("cannot handle no EXPIRES + keys", async () => {
76
- await expect(() =>
77
- kv(
78
- class extends Base {
79
- keys() {}
80
- }
81
- ).get("any")
82
- ).rejects.toThrow({
83
- message:
84
- "You can only define client.keys() when the client manages the expiration; otherwise please do NOT define .keys() and let us manage them",
85
- });
86
- });
87
-
88
- it("warns the user with no EXPIRES + .values()", async () => {
89
- const warn = jest
90
- .spyOn(console, "warn")
91
- .mockImplementationOnce(() => "Hello");
92
- await kv(
93
- class extends Base {
94
- values() {}
95
- }
96
- ).get("any");
97
- expect(warn).toHaveBeenCalled(); // But at least we warn them
98
- warn.mockClear();
99
- });
100
- });
101
-
102
- for (let [name, store] of stores) {
103
- describe(name, () => {
104
- beforeEach(async () => {
105
- await store.clear();
106
- });
107
-
108
- afterAll(async () => {
109
- await store.clear();
110
- await store.close();
111
- });
112
-
113
- it("can perform a CRUD", async () => {
114
- expect(await store.get("a")).toBe(null);
115
- expect(await store.has("a")).toBe(false);
116
- expect(await store.set("a", "b")).toBe("a");
117
- expect(await store.has("a")).toBe(true);
118
- expect(await store.get("a")).toBe("b");
119
- expect(await store.del("a")).toBe("a");
120
- expect(await store.get("a")).toBe(null);
121
- });
122
-
123
- it("is empty on the start", async () => {
124
- expect(await store.get("a")).toBe(null);
125
- expect(await store.has("a")).toBe(false);
126
- });
127
-
128
- it("can add() arbitrary values", async () => {
129
- const key = await store.add("b");
130
- expect(typeof key).toBe("string");
131
- expect(await store.get(key)).toBe("b");
132
- expect(await store.has(key)).toBe(true);
133
- expect(key.length).toBe(24);
134
- expect(key).toMatch(/^[a-zA-Z0-9]{24}$/);
135
- });
136
-
137
- it("can store values", async () => {
138
- const key = await store.set("a", "b");
139
- expect(await store.get("a")).toBe("b");
140
- expect(await store.has("a")).toBe(true);
141
- expect(key).toBe("a");
142
- });
143
-
144
- it("can store values with a semicolon", async () => {
145
- const key = await store.set("a", "b;c");
146
- expect(await store.get("a")).toBe("b;c");
147
- expect(await store.has("a")).toBe(true);
148
- expect(key).toBe("a");
149
- });
150
-
151
- it("can store values with an equal", async () => {
152
- const key = await store.set("a", "b=c");
153
- expect(await store.get("a")).toBe("b=c");
154
- expect(await store.has("a")).toBe(true);
155
- expect(key).toBe("a");
156
- });
157
-
158
- it("can store values with a semicolon in the key", async () => {
159
- const key = await store.set("a;b", "c");
160
- expect(await store.get("a;b")).toBe("c");
161
- expect(await store.has("a;b")).toBe(true);
162
- expect(key).toBe("a;b");
163
- });
164
-
165
- it("can store values with an equal in the key", async () => {
166
- const key = await store.set("a=b", "c");
167
- expect(await store.get("a=b")).toBe("c");
168
- expect(await store.has("a=b")).toBe(true);
169
- expect(key).toBe("a=b");
170
- });
171
-
172
- it("can store basic types", async () => {
173
- await store.set("a", 10);
174
- expect(await store.get("a")).toEqual(10);
175
- await store.set("a", "b");
176
- expect(await store.get("a")).toEqual("b");
177
- await store.set("a", true);
178
- expect(await store.get("a")).toEqual(true);
179
- });
180
-
181
- it("can store arrays of JSON values", async () => {
182
- await store.set("a", ["b", "c"]);
183
- expect(await store.get("a")).toEqual(["b", "c"]);
184
- expect(await store.has("a")).toBe(true);
185
- });
186
-
187
- it("can store objects", async () => {
188
- await store.set("a", { b: "c" });
189
- expect(await store.get("a")).toEqual({ b: "c" });
190
- expect(await store.has("a")).toBe(true);
191
- });
192
-
193
- it("can get the keys", async () => {
194
- await store.set("a", "b");
195
- await store.set("c", "d");
196
- expect(await store.keys()).toEqual(["a", "c"]);
197
- });
198
-
199
- it("can get the values", async () => {
200
- await store.set("a", "b");
201
- await store.set("c", "d");
202
- expect(await store.values()).toEqual(["b", "d"]);
203
- });
204
-
205
- it("can get the entries", async () => {
206
- await store.set("a", "b");
207
- await store.set("c", "d");
208
- expect(await store.entries()).toEqual([
209
- ["a", "b"],
210
- ["c", "d"],
211
- ]);
212
- });
213
-
214
- it("can get the all object", async () => {
215
- await store.set("a", "b");
216
- await store.set("c", "d");
217
- expect(await store.all()).toEqual({
218
- a: "b",
219
- c: "d",
220
- });
221
- });
222
-
223
- it("can get the keys with a colon prefix", async () => {
224
- await store.set("a:0", "a0");
225
- await store.set("a:1", "a1");
226
- await store.set("b:0", "b0");
227
- await store.set("a:2", "a2");
228
- expect((await store.keys()).sort()).toEqual(["a:0", "a:1", "a:2", "b:0"]);
229
- expect((await store.prefix("a:").keys()).sort()).toEqual(["0", "1", "2"]);
230
- });
231
-
232
- it("can get the values with a colon prefix", async () => {
233
- await store.set("a:0", "a0");
234
- await store.set("a:1", "a1");
235
- await store.set("b:0", "b0");
236
- await store.set("a:2", "a2");
237
- expect((await store.prefix("a:").values()).sort()).toEqual([
238
- "a0",
239
- "a1",
240
- "a2",
241
- ]);
242
- });
243
-
244
- it("can get the entries with a colon prefix", async () => {
245
- await store.set("a:0", "a0");
246
- await store.set("a:1", "a1");
247
- await store.set("b:0", "b0");
248
- await store.set("a:2", "a2");
249
- expect((await store.entries()).sort()).toEqual([
250
- ["a:0", "a0"],
251
- ["a:1", "a1"],
252
- ["a:2", "a2"],
253
- ["b:0", "b0"],
254
- ]);
255
- expect((await store.prefix("a:").entries()).sort()).toEqual([
256
- ["0", "a0"],
257
- ["1", "a1"],
258
- ["2", "a2"],
259
- ]);
260
- });
261
-
262
- it("can get the all object with a colon prefix", async () => {
263
- await store.set("a:0", "a0");
264
- await store.set("a:1", "a1");
265
- await store.set("b:0", "b0");
266
- await store.set("a:2", "a2");
267
- expect(await store.prefix("a:").all()).toEqual({
268
- 0: "a0",
269
- 1: "a1",
270
- 2: "a2",
271
- });
272
- });
273
-
274
- it("can get the keys with a dash prefix", async () => {
275
- await store.set("a-0", "a0");
276
- await store.set("a-1", "a1");
277
- await store.set("b-0", "b0");
278
- await store.set("a-2", "a2");
279
- expect((await store.keys()).sort()).toEqual(["a-0", "a-1", "a-2", "b-0"]);
280
- expect((await store.prefix("a-").keys()).sort()).toEqual(["0", "1", "2"]);
281
- });
282
-
283
- it("can get the values with a dash prefix", async () => {
284
- await store.set("a-0", "a0");
285
- await store.set("a-1", "a1");
286
- await store.set("b-0", "b0");
287
- await store.set("a-2", "a2");
288
- expect((await store.prefix("a-").values()).sort()).toEqual([
289
- "a0",
290
- "a1",
291
- "a2",
292
- ]);
293
- });
294
-
295
- it("can get the entries with a dash prefix", async () => {
296
- await store.set("a-0", "a0");
297
- await store.set("a-1", "a1");
298
- await store.set("b-0", "b0");
299
- await store.set("a-2", "a2");
300
- expect((await store.prefix("a-").entries()).sort()).toEqual([
301
- ["0", "a0"],
302
- ["1", "a1"],
303
- ["2", "a2"],
304
- ]);
305
- });
306
-
307
- it("can get the all object with a dash prefix", async () => {
308
- await store.set("a-0", "a0");
309
- await store.set("a-1", "a1");
310
- await store.set("b-0", "b0");
311
- await store.set("a-2", "a2");
312
- expect(await store.prefix("a-").all()).toEqual({
313
- 0: "a0",
314
- 1: "a1",
315
- 2: "a2",
316
- });
317
- });
318
-
319
- it("can delete the data", async () => {
320
- await store.set("a", "b");
321
- expect(await store.get("a")).toBe("b");
322
- await store.del("a");
323
- expect(await store.get("a")).toBe(null);
324
- expect(await store.keys()).toEqual([]);
325
- });
326
-
327
- it("can delete the data by setting it to null", async () => {
328
- await store.set("a", "b");
329
- expect(await store.get("a")).toBe("b");
330
- await store.set("a", null);
331
- expect(await store.get("a")).toBe(null);
332
- expect(await store.keys()).toEqual([]);
333
- });
334
-
335
- it("can clear all the values", async () => {
336
- await store.set("a", "b");
337
- await store.set("c", "d");
338
- expect(await store.get("a")).toBe("b");
339
- await store.clear();
340
- expect(await store.get("a")).toBe(null);
341
- await store.set("a", "b");
342
- expect(await store.get("a")).toBe("b");
343
- });
344
-
345
- describe("iteration", () => {
346
- beforeEach(async () => {
347
- await store.clear();
348
- });
349
-
350
- it("supports raw iteration", async () => {
351
- await store.set("a", "b");
352
- await store.set("c", "d");
353
-
354
- const entries = [];
355
- for await (const entry of store) {
356
- entries.push(entry);
357
- }
358
- expect(entries).toEqual([
359
- ["a", "b"],
360
- ["c", "d"],
361
- ]);
362
- });
363
-
364
- it("supports raw prefix iteration", async () => {
365
- await store.set("a:a", "b");
366
- await store.set("b:a", "d");
367
- await store.set("a:c", "d");
368
- await store.set("b:c", "d");
369
-
370
- const entries = [];
371
- for await (const entry of store.prefix("a:")) {
372
- entries.push(entry);
373
- }
374
- expect(entries.sort()).toEqual([
375
- ["a", "b"],
376
- ["c", "d"],
377
- ]);
378
- });
379
- });
380
-
381
- describe("expires", () => {
382
- // The mock implementation does NOT support expiration 😪
383
- if (name === "kv(new KVNamespace())") return;
384
-
385
- afterEach(() => {
386
- jest.restoreAllMocks();
387
- });
388
-
389
- it("expires = 0 means immediately", async () => {
390
- await store.set("a", "b", { expires: 0 });
391
- expect(await store.get("a")).toBe(null);
392
- expect(await store.has("a")).toBe(false);
393
- expect(await store.keys()).toEqual([]);
394
- expect(await store.values()).toEqual([]);
395
- expect(await store.entries()).toEqual([]);
396
- });
397
-
398
- it("expires = potato means undefined = forever", async () => {
399
- await store.set("a", "b", { expires: "potato" });
400
- expect(await store.get("a")).toBe("b");
401
- await delay(100);
402
- expect(await store.get("a")).toBe("b");
403
- });
404
-
405
- it("expires = 5potato means undefined = forever", async () => {
406
- await store.set("a", "b", { expires: "5potato" });
407
- expect(await store.get("a")).toBe("b");
408
- await delay(100);
409
- expect(await store.get("a")).toBe("b");
410
- });
411
-
412
- it("expires = null means never to expire it", async () => {
413
- await store.set("a", "b", { expires: null });
414
- expect(await store.get("a")).toBe("b");
415
- await delay(100);
416
- expect(await store.get("a")).toBe("b");
417
- });
418
-
419
- it("expires = undefined means never to expire it", async () => {
420
- await store.set("a", "b");
421
- expect(await store.get("a")).toBe("b");
422
- await delay(100);
423
- expect(await store.get("a")).toBe("b");
424
- });
425
-
426
- if (
427
- name !== `kv("cookie")` &&
428
- name !== `kv(redis)` &&
429
- name !== `kv(new Etcd3())`
430
- ) {
431
- it("can use 0.01 expire", async () => {
432
- // 10ms
433
- await store.set("a", "b", { expires: 0.01 });
434
- expect(await store.keys()).toEqual(["a"]);
435
- expect(await store.values()).toEqual(["b"]);
436
- expect(await store.get("a")).toBe("b");
437
- await delay(100);
438
- expect(await store.keys()).toEqual([]);
439
- expect(await store.values()).toEqual([]);
440
- expect(await store.get("a")).toBe(null);
441
- });
442
-
443
- it("can use 0.01s expire", async () => {
444
- await store.set("a", "b", { expires: "0.01s" });
445
- expect(await store.keys()).toEqual(["a"]);
446
- expect(await store.values()).toEqual(["b"]);
447
- expect(await store.get("a")).toBe("b");
448
- await delay(100);
449
- expect(await store.keys()).toEqual([]);
450
- expect(await store.values()).toEqual([]);
451
- expect(await store.get("a")).toBe(null);
452
- });
453
-
454
- it("can use 0.01seconds expire", async () => {
455
- await store.set("a", "b", { expires: "0.01seconds" });
456
- expect(await store.keys()).toEqual(["a"]);
457
- expect(await store.values()).toEqual(["b"]);
458
- expect(await store.get("a")).toBe("b");
459
- await delay(100);
460
- expect(await store.keys()).toEqual([]);
461
- expect(await store.values()).toEqual([]);
462
- expect(await store.get("a")).toBe(null);
463
- });
464
-
465
- it("can use 10ms expire", async () => {
466
- await store.set("a", "b", { expires: "10ms" });
467
- expect(await store.keys()).toEqual(["a"]);
468
- expect(await store.values()).toEqual(["b"]);
469
- expect(await store.get("a")).toBe("b");
470
- await delay(100);
471
- expect(await store.keys()).toEqual([]);
472
- expect(await store.values()).toEqual([]);
473
- expect(await store.get("a")).toBe(null);
474
- });
475
-
476
- it("removes the expired key with .get()", async () => {
477
- await store.set("a", "b", { expires: "10ms" });
478
- const spy = jest.spyOn(store, "del");
479
- expect(spy).not.toHaveBeenCalled();
480
- await delay(100);
481
- expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪
482
- expect(await store.get("a")).toBe(null);
483
- expect(spy).toHaveBeenCalled();
484
- });
485
-
486
- it("removes the expired key with .keys()", async () => {
487
- await store.set("a", "b", { expires: "10ms" });
488
- const spy = jest.spyOn(store, "del");
489
- expect(spy).not.toHaveBeenCalled();
490
- await delay(100);
491
- expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪
492
- expect(await store.keys()).toEqual([]);
493
- expect(spy).toHaveBeenCalled();
494
- });
495
-
496
- it("CANNOT remove the expired key with .values()", async () => {
497
- await store.set("a", "b", { expires: "10ms" });
498
- const spy = jest.spyOn(store, "del");
499
- expect(spy).not.toHaveBeenCalled();
500
- await delay(100);
501
- expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪
502
- expect(await store.values()).toEqual([]);
503
- if (!store.client.EXPIRES && store.client.values) {
504
- expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪😪
505
- } else {
506
- expect(spy).toHaveBeenCalled();
507
- }
508
- });
509
- } else {
510
- it("can use 1 (second) expire", async () => {
511
- await store.set("a", "b", { expires: 1 });
512
- expect(await store.keys()).toEqual(["a"]);
513
- expect(await store.values()).toEqual(["b"]);
514
- expect(await store.get("a")).toBe("b");
515
- await delay(2000);
516
- expect(await store.keys()).toEqual([]);
517
- expect(await store.values()).toEqual([]);
518
- expect(await store.get("a")).toBe(null);
519
- });
520
- it("can use 1s expire", async () => {
521
- await store.set("a", "b", { expires: "1s" });
522
- expect(await store.keys()).toEqual(["a"]);
523
- expect(await store.values()).toEqual(["b"]);
524
- expect(await store.get("a")).toBe("b");
525
- await delay(2000);
526
- expect(await store.keys()).toEqual([]);
527
- expect(await store.values()).toEqual([]);
528
- expect(await store.get("a")).toBe(null);
529
- });
530
- }
531
- });
532
-
533
- describe(".prefix()", () => {
534
- let session;
535
- beforeAll(() => {
536
- session = store.prefix("session:");
537
- });
538
-
539
- it("has the same methods", () => {
540
- expect(Object.keys(store)).toEqual(Object.keys(session));
541
- });
542
-
543
- it("can write/read one", async () => {
544
- const id = await session.set("a", "b");
545
- expect(id).toBe("a");
546
- expect(await session.get("a")).toBe("b");
547
- expect(await store.get("session:a")).toBe("b");
548
- });
549
-
550
- it("checks the has properly", async () => {
551
- expect(await session.has("a")).toBe(false);
552
- await session.set("a", "b");
553
- expect(await session.has("a")).toBe(true);
554
- });
555
-
556
- it("can add with the prefix", async () => {
557
- const id = await session.add("b");
558
- expect(id.length).toBe(24);
559
- expect(id).not.toMatch(/^session\:/);
560
-
561
- const keys = await store.keys();
562
- expect(keys[0]).toMatch(/^session\:/);
563
- });
564
-
565
- it("the group operations return the proper values", async () => {
566
- await session.set("a", "b");
567
-
568
- expect(await session.keys()).toEqual(["a"]);
569
- expect(await session.values()).toEqual(["b"]);
570
- expect(await session.entries()).toEqual([["a", "b"]]);
571
-
572
- expect(await store.keys()).toEqual(["session:a"]);
573
- expect(await store.values()).toEqual(["b"]);
574
- expect(await store.entries()).toEqual([["session:a", "b"]]);
575
- });
576
-
577
- it("clears only the substore", async () => {
578
- await store.set("a", "b");
579
- await session.set("c", "d");
580
-
581
- expect((await store.keys()).sort()).toEqual(["a", "session:c"]);
582
- await session.clear();
583
- expect(await store.keys()).toEqual(["a"]);
584
- });
585
- });
586
-
587
- describe(".prefix().prefix()", () => {
588
- let auth;
589
- beforeAll(() => {
590
- auth = store.prefix("session:").prefix("auth:");
591
- });
592
-
593
- it("can write/read one", async () => {
594
- const id = await auth.set("a", "b");
595
- expect(id).toBe("a");
596
- expect(await auth.get("a")).toBe("b");
597
- expect(await store.get("session:auth:a")).toBe("b");
598
- });
599
-
600
- it("can add with the prefix", async () => {
601
- const id = await auth.add("b");
602
- expect(id.length).toBe(24);
603
- expect(id).not.toMatch(/^session\:/);
604
-
605
- const keys = await store.keys();
606
- expect(keys[0]).toMatch(/^session\:auth\:/);
607
- });
608
-
609
- it("the group operations return the proper values", async () => {
610
- await auth.set("a", "b");
611
-
612
- expect(await auth.keys()).toEqual(["a"]);
613
- expect(await auth.values()).toEqual(["b"]);
614
- expect(await auth.entries()).toEqual([["a", "b"]]);
615
-
616
- expect(await store.keys()).toEqual(["session:auth:a"]);
617
- expect(await store.values()).toEqual(["b"]);
618
- expect(await store.entries()).toEqual([["session:auth:a", "b"]]);
619
- });
620
-
621
- it("clears only the substore", async () => {
622
- await store.set("a", "b");
623
- await auth.set("c", "d");
624
-
625
- expect((await store.keys()).sort()).toEqual(["a", "session:auth:c"]);
626
- await auth.clear();
627
- expect(await store.keys()).toEqual(["a"]);
628
- });
629
- });
630
- });
631
- }
@@ -1,18 +0,0 @@
1
- import kv from "..";
2
-
3
- const store = kv();
4
-
5
- (async () => {
6
- await store.get("key");
7
- await store.set("key", "value");
8
- await store.set("key", "value", {});
9
- await store.set("key", "value", { expires: 100 });
10
- await store.set("key", "value", { expires: "100s" });
11
- await store.prefix("a:").prefix("b:").get("hello");
12
- const key1: string = await store.add("value");
13
- const key2: string = await store.add("value", { expires: 100 });
14
- const key3: string = await store.add("value", { expires: "100s" });
15
- console.log(key1, key2, key3);
16
- if (await store.has("key")) {
17
- }
18
- })();
@@ -1,45 +0,0 @@
1
- const dataSource = {};
2
-
3
- export default class MyClient {
4
- get(key) {
5
- return dataSource[key];
6
- }
7
-
8
- set(key, value) {
9
- dataSource[key] = value;
10
- }
11
-
12
- add(prefix, value) {
13
- const id = Math.random().toString(16).slice(2).padStart(24, "0");
14
- this.set(prefix + id, value);
15
- return id;
16
- }
17
-
18
- del(key) {
19
- delete dataSource[key];
20
- }
21
-
22
- *iterate(prefix) {
23
- const entries = this.entries(prefix);
24
- for (const entry of entries) {
25
- yield entry;
26
- }
27
- }
28
-
29
- // Filter them by the prefix, note that `prefix` will always be a string
30
- entries(prefix) {
31
- const entries = Object.entries(dataSource);
32
- if (!prefix) return entries;
33
- return entries.filter(([key]) => key.startsWith(prefix));
34
- }
35
-
36
- values(prefix) {
37
- const list = this.entries(prefix);
38
- return list.map((e) => e[1]);
39
- }
40
-
41
- // Cannot have a keys() if it's an unamanaged store
42
- // keys(prefix) {
43
- // // This should throw
44
- // }
45
- }
@@ -1,27 +0,0 @@
1
- const dataSource = {};
2
-
3
- export default class MyClient {
4
- get(key) {
5
- return dataSource[key];
6
- }
7
-
8
- // No need to stringify it or anything for a plain object storage
9
- set(key, value) {
10
- if (value === null) {
11
- delete dataSource[key];
12
- } else {
13
- dataSource[key] = value;
14
- }
15
- }
16
-
17
- // Filter them by the prefix, note that `prefix` will always be a string
18
- *iterate(prefix) {
19
- const raw = Object.entries(dataSource);
20
- const entries = prefix
21
- ? raw.filter(([key, value]) => key.startsWith(prefix))
22
- : raw;
23
- for (const entry of entries) {
24
- yield entry;
25
- }
26
- }
27
- }
@@ -1 +0,0 @@
1
- {}
package/src/test/setup.js DELETED
@@ -1,23 +0,0 @@
1
- import * as util from "util";
2
-
3
- // ref: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
4
- // ref: https://github.com/jsdom/jsdom/issues/2524
5
- if (typeof TextEncoder === "undefined") {
6
- Object.defineProperty(window, "TextEncoder", {
7
- writable: true,
8
- value: util.TextEncoder,
9
- });
10
- }
11
- if (typeof TextDecoder === "undefined") {
12
- Object.defineProperty(window, "TextDecoder", {
13
- writable: true,
14
- value: util.TextDecoder,
15
- });
16
- }
17
-
18
- if (typeof setImmediate === "undefined") {
19
- Object.defineProperty(window, "setImmediate", {
20
- writable: true,
21
- value: (cb) => setTimeout(cb, 0),
22
- });
23
- }