UNPKG

@jbagatta/johnny-cache

Version:

A robust distributed dictionary for coordinating and caching expensive operations in a distributed environment

104 lines (103 loc) 3.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createRedisDataStore = exports.RedisConnectionOptions = exports.tryUpdateReservationLuaScript = exports.tryReserveAndReturnExistingBuildLuaScript = exports.RedisDataStore = void 0; const ioredis_1 = require("ioredis"); const uuid_1 = require("uuid"); const buildIdField = 'buildId'; const buildResultField = 'buildResult'; class RedisDataStore { constructor(client) { this.client = client; } async tryReserve(key, reservationExpiryMs) { const buildId = (0, uuid_1.v4)(); const result = await this.client.eval(exports.tryReserveAndReturnExistingBuildLuaScript, 1, key, buildId, reservationExpiryMs); return { isNew: result[0] === buildId, buildId: result[0], completedBuild: result[1] ? JSON.parse(result[1]) : null }; } async has(key) { return await this.client.exists(key) === 1; } async get(key) { const result = await this.client.hgetall(key); if (Object.keys(result).length === 0) { return null; } return { isNew: false, buildId: result[buildIdField], completedBuild: buildResultField in result ? JSON.parse(result[buildResultField]) : null }; } async tryUpdateReservation(key, buildId, result, expiry) { return await this.client.eval(exports.tryUpdateReservationLuaScript, 1, key, buildId, JSON.stringify(result), expiry ?? -1); } async delete(key) { await this.client.del(key); } async updateExpiry(key, expiry) { if (expiry) { await this.client.pexpire(key, expiry); } } close() { this.client.disconnect(); } } exports.RedisDataStore = RedisDataStore; exports.tryReserveAndReturnExistingBuildLuaScript = ` \ local isNew = redis.call('HSETNX', KEYS[1], '${buildIdField}', ARGV[1]) \ if (isNew == 1) then \ redis.call('PEXPIRE', KEYS[1], ARGV[2]) \ return {ARGV[1], nil} \ else \ local buildKey = redis.call('HGET', KEYS[1], '${buildIdField}') \ local buildResult = redis.call('HGET', KEYS[1], '${buildResultField}') \ return {buildKey, buildResult} \ end \ `; exports.tryUpdateReservationLuaScript = ` \ local exists = redis.call('EXISTS', KEYS[1]) \ if (exists == 0) then \ redis.call('HSET', KEYS[1], '${buildIdField}', ARGV[1]) \ end \ if (redis.call('HGET', KEYS[1], '${buildIdField}') == ARGV[1]) then \ redis.call('HSET', KEYS[1], '${buildResultField}', ARGV[2]) \ if (ARGV[3] == -1) then \ redis.call('PERSIST', KEYS[1]) \ else \ redis.call('PEXPIRE', KEYS[1], ARGV[3]) \ end \ return true \ else \ return false \ end \ `; class RedisConnectionOptions { } exports.RedisConnectionOptions = RedisConnectionOptions; function createRedisDataStore(redisConnectionOptions) { if (!redisConnectionOptions.sentinel && !redisConnectionOptions.url) { throw new Error("Missing Redis connection configration (sentinel or url)"); } let redisClient; if (redisConnectionOptions.sentinel) { redisClient = new ioredis_1.Redis({ sentinels: [{ host: redisConnectionOptions.sentinel.host, port: redisConnectionOptions.sentinel.port }], name: redisConnectionOptions.sentinel.primaryName, password: redisConnectionOptions.password, sentinelPassword: redisConnectionOptions.password }); } else { redisClient = new ioredis_1.Redis({ path: redisConnectionOptions.url, password: redisConnectionOptions.password }); } return new RedisDataStore(redisClient); } exports.createRedisDataStore = createRedisDataStore;