@zauru-sdk/services 6.15.1 → 6.17.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.
@@ -1,26 +1,97 @@
1
1
  import { Redis } from "@upstash/redis";
2
2
  import { config } from "@zauru-sdk/config";
3
- const redis = new Redis({
4
- url: config.redisBaseURL,
5
- token: config.redisToken,
6
- });
7
- // Función para intentar adquirir el lock
8
- export async function acquireLock(key, lockValue, expiration) {
9
- // Usamos SET con NX (solo si no existe) y PX para expirar el lock en milisegundos
10
- const result = await redis.set(key, lockValue, {
11
- nx: true,
12
- pxat: Date.now() + expiration,
13
- });
14
- return result === "OK";
15
- }
16
- // Función para liberar el lock de forma segura usando un script Lua
17
- export async function releaseLock(key, lockValue) {
18
- const script = `
19
- if redis.call("get", KEYS[1]) == ARGV[1] then
20
- return redis.call("del", KEYS[1])
21
- else
22
- return 0
23
- end
24
- `;
25
- await redis.eval(script, [key], [lockValue]);
26
- }
3
+ const EXTEND_LOCK_SCRIPT = `
4
+ if redis.call("get", KEYS[1]) == ARGV[1] then
5
+ return redis.call("pexpire", KEYS[1], ARGV[2])
6
+ else
7
+ return 0
8
+ end
9
+ `;
10
+ const RELEASE_LOCK_SCRIPT = `
11
+ if redis.call("get", KEYS[1]) == ARGV[1] then
12
+ return redis.call("del", KEYS[1])
13
+ else
14
+ return 0
15
+ end
16
+ `;
17
+ let defaultRedis;
18
+ const getDefaultRedis = () => {
19
+ if (!defaultRedis) {
20
+ defaultRedis = new Redis({
21
+ url: config.redisBaseURL,
22
+ token: config.redisToken,
23
+ });
24
+ }
25
+ return defaultRedis;
26
+ };
27
+ const isSuccessfulEval = (result) => result === 1 || result === "1";
28
+ export const createLockClient = (redis) => {
29
+ const acquireLock = async (key, lockValue, expiration) => {
30
+ const result = await redis.set(key, lockValue, {
31
+ nx: true,
32
+ px: expiration,
33
+ });
34
+ return result === "OK";
35
+ };
36
+ const extendLock = async (key, lockValue, expiration) => {
37
+ const result = await redis.eval(EXTEND_LOCK_SCRIPT, [key], [
38
+ lockValue,
39
+ expiration,
40
+ ]);
41
+ return isSuccessfulEval(result);
42
+ };
43
+ const releaseLock = async (key, lockValue) => {
44
+ await redis.eval(RELEASE_LOCK_SCRIPT, [key], [lockValue]);
45
+ };
46
+ const startLockHeartbeat = (key, lockValue, expiration, intervalMs = Math.max(1_000, Math.floor(expiration / 3))) => {
47
+ const timer = setInterval(() => {
48
+ void extendLock(key, lockValue, expiration).catch((error) => {
49
+ console.error("extendLock failed", error);
50
+ });
51
+ }, intervalMs);
52
+ timer.unref?.();
53
+ return () => clearInterval(timer);
54
+ };
55
+ const runWithLock = async (options, fn) => {
56
+ const retryMs = options.retryMs ?? 10_000;
57
+ const startedAt = Date.now();
58
+ let acquired = false;
59
+ while (!acquired) {
60
+ if (Date.now() - startedAt >= options.waitMs) {
61
+ return { acquired: false };
62
+ }
63
+ acquired = await acquireLock(options.key, options.lockValue, options.ttlMs);
64
+ if (!acquired) {
65
+ await new Promise((resolve) => setTimeout(resolve, retryMs));
66
+ }
67
+ }
68
+ const stopHeartbeat = startLockHeartbeat(options.key, options.lockValue, options.ttlMs);
69
+ try {
70
+ const result = await fn();
71
+ return { acquired: true, result };
72
+ }
73
+ finally {
74
+ stopHeartbeat();
75
+ await releaseLock(options.key, options.lockValue);
76
+ }
77
+ };
78
+ return {
79
+ acquireLock,
80
+ extendLock,
81
+ releaseLock,
82
+ startLockHeartbeat,
83
+ runWithLock,
84
+ };
85
+ };
86
+ let defaultLockClient;
87
+ const getDefaultLockClient = () => {
88
+ if (!defaultLockClient) {
89
+ defaultLockClient = createLockClient(getDefaultRedis());
90
+ }
91
+ return defaultLockClient;
92
+ };
93
+ export const acquireLock = (key, lockValue, expiration) => getDefaultLockClient().acquireLock(key, lockValue, expiration);
94
+ export const extendLock = (key, lockValue, expiration) => getDefaultLockClient().extendLock(key, lockValue, expiration);
95
+ export const releaseLock = (key, lockValue) => getDefaultLockClient().releaseLock(key, lockValue);
96
+ export const startLockHeartbeat = (key, lockValue, expiration, intervalMs) => getDefaultLockClient().startLockHeartbeat(key, lockValue, expiration, intervalMs);
97
+ export const runWithLock = (options, fn) => getDefaultLockClient().runWithLock(options, fn);
@@ -1,2 +1,32 @@
1
- export declare function acquireLock(key: string, lockValue: string, expiration: number): Promise<boolean>;
2
- export declare function releaseLock(key: string, lockValue: string): Promise<void>;
1
+ export type RedisLockClient = {
2
+ set: (key: string, value: string, opts: {
3
+ nx: true;
4
+ px: number;
5
+ }) => Promise<string | null>;
6
+ eval: (script: string, keys: string[], args: (string | number)[]) => Promise<unknown>;
7
+ };
8
+ export type RunWithLockOptions = {
9
+ key: string;
10
+ lockValue: string;
11
+ ttlMs: number;
12
+ waitMs: number;
13
+ retryMs?: number;
14
+ };
15
+ export type RunWithLockResult<T> = {
16
+ acquired: false;
17
+ } | {
18
+ acquired: true;
19
+ result: T;
20
+ };
21
+ export declare const createLockClient: (redis: RedisLockClient) => {
22
+ acquireLock: (key: string, lockValue: string, expiration: number) => Promise<boolean>;
23
+ extendLock: (key: string, lockValue: string, expiration: number) => Promise<boolean>;
24
+ releaseLock: (key: string, lockValue: string) => Promise<void>;
25
+ startLockHeartbeat: (key: string, lockValue: string, expiration: number, intervalMs?: number) => (() => void);
26
+ runWithLock: <T>(options: RunWithLockOptions, fn: () => Promise<T>) => Promise<RunWithLockResult<T>>;
27
+ };
28
+ export declare const acquireLock: (key: string, lockValue: string, expiration: number) => Promise<boolean>;
29
+ export declare const extendLock: (key: string, lockValue: string, expiration: number) => Promise<boolean>;
30
+ export declare const releaseLock: (key: string, lockValue: string) => Promise<void>;
31
+ export declare const startLockHeartbeat: (key: string, lockValue: string, expiration: number, intervalMs?: number) => () => void;
32
+ export declare const runWithLock: <T>(options: RunWithLockOptions, fn: () => Promise<T>) => Promise<RunWithLockResult<T>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/services",
3
- "version": "6.15.1",
3
+ "version": "6.17.0",
4
4
  "description": "Servicios de consulta a Zauru",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -13,7 +13,7 @@
