@lakutata/cache 3.0.0-beta.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/LICENSE +23 -0
- package/README.md +175 -0
- package/dist/cjs/cache/Cacher.d.ts +446 -0
- package/dist/cjs/cache/Cacher.js +523 -0
- package/dist/cjs/cache/exceptions/CacheDriverNotFoundException.d.ts +10 -0
- package/dist/cjs/cache/exceptions/CacheDriverNotFoundException.js +17 -0
- package/dist/cjs/cache/interfaces/CacherOptions.d.ts +42 -0
- package/dist/cjs/cache/interfaces/CacherOptions.js +2 -0
- package/dist/cjs/cache/lib/LikePrefix.d.ts +5 -0
- package/dist/cjs/cache/lib/LikePrefix.js +10 -0
- package/dist/cjs/cache/lib/LoadDriver.d.ts +6 -0
- package/dist/cjs/cache/lib/LoadDriver.js +66 -0
- package/dist/cjs/cache/lib/Serializer.d.ts +18 -0
- package/dist/cjs/cache/lib/Serializer.js +46 -0
- package/dist/cjs/cache/lib/WithTimeout.d.ts +7 -0
- package/dist/cjs/cache/lib/WithTimeout.js +23 -0
- package/dist/cjs/cache/options/FileCacheOptions.d.ts +54 -0
- package/dist/cjs/cache/options/FileCacheOptions.js +62 -0
- package/dist/cjs/cache/options/MemcacheCacheOptions.d.ts +57 -0
- package/dist/cjs/cache/options/MemcacheCacheOptions.js +66 -0
- package/dist/cjs/cache/options/MemoryCacheOptions.d.ts +39 -0
- package/dist/cjs/cache/options/MemoryCacheOptions.js +52 -0
- package/dist/cjs/cache/options/MongoCacheOptions.d.ts +72 -0
- package/dist/cjs/cache/options/MongoCacheOptions.js +81 -0
- package/dist/cjs/cache/options/MysqlCacheOptions.d.ts +73 -0
- package/dist/cjs/cache/options/MysqlCacheOptions.js +82 -0
- package/dist/cjs/cache/options/PostgresCacheOptions.d.ts +87 -0
- package/dist/cjs/cache/options/PostgresCacheOptions.js +92 -0
- package/dist/cjs/cache/options/RedisCacheOptions.d.ts +120 -0
- package/dist/cjs/cache/options/RedisCacheOptions.js +113 -0
- package/dist/cjs/cache/options/SqliteCacheOptions.d.ts +53 -0
- package/dist/cjs/cache/options/SqliteCacheOptions.js +61 -0
- package/dist/cjs/cache/stores/CacheStore.d.ts +44 -0
- package/dist/cjs/cache/stores/CacheStore.js +42 -0
- package/dist/cjs/cache/stores/CreateCacheStore.d.ts +7 -0
- package/dist/cjs/cache/stores/CreateCacheStore.js +37 -0
- package/dist/cjs/cache/stores/FileStore.d.ts +51 -0
- package/dist/cjs/cache/stores/FileStore.js +143 -0
- package/dist/cjs/cache/stores/MemcacheStore.d.ts +42 -0
- package/dist/cjs/cache/stores/MemcacheStore.js +69 -0
- package/dist/cjs/cache/stores/MemoryStore.d.ts +34 -0
- package/dist/cjs/cache/stores/MemoryStore.js +75 -0
- package/dist/cjs/cache/stores/MongoStore.d.ts +55 -0
- package/dist/cjs/cache/stores/MongoStore.js +72 -0
- package/dist/cjs/cache/stores/MysqlStore.d.ts +27 -0
- package/dist/cjs/cache/stores/MysqlStore.js +71 -0
- package/dist/cjs/cache/stores/PostgresStore.d.ts +30 -0
- package/dist/cjs/cache/stores/PostgresStore.js +85 -0
- package/dist/cjs/cache/stores/RedisStore.d.ts +58 -0
- package/dist/cjs/cache/stores/RedisStore.js +131 -0
- package/dist/cjs/cache/stores/SerializedCacheStore.d.ts +48 -0
- package/dist/cjs/cache/stores/SerializedCacheStore.js +80 -0
- package/dist/cjs/cache/stores/SqliteStore.d.ts +44 -0
- package/dist/cjs/cache/stores/SqliteStore.js +91 -0
- package/dist/cjs/cache/types/CacheStoreOptions.d.ts +15 -0
- package/dist/cjs/cache/types/CacheStoreOptions.js +2 -0
- package/dist/cjs/exports/Cache.d.ts +12 -0
- package/dist/cjs/exports/Cache.js +31 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/cache/Cacher.js +518 -0
- package/dist/esm/cache/exceptions/CacheDriverNotFoundException.js +13 -0
- package/dist/esm/cache/interfaces/CacherOptions.js +1 -0
- package/dist/esm/cache/lib/LikePrefix.js +7 -0
- package/dist/esm/cache/lib/LoadDriver.js +30 -0
- package/dist/esm/cache/lib/Serializer.js +42 -0
- package/dist/esm/cache/lib/WithTimeout.js +20 -0
- package/dist/esm/cache/options/FileCacheOptions.js +58 -0
- package/dist/esm/cache/options/MemcacheCacheOptions.js +62 -0
- package/dist/esm/cache/options/MemoryCacheOptions.js +48 -0
- package/dist/esm/cache/options/MongoCacheOptions.js +77 -0
- package/dist/esm/cache/options/MysqlCacheOptions.js +78 -0
- package/dist/esm/cache/options/PostgresCacheOptions.js +88 -0
- package/dist/esm/cache/options/RedisCacheOptions.js +109 -0
- package/dist/esm/cache/options/SqliteCacheOptions.js +57 -0
- package/dist/esm/cache/stores/CacheStore.js +36 -0
- package/dist/esm/cache/stores/CreateCacheStore.js +34 -0
- package/dist/esm/cache/stores/FileStore.js +136 -0
- package/dist/esm/cache/stores/MemcacheStore.js +65 -0
- package/dist/esm/cache/stores/MemoryStore.js +71 -0
- package/dist/esm/cache/stores/MongoStore.js +68 -0
- package/dist/esm/cache/stores/MysqlStore.js +67 -0
- package/dist/esm/cache/stores/PostgresStore.js +81 -0
- package/dist/esm/cache/stores/RedisStore.js +127 -0
- package/dist/esm/cache/stores/SerializedCacheStore.js +76 -0
- package/dist/esm/cache/stores/SqliteStore.js +87 -0
- package/dist/esm/cache/types/CacheStoreOptions.js +1 -0
- package/dist/esm/exports/Cache.js +12 -0
- package/dist/types/cache/Cacher.d.ts +446 -0
- package/dist/types/cache/exceptions/CacheDriverNotFoundException.d.ts +10 -0
- package/dist/types/cache/interfaces/CacherOptions.d.ts +42 -0
- package/dist/types/cache/lib/LikePrefix.d.ts +5 -0
- package/dist/types/cache/lib/LoadDriver.d.ts +6 -0
- package/dist/types/cache/lib/Serializer.d.ts +18 -0
- package/dist/types/cache/lib/WithTimeout.d.ts +7 -0
- package/dist/types/cache/options/FileCacheOptions.d.ts +54 -0
- package/dist/types/cache/options/MemcacheCacheOptions.d.ts +57 -0
- package/dist/types/cache/options/MemoryCacheOptions.d.ts +39 -0
- package/dist/types/cache/options/MongoCacheOptions.d.ts +72 -0
- package/dist/types/cache/options/MysqlCacheOptions.d.ts +73 -0
- package/dist/types/cache/options/PostgresCacheOptions.d.ts +87 -0
- package/dist/types/cache/options/RedisCacheOptions.d.ts +120 -0
- package/dist/types/cache/options/SqliteCacheOptions.d.ts +53 -0
- package/dist/types/cache/stores/CacheStore.d.ts +44 -0
- package/dist/types/cache/stores/CreateCacheStore.d.ts +7 -0
- package/dist/types/cache/stores/FileStore.d.ts +51 -0
- package/dist/types/cache/stores/MemcacheStore.d.ts +42 -0
- package/dist/types/cache/stores/MemoryStore.d.ts +34 -0
- package/dist/types/cache/stores/MongoStore.d.ts +55 -0
- package/dist/types/cache/stores/MysqlStore.d.ts +27 -0
- package/dist/types/cache/stores/PostgresStore.d.ts +30 -0
- package/dist/types/cache/stores/RedisStore.d.ts +58 -0
- package/dist/types/cache/stores/SerializedCacheStore.d.ts +48 -0
- package/dist/types/cache/stores/SqliteStore.d.ts +44 -0
- package/dist/types/cache/types/CacheStoreOptions.d.ts +15 -0
- package/dist/types/exports/Cache.d.ts +12 -0
- package/package.json +72 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongoStore = void 0;
|
|
4
|
+
const SerializedCacheStore_js_1 = require("./SerializedCacheStore.js");
|
|
5
|
+
const LoadDriver_js_1 = require("../lib/LoadDriver.js");
|
|
6
|
+
/**
|
|
7
|
+
* Escape the regular expression characters
|
|
8
|
+
* @param text
|
|
9
|
+
*/
|
|
10
|
+
function escapeRegExp(text) {
|
|
11
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The MongoDB store
|
|
15
|
+
* Document format: {key: "{namespace}:{key}", value: serialized entry, expiresAt: Date | null}, removed by a TTL index
|
|
16
|
+
*/
|
|
17
|
+
class MongoStore extends SerializedCacheStore_js_1.SerializedCacheStore {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super(options.namespace);
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
async connect() {
|
|
23
|
+
const driver = await (0, LoadDriver_js_1.loadDriver)('MongoClient', 'mongodb');
|
|
24
|
+
const options = this.options;
|
|
25
|
+
const auth = options.username ? `${encodeURIComponent(options.username)}:${encodeURIComponent(options.password ?? '')}@` : '';
|
|
26
|
+
this.client = new driver.MongoClient(`mongodb://${auth}${options.host}:${options.port ?? 27017}`);
|
|
27
|
+
await this.client.connect();
|
|
28
|
+
this.collection = this.client.db(options.database).collection(options.collection);
|
|
29
|
+
await this.collection.createIndex({ key: 1 }, { unique: true, background: true });
|
|
30
|
+
await this.collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0, background: true });
|
|
31
|
+
}
|
|
32
|
+
async disconnect() {
|
|
33
|
+
await this.client?.close();
|
|
34
|
+
}
|
|
35
|
+
async read(key) {
|
|
36
|
+
const document = await this.collection.findOne({ key: { $eq: key } }, { projection: { _id: 0, value: 1 } });
|
|
37
|
+
return document ? document.value : undefined;
|
|
38
|
+
}
|
|
39
|
+
async readMany(keys) {
|
|
40
|
+
const documents = await this.collection.find({ key: { $in: keys } }, { projection: { _id: 0, key: 1, value: 1 } }).toArray();
|
|
41
|
+
const values = new Map(documents.map((document) => [document.key, document.value]));
|
|
42
|
+
return keys.map((key) => values.get(key));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The upsert of an entry
|
|
46
|
+
* @param entry
|
|
47
|
+
* @protected
|
|
48
|
+
*/
|
|
49
|
+
update(entry) {
|
|
50
|
+
return [{ key: { $eq: entry.key } }, { $set: { key: entry.key, value: entry.data, expiresAt: entry.expires ? new Date(entry.expires) : null } }];
|
|
51
|
+
}
|
|
52
|
+
async write(entry) {
|
|
53
|
+
const [filter, update] = this.update(entry);
|
|
54
|
+
await this.collection.updateOne(filter, update, { upsert: true });
|
|
55
|
+
}
|
|
56
|
+
async writeMany(entries) {
|
|
57
|
+
await this.collection.bulkWrite(entries.map((entry) => {
|
|
58
|
+
const [filter, update] = this.update(entry);
|
|
59
|
+
return { updateOne: { filter: filter, update: update, upsert: true } };
|
|
60
|
+
}), { ordered: false });
|
|
61
|
+
}
|
|
62
|
+
async remove(key) {
|
|
63
|
+
return (await this.collection.deleteOne({ key: { $eq: key } })).deletedCount > 0;
|
|
64
|
+
}
|
|
65
|
+
async removeMany(keys) {
|
|
66
|
+
return (await this.collection.deleteMany({ key: { $in: keys } })).deletedCount > 0;
|
|
67
|
+
}
|
|
68
|
+
async clear() {
|
|
69
|
+
await this.collection.deleteMany(this.namespace ? { key: { $regex: `^${escapeRegExp(this.namespace)}:` } } : {});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.MongoStore = MongoStore;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SerializedCacheStore, SerializedEntry } from './SerializedCacheStore.js';
|
|
2
|
+
import { MysqlCacheOptions } from '../options/MysqlCacheOptions.js';
|
|
3
|
+
interface MysqlPool {
|
|
4
|
+
query(sql: string, values?: unknown[]): Promise<[any, unknown]>;
|
|
5
|
+
escapeId(identifier: string): string;
|
|
6
|
+
end(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The MySQL store
|
|
10
|
+
* Table format: (id VARCHAR(255) PRIMARY KEY, value TEXT), with "{namespace}:{key}" ids
|
|
11
|
+
*/
|
|
12
|
+
export declare class MysqlStore extends SerializedCacheStore {
|
|
13
|
+
protected readonly options: MysqlCacheOptions;
|
|
14
|
+
protected pool: MysqlPool;
|
|
15
|
+
protected table: string;
|
|
16
|
+
constructor(options: MysqlCacheOptions);
|
|
17
|
+
connect(): Promise<void>;
|
|
18
|
+
disconnect(): Promise<void>;
|
|
19
|
+
protected read(key: string): Promise<string | undefined>;
|
|
20
|
+
protected readMany(keys: string[]): Promise<(string | undefined)[]>;
|
|
21
|
+
protected write(entry: SerializedEntry): Promise<void>;
|
|
22
|
+
protected writeMany(entries: SerializedEntry[]): Promise<void>;
|
|
23
|
+
protected remove(key: string): Promise<boolean>;
|
|
24
|
+
protected removeMany(keys: string[]): Promise<boolean>;
|
|
25
|
+
clear(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MysqlStore = void 0;
|
|
4
|
+
const SerializedCacheStore_js_1 = require("./SerializedCacheStore.js");
|
|
5
|
+
const LoadDriver_js_1 = require("../lib/LoadDriver.js");
|
|
6
|
+
const LikePrefix_js_1 = require("../lib/LikePrefix.js");
|
|
7
|
+
/**
|
|
8
|
+
* The MySQL store
|
|
9
|
+
* Table format: (id VARCHAR(255) PRIMARY KEY, value TEXT), with "{namespace}:{key}" ids
|
|
10
|
+
*/
|
|
11
|
+
class MysqlStore extends SerializedCacheStore_js_1.SerializedCacheStore {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options.namespace);
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
async connect() {
|
|
17
|
+
const driver = await (0, LoadDriver_js_1.loadDriver)('createPool', 'mysql2');
|
|
18
|
+
const options = this.options;
|
|
19
|
+
this.pool = driver.createPool({
|
|
20
|
+
host: options.host,
|
|
21
|
+
port: options.port,
|
|
22
|
+
user: options.username,
|
|
23
|
+
password: options.password,
|
|
24
|
+
database: options.database
|
|
25
|
+
}).promise();
|
|
26
|
+
this.table = this.pool.escapeId(options.table);
|
|
27
|
+
try {
|
|
28
|
+
//The binary collation keeps the keys case sensitive, the tables of 2.x are used as they are
|
|
29
|
+
await this.pool.query(`CREATE TABLE IF NOT EXISTS ${this.table}(id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin PRIMARY KEY, value MEDIUMTEXT)`);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
await this.pool.end().catch(() => undefined);
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async disconnect() {
|
|
37
|
+
await this.pool?.end();
|
|
38
|
+
}
|
|
39
|
+
async read(key) {
|
|
40
|
+
const [rows] = await this.pool.query(`SELECT value FROM ${this.table} WHERE id = ?`, [key]);
|
|
41
|
+
return rows[0]?.value;
|
|
42
|
+
}
|
|
43
|
+
async readMany(keys) {
|
|
44
|
+
const [rows] = await this.pool.query(`SELECT id, value FROM ${this.table} WHERE id IN (?)`, [keys]);
|
|
45
|
+
const values = new Map(rows.map((row) => [row.id, row.value]));
|
|
46
|
+
return keys.map((key) => values.get(key));
|
|
47
|
+
}
|
|
48
|
+
async write(entry) {
|
|
49
|
+
await this.pool.query(`INSERT INTO ${this.table} (id, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value)`, [entry.key, entry.data]);
|
|
50
|
+
}
|
|
51
|
+
async writeMany(entries) {
|
|
52
|
+
await this.pool.query(`INSERT INTO ${this.table} (id, value) VALUES ? ON DUPLICATE KEY UPDATE value = VALUES(value)`, [entries.map((entry) => [entry.key, entry.data])]);
|
|
53
|
+
}
|
|
54
|
+
async remove(key) {
|
|
55
|
+
const [result] = await this.pool.query(`DELETE FROM ${this.table} WHERE id = ?`, [key]);
|
|
56
|
+
return result.affectedRows > 0;
|
|
57
|
+
}
|
|
58
|
+
async removeMany(keys) {
|
|
59
|
+
const [result] = await this.pool.query(`DELETE FROM ${this.table} WHERE id IN (?)`, [keys]);
|
|
60
|
+
return result.affectedRows > 0;
|
|
61
|
+
}
|
|
62
|
+
async clear() {
|
|
63
|
+
if (this.namespace) {
|
|
64
|
+
await this.pool.query(`DELETE FROM ${this.table} WHERE id LIKE ? ESCAPE '!'`, [(0, LikePrefix_js_1.likePrefix)(this.namespace)]);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
await this.pool.query(`DELETE FROM ${this.table}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.MysqlStore = MysqlStore;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SerializedCacheStore, SerializedEntry } from './SerializedCacheStore.js';
|
|
2
|
+
import { PostgresCacheOptions } from '../options/PostgresCacheOptions.js';
|
|
3
|
+
interface PostgresPool {
|
|
4
|
+
query(sql: string, values?: unknown[]): Promise<{
|
|
5
|
+
rows: any[];
|
|
6
|
+
rowCount: number | null;
|
|
7
|
+
}>;
|
|
8
|
+
on(event: string, listener: (...args: any[]) => void): unknown;
|
|
9
|
+
end(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The PostgreSQL store
|
|
13
|
+
* Table format: (key VARCHAR(255) PRIMARY KEY, value TEXT), with "{namespace}:{key}" keys
|
|
14
|
+
*/
|
|
15
|
+
export declare class PostgresStore extends SerializedCacheStore {
|
|
16
|
+
protected readonly options: PostgresCacheOptions;
|
|
17
|
+
protected pool: PostgresPool;
|
|
18
|
+
protected table: string;
|
|
19
|
+
constructor(options: PostgresCacheOptions);
|
|
20
|
+
connect(): Promise<void>;
|
|
21
|
+
disconnect(): Promise<void>;
|
|
22
|
+
protected read(key: string): Promise<string | undefined>;
|
|
23
|
+
protected readMany(keys: string[]): Promise<(string | undefined)[]>;
|
|
24
|
+
protected write(entry: SerializedEntry): Promise<void>;
|
|
25
|
+
protected writeMany(entries: SerializedEntry[]): Promise<void>;
|
|
26
|
+
protected remove(key: string): Promise<boolean>;
|
|
27
|
+
protected removeMany(keys: string[]): Promise<boolean>;
|
|
28
|
+
clear(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresStore = void 0;
|
|
4
|
+
const SerializedCacheStore_js_1 = require("./SerializedCacheStore.js");
|
|
5
|
+
const LoadDriver_js_1 = require("../lib/LoadDriver.js");
|
|
6
|
+
const LikePrefix_js_1 = require("../lib/LikePrefix.js");
|
|
7
|
+
/**
|
|
8
|
+
* An identifier of the SQL text, the plain names are kept unquoted as in 2.x (and so folded to lower case)
|
|
9
|
+
* @param name
|
|
10
|
+
*/
|
|
11
|
+
function identifier(name) {
|
|
12
|
+
return /^[A-Za-z_][A-Za-z0-9_$]*$/.test(name) ? name : `"${name.replace(/"/g, '""')}"`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The PostgreSQL store
|
|
16
|
+
* Table format: (key VARCHAR(255) PRIMARY KEY, value TEXT), with "{namespace}:{key}" keys
|
|
17
|
+
*/
|
|
18
|
+
class PostgresStore extends SerializedCacheStore_js_1.SerializedCacheStore {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super(options.namespace);
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
async connect() {
|
|
24
|
+
const driver = await (0, LoadDriver_js_1.loadDriver)('Pool', 'pg');
|
|
25
|
+
const options = this.options;
|
|
26
|
+
this.pool = new driver.Pool({
|
|
27
|
+
host: options.host,
|
|
28
|
+
port: options.port,
|
|
29
|
+
user: options.username,
|
|
30
|
+
password: options.password,
|
|
31
|
+
database: options.database,
|
|
32
|
+
max: options.maxPoolSize
|
|
33
|
+
});
|
|
34
|
+
this.pool.on('error', (error) => this.onError(error));
|
|
35
|
+
const schema = identifier(options.schema ?? 'public');
|
|
36
|
+
this.table = `${schema}.${identifier(options.table)}`;
|
|
37
|
+
try {
|
|
38
|
+
if (schema !== 'public')
|
|
39
|
+
await this.pool.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
|
40
|
+
await this.pool.query(`CREATE TABLE IF NOT EXISTS ${this.table}(key VARCHAR(255) PRIMARY KEY, value TEXT)`);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
//A concurrent creation of the same table
|
|
44
|
+
if (e.code !== '23505') {
|
|
45
|
+
await this.pool.end().catch(() => undefined);
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async disconnect() {
|
|
51
|
+
await this.pool?.end();
|
|
52
|
+
}
|
|
53
|
+
async read(key) {
|
|
54
|
+
const { rows } = await this.pool.query(`SELECT value FROM ${this.table} WHERE key = $1`, [key]);
|
|
55
|
+
return rows[0]?.value;
|
|
56
|
+
}
|
|
57
|
+
async readMany(keys) {
|
|
58
|
+
const { rows } = await this.pool.query(`SELECT key, value FROM ${this.table} WHERE key = ANY($1)`, [keys]);
|
|
59
|
+
const values = new Map(rows.map((row) => [row.key, row.value]));
|
|
60
|
+
return keys.map((key) => values.get(key));
|
|
61
|
+
}
|
|
62
|
+
async write(entry) {
|
|
63
|
+
await this.pool.query(`INSERT INTO ${this.table} (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = excluded.value`, [entry.key, entry.data]);
|
|
64
|
+
}
|
|
65
|
+
async writeMany(entries) {
|
|
66
|
+
//The last of the duplicate keys wins, one statement cannot update a row twice
|
|
67
|
+
const latest = new Map(entries.map((entry) => [entry.key, entry.data]));
|
|
68
|
+
await this.pool.query(`INSERT INTO ${this.table} (key, value) SELECT * FROM UNNEST($1::text[], $2::text[]) ON CONFLICT (key) DO UPDATE SET value = excluded.value`, [[...latest.keys()], [...latest.values()]]);
|
|
69
|
+
}
|
|
70
|
+
async remove(key) {
|
|
71
|
+
return !!(await this.pool.query(`DELETE FROM ${this.table} WHERE key = $1`, [key])).rowCount;
|
|
72
|
+
}
|
|
73
|
+
async removeMany(keys) {
|
|
74
|
+
return !!(await this.pool.query(`DELETE FROM ${this.table} WHERE key = ANY($1)`, [keys])).rowCount;
|
|
75
|
+
}
|
|
76
|
+
async clear() {
|
|
77
|
+
if (this.namespace) {
|
|
78
|
+
await this.pool.query(`DELETE FROM ${this.table} WHERE key LIKE $1 ESCAPE '!'`, [(0, LikePrefix_js_1.likePrefix)(this.namespace)]);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await this.pool.query(`DELETE FROM ${this.table}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.PostgresStore = PostgresStore;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SerializedCacheStore, SerializedEntry } from './SerializedCacheStore.js';
|
|
2
|
+
import { RedisCacheOptions } from '../options/RedisCacheOptions.js';
|
|
3
|
+
interface RedisCommands<R> {
|
|
4
|
+
set(key: string, value: string): R;
|
|
5
|
+
pSetEx(key: string, milliseconds: number, value: string): R;
|
|
6
|
+
}
|
|
7
|
+
interface RedisMulti extends RedisCommands<RedisMulti> {
|
|
8
|
+
exec(): Promise<unknown[]>;
|
|
9
|
+
}
|
|
10
|
+
interface RedisClient extends RedisCommands<Promise<unknown>> {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
isReady: boolean;
|
|
13
|
+
on(event: string, listener: (...args: any[]) => void): unknown;
|
|
14
|
+
connect(): Promise<unknown>;
|
|
15
|
+
disconnect(): Promise<unknown>;
|
|
16
|
+
quit?(): Promise<unknown>;
|
|
17
|
+
close?(): Promise<unknown>;
|
|
18
|
+
destroy?(): void;
|
|
19
|
+
get(key: string): Promise<string | null>;
|
|
20
|
+
mGet(keys: string[]): Promise<(string | null)[]>;
|
|
21
|
+
del(keys: string | string[]): Promise<number>;
|
|
22
|
+
unlink(keys: string | string[]): Promise<number>;
|
|
23
|
+
flushDb(): Promise<unknown>;
|
|
24
|
+
multi(): RedisMulti;
|
|
25
|
+
scanIterator(options: {
|
|
26
|
+
MATCH?: string;
|
|
27
|
+
COUNT?: number;
|
|
28
|
+
TYPE?: string;
|
|
29
|
+
}): AsyncIterable<string | string[]>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The Redis store
|
|
33
|
+
* Key format: "{namespace}{keyPrefixSeparator}{namespace}:{key}", or "{key}" without namespace
|
|
34
|
+
*/
|
|
35
|
+
export declare class RedisStore extends SerializedCacheStore {
|
|
36
|
+
protected readonly options: RedisCacheOptions;
|
|
37
|
+
protected readonly separator: string;
|
|
38
|
+
protected readonly clearBatchSize: number;
|
|
39
|
+
protected client: RedisClient;
|
|
40
|
+
constructor(options: RedisCacheOptions);
|
|
41
|
+
protected storeKey(key: string): string;
|
|
42
|
+
connect(): Promise<void>;
|
|
43
|
+
disconnect(): Promise<void>;
|
|
44
|
+
protected read(key: string): Promise<string | undefined>;
|
|
45
|
+
protected readMany(keys: string[]): Promise<(string | undefined)[]>;
|
|
46
|
+
protected write(entry: SerializedEntry): Promise<void>;
|
|
47
|
+
protected writeMany(entries: SerializedEntry[]): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Delete keys
|
|
50
|
+
* @param keys
|
|
51
|
+
* @protected
|
|
52
|
+
*/
|
|
53
|
+
protected unlink(keys: string | string[]): Promise<number>;
|
|
54
|
+
protected remove(key: string): Promise<boolean>;
|
|
55
|
+
protected removeMany(keys: string[]): Promise<boolean>;
|
|
56
|
+
clear(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisStore = void 0;
|
|
4
|
+
const SerializedCacheStore_js_1 = require("./SerializedCacheStore.js");
|
|
5
|
+
const LoadDriver_js_1 = require("../lib/LoadDriver.js");
|
|
6
|
+
const WithTimeout_js_1 = require("../lib/WithTimeout.js");
|
|
7
|
+
/**
|
|
8
|
+
* Escape the glob pattern characters of a SCAN match
|
|
9
|
+
* @param text
|
|
10
|
+
*/
|
|
11
|
+
function escapeGlob(text) {
|
|
12
|
+
return text.replace(/[*?[\]\\]/g, '\\$&');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The Redis store
|
|
16
|
+
* Key format: "{namespace}{keyPrefixSeparator}{namespace}:{key}", or "{key}" without namespace
|
|
17
|
+
*/
|
|
18
|
+
class RedisStore extends SerializedCacheStore_js_1.SerializedCacheStore {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super(options.namespace);
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.separator = options.keyPrefixSeparator ?? '::';
|
|
23
|
+
this.clearBatchSize = options.clearBatchSize || 1000;
|
|
24
|
+
}
|
|
25
|
+
storeKey(key) {
|
|
26
|
+
return this.namespace ? `${this.namespace}${this.separator}${this.namespace}:${key}` : key;
|
|
27
|
+
}
|
|
28
|
+
async connect() {
|
|
29
|
+
const driver = await (0, LoadDriver_js_1.loadDriver)('createClient', 'redis', '@redis/client');
|
|
30
|
+
const options = this.options;
|
|
31
|
+
const throwOnConnectError = options.throwOnConnectError !== false;
|
|
32
|
+
let connected = false;
|
|
33
|
+
this.client = driver.createClient({
|
|
34
|
+
username: options.username,
|
|
35
|
+
password: options.password,
|
|
36
|
+
database: options.database,
|
|
37
|
+
//Fail the commands while disconnected, a cache miss is better than a wait
|
|
38
|
+
disableOfflineQueue: true,
|
|
39
|
+
socket: {
|
|
40
|
+
host: options.host,
|
|
41
|
+
port: options.port,
|
|
42
|
+
tls: options.tls || undefined,
|
|
43
|
+
keepAlive: options.keepAlive,
|
|
44
|
+
keepAliveInitialDelay: options.keepAlive,
|
|
45
|
+
connectTimeout: options.connectTimeout,
|
|
46
|
+
reconnectStrategy: (retries, cause) => {
|
|
47
|
+
//The first connection fails at once when its errors are thrown
|
|
48
|
+
if (!connected && throwOnConnectError)
|
|
49
|
+
return cause;
|
|
50
|
+
return options.reconnect === false ? false : Math.min(retries * 100, 3000);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
this.client.on('error', (error) => this.onError(error));
|
|
55
|
+
this.client.on('ready', () => {
|
|
56
|
+
connected = true;
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
await (0, WithTimeout_js_1.withTimeout)(this.client.connect(), options.connectTimeout ?? 5000, `Redis connection timed out after ${options.connectTimeout ?? 5000}ms`);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
if (!throwOnConnectError)
|
|
63
|
+
return this.onError(e);
|
|
64
|
+
await this.disconnect();
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async disconnect() {
|
|
69
|
+
const client = this.client;
|
|
70
|
+
if (!client?.isOpen)
|
|
71
|
+
return;
|
|
72
|
+
//An offline client cannot quit gracefully
|
|
73
|
+
if (!client.isReady)
|
|
74
|
+
return client.destroy ? client.destroy() : void await client.disconnect().catch(() => undefined);
|
|
75
|
+
await (client.quit ? client.quit() : client.close());
|
|
76
|
+
}
|
|
77
|
+
async read(key) {
|
|
78
|
+
return (await this.client.get(key)) ?? undefined;
|
|
79
|
+
}
|
|
80
|
+
async readMany(keys) {
|
|
81
|
+
return (await this.client.mGet(keys)).map((data) => data ?? undefined);
|
|
82
|
+
}
|
|
83
|
+
async write(entry) {
|
|
84
|
+
entry.ttl ? await this.client.pSetEx(entry.key, Math.ceil(entry.ttl), entry.data) : await this.client.set(entry.key, entry.data);
|
|
85
|
+
}
|
|
86
|
+
async writeMany(entries) {
|
|
87
|
+
const multi = this.client.multi();
|
|
88
|
+
for (const entry of entries)
|
|
89
|
+
entry.ttl ? multi.pSetEx(entry.key, Math.ceil(entry.ttl), entry.data) : multi.set(entry.key, entry.data);
|
|
90
|
+
await multi.exec();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Delete keys
|
|
94
|
+
* @param keys
|
|
95
|
+
* @protected
|
|
96
|
+
*/
|
|
97
|
+
async unlink(keys) {
|
|
98
|
+
return this.options.useUnlink === false ? await this.client.del(keys) : await this.client.unlink(keys);
|
|
99
|
+
}
|
|
100
|
+
async remove(key) {
|
|
101
|
+
return (await this.unlink(key)) > 0;
|
|
102
|
+
}
|
|
103
|
+
async removeMany(keys) {
|
|
104
|
+
return (await this.unlink(keys)) > 0;
|
|
105
|
+
}
|
|
106
|
+
async clear() {
|
|
107
|
+
if (!this.namespace && this.options.noNamespaceAffectsAll) {
|
|
108
|
+
await this.client.flushDb();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const match = this.namespace ? `${escapeGlob(this.namespace + this.separator)}*` : '*';
|
|
112
|
+
let batch = [];
|
|
113
|
+
const deletions = [];
|
|
114
|
+
for await (const scanned of this.client.scanIterator({ MATCH: match, COUNT: this.clearBatchSize, TYPE: 'string' })) {
|
|
115
|
+
for (const key of Array.isArray(scanned) ? scanned : [scanned]) {
|
|
116
|
+
//Without namespace, the namespaced keys are kept
|
|
117
|
+
if (!this.namespace && key.includes(this.separator))
|
|
118
|
+
continue;
|
|
119
|
+
batch.push(key);
|
|
120
|
+
if (batch.length >= this.clearBatchSize) {
|
|
121
|
+
deletions.push(this.unlink(batch));
|
|
122
|
+
batch = [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (batch.length)
|
|
127
|
+
deletions.push(this.unlink(batch));
|
|
128
|
+
await Promise.all(deletions);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.RedisStore = RedisStore;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CacheStore, CacheStoreSetEntry } from './CacheStore.js';
|
|
2
|
+
import { CacheEntry } from '../lib/Serializer.js';
|
|
3
|
+
export type SerializedEntry = {
|
|
4
|
+
key: string;
|
|
5
|
+
data: string;
|
|
6
|
+
ttl?: number;
|
|
7
|
+
expires?: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A store keeping the entries serialized, under namespaced keys
|
|
11
|
+
*/
|
|
12
|
+
export declare abstract class SerializedCacheStore extends CacheStore {
|
|
13
|
+
protected readonly namespace?: string;
|
|
14
|
+
protected constructor(namespace?: string);
|
|
15
|
+
/**
|
|
16
|
+
* The key of an entry in the backend
|
|
17
|
+
* @param key
|
|
18
|
+
* @protected
|
|
19
|
+
*/
|
|
20
|
+
protected storeKey(key: string): string;
|
|
21
|
+
protected abstract read(key: string): Promise<string | undefined>;
|
|
22
|
+
protected abstract readMany(keys: string[]): Promise<(string | undefined)[]>;
|
|
23
|
+
protected abstract write(entry: SerializedEntry): Promise<void>;
|
|
24
|
+
protected abstract writeMany(entries: SerializedEntry[]): Promise<void>;
|
|
25
|
+
protected abstract remove(key: string): Promise<boolean>;
|
|
26
|
+
protected abstract removeMany(keys: string[]): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Parse an entry read from the backend, the expired entries are removed
|
|
29
|
+
* @param storeKey
|
|
30
|
+
* @param data
|
|
31
|
+
* @protected
|
|
32
|
+
*/
|
|
33
|
+
protected parse(storeKey: string, data: string | undefined): CacheEntry | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Serialize an entry
|
|
36
|
+
* @param key
|
|
37
|
+
* @param value
|
|
38
|
+
* @param ttl
|
|
39
|
+
* @protected
|
|
40
|
+
*/
|
|
41
|
+
protected serialize(key: string, value: unknown, ttl?: number): SerializedEntry;
|
|
42
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
43
|
+
getMany(keys: string[]): Promise<(CacheEntry | undefined)[]>;
|
|
44
|
+
set(key: string, value: unknown, ttl?: number): Promise<void>;
|
|
45
|
+
setMany(entries: CacheStoreSetEntry[]): Promise<void>;
|
|
46
|
+
delete(key: string): Promise<boolean>;
|
|
47
|
+
deleteMany(keys: string[]): Promise<boolean>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SerializedCacheStore = void 0;
|
|
4
|
+
const CacheStore_js_1 = require("./CacheStore.js");
|
|
5
|
+
const Serializer_js_1 = require("../lib/Serializer.js");
|
|
6
|
+
/**
|
|
7
|
+
* A store keeping the entries serialized, under namespaced keys
|
|
8
|
+
*/
|
|
9
|
+
class SerializedCacheStore extends CacheStore_js_1.CacheStore {
|
|
10
|
+
constructor(namespace) {
|
|
11
|
+
super();
|
|
12
|
+
this.namespace = namespace || undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The key of an entry in the backend
|
|
16
|
+
* @param key
|
|
17
|
+
* @protected
|
|
18
|
+
*/
|
|
19
|
+
storeKey(key) {
|
|
20
|
+
return this.namespace ? `${this.namespace}:${key}` : key;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse an entry read from the backend, the expired entries are removed
|
|
24
|
+
* @param storeKey
|
|
25
|
+
* @param data
|
|
26
|
+
* @protected
|
|
27
|
+
*/
|
|
28
|
+
parse(storeKey, data) {
|
|
29
|
+
if (data === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const entry = (0, Serializer_js_1.deserialize)(data);
|
|
32
|
+
if (entry === null || typeof entry !== 'object')
|
|
33
|
+
return undefined;
|
|
34
|
+
if ((0, CacheStore_js_1.isExpired)(entry)) {
|
|
35
|
+
this.remove(storeKey).catch(() => undefined);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return entry;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Serialize an entry
|
|
42
|
+
* @param key
|
|
43
|
+
* @param value
|
|
44
|
+
* @param ttl
|
|
45
|
+
* @protected
|
|
46
|
+
*/
|
|
47
|
+
serialize(key, value, ttl) {
|
|
48
|
+
ttl = ttl || undefined;
|
|
49
|
+
const expires = (0, CacheStore_js_1.expiresOf)(ttl);
|
|
50
|
+
return { key: this.storeKey(key), data: (0, Serializer_js_1.serialize)({ value: value, expires: expires }), ttl: ttl, expires: expires };
|
|
51
|
+
}
|
|
52
|
+
async get(key) {
|
|
53
|
+
const storeKey = this.storeKey(key);
|
|
54
|
+
return this.parse(storeKey, await this.read(storeKey));
|
|
55
|
+
}
|
|
56
|
+
async getMany(keys) {
|
|
57
|
+
if (!keys.length)
|
|
58
|
+
return [];
|
|
59
|
+
const storeKeys = keys.map((key) => this.storeKey(key));
|
|
60
|
+
const data = await this.readMany(storeKeys);
|
|
61
|
+
return storeKeys.map((storeKey, index) => this.parse(storeKey, data[index]));
|
|
62
|
+
}
|
|
63
|
+
async set(key, value, ttl) {
|
|
64
|
+
await this.write(this.serialize(key, value, ttl));
|
|
65
|
+
}
|
|
66
|
+
async setMany(entries) {
|
|
67
|
+
if (!entries.length)
|
|
68
|
+
return;
|
|
69
|
+
await this.writeMany(entries.map((entry) => this.serialize(entry.key, entry.value, entry.ttl)));
|
|
70
|
+
}
|
|
71
|
+
async delete(key) {
|
|
72
|
+
return await this.remove(this.storeKey(key));
|
|
73
|
+
}
|
|
74
|
+
async deleteMany(keys) {
|
|
75
|
+
if (!keys.length)
|
|
76
|
+
return false;
|
|
77
|
+
return await this.removeMany(keys.map((key) => this.storeKey(key)));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.SerializedCacheStore = SerializedCacheStore;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SerializedCacheStore, SerializedEntry } from './SerializedCacheStore.js';
|
|
2
|
+
import { SqliteCacheOptions } from '../options/SqliteCacheOptions.js';
|
|
3
|
+
interface SqliteDatabase {
|
|
4
|
+
configure(option: 'busyTimeout', value: number): void;
|
|
5
|
+
all(sql: string, params: unknown[], callback: (error: Error | null, rows: any[]) => void): void;
|
|
6
|
+
run(sql: string, params: unknown[], callback: (this: {
|
|
7
|
+
changes: number;
|
|
8
|
+
}, error: Error | null) => void): void;
|
|
9
|
+
close(callback: (error: Error | null) => void): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The SQLite store
|
|
13
|
+
* Table format: (key VARCHAR(255) PRIMARY KEY, value TEXT), with "{namespace}:{key}" keys
|
|
14
|
+
*/
|
|
15
|
+
export declare class SqliteStore extends SerializedCacheStore {
|
|
16
|
+
protected readonly options: SqliteCacheOptions;
|
|
17
|
+
protected database: SqliteDatabase;
|
|
18
|
+
protected readonly table: string;
|
|
19
|
+
constructor(options: SqliteCacheOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Query the rows
|
|
22
|
+
* @param sql
|
|
23
|
+
* @param params
|
|
24
|
+
* @protected
|
|
25
|
+
*/
|
|
26
|
+
protected all(sql: string, params: unknown[]): Promise<any[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Run a statement, resolve the number of changed rows
|
|
29
|
+
* @param sql
|
|
30
|
+
* @param params
|
|
31
|
+
* @protected
|
|
32
|
+
*/
|
|
33
|
+
protected run(sql: string, params?: unknown[]): Promise<number>;
|
|
34
|
+
connect(): Promise<void>;
|
|
35
|
+
disconnect(): Promise<void>;
|
|
36
|
+
protected read(key: string): Promise<string | undefined>;
|
|
37
|
+
protected readMany(keys: string[]): Promise<(string | undefined)[]>;
|
|
38
|
+
protected write(entry: SerializedEntry): Promise<void>;
|
|
39
|
+
protected writeMany(entries: SerializedEntry[]): Promise<void>;
|
|
40
|
+
protected remove(key: string): Promise<boolean>;
|
|
41
|
+
protected removeMany(keys: string[]): Promise<boolean>;
|
|
42
|
+
clear(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
export {};
|