polystore 0.7.0 → 0.9.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/src/index.test.js CHANGED
@@ -1,9 +1,15 @@
1
1
  import "dotenv/config";
2
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";
3
7
  import localForage from "localforage";
4
8
  import { createClient } from "redis";
5
9
 
6
10
  import kv from "./";
11
+ import customFull from "./test/customFull.js";
12
+ import customSimple from "./test/customSimple.js";
7
13
 
8
14
  global.setImmediate = global.setImmediate || ((cb) => setTimeout(cb, 0));
9
15
 
@@ -15,17 +21,83 @@ stores.push(["kv(sessionStorage)", kv(sessionStorage)]);
15
21
  stores.push(["kv(localForage)", kv(localForage)]);
16
22
  const path = `file://${process.cwd()}/src/test/data.json`;
17
23
  stores.push([`kv(new URL("${path}"))`, kv(new URL(path))]);
24
+ const path2 = `file://${process.cwd()}/src/test/data.json`;
25
+ stores.push([`kv("${path2}")`, kv(path2)]);
26
+ stores.push([`kv("cookie")`, kv("cookie")]);
27
+ stores.push(["kv(new KVNamespace())", kv(new KVNamespace())]);
28
+ stores.push([`kv(new Level("data"))`, kv(new Level("data"))]);
18
29
  if (process.env.REDIS) {
19
30
  stores.push(["kv(redis)", kv(createClient().connect())]);
20
31
  }
21
- stores.push(["kv('cookie')", kv("cookie")]);
32
+ if (process.env.ETCD) {
33
+ stores.push(["kv(new Etcd3())", kv(new Etcd3())]);
34
+ }
35
+
36
+ stores.push(["kv(customSimple)", kv(customSimple)]);
37
+ stores.push(["kv(customFull)", kv(customFull)]);
22
38
 
23
39
  const delay = (t) => new Promise((done) => setTimeout(done, t));
24
40
 
41
+ class Base {
42
+ get() {}
43
+ set() {}
44
+ entries() {}
45
+ }
46
+
47
+ global.console = {
48
+ ...console,
49
+ warn: jest.fn(),
50
+ };
51
+
25
52
  describe("potato", () => {
26
53
  it("a potato is not a valid store", async () => {
27
54
  await expect(() => kv("potato").get("any")).rejects.toThrow();
28
55
  });
56
+
57
+ it("an empty object is not a valid store", async () => {
58
+ await expect(() => kv({}).get("any")).rejects.toThrow({
59
+ message: "A client should have at least a .get(), .set() and .entries()",
60
+ });
61
+ });
62
+
63
+ it("cannot handle no EXPIRES + keys", async () => {
64
+ await expect(() =>
65
+ kv(
66
+ class extends Base {
67
+ has() {}
68
+ }
69
+ ).get("any")
70
+ ).rejects.toThrow({
71
+ message:
72
+ "You can only define client.has() when the client manages the expiration; otherwise please do NOT define .has() and let us manage it",
73
+ });
74
+ });
75
+
76
+ it("cannot handle no EXPIRES + keys", async () => {
77
+ await expect(() =>
78
+ kv(
79
+ class extends Base {
80
+ keys() {}
81
+ }
82
+ ).get("any")
83
+ ).rejects.toThrow({
84
+ message:
85
+ "You can only define client.keys() when the client manages the expiration; otherwise please do NOT define .keys() and let us manage them",
86
+ });
87
+ });
88
+
89
+ it("warns the user with no EXPIRES + .values()", async () => {
90
+ const warn = jest
91
+ .spyOn(console, "warn")
92
+ .mockImplementationOnce(() => "Hello");
93
+ await kv(
94
+ class extends Base {
95
+ values() {}
96
+ }
97
+ ).get("any");
98
+ expect(warn).toHaveBeenCalled(); // But at least we warn them
99
+ warn.mockClear();
100
+ });
29
101
  });
30
102
 
