create-dacosta-proj 1.0.24 → 1.0.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dacosta-proj",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "bin": {
5
5
  "create-dacosta-proj": "./index.js"
6
6
  }
@@ -3,6 +3,7 @@
3
3
  * db: import('simple-supabase').DB | null,
4
4
  * supabase: import('@supabase/supabase-js').SupabaseClient | null,
5
5
  * redis: import('redis').RedisClientType | null,
6
+ * redisLock: ((lockName: string, timeout?: number) => Promise<() => Promise<void>>) | null,
6
7
  * }}
7
8
  */
8
9
 
@@ -10,5 +11,6 @@ const global = {
10
11
  db: null,
11
12
  supabase: null,
12
13
  redis: null,
14
+ redisLock: null,
13
15
  };
14
16
  module.exports = { global };
@@ -5,6 +5,7 @@ const { global } = require('@/config/global');
5
5
  const { SimpleSupabase } = require('simple-supabase');
6
6
  const { createClient: createSupabaseClient } = require('@supabase/supabase-js');
7
7
  const { createClient: createRedisClient } = require('redis');
8
+ const redisLock = require('@/helpers/redisLock');
8
9
 
9
10
  module.exports = async () => {
10
11
 
@@ -29,4 +30,5 @@ module.exports = async () => {
29
30
  await redis.connect();
30
31
 
31
32
  global.redis = redis;
33
+ global.redisLock = redisLock(redis);
32
34
  };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ const DEFAULT_TIMEOUT = 5_000;
6
+ const DEFAULT_RETRY_DELAY = 50;
7
+ const DEFAULT_WAIT_TIMEOUT = 30_000;
8
+
9
+ // Atomic compare-and-delete so we only release a lock we still hold.
10
+ // Without this, an expired lock that another caller already re-acquired
11
+ // would be deleted by our release(), letting a third caller acquire it
12
+ // while the second is still inside its critical section.
13
+ const RELEASE_SCRIPT = 'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end';
14
+
15
+ function redisLock(client, retryDelay = DEFAULT_RETRY_DELAY) {
16
+ async function lock(lockName, timeout = DEFAULT_TIMEOUT, waitTimeout = DEFAULT_WAIT_TIMEOUT) {
17
+ const lockKey = `lock.${lockName}`;
18
+ const token = crypto.randomBytes(16).toString('hex');
19
+ const deadline = Date.now() + waitTimeout;
20
+ let released = false;
21
+
22
+ while (true) {
23
+ const result = await client.set(lockKey, token, { PX: timeout, NX: true });
24
+ if (result !== null) {
25
+ return async () => {
26
+ if (released) return;
27
+ released = true;
28
+ try { await client.sendCommand(['EVAL', RELEASE_SCRIPT, '1', lockKey, token]); }
29
+ catch {}
30
+ };
31
+ }
32
+ if (Date.now() >= deadline) throw new Error(`redisLock: timed out waiting for "${lockName}" after ${waitTimeout}ms`);
33
+ await new Promise(r => setTimeout(r, retryDelay));
34
+ }
35
+ }
36
+ return lock;
37
+ }
38
+
39
+ module.exports = redisLock;
File without changes