Spaces:
Sleeping
Sleeping
File size: 6,222 Bytes
6491ad4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | import { Redis } from "@upstash/redis";
import { Cache } from "../core/index.js";
import { entityKind, is } from "../../entity.js";
import { OriginalName, Table } from "../../index.js";
const getByTagScript = `
local tagsMapKey = KEYS[1] -- tags map key
local tag = ARGV[1] -- tag
local compositeTableName = redis.call('HGET', tagsMapKey, tag)
if not compositeTableName then
return nil
end
local value = redis.call('HGET', compositeTableName, tag)
return value
`;
const onMutateScript = `
local tagsMapKey = KEYS[1] -- tags map key
local tables = {} -- initialize tables array
local tags = ARGV -- tags array
for i = 2, #KEYS do
tables[#tables + 1] = KEYS[i] -- add all keys except the first one to tables
end
if #tags > 0 then
for _, tag in ipairs(tags) do
if tag ~= nil and tag ~= '' then
local compositeTableName = redis.call('HGET', tagsMapKey, tag)
if compositeTableName then
redis.call('HDEL', compositeTableName, tag)
end
end
end
redis.call('HDEL', tagsMapKey, unpack(tags))
end
local keysToDelete = {}
if #tables > 0 then
local compositeTableNames = redis.call('SUNION', unpack(tables))
for _, compositeTableName in ipairs(compositeTableNames) do
keysToDelete[#keysToDelete + 1] = compositeTableName
end
for _, table in ipairs(tables) do
keysToDelete[#keysToDelete + 1] = table
end
redis.call('DEL', unpack(keysToDelete))
end
`;
class UpstashCache extends Cache {
constructor(redis, config, useGlobally) {
super();
this.redis = redis;
this.useGlobally = useGlobally;
this.internalConfig = this.toInternalConfig(config);
this.luaScripts = {
getByTagScript: this.redis.createScript(getByTagScript, { readonly: true }),
onMutateScript: this.redis.createScript(onMutateScript)
};
}
static [entityKind] = "UpstashCache";
/**
* Prefix for sets which denote the composite table names for each unique table
*
* Example: In the composite table set of "table1", you may find
* `${compositeTablePrefix}table1,table2` and `${compositeTablePrefix}table1,table3`
*/
static compositeTableSetPrefix = "__CTS__";
/**
* Prefix for hashes which map hash or tags to cache values
*/
static compositeTablePrefix = "__CT__";
/**
* Key which holds the mapping of tags to composite table names
*
* Using this tagsMapKey, you can find the composite table name for a given tag
* and get the cache value for that tag:
*
* ```ts
* const compositeTable = redis.hget(tagsMapKey, 'tag1')
* console.log(compositeTable) // `${compositeTablePrefix}table1,table2`
*
* const cachevalue = redis.hget(compositeTable, 'tag1')
*/
static tagsMapKey = "__tagsMap__";
/**
* Queries whose auto invalidation is false aren't stored in their respective
* composite table hashes because those hashes are deleted when a mutation
* occurs on related tables.
*
* Instead, they are stored in a separate hash with the prefix
* `__nonAutoInvalidate__` to prevent them from being deleted when a mutation
*/
static nonAutoInvalidateTablePrefix = "__nonAutoInvalidate__";
luaScripts;
internalConfig;
strategy() {
return this.useGlobally ? "all" : "explicit";
}
toInternalConfig(config) {
return config ? {
seconds: config.ex,
hexOptions: config.hexOptions
} : {
seconds: 1
};
}
async get(key, tables, isTag = false, isAutoInvalidate) {
if (!isAutoInvalidate) {
const result2 = await this.redis.hget(UpstashCache.nonAutoInvalidateTablePrefix, key);
return result2 === null ? void 0 : result2;
}
if (isTag) {
const result2 = await this.luaScripts.getByTagScript.exec([UpstashCache.tagsMapKey], [key]);
return result2 === null ? void 0 : result2;
}
const compositeKey = this.getCompositeKey(tables);
const result = (await this.redis.hget(compositeKey, key)) ?? void 0;
return result === null ? void 0 : result;
}
async put(key, response, tables, isTag = false, config) {
const isAutoInvalidate = tables.length !== 0;
const pipeline = this.redis.pipeline();
const ttlSeconds = config && config.ex ? config.ex : this.internalConfig.seconds;
const hexOptions = config && config.hexOptions ? config.hexOptions : this.internalConfig?.hexOptions;
if (!isAutoInvalidate) {
if (isTag) {
pipeline.hset(UpstashCache.tagsMapKey, { [key]: UpstashCache.nonAutoInvalidateTablePrefix });
pipeline.hexpire(UpstashCache.tagsMapKey, key, ttlSeconds, hexOptions);
}
pipeline.hset(UpstashCache.nonAutoInvalidateTablePrefix, { [key]: response });
pipeline.hexpire(UpstashCache.nonAutoInvalidateTablePrefix, key, ttlSeconds, hexOptions);
await pipeline.exec();
return;
}
const compositeKey = this.getCompositeKey(tables);
pipeline.hset(compositeKey, { [key]: response });
pipeline.hexpire(compositeKey, key, ttlSeconds, hexOptions);
if (isTag) {
pipeline.hset(UpstashCache.tagsMapKey, { [key]: compositeKey });
pipeline.hexpire(UpstashCache.tagsMapKey, key, ttlSeconds, hexOptions);
}
for (const table of tables) {
pipeline.sadd(this.addTablePrefix(table), compositeKey);
}
await pipeline.exec();
}
async onMutate(params) {
const tags = Array.isArray(params.tags) ? params.tags : params.tags ? [params.tags] : [];
const tables = Array.isArray(params.tables) ? params.tables : params.tables ? [params.tables] : [];
const tableNames = tables.map((table) => is(table, Table) ? table[OriginalName] : table);
const compositeTableSets = tableNames.map((table) => this.addTablePrefix(table));
await this.luaScripts.onMutateScript.exec([UpstashCache.tagsMapKey, ...compositeTableSets], tags);
}
addTablePrefix = (table) => `${UpstashCache.compositeTableSetPrefix}${table}`;
getCompositeKey = (tables) => `${UpstashCache.compositeTablePrefix}${tables.sort().join(",")}`;
}
function upstashCache({ url, token, config, global = false }) {
const redis = new Redis({
url,
token
});
return new UpstashCache(redis, config, global);
}
export {
UpstashCache,
upstashCache
};
//# sourceMappingURL=cache.js.map |