31
103
  for (let [name, store] of stores) {
@@ -36,9 +108,15 @@ for (let [name, store] of stores) {
36
108
 
37
109
  afterAll(async () => {
38
110
  await store.clear();
39
- if (store.close) {
40
- await store.close();
41
- }
111
+ await store.close();
112
+ });
113
+
114
+ it("can perform a CRUD", async () => {
115
+ expect(await store.get("a")).toBe(null);
116
+ expect(await store.set("a", "b")).toBe("a");
117
+ expect(await store.get("a")).toBe("b");
118
+ expect(await store.del("a")).toBe("a");
119
+ expect(await store.get("a")).toBe(null);
42
120
  });
43
121
 
44
122
  it("is empty on the start", async () => {
@@ -62,6 +140,34 @@ for (let [name, store] of stores) {
62
140
  expect(key).toBe("a");
63
141
  });
64
142
 
143
+ it("can store values with a semicolon", async () => {
144
+ const key = await store.set("a", "b;c");
145
+ expect(await store.get("a")).toBe("b;c");
146
+ expect(await store.has("a")).toBe(true);
147
+ expect(key).toBe("a");
148
+ });
149
+
150
+ it("can store values with an equal", async () => {
151
+ const key = await store.set("a", "b=c");
152
+ expect(await store.get("a")).toBe("b=c");
153
+ expect(await store.has("a")).toBe(true);
154
+ expect(key).toBe("a");
155
+ });
156
+
157
+ it("can store values with a semicolon in the key", async () => {
158
+ const key = await store.set("a;b", "c");
159
+ expect(await store.get("a;b")).toBe("c");
160
+ expect(await store.has("a;b")).toBe(true);
161
+ expect(key).toBe("a;b");
162
+ });
163
+
164
+ it("can store values with an equal in the key", async () => {
165
+ const key = await store.set("a=b", "c");
166
+ expect(await store.get("a=b")).toBe("c");
167
+ expect(await store.has("a=b")).toBe(true);
168
+ expect(key).toBe("a=b");
169
+ });
170
+
65
171
  it("can store basic types", async () => {
66
172
  await store.set("a", 10);
67
173
  expect(await store.get("a")).toEqual(10);
@@ -118,7 +224,8 @@ for (let [name, store] of stores) {
118
224
  await store.set("a:1", "a1");
119
225
  await store.set("b:0", "b0");
120
226
  await store.set("a:2", "a2");
121
- expect((await store.keys("a:")).sort()).toEqual(["a:0", "a:1", "a:2"]);
227
+ expect((await store.keys()).sort()).toEqual(["a:0", "a:1", "a:2", "b:0"]);
228
+ expect((await store.prefix("a:").keys()).sort()).toEqual(["0", "1", "2"]);
122
229
  });
123
230
 
124
231
  it("can get the values with a colon prefix", async () => {
@@ -126,7 +233,11 @@ for (let [name, store] of stores) {
126
233
  await store.set("a:1", "a1");
127
234
  await store.set("b:0", "b0");
128
235
  await store.set("a:2", "a2");
129
- expect((await store.values("a:")).sort()).toEqual(["a0", "a1", "a2"]);
236
+ expect((await store.prefix("a:").values()).sort()).toEqual([
237
+ "a0",
238
+ "a1",
239
+ "a2",
240
+ ]);
130
241
  });
131
242
 
132
243
  it("can get the entries with a colon prefix", async () => {
@@ -134,10 +245,16 @@ for (let [name, store] of stores) {
134
245
  await store.set("a:1", "a1");
135
246
  await store.set("b:0", "b0");
136
247
  await store.set("a:2", "a2");
137
- expect((await store.entries("a:")).sort()).toEqual([
248
+ expect((await store.entries()).sort()).toEqual([
138
249
  ["a:0", "a0"],
139
250
  ["a:1", "a1"],
140
251
  ["a:2", "a2"],
252
+ ["b:0", "b0"],
253
+ ]);
254
+ expect((await store.prefix("a:").entries()).sort()).toEqual([
255
+ ["0", "a0"],
256
+ ["1", "a1"],
257
+ ["2", "a2"],
141
258
  ]);
142
259
  });
143
260
 
@@ -146,10 +263,10 @@ for (let [name, store] of stores) {
146
263
  await store.set("a:1", "a1");
147
264
  await store.set("b:0", "b0");
148
265
  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",
266
+ expect(await store.prefix("a:").all()).toEqual({
267
+ 0: "a0",
268
+ 1: "a1",
269
+ 2: "a2",
153
270
  });
154
271
  });
155
272
 
@@ -158,7 +275,8 @@ for (let [name, store] of stores) {
158
275
  await store.set("a-1", "a1");
159
276
  await store.set("b-0", "b0");
160
277
  await store.set("a-2", "a2");
161
- expect((await store.keys("a-")).sort()).toEqual(["a-0", "a-1", "a-2"]);
278
+ expect((await store.keys()).sort()).toEqual(["a-0", "a-1", "a-2", "b-0"]);
279
+ expect((await store.prefix("a-").keys()).sort()).toEqual(["0", "1", "2"]);
162
280
  });
163
281
 
164
282
  it("can get the values with a dash prefix", async () => {
@@ -166,7 +284,11 @@ for (let [name, store] of stores) {
166
284
  await store.set("a-1", "a1");
167
285
  await store.set("b-0", "b0");
168
286
  await store.set("a-2", "a2");
169
- expect((await store.values("a-")).sort()).toEqual(["a0", "a1", "a2"]);
287
+ expect((await store.prefix("a-").values()).sort()).toEqual([
288
+ "a0",
289
+ "a1",
290
+ "a2",
291
+ ]);
170
292
  });
171
293
 
172
294
  it("can get the entries with a dash prefix", async () => {
@@ -174,10 +296,10 @@ for (let [name, store] of stores) {
174
296
  await store.set("a-1", "a1");
175
297
  await store.set("b-0", "b0");
176
298
  await store.set("a-2", "a2");
177
- expect((await store.entries("a-")).sort()).toEqual([
178
- ["a-0", "a0"],
179
- ["a-1", "a1"],
180
- ["a-2", "a2"],
299
+ expect((await store.prefix("a-").entries()).sort()).toEqual([
300
+ ["0", "a0"],
301
+ ["1", "a1"],
302
+ ["2", "a2"],
181
303
  ]);
182
304
  });
183
305
 
@@ -186,10 +308,10 @@ for (let [name, store] of stores) {
186
308
  await store.set("a-1", "a1");
187
309
  await store.set("b-0", "b0");
188
310
  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",
311
+ expect(await store.prefix("a-").all()).toEqual({
312
+ 0: "a0",
313
+ 1: "a1",
314
+ 2: "a2",
193
315
  });
194
316
  });
195
317
 
@@ -198,6 +320,15 @@ for (let [name, store] of stores) {
198
320
  expect(await store.get("a")).toBe("b");
199
321
  await store.del("a");
200
322
  expect(await store.get("a")).toBe(null);
323
+ expect(await store.keys()).toEqual([]);
324
+ });
325
+
326
+ it("can delete the data by setting it to null", async () => {
327
+ await store.set("a", "b");
328
+ expect(await store.get("a")).toBe("b");
329
+ await store.set("a", null);
330
+ expect(await store.get("a")).toBe(null);
331
+ expect(await store.keys()).toEqual([]);
201
332
  });
202
333
 
203
334
  it("can clear all the values", async () => {
@@ -211,6 +342,13 @@ for (let [name, store] of stores) {
211
342
  });
212
343
 
213
344
  describe("expires", () => {
345
+ // The mock implementation does NOT support expiration 😪
346
+ if (name === "kv(new KVNamespace())") return;
347
+
348
+ afterEach(() => {
349
+ jest.restoreAllMocks();
350
+ });
351
+
214
352
  it("expires = 0 means immediately", async () => {
215
353
  await store.set("a", "b", { expires: 0 });
216
354
  expect(await store.get("a")).toBe(null);
@@ -248,49 +386,203 @@ for (let [name, store] of stores) {
248
386
  expect(await store.get("a")).toBe("b");
249
387
  });
250
388
 
251
- if (name !== "kv('cookie')" && name !== "kv(redis)") {
389
+ if (
390
+ name !== `kv("cookie")` &&
391
+ name !== `kv(redis)` &&
392
+ name !== `kv(new Etcd3())`
393
+ ) {
252
394
  it("can use 0.01 expire", async () => {
253
395
  // 10ms
254
396
  await store.set("a", "b", { expires: 0.01 });
397
+ expect(await store.keys()).toEqual(["a"]);
398
+ expect(await store.values()).toEqual(["b"]);
255
399
  expect(await store.get("a")).toBe("b");
256
400
  await delay(100);
401
+ expect(await store.keys()).toEqual([]);
402
+ expect(await store.values()).toEqual([]);
257
403
  expect(await store.get("a")).toBe(null);
258
404
  });
259
405
 
260
406
  it("can use 0.01s expire", async () => {
261
407
  await store.set("a", "b", { expires: "0.01s" });
408
+ expect(await store.keys()).toEqual(["a"]);
409
+ expect(await store.values()).toEqual(["b"]);
262
410
  expect(await store.get("a")).toBe("b");
263
411
  await delay(100);
412
+ expect(await store.keys()).toEqual([]);
413
+ expect(await store.values()).toEqual([]);
264
414
  expect(await store.get("a")).toBe(null);
265
415
  });
266
416
 
267
417
  it("can use 0.01seconds expire", async () => {
268
418
  await store.set("a", "b", { expires: "0.01seconds" });
419
+ expect(await store.keys()).toEqual(["a"]);
420
+ expect(await store.values()).toEqual(["b"]);
269
421
  expect(await store.get("a")).toBe("b");
270
422
  await delay(100);
423
+ expect(await store.keys()).toEqual([]);
424
+ expect(await store.values()).toEqual([]);
271
425
  expect(await store.get("a")).toBe(null);
272
426
  });
273
427
 
274
428
  it("can use 10ms expire", async () => {
275
429
  await store.set("a", "b", { expires: "10ms" });
430
+ expect(await store.keys()).toEqual(["a"]);
431
+ expect(await store.values()).toEqual(["b"]);
276
432
  expect(await store.get("a")).toBe("b");
277
433
  await delay(100);
434
+ expect(await store.keys()).toEqual([]);
435
+ expect(await store.values()).toEqual([]);
278
436
  expect(await store.get("a")).toBe(null);
279
437
  });
438
+
439
+ it("removes the expired key with .get()", async () => {
440
+ await store.set("a", "b", { expires: "10ms" });
441
+ const spy = jest.spyOn(store, "del");
442
+ expect(spy).not.toHaveBeenCalled();
443
+ await delay(100);
444
+ expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪
445
+ expect(await store.get("a")).toBe(null);
446
+ expect(spy).toHaveBeenCalled();
447
+ });
448
+
449
+ it("removes the expired key with .keys()", async () => {
450
+ await store.set("a", "b", { expires: "10ms" });
451
+ const spy = jest.spyOn(store, "del");
452
+ expect(spy).not.toHaveBeenCalled();
453
+ await delay(100);
454
+ expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪
455
+ expect(await store.keys()).toEqual([]);
456
+ expect(spy).toHaveBeenCalled();
457
+ });
458
+
459
+ it("CANNOT remove the expired key with .values()", async () => {
460
+ await store.set("a", "b", { expires: "10ms" });
461
+ const spy = jest.spyOn(store, "del");
462
+ expect(spy).not.toHaveBeenCalled();
463
+ await delay(100);
464
+ expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪
465
+ expect(await store.values()).toEqual([]);
466
+ if (!store.client.EXPIRES && store.client.values) {
467
+ expect(spy).not.toHaveBeenCalled(); // Nothing we can do 😪😪
468
+ } else {
469
+ expect(spy).toHaveBeenCalled();
470
+ }
471
+ });
280
472
  } else {
281
473
  it("can use 1 (second) expire", async () => {
282
474
  await store.set("a", "b", { expires: 1 });
475
+ expect(await store.keys()).toEqual(["a"]);
476
+ expect(await store.values()).toEqual(["b"]);
283
477
  expect(await store.get("a")).toBe("b");
284
478
  await delay(2000);
479
+ expect(await store.keys()).toEqual([]);
480
+ expect(await store.values()).toEqual([]);
285
481
  expect(await store.get("a")).toBe(null);
286
482
  });
287
483
  it("can use 1s expire", async () => {
288
484
  await store.set("a", "b", { expires: "1s" });
485
+ expect(await store.keys()).toEqual(["a"]);
486
+ expect(await store.values()).toEqual(["b"]);
289
487
  expect(await store.get("a")).toBe("b");
290
488
  await delay(2000);
489
+ expect(await store.keys()).toEqual([]);
490
+ expect(await store.values()).toEqual([]);
291
491
  expect(await store.get("a")).toBe(null);
292
492
  });
293
493
  }
294
494
  });
495
+
496
+ describe(".prefix()", () => {
497
+ let session;
498
+ beforeAll(() => {
499
+ session = store.prefix("session:");
500
+ });
501
+
502
+ it("has the same methods", () => {
503
+ expect(Object.keys(store)).toEqual(Object.keys(session));
504
+ });
505
+
506
+ it("can write/read one", async () => {
507
+ const id = await session.set("a", "b");
508
+ expect(id).toBe("a");
509
+ expect(await session.get("a")).toBe("b");
510
+ expect(await store.get("session:a")).toBe("b");
511
+ });
512
+
513
+ it("can add with the prefix", async () => {
514
+ const id = await session.add("b");
515
+ expect(id.length).toBe(24);
516
+ expect(id).not.toMatch(/^session\:/);
517
+
518
+ const keys = await store.keys();
519
+ expect(keys[0]).toMatch(/^session\:/);
520
+ });
521
+
522
+ it("the group operations return the proper values", async () => {
523
+ await session.set("a", "b");
524
+
525
+ expect(await session.keys()).toEqual(["a"]);
526
+ expect(await session.values()).toEqual(["b"]);
527
+ expect(await session.entries()).toEqual([["a", "b"]]);
528
+
529
+ expect(await store.keys()).toEqual(["session:a"]);
530
+ expect(await store.values()).toEqual(["b"]);
531
+ expect(await store.entries()).toEqual([["session:a", "b"]]);
532
+ });
533
+
534
+ it("clears only the substore", async () => {
535
+ await store.set("a", "b");
536
+ await session.set("c", "d");
537
+
538
+ expect((await store.keys()).sort()).toEqual(["a", "session:c"]);
539
+ await session.clear();
540
+ expect(await store.keys()).toEqual(["a"]);
541
+ });
542
+ });
543
+
544
+ describe(".prefix().prefix()", () => {
545
+ let auth;
546
+ beforeAll(() => {
547
+ auth = store.prefix("session:").prefix("auth:");
548
+ });
549
+
550
+ it("can write/read one", async () => {
551
+ const id = await auth.set("a", "b");
552
+ expect(id).toBe("a");
553
+ expect(await auth.get("a")).toBe("b");
554
+ expect(await store.get("session:auth:a")).toBe("b");
555
+ });
556
+
557
+ it("can add with the prefix", async () => {
558
+ const id = await auth.add("b");
559
+ expect(id.length).toBe(24);
560
+ expect(id).not.toMatch(/^session\:/);
561
+
562
+ const keys = await store.keys();
563
+ expect(keys[0]).toMatch(/^session\:auth\:/);
564
+ });
565
+
566
+ it("the group operations return the proper values", async () => {
567
+ await auth.set("a", "b");
568
+
569
+ expect(await auth.keys()).toEqual(["a"]);
570
+ expect(await auth.values()).toEqual(["b"]);
571
+ expect(await auth.entries()).toEqual([["a", "b"]]);
572
+
573
+ expect(await store.keys()).toEqual(["session:auth:a"]);
574
+ expect(await store.values()).toEqual(["b"]);
575
+ expect(await store.entries()).toEqual([["session:auth:a", "b"]]);
576
+ });
577
+
578
+ it("clears only the substore", async () => {
579
+ await store.set("a", "b");
580
+ await auth.set("c", "d");
581
+
582
+ expect((await store.keys()).sort()).toEqual(["a", "session:auth:c"]);
583
+ await auth.clear();
584
+ expect(await store.keys()).toEqual(["a"]);
585
+ });
586
+ });
295
587
  });
296
588
  }
@@ -8,6 +8,7 @@ const store = kv();
8
8
  await store.set("key", "value", {});
9
9
  await store.set("key", "value", { expires: 100 });
10
10
  await store.set("key", "value", { expires: "100s" });
11
+ await store.prefix("a:").prefix("b:").get("hello");
11
12
  const key1: string = await store.add("value");
12
13
  const key2: string = await store.add("value", { expires: 100 });
13
14
  const key3: string = await store.add("value", { expires: "100s" });
@@ -8,9 +8,11 @@ type Store = {
8
8
  has: (key: Key) => Promise<boolean>;
9
9
  del: (key: Key) => Promise<null>;
10
10
 
11
- keys: (prefix?: string) => Promise<string[]>;
12
- values: (prefix?: string) => Promise<any[]>;
13
- entries: (prefix?: string) => Promise<[key: string, value: any][]>;
11
+ keys: () => Promise<string[]>;
12
+ values: () => Promise<any[]>;
13
+ entries: () => Promise<[key: string, value: any][]>;
14
+
15
+ prefix: (prefix: string) => Store;
14
16
 
15
17
  clear: () => Promise<null>;
16
18
  close?: () => Promise<null>;
@@ -0,0 +1,38 @@
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
+ // Filter them by the prefix, note that `prefix` will always be a string
23
+ entries(prefix) {
24
+ const entries = Object.entries(dataSource);
25
+ if (!prefix) return entries;
26
+ return entries.filter(([key]) => key.startsWith(prefix));
27
+ }
28
+
29
+ values(prefix) {
30
+ const list = this.entries(prefix);
31
+ return list.map((e) => e[1]);
32
+ }
33
+
34
+ // Cannot have a keys() if it's an unamanaged store
35
+ // keys(prefix) {
36
+ // // This should throw
37
+ // }
38
+ }
@@ -0,0 +1,23 @@
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
+ entries(prefix) {
19
+ const entries = Object.entries(dataSource);
20
+ if (!prefix) return entries;
21
+ return entries.filter(([key, value]) => key.startsWith(prefix));
22
+ }
23
+ }
@@ -0,0 +1,12 @@
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
+ Object.defineProperty(window, "TextEncoder", {
6
+ writable: true,
7
+ value: util.TextEncoder,
8
+ });
9
+ Object.defineProperty(window, "TextDecoder", {
10
+ writable: true,
11
+ value: util.TextDecoder,
12
+ });
package/src/utils.js ADDED
@@ -0,0 +1,44 @@
1
+ const times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
2
+
3
+ parse.millisecond = parse.ms = 0.001;
4
+ parse.second = parse.sec = parse.s = parse[""] = 1;
5
+ parse.minute = parse.min = parse.m = parse.s * 60;
6
+ parse.hour = parse.hr = parse.h = parse.m * 60;
7
+ parse.day = parse.d = parse.h * 24;
8
+ parse.week = parse.wk = parse.w = parse.d * 7;
9
+ parse.year = parse.yr = parse.y = parse.d * 365.25;
10
+ parse.month = parse.b = parse.y / 12;
11
+
12
+ // Returns the time in milliseconds
13
+ function parse(str) {
14
+ if (str === null || str === undefined) return null;
15
+ if (typeof str === "number") return str;
16
+ // ignore commas/placeholders
17
+ str = str.toLowerCase().replace(/[,_]/g, "");
18
+ let [_, value, units] = times.exec(str) || [];
19
+ if (!units) return null;
20
+ const unitValue = parse[units] || parse[units.replace(/s$/, "")];
21
+ if (!unitValue) return null;
22
+ const result = unitValue * parseFloat(value, 10);
23
+ return Math.abs(Math.round(result * 1000) / 1000);
24
+ }
25
+
26
+ // "nanoid" imported manually
27
+ // Something about improved GZIP performance with this string
28
+ const urlAlphabet =
29
+ "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
30
+
31
+ function createId() {
32
+ let size = 24;
33
+ let id = "";
34
+ let bytes = crypto.getRandomValues(new Uint8Array(size));
35
+ while (size--) {
36
+ // Using the bitwise AND operator to "cap" the value of
37
+ // the random byte from 255 to 63, in that way we can make sure
38
+ // that the value will be a valid index for the "chars" string.
39
+ id += urlAlphabet[bytes[size] & 61];
40
+ }
41
+ return id;
42
+ }
43
+
44
+ export { parse, createId };