polystore 0.19.0 → 0.21.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 +1 -0
- package/index.js +115 -26
- package/package.json +32 -8
- package/readme.md +237 -46
- package/src/integrations/express.d.ts +307 -0
- package/src/integrations/express.js +42 -0
- package/src/integrations/hono-sessions.d.ts +299 -0
- package/src/integrations/hono-sessions.js +36 -0
- package/src/express.js +0 -47
package/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ interface ClientNonExpires {
|
|
|
45
45
|
values?<T extends Serializable>(prefix: string): Promise<StoreData<T>[]> | StoreData<T>[];
|
|
46
46
|
entries?<T extends Serializable>(prefix: string): Promise<[string, StoreData<T>][]> | [string, StoreData<T>][];
|
|
47
47
|
all?<T extends Serializable>(prefix: string): Promise<Record<string, StoreData<T>>> | Record<string, StoreData<T>>;
|
|
48
|
+
prune?(): Promise<any> | any;
|
|
48
49
|
clear?(prefix: string): Promise<any> | any;
|
|
49
50
|
clearAll?(): Promise<any> | any;
|
|
50
51
|
close?(): Promise<any> | any;
|
package/index.js
CHANGED
|
@@ -244,8 +244,10 @@ var File = class extends Client {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
// Bulk updates are worth creating a custom method here
|
|
247
|
-
clearAll = () => this.#withLock(() => this.#write({}));
|
|
248
247
|
clear = async (prefix = "") => {
|
|
248
|
+
if (!prefix) {
|
|
249
|
+
await this.#withLock(() => this.#write({}));
|
|
250
|
+
}
|
|
249
251
|
return this.#withLock(async () => {
|
|
250
252
|
const data = await this.#read();
|
|
251
253
|
for (let key in data) {
|
|
@@ -366,8 +368,10 @@ var Level = class extends Client {
|
|
|
366
368
|
list.map(async (k) => [k, await this.get(k)])
|
|
367
369
|
);
|
|
368
370
|
};
|
|
369
|
-
clearAll = () => this.client.clear();
|
|
370
371
|
clear = async (prefix = "") => {
|
|
372
|
+
if (!prefix) {
|
|
373
|
+
return await this.client.clear();
|
|
374
|
+
}
|
|
371
375
|
const keys = await this.client.keys().all();
|
|
372
376
|
const list = keys.filter((k) => k.startsWith(prefix));
|
|
373
377
|
return this.client.batch(
|
|
@@ -395,6 +399,97 @@ var Memory = class extends Client {
|
|
|
395
399
|
clearAll = () => this.client.clear();
|
|
396
400
|
};
|
|
397
401
|
|
|
402
|
+
// src/clients/postgres.ts
|
|
403
|
+
var Postgres = class extends Client {
|
|
404
|
+
TYPE = "POSTGRES";
|
|
405
|
+
// This one is doing manual time management internally even though
|
|
406
|
+
// sqlite does not natively support expirations. This is because it does
|
|
407
|
+
// support creating a `expires_at:Date` column that makes managing
|
|
408
|
+
// expirations much easier, so it's really "somewhere in between"
|
|
409
|
+
HAS_EXPIRATION = true;
|
|
410
|
+
// The table name to use
|
|
411
|
+
table = "kv";
|
|
412
|
+
// Ensure schema exists before any operation
|
|
413
|
+
promise = (async () => {
|
|
414
|
+
if (!/^[a-zA-Z_]+$/.test(this.table)) {
|
|
415
|
+
throw new Error(`Invalid table name ${this.table}`);
|
|
416
|
+
}
|
|
417
|
+
await this.client.query(`
|
|
418
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
419
|
+
id TEXT PRIMARY KEY,
|
|
420
|
+
value TEXT NOT NULL,
|
|
421
|
+
expires_at TIMESTAMPTZ
|
|
422
|
+
)
|
|
423
|
+
`);
|
|
424
|
+
await this.client.query(
|
|
425
|
+
`CREATE INDEX IF NOT EXISTS idx_${this.table}_expires_at ON ${this.table} (expires_at)`
|
|
426
|
+
);
|
|
427
|
+
})();
|
|
428
|
+
static test = (client) => {
|
|
429
|
+
return client && client.query && !client.filename;
|
|
430
|
+
};
|
|
431
|
+
get = async (id) => {
|
|
432
|
+
const result = await this.client.query(
|
|
433
|
+
`SELECT value
|
|
434
|
+
FROM ${this.table}
|
|
435
|
+
WHERE id = $1 AND (expires_at IS NULL OR expires_at > NOW())`,
|
|
436
|
+
[id]
|
|
437
|
+
);
|
|
438
|
+
if (!result.rows.length) return null;
|
|
439
|
+
return this.decode(result.rows[0].value);
|
|
440
|
+
};
|
|
441
|
+
set = async (id, data, expires) => {
|
|
442
|
+
const value = this.encode(data);
|
|
443
|
+
const expires_at = expires ? new Date(Date.now() + expires * 1e3) : null;
|
|
444
|
+
await this.client.query(
|
|
445
|
+
`INSERT INTO ${this.table} (id, value, expires_at)
|
|
446
|
+
VALUES ($1, $2, $3)
|
|
447
|
+
ON CONFLICT (id) DO UPDATE
|
|
448
|
+
SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at`,
|
|
449
|
+
[id, value, expires_at]
|
|
450
|
+
);
|
|
451
|
+
};
|
|
452
|
+
del = async (id) => {
|
|
453
|
+
await this.client.query(`DELETE FROM ${this.table} WHERE id = $1`, [id]);
|
|
454
|
+
};
|
|
455
|
+
async *iterate(prefix = "") {
|
|
456
|
+
const result = await this.client.query(
|
|
457
|
+
`SELECT id, value FROM ${this.table}
|
|
458
|
+
WHERE (expires_at IS NULL OR expires_at > NOW()) ${prefix ? `AND id LIKE $1` : ""}`,
|
|
459
|
+
prefix ? [`${prefix}%`] : []
|
|
460
|
+
);
|
|
461
|
+
for (const row of result.rows) {
|
|
462
|
+
yield [row.id, this.decode(row.value)];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async keys(prefix = "") {
|
|
466
|
+
const result = await this.client.query(
|
|
467
|
+
`SELECT id FROM ${this.table}
|
|
468
|
+
WHERE (expires_at IS NULL OR expires_at > NOW())
|
|
469
|
+
${prefix ? `AND id LIKE $1` : ""}`,
|
|
470
|
+
prefix ? [`${prefix}%`] : []
|
|
471
|
+
);
|
|
472
|
+
return result.rows.map((r) => r.id);
|
|
473
|
+
}
|
|
474
|
+
prune = async () => {
|
|
475
|
+
await this.client.query(
|
|
476
|
+
`DELETE FROM ${this.table}
|
|
477
|
+
WHERE expires_at IS NOT NULL AND expires_at <= NOW()`
|
|
478
|
+
);
|
|
479
|
+
};
|
|
480
|
+
clear = async (prefix = "") => {
|
|
481
|
+
await this.client.query(
|
|
482
|
+
`DELETE FROM ${this.table} ${prefix ? `WHERE id LIKE $1` : ""}`,
|
|
483
|
+
prefix ? [`${prefix}%`] : []
|
|
484
|
+
);
|
|
485
|
+
};
|
|
486
|
+
close = async () => {
|
|
487
|
+
if (this.client.end) {
|
|
488
|
+
await this.client.end();
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
|
|
398
493
|
// src/clients/redis.ts
|
|
399
494
|
var Redis = class extends Client {
|
|
400
495
|
TYPE = "REDIS";
|
|
@@ -470,13 +565,11 @@ var SQLite = class extends Client {
|
|
|
470
565
|
return typeof client?.prepare === "function" && typeof client?.exec === "function";
|
|
471
566
|
};
|
|
472
567
|
get = (id) => {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
return this.decode(row.value);
|
|
568
|
+
const value = this.client.prepare(
|
|
569
|
+
`SELECT value, expires_at FROM kv WHERE id = ? AND (expires_at IS NULL OR expires_at > ?)`
|
|
570
|
+
).get(id, Date.now())?.value;
|
|
571
|
+
if (!value) return null;
|
|
572
|
+
return this.decode(value);
|
|
480
573
|
};
|
|
481
574
|
set = (id, data, expires) => {
|
|
482
575
|
const value = this.encode(data);
|
|
@@ -485,8 +578,8 @@ var SQLite = class extends Client {
|
|
|
485
578
|
`INSERT INTO kv (id, value, expires_at) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at`
|
|
486
579
|
).run(id, value, expires_at);
|
|
487
580
|
};
|
|
488
|
-
del =
|
|
489
|
-
|
|
581
|
+
del = (id) => {
|
|
582
|
+
this.client.prepare(`DELETE FROM kv WHERE id = ?`).run(id);
|
|
490
583
|
};
|
|
491
584
|
has = (id) => {
|
|
492
585
|
const row = this.client.prepare(`SELECT expires_at FROM kv WHERE id = ?`).get(id);
|
|
@@ -498,7 +591,6 @@ var SQLite = class extends Client {
|
|
|
498
591
|
return true;
|
|
499
592
|
};
|
|
500
593
|
*iterate(prefix = "") {
|
|
501
|
-
this.#clearExpired();
|
|
502
594
|
const sql = `SELECT id, value FROM kv WHERE (expires_at IS NULL OR expires_at > ?) ${prefix ? "AND id LIKE ?" : ""}
|
|
503
595
|
`;
|
|
504
596
|
const params = prefix ? [Date.now(), `${prefix}%`] : [Date.now()];
|
|
@@ -507,7 +599,6 @@ var SQLite = class extends Client {
|
|
|
507
599
|
}
|
|
508
600
|
}
|
|
509
601
|
keys = (prefix = "") => {
|
|
510
|
-
this.#clearExpired();
|
|
511
602
|
const sql = `SELECT id FROM kv WHERE (expires_at IS NULL OR expires_at > ?)
|
|
512
603
|
${prefix ? "AND id LIKE ?" : ""}
|
|
513
604
|
`;
|
|
@@ -515,11 +606,15 @@ ${prefix ? "AND id LIKE ?" : ""}
|
|
|
515
606
|
const rows = this.client.prepare(sql).all(...params);
|
|
516
607
|
return rows.map((r) => r.id);
|
|
517
608
|
};
|
|
518
|
-
|
|
519
|
-
this.client.prepare(`DELETE FROM kv WHERE expires_at
|
|
609
|
+
prune = () => {
|
|
610
|
+
this.client.prepare(`DELETE FROM kv WHERE expires_at <= ?`).run(Date.now());
|
|
520
611
|
};
|
|
521
|
-
|
|
522
|
-
|
|
612
|
+
clear = (prefix = "") => {
|
|
613
|
+
if (!prefix) {
|
|
614
|
+
this.client.prepare(`DELETE FROM ${this.table}`).run();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.client.prepare(`DELETE FROM ${this.table} WHERE id LIKE ?`).run(`${prefix}%`);
|
|
523
618
|
};
|
|
524
619
|
close = () => {
|
|
525
620
|
this.client.close?.();
|
|
@@ -564,8 +659,7 @@ var clients_default = {
|
|
|
564
659
|
forage: Forage,
|
|
565
660
|
level: Level,
|
|
566
661
|
memory: Memory,
|
|
567
|
-
|
|
568
|
-
// prisma,
|
|
662
|
+
postgres: Postgres,
|
|
569
663
|
redis: Redis,
|
|
570
664
|
storage: WebStorage,
|
|
571
665
|
sqlite: SQLite
|
|
@@ -932,13 +1026,8 @@ var Store = class _Store {
|
|
|
932
1026
|
async prune() {
|
|
933
1027
|
await this.promise;
|
|
934
1028
|
if (this.client.HAS_EXPIRATION) return;
|
|
935
|
-
|
|
936
|
-
this.
|
|
937
|
-
)) {
|
|
938
|
-
const key = name.slice(this.PREFIX.length);
|
|
939
|
-
if (!this.#isFresh(data, key)) {
|
|
940
|
-
await this.del(key);
|
|
941
|
-
}
|
|
1029
|
+
if (this.client.prune) {
|
|
1030
|
+
await this.client.prune();
|
|
942
1031
|
}
|
|
943
1032
|
}
|
|
944
1033
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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",
|
|
@@ -16,23 +16,36 @@
|
|
|
16
16
|
"types": "./index.d.ts",
|
|
17
17
|
"import": "./index.js"
|
|
18
18
|
},
|
|
19
|
-
"./express":
|
|
19
|
+
"./express": {
|
|
20
|
+
"types": "./src/integrations/express.d.ts",
|
|
21
|
+
"import": "./src/integrations/express.js"
|
|
22
|
+
},
|
|
23
|
+
"./hono-sessions": {
|
|
24
|
+
"types": "./src/integrations/hono-sessions.d.ts",
|
|
25
|
+
"import": "./src/integrations/hono-sessions.js"
|
|
26
|
+
}
|
|
20
27
|
},
|
|
21
28
|
"files": [
|
|
22
29
|
"index.js",
|
|
23
30
|
"index.d.ts",
|
|
24
|
-
"src/express.js"
|
|
31
|
+
"src/integrations/express.js",
|
|
32
|
+
"src/integrations/express.d.ts",
|
|
33
|
+
"src/integrations/hono-sessions.js",
|
|
34
|
+
"src/integrations/hono-sessions.d.ts"
|
|
25
35
|
],
|
|
26
36
|
"scripts": {
|
|
27
37
|
"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",
|
|
28
|
-
"build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24",
|
|
38
|
+
"build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24 && bunx tsup src/integrations/express.ts src/integrations/hono-sessions.ts --format esm --dts --out-dir src/integrations --target node24 --external polystore --external express-session --external hono-sessions",
|
|
29
39
|
"lint": "npx tsc --noEmit",
|
|
30
40
|
"start": "bun test --watch",
|
|
41
|
+
"service:db": "etcd",
|
|
42
|
+
"service:redis": "brew services start redis",
|
|
43
|
+
"service:postgres": "brew services start postgresql",
|
|
44
|
+
"service:server": "bun ./src/server.ts",
|
|
45
|
+
"services": "concurrently \"npm run service:db\" \"npm run service:redis\" \"npm run service:postgres\" \"npm run service:server\"",
|
|
31
46
|
"test": "npm run test:bun && npm run test:jest",
|
|
32
|
-
"test:bun": "bun test ./test/index.test.ts",
|
|
33
|
-
"test:jest": "jest ./test/index.test.ts --detectOpenHandles --forceExit"
|
|
34
|
-
"run:db": "etcd",
|
|
35
|
-
"run:server": "bun ./src/server.ts"
|
|
47
|
+
"test:bun": "bun test ./test/index.test.ts ./src/integrations/",
|
|
48
|
+
"test:jest": "jest ./test/index.test.ts --detectOpenHandles --forceExit"
|
|
36
49
|
},
|
|
37
50
|
"keywords": [
|
|
38
51
|
"kv",
|
|
@@ -47,21 +60,32 @@
|
|
|
47
60
|
"@deno/kv": "^0.8.1",
|
|
48
61
|
"@types/better-sqlite3": "^7.6.13",
|
|
49
62
|
"@types/bun": "^1.3.3",
|
|
63
|
+
"@types/express": "^5.0.6",
|
|
64
|
+
"@types/express-session": "^1.18.2",
|
|
50
65
|
"@types/jest": "^30.0.0",
|
|
51
66
|
"@types/jsdom": "^27.0.0",
|
|
67
|
+
"@types/pg": "^8.11.10",
|
|
68
|
+
"@types/supertest": "^7.2.0",
|
|
52
69
|
"better-sqlite3": "^12.6.0",
|
|
53
70
|
"check-dts": "^0.8.0",
|
|
71
|
+
"concurrently": "^9.2.1",
|
|
54
72
|
"cross-fetch": "^4.1.0",
|
|
55
73
|
"dotenv": "^16.3.1",
|
|
56
74
|
"edge-mock": "^0.0.15",
|
|
57
75
|
"esbuild": "^0.27.0",
|
|
58
76
|
"etcd3": "^1.1.2",
|
|
77
|
+
"express": "^5.2.1",
|
|
78
|
+
"express-session": "^1.19.0",
|
|
59
79
|
"gzip-size-cli": "^5.1.0",
|
|
80
|
+
"hono": "^4.12.10",
|
|
81
|
+
"hono-sessions": "^0.8.1",
|
|
60
82
|
"jest": "^30.2.0",
|
|
61
83
|
"jsdom": "^27.2.0",
|
|
62
84
|
"level": "^8.0.1",
|
|
63
85
|
"localforage": "^1.10.0",
|
|
86
|
+
"pg": "^8.13.1",
|
|
64
87
|
"redis": "^4.6.10",
|
|
88
|
+
"supertest": "^7.2.2",
|
|
65
89
|
"ts-jest": "^29.4.6",
|
|
66
90
|
"ts-node": "^10.9.2",
|
|
67
91
|
"tsup": "^8.5.1",
|