effect-distributed-lock 0.0.3 → 0.0.4
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/README.md +90 -56
- package/examples/index.ts +49 -21
- package/package.json +2 -2
- package/redis-semaphore/.codeclimate.yml +5 -0
- package/redis-semaphore/.fossa.yml +14 -0
- package/redis-semaphore/.github/dependabot.yml +6 -0
- package/redis-semaphore/.github/workflows/branches.yml +39 -0
- package/redis-semaphore/.github/workflows/pull-requests.yml +35 -0
- package/redis-semaphore/.mocharc.yaml +6 -0
- package/redis-semaphore/.prettierrc +6 -0
- package/redis-semaphore/.snyk +4 -0
- package/redis-semaphore/.yarnrc.yml +2 -0
- package/redis-semaphore/CHANGELOG.md +70 -0
- package/redis-semaphore/Dockerfile +5 -0
- package/redis-semaphore/LICENSE +21 -0
- package/redis-semaphore/README.md +445 -0
- package/redis-semaphore/docker-compose.yml +31 -0
- package/redis-semaphore/eslint.config.mjs +73 -0
- package/redis-semaphore/package.json +79 -0
- package/redis-semaphore/setup-redis-servers.sh +2 -0
- package/redis-semaphore/src/Lock.ts +172 -0
- package/redis-semaphore/src/RedisMultiSemaphore.ts +56 -0
- package/redis-semaphore/src/RedisMutex.ts +45 -0
- package/redis-semaphore/src/RedisSemaphore.ts +49 -0
- package/redis-semaphore/src/RedlockMultiSemaphore.ts +56 -0
- package/redis-semaphore/src/RedlockMutex.ts +52 -0
- package/redis-semaphore/src/RedlockSemaphore.ts +49 -0
- package/redis-semaphore/src/errors/LostLockError.ts +1 -0
- package/redis-semaphore/src/errors/TimeoutError.ts +1 -0
- package/redis-semaphore/src/index.ts +23 -0
- package/redis-semaphore/src/misc.ts +12 -0
- package/redis-semaphore/src/multiSemaphore/acquire/index.ts +53 -0
- package/redis-semaphore/src/multiSemaphore/acquire/lua.ts +31 -0
- package/redis-semaphore/src/multiSemaphore/refresh/index.ts +32 -0
- package/redis-semaphore/src/multiSemaphore/refresh/lua.ts +31 -0
- package/redis-semaphore/src/multiSemaphore/release/index.ts +22 -0
- package/redis-semaphore/src/multiSemaphore/release/lua.ts +17 -0
- package/redis-semaphore/src/mutex/acquire.ts +42 -0
- package/redis-semaphore/src/mutex/refresh.ts +37 -0
- package/redis-semaphore/src/mutex/release.ts +30 -0
- package/redis-semaphore/src/redlockMultiSemaphore/acquire.ts +56 -0
- package/redis-semaphore/src/redlockMultiSemaphore/refresh.ts +68 -0
- package/redis-semaphore/src/redlockMultiSemaphore/release.ts +19 -0
- package/redis-semaphore/src/redlockMutex/acquire.ts +54 -0
- package/redis-semaphore/src/redlockMutex/refresh.ts +53 -0
- package/redis-semaphore/src/redlockMutex/release.ts +19 -0
- package/redis-semaphore/src/redlockSemaphore/acquire.ts +55 -0
- package/redis-semaphore/src/redlockSemaphore/refresh.ts +60 -0
- package/redis-semaphore/src/redlockSemaphore/release.ts +18 -0
- package/redis-semaphore/src/semaphore/acquire/index.ts +52 -0
- package/redis-semaphore/src/semaphore/acquire/lua.ts +25 -0
- package/redis-semaphore/src/semaphore/refresh/index.ts +31 -0
- package/redis-semaphore/src/semaphore/refresh/lua.ts +25 -0
- package/redis-semaphore/src/semaphore/release.ts +14 -0
- package/redis-semaphore/src/types.ts +63 -0
- package/redis-semaphore/src/utils/createEval.ts +45 -0
- package/redis-semaphore/src/utils/index.ts +13 -0
- package/redis-semaphore/src/utils/redlock.ts +7 -0
- package/redis-semaphore/test/init.test.ts +9 -0
- package/redis-semaphore/test/redisClient.ts +82 -0
- package/redis-semaphore/test/setup.ts +6 -0
- package/redis-semaphore/test/shell.test.ts +15 -0
- package/redis-semaphore/test/shell.ts +48 -0
- package/redis-semaphore/test/src/Lock.test.ts +37 -0
- package/redis-semaphore/test/src/RedisMultiSemaphore.test.ts +425 -0
- package/redis-semaphore/test/src/RedisMutex.test.ts +334 -0
- package/redis-semaphore/test/src/RedisSemaphore.test.ts +367 -0
- package/redis-semaphore/test/src/RedlockMultiSemaphore.test.ts +671 -0
- package/redis-semaphore/test/src/RedlockMutex.test.ts +328 -0
- package/redis-semaphore/test/src/RedlockSemaphore.test.ts +579 -0
- package/redis-semaphore/test/src/index.test.ts +22 -0
- package/redis-semaphore/test/src/multiSemaphore/acquire/index.test.ts +51 -0
- package/redis-semaphore/test/src/multiSemaphore/acquire/internal.test.ts +67 -0
- package/redis-semaphore/test/src/multiSemaphore/refresh/index.test.ts +52 -0
- package/redis-semaphore/test/src/multiSemaphore/release/index.test.ts +18 -0
- package/redis-semaphore/test/src/mutex/acquire.test.ts +78 -0
- package/redis-semaphore/test/src/mutex/refresh.test.ts +22 -0
- package/redis-semaphore/test/src/mutex/release.test.ts +17 -0
- package/redis-semaphore/test/src/redlockMutex/acquire.test.ts +90 -0
- package/redis-semaphore/test/src/redlockMutex/refresh.test.ts +27 -0
- package/redis-semaphore/test/src/redlockMutex/release.test.ts +17 -0
- package/redis-semaphore/test/src/semaphore/acquire/index.test.ts +49 -0
- package/redis-semaphore/test/src/semaphore/acquire/internal.test.ts +65 -0
- package/redis-semaphore/test/src/semaphore/refresh/index.test.ts +44 -0
- package/redis-semaphore/test/src/semaphore/release.test.ts +18 -0
- package/redis-semaphore/test/src/utils/eval.test.ts +22 -0
- package/redis-semaphore/test/src/utils/index.test.ts +19 -0
- package/redis-semaphore/test/src/utils/redlock.test.ts +31 -0
- package/redis-semaphore/test/unhandledRejection.ts +28 -0
- package/redis-semaphore/tsconfig.build-commonjs.json +9 -0
- package/redis-semaphore/tsconfig.build-es.json +9 -0
- package/redis-semaphore/tsconfig.json +11 -0
- package/redis-semaphore/yarn.lock +5338 -0
- package/src/Backing.ts +87 -0
- package/src/DistributedSemaphore.ts +384 -0
- package/src/Errors.ts +3 -15
- package/src/RedisBacking.ts +165 -59
- package/src/index.ts +28 -12
- package/src/DistributedMutex.ts +0 -356
package/src/RedisBacking.ts
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
|
-
import { Duration, Effect, Layer
|
|
1
|
+
import { Duration, Effect, Layer } from "effect";
|
|
2
2
|
import { Redis } from "ioredis";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
DistributedSemaphoreBacking,
|
|
5
|
+
SemaphoreBackingError,
|
|
6
|
+
} from "./Backing.js";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
|
-
* Lua script for atomic
|
|
8
|
-
*
|
|
9
|
+
* Lua script for atomic semaphore acquisition.
|
|
10
|
+
*
|
|
11
|
+
* Uses a sorted set where:
|
|
12
|
+
* - Each member is `holderId_permitIndex` (e.g., "abc123_0", "abc123_1")
|
|
13
|
+
* - Score is the acquisition timestamp
|
|
14
|
+
* - Expired entries are removed before checking capacity
|
|
15
|
+
*
|
|
16
|
+
* Arguments:
|
|
17
|
+
* - KEYS[1]: the semaphore key
|
|
18
|
+
* - ARGV[1]: limit (max permits)
|
|
19
|
+
* - ARGV[2]: permits to acquire
|
|
20
|
+
* - ARGV[3]: holderId
|
|
21
|
+
* - ARGV[4]: ttlMs (lock timeout in ms)
|
|
22
|
+
* - ARGV[5]: now (current timestamp in ms)
|
|
23
|
+
*
|
|
24
|
+
* Returns 1 if acquired, 0 if not enough permits available.
|
|
9
25
|
*/
|
|
10
26
|
const ACQUIRE_SCRIPT = `
|
|
11
27
|
local key = KEYS[1]
|
|
12
|
-
local
|
|
13
|
-
local
|
|
28
|
+
local limit = tonumber(ARGV[1])
|
|
29
|
+
local permits = tonumber(ARGV[2])
|
|
30
|
+
local holderId = ARGV[3]
|
|
31
|
+
local ttlMs = tonumber(ARGV[4])
|
|
32
|
+
local now = tonumber(ARGV[5])
|
|
33
|
+
local expiredTimestamp = now - ttlMs
|
|
14
34
|
|
|
15
|
-
|
|
16
|
-
|
|
35
|
+
-- Remove expired entries
|
|
36
|
+
redis.call('zremrangebyscore', key, '-inf', expiredTimestamp)
|
|
37
|
+
|
|
38
|
+
-- Check if there's room for the requested permits
|
|
39
|
+
if (redis.call('zcard', key) + permits) <= limit then
|
|
40
|
+
-- Add all permits with current timestamp
|
|
41
|
+
local args = {}
|
|
42
|
+
for i = 0, permits - 1 do
|
|
43
|
+
table.insert(args, now)
|
|
44
|
+
table.insert(args, holderId .. '_' .. i)
|
|
45
|
+
end
|
|
46
|
+
redis.call('zadd', key, unpack(args))
|
|
47
|
+
redis.call('pexpire', key, ttlMs)
|
|
17
48
|
return 1
|
|
18
49
|
else
|
|
19
50
|
return 0
|
|
@@ -21,34 +52,68 @@ end
|
|
|
21
52
|
`;
|
|
22
53
|
|
|
23
54
|
/**
|
|
24
|
-
* Lua script for atomic
|
|
25
|
-
*
|
|
55
|
+
* Lua script for atomic release.
|
|
56
|
+
*
|
|
57
|
+
* Removes all permits held by this holder.
|
|
58
|
+
*
|
|
59
|
+
* Arguments:
|
|
60
|
+
* - KEYS[1]: the semaphore key
|
|
61
|
+
* - ARGV[1]: permits to release
|
|
62
|
+
* - ARGV[2]: holderId
|
|
63
|
+
*
|
|
64
|
+
* Returns the number of permits released.
|
|
26
65
|
*/
|
|
27
66
|
const RELEASE_SCRIPT = `
|
|
28
67
|
local key = KEYS[1]
|
|
29
|
-
local
|
|
68
|
+
local permits = tonumber(ARGV[1])
|
|
69
|
+
local holderId = ARGV[2]
|
|
70
|
+
local args = {}
|
|
30
71
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
redis.call('DEL', key)
|
|
34
|
-
return 1
|
|
35
|
-
else
|
|
36
|
-
return 0
|
|
72
|
+
for i = 0, permits - 1 do
|
|
73
|
+
table.insert(args, holderId .. '_' .. i)
|
|
37
74
|
end
|
|
75
|
+
|
|
76
|
+
return redis.call('zrem', key, unpack(args))
|
|
38
77
|
`;
|
|
39
78
|
|
|
40
79
|
/**
|
|
41
80
|
* Lua script for atomic TTL refresh.
|
|
42
|
-
*
|
|
81
|
+
*
|
|
82
|
+
* Updates the timestamp (score) for all permits held by this holder.
|
|
83
|
+
* Returns 0 if the holder doesn't have any permits (lock was lost).
|
|
84
|
+
*
|
|
85
|
+
* Arguments:
|
|
86
|
+
* - KEYS[1]: the semaphore key
|
|
87
|
+
* - ARGV[1]: limit (for consistency, though not strictly needed for refresh)
|
|
88
|
+
* - ARGV[2]: permits
|
|
89
|
+
* - ARGV[3]: holderId
|
|
90
|
+
* - ARGV[4]: ttlMs
|
|
91
|
+
* - ARGV[5]: now
|
|
92
|
+
*
|
|
93
|
+
* Returns 1 if refreshed, 0 if permits were lost.
|
|
43
94
|
*/
|
|
44
95
|
const REFRESH_SCRIPT = `
|
|
45
96
|
local key = KEYS[1]
|
|
46
|
-
local
|
|
47
|
-
local
|
|
97
|
+
local limit = tonumber(ARGV[1])
|
|
98
|
+
local permits = tonumber(ARGV[2])
|
|
99
|
+
local holderId = ARGV[3]
|
|
100
|
+
local ttlMs = tonumber(ARGV[4])
|
|
101
|
+
local now = tonumber(ARGV[5])
|
|
102
|
+
local expiredTimestamp = now - ttlMs
|
|
48
103
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
104
|
+
-- Remove expired entries
|
|
105
|
+
redis.call('zremrangebyscore', key, '-inf', expiredTimestamp)
|
|
106
|
+
|
|
107
|
+
-- Check if we still hold the first permit (indicator that we still own it)
|
|
108
|
+
if redis.call('zscore', key, holderId .. '_0') then
|
|
109
|
+
-- Update all permits with new timestamp
|
|
110
|
+
local args = {}
|
|
111
|
+
for i = 0, permits - 1 do
|
|
112
|
+
table.insert(args, now)
|
|
113
|
+
table.insert(args, holderId .. '_' .. i)
|
|
114
|
+
end
|
|
115
|
+
redis.call('zadd', key, unpack(args))
|
|
116
|
+
redis.call('pexpire', key, ttlMs)
|
|
52
117
|
return 1
|
|
53
118
|
else
|
|
54
119
|
return 0
|
|
@@ -56,99 +121,140 @@ end
|
|
|
56
121
|
`;
|
|
57
122
|
|
|
58
123
|
/**
|
|
59
|
-
*
|
|
124
|
+
* Lua script to get the current count of active permits.
|
|
125
|
+
*
|
|
126
|
+
* Arguments:
|
|
127
|
+
* - KEYS[1]: the semaphore key
|
|
128
|
+
* - ARGV[1]: ttlMs
|
|
129
|
+
* - ARGV[2]: now
|
|
130
|
+
*
|
|
131
|
+
* Returns the number of active (non-expired) permits.
|
|
132
|
+
*/
|
|
133
|
+
const GET_COUNT_SCRIPT = `
|
|
134
|
+
local key = KEYS[1]
|
|
135
|
+
local ttlMs = tonumber(ARGV[1])
|
|
136
|
+
local now = tonumber(ARGV[2])
|
|
137
|
+
local expiredTimestamp = now - ttlMs
|
|
138
|
+
|
|
139
|
+
-- Remove expired entries
|
|
140
|
+
redis.call('zremrangebyscore', key, '-inf', expiredTimestamp)
|
|
141
|
+
|
|
142
|
+
return redis.call('zcard', key)
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create a Redis-backed distributed semaphore backing layer.
|
|
60
147
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
148
|
+
* **Important:** This implementation is for single-instance Redis only.
|
|
149
|
+
* It does not implement the Redlock algorithm and should not be used with
|
|
150
|
+
* Redis Cluster or Redis Sentinel for distributed locking guarantees.
|
|
151
|
+
* For multi-instance Redis, consider implementing a Redlock-based backing.
|
|
152
|
+
*
|
|
153
|
+
* @param redis - An ioredis client instance (single instance, not cluster)
|
|
154
|
+
* @param keyPrefix - Optional prefix for all keys (default: "dsem:")
|
|
63
155
|
*/
|
|
64
156
|
export const layer = (
|
|
65
157
|
redis: Redis,
|
|
66
|
-
keyPrefix = "
|
|
67
|
-
): Layer.Layer<
|
|
158
|
+
keyPrefix = "dsem:"
|
|
159
|
+
): Layer.Layer<DistributedSemaphoreBacking> => {
|
|
68
160
|
const prefixKey = (key: string) => `${keyPrefix}${key}`;
|
|
69
161
|
|
|
70
162
|
const tryAcquire = (
|
|
71
163
|
key: string,
|
|
72
164
|
holderId: string,
|
|
73
|
-
ttl: Duration.Duration
|
|
74
|
-
|
|
165
|
+
ttl: Duration.Duration,
|
|
166
|
+
limit: number,
|
|
167
|
+
permits: number
|
|
168
|
+
): Effect.Effect<boolean, SemaphoreBackingError> =>
|
|
75
169
|
Effect.tryPromise({
|
|
76
170
|
try: async () => {
|
|
171
|
+
const now = Date.now();
|
|
77
172
|
const result = await redis.eval(
|
|
78
173
|
ACQUIRE_SCRIPT,
|
|
79
174
|
1,
|
|
80
175
|
prefixKey(key),
|
|
176
|
+
limit.toString(),
|
|
177
|
+
permits.toString(),
|
|
81
178
|
holderId,
|
|
82
|
-
Duration.toMillis(ttl).toString()
|
|
179
|
+
Duration.toMillis(ttl).toString(),
|
|
180
|
+
now.toString()
|
|
83
181
|
);
|
|
84
182
|
return result === 1;
|
|
85
183
|
},
|
|
86
184
|
catch: (cause) =>
|
|
87
|
-
new
|
|
185
|
+
new SemaphoreBackingError({ operation: "tryAcquire", cause }),
|
|
88
186
|
});
|
|
89
187
|
|
|
90
188
|
const release = (
|
|
91
189
|
key: string,
|
|
92
|
-
holderId: string
|
|
93
|
-
|
|
190
|
+
holderId: string,
|
|
191
|
+
permits: number
|
|
192
|
+
): Effect.Effect<number, SemaphoreBackingError> =>
|
|
94
193
|
Effect.tryPromise({
|
|
95
194
|
try: async () => {
|
|
96
195
|
const result = await redis.eval(
|
|
97
196
|
RELEASE_SCRIPT,
|
|
98
197
|
1,
|
|
99
198
|
prefixKey(key),
|
|
199
|
+
permits.toString(),
|
|
100
200
|
holderId
|
|
101
201
|
);
|
|
102
|
-
return result
|
|
202
|
+
return result as number;
|
|
103
203
|
},
|
|
104
|
-
catch: (cause) =>
|
|
204
|
+
catch: (cause) =>
|
|
205
|
+
new SemaphoreBackingError({ operation: "release", cause }),
|
|
105
206
|
});
|
|
106
207
|
|
|
107
208
|
const refresh = (
|
|
108
209
|
key: string,
|
|
109
210
|
holderId: string,
|
|
110
|
-
ttl: Duration.Duration
|
|
111
|
-
|
|
211
|
+
ttl: Duration.Duration,
|
|
212
|
+
limit: number,
|
|
213
|
+
permits: number
|
|
214
|
+
): Effect.Effect<boolean, SemaphoreBackingError> =>
|
|
112
215
|
Effect.tryPromise({
|
|
113
216
|
try: async () => {
|
|
217
|
+
const now = Date.now();
|
|
114
218
|
const result = await redis.eval(
|
|
115
219
|
REFRESH_SCRIPT,
|
|
116
220
|
1,
|
|
117
221
|
prefixKey(key),
|
|
222
|
+
limit.toString(),
|
|
223
|
+
permits.toString(),
|
|
118
224
|
holderId,
|
|
119
|
-
Duration.toMillis(ttl).toString()
|
|
225
|
+
Duration.toMillis(ttl).toString(),
|
|
226
|
+
now.toString()
|
|
120
227
|
);
|
|
121
228
|
return result === 1;
|
|
122
229
|
},
|
|
123
|
-
catch: (cause) =>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const isLocked = (key: string): Effect.Effect<boolean, MutexBackingError> =>
|
|
127
|
-
Effect.tryPromise({
|
|
128
|
-
try: async () => {
|
|
129
|
-
const exists = await redis.exists(prefixKey(key));
|
|
130
|
-
return exists === 1;
|
|
131
|
-
},
|
|
132
|
-
catch: (cause) => new MutexBackingError({ operation: "isLocked", cause }),
|
|
230
|
+
catch: (cause) =>
|
|
231
|
+
new SemaphoreBackingError({ operation: "refresh", cause }),
|
|
133
232
|
});
|
|
134
233
|
|
|
135
|
-
const
|
|
136
|
-
key: string
|
|
137
|
-
|
|
234
|
+
const getCount = (
|
|
235
|
+
key: string,
|
|
236
|
+
ttl: Duration.Duration
|
|
237
|
+
): Effect.Effect<number, SemaphoreBackingError> =>
|
|
138
238
|
Effect.tryPromise({
|
|
139
239
|
try: async () => {
|
|
140
|
-
const
|
|
141
|
-
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
const result = await redis.eval(
|
|
242
|
+
GET_COUNT_SCRIPT,
|
|
243
|
+
1,
|
|
244
|
+
prefixKey(key),
|
|
245
|
+
Duration.toMillis(ttl).toString(),
|
|
246
|
+
now.toString()
|
|
247
|
+
);
|
|
248
|
+
return result as number;
|
|
142
249
|
},
|
|
143
250
|
catch: (cause) =>
|
|
144
|
-
new
|
|
251
|
+
new SemaphoreBackingError({ operation: "getCount", cause }),
|
|
145
252
|
});
|
|
146
253
|
|
|
147
|
-
return Layer.succeed(
|
|
254
|
+
return Layer.succeed(DistributedSemaphoreBacking, {
|
|
148
255
|
tryAcquire,
|
|
149
256
|
release,
|
|
150
257
|
refresh,
|
|
151
|
-
|
|
152
|
-
getHolder,
|
|
258
|
+
getCount,
|
|
153
259
|
});
|
|
154
260
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Effect Distributed
|
|
2
|
+
* Effect Distributed Semaphore
|
|
3
3
|
*
|
|
4
|
-
* A distributed
|
|
4
|
+
* A distributed semaphore library for Effect with pluggable backends.
|
|
5
|
+
* Implements a multi-semaphore that can be used to implement:
|
|
6
|
+
* - Mutex (limit=1, permits=1)
|
|
7
|
+
* - Semaphore (limit=N, permits=1)
|
|
8
|
+
* - Multi-semaphore (limit=N, permits=M)
|
|
5
9
|
*
|
|
6
10
|
* @example
|
|
7
11
|
* ```ts
|
|
8
|
-
* import {
|
|
12
|
+
* import { DistributedSemaphore, RedisBacking } from "effect-distributed-lock";
|
|
9
13
|
* import { Effect } from "effect";
|
|
10
14
|
* import Redis from "ioredis";
|
|
11
15
|
*
|
|
12
16
|
* const redis = new Redis(process.env.REDIS_URL);
|
|
13
17
|
*
|
|
14
18
|
* const program = Effect.gen(function* () {
|
|
15
|
-
* // Create a
|
|
16
|
-
* const
|
|
19
|
+
* // Create a semaphore that allows 5 concurrent operations
|
|
20
|
+
* const sem = yield* DistributedSemaphore.make("my-resource", {
|
|
21
|
+
* limit: 5,
|
|
17
22
|
* ttl: "30 seconds",
|
|
18
|
-
* acquireTimeout: "10 seconds",
|
|
19
23
|
* });
|
|
20
24
|
*
|
|
21
|
-
* //
|
|
22
|
-
* yield*
|
|
25
|
+
* // Acquire 2 permits, run effect, release when done
|
|
26
|
+
* yield* sem.withPermits(2)(
|
|
23
27
|
* Effect.gen(function* () {
|
|
24
|
-
* //
|
|
25
|
-
* yield*
|
|
28
|
+
* // Only 2 of the 5 slots are used
|
|
29
|
+
* yield* doSomethingLimited();
|
|
26
30
|
* })
|
|
27
31
|
* );
|
|
32
|
+
*
|
|
33
|
+
* // For mutex behavior, use limit=1 and withPermits(1)
|
|
34
|
+
* const mutex = yield* DistributedSemaphore.make("my-lock", { limit: 1 });
|
|
35
|
+
* yield* mutex.withPermits(1)(criticalSection);
|
|
28
36
|
* });
|
|
29
37
|
*
|
|
30
38
|
* program.pipe(
|
|
@@ -36,10 +44,18 @@
|
|
|
36
44
|
* @module
|
|
37
45
|
*/
|
|
38
46
|
|
|
47
|
+
// Backing interface
|
|
48
|
+
export * as Backing from "./Backing.js";
|
|
49
|
+
export {
|
|
50
|
+
DistributedSemaphoreBacking,
|
|
51
|
+
SemaphoreBackingError,
|
|
52
|
+
} from "./Backing.js";
|
|
53
|
+
|
|
39
54
|
// Core module (namespace with types and functions)
|
|
40
|
-
export * as
|
|
55
|
+
export * as DistributedSemaphore from "./DistributedSemaphore.js";
|
|
56
|
+
|
|
41
57
|
// Errors
|
|
42
|
-
export {
|
|
58
|
+
export { LockLostError } from "./Errors.js";
|
|
43
59
|
|
|
44
60
|
// Redis backing
|
|
45
61
|
export * as RedisBacking from "./RedisBacking.js";
|