13
13
  "scripts": {
14
14
  "build": "npm run build:esm",
15
15
  "build:esm": "tsc -p tsconfig.esm.json",
16
- "test": "tsx --test src/zauru/*.test.ts"
16
+ "test": "tsx --test src/zauru/*.test.ts src/redis/*.test.ts"
17
17
  },
18
18
  "files": [
19
19
  "dist"
@@ -27,13 +27,13 @@
27
27
  "dependencies": {
28
28
  "@remix-run/node": "^2.8.1",
29
29
  "@upstash/redis": "^1.34.5",
30
- "@zauru-sdk/common": "^6.12.0",
30
+ "@zauru-sdk/common": "^6.14.0",
31
31
  "@zauru-sdk/config": "^6.0.0",
32
- "@zauru-sdk/graphql": "^6.9.0",
33
- "@zauru-sdk/types": "^6.28.0",
32
+ "@zauru-sdk/graphql": "^6.9.1",
33
+ "@zauru-sdk/types": "^6.29.0",
34
34
  "axios": "^1.6.7",
35
35
  "chalk": "5.3.0",
36
36
  "moment": "^2.30.1"
37
37
  },
38
- "gitHead": "2fda4a18e3f62d22c38fbb91052869db9ebc16f4"
38
+ "gitHead": "8e5cb991b0da7d1a5d70cd6275d7bfdc52ee1102"
39
39
  }