@zauru-sdk/services 6.15.1 → 6.16.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/dist/esm/redis/redisQueue.js +95 -24
- package/dist/redis/redisQueue.d.ts +32 -2
- package/package.json +4 -4
|
@@ -1,26 +1,97 @@
|
|
|
1
1
|
import { Redis } from "@upstash/redis";
|
|
2
2
|
import { config } from "@zauru-sdk/config";
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
2
|
-
|
|
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.
|
|
3
|
+
"version": "6.16.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"
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"@upstash/redis": "^1.34.5",
|
|
30
30
|
"@zauru-sdk/common": "^6.12.0",
|
|
31
31
|
"@zauru-sdk/config": "^6.0.0",
|
|
32
|
-
"@zauru-sdk/graphql": "^6.9.
|
|
32
|
+
"@zauru-sdk/graphql": "^6.9.1",
|
|
33
33
|
"@zauru-sdk/types": "^6.28.0",
|
|
34
34
|
"axios": "^1.6.7",
|
|
35
35
|
"chalk": "5.3.0",
|
|
36
36
|
"moment": "^2.30.1"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "91be71ebd8225a82f533d4e62ec5fbf5aeab09c8"
|
|
39
39
|
}
|