effect-distributed-lock 0.0.6 → 0.0.7

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.
Files changed (95) hide show
  1. package/examples/concurrent.ts +111 -0
  2. package/package.json +1 -1
  3. package/src/DistributedSemaphore.ts +1 -0
  4. package/src/RedisBacking.ts +2 -2
  5. package/redis-semaphore/.codeclimate.yml +0 -5
  6. package/redis-semaphore/.fossa.yml +0 -14
  7. package/redis-semaphore/.github/dependabot.yml +0 -6
  8. package/redis-semaphore/.github/workflows/branches.yml +0 -39
  9. package/redis-semaphore/.github/workflows/pull-requests.yml +0 -35
  10. package/redis-semaphore/.mocharc.yaml +0 -6
  11. package/redis-semaphore/.prettierrc +0 -6
  12. package/redis-semaphore/.snyk +0 -4
  13. package/redis-semaphore/.yarnrc.yml +0 -2
  14. package/redis-semaphore/CHANGELOG.md +0 -70
  15. package/redis-semaphore/Dockerfile +0 -5
  16. package/redis-semaphore/LICENSE +0 -21
  17. package/redis-semaphore/README.md +0 -445
  18. package/redis-semaphore/docker-compose.yml +0 -31
  19. package/redis-semaphore/eslint.config.mjs +0 -73
  20. package/redis-semaphore/package.json +0 -79
  21. package/redis-semaphore/setup-redis-servers.sh +0 -2
  22. package/redis-semaphore/src/Lock.ts +0 -172
  23. package/redis-semaphore/src/RedisMultiSemaphore.ts +0 -56
  24. package/redis-semaphore/src/RedisMutex.ts +0 -45
  25. package/redis-semaphore/src/RedisSemaphore.ts +0 -49
  26. package/redis-semaphore/src/RedlockMultiSemaphore.ts +0 -56
  27. package/redis-semaphore/src/RedlockMutex.ts +0 -52
  28. package/redis-semaphore/src/RedlockSemaphore.ts +0 -49
  29. package/redis-semaphore/src/errors/LostLockError.ts +0 -1
  30. package/redis-semaphore/src/errors/TimeoutError.ts +0 -1
  31. package/redis-semaphore/src/index.ts +0 -23
  32. package/redis-semaphore/src/misc.ts +0 -12
  33. package/redis-semaphore/src/multiSemaphore/acquire/index.ts +0 -53
  34. package/redis-semaphore/src/multiSemaphore/acquire/lua.ts +0 -31
  35. package/redis-semaphore/src/multiSemaphore/refresh/index.ts +0 -32
  36. package/redis-semaphore/src/multiSemaphore/refresh/lua.ts +0 -31
  37. package/redis-semaphore/src/multiSemaphore/release/index.ts +0 -22
  38. package/redis-semaphore/src/multiSemaphore/release/lua.ts +0 -17
  39. package/redis-semaphore/src/mutex/acquire.ts +0 -42
  40. package/redis-semaphore/src/mutex/refresh.ts +0 -37
  41. package/redis-semaphore/src/mutex/release.ts +0 -30
  42. package/redis-semaphore/src/redlockMultiSemaphore/acquire.ts +0 -56
  43. package/redis-semaphore/src/redlockMultiSemaphore/refresh.ts +0 -68
  44. package/redis-semaphore/src/redlockMultiSemaphore/release.ts +0 -19
  45. package/redis-semaphore/src/redlockMutex/acquire.ts +0 -54
  46. package/redis-semaphore/src/redlockMutex/refresh.ts +0 -53
  47. package/redis-semaphore/src/redlockMutex/release.ts +0 -19
  48. package/redis-semaphore/src/redlockSemaphore/acquire.ts +0 -55
  49. package/redis-semaphore/src/redlockSemaphore/refresh.ts +0 -60
  50. package/redis-semaphore/src/redlockSemaphore/release.ts +0 -18
  51. package/redis-semaphore/src/semaphore/acquire/index.ts +0 -52
  52. package/redis-semaphore/src/semaphore/acquire/lua.ts +0 -25
  53. package/redis-semaphore/src/semaphore/refresh/index.ts +0 -31
  54. package/redis-semaphore/src/semaphore/refresh/lua.ts +0 -25
  55. package/redis-semaphore/src/semaphore/release.ts +0 -14
  56. package/redis-semaphore/src/types.ts +0 -63
  57. package/redis-semaphore/src/utils/createEval.ts +0 -45
  58. package/redis-semaphore/src/utils/index.ts +0 -13
  59. package/redis-semaphore/src/utils/redlock.ts +0 -7
  60. package/redis-semaphore/test/init.test.ts +0 -9
  61. package/redis-semaphore/test/redisClient.ts +0 -82
  62. package/redis-semaphore/test/setup.ts +0 -6
  63. package/redis-semaphore/test/shell.test.ts +0 -15
  64. package/redis-semaphore/test/shell.ts +0 -48
  65. package/redis-semaphore/test/src/Lock.test.ts +0 -37
  66. package/redis-semaphore/test/src/RedisMultiSemaphore.test.ts +0 -425
  67. package/redis-semaphore/test/src/RedisMutex.test.ts +0 -334
  68. package/redis-semaphore/test/src/RedisSemaphore.test.ts +0 -367
  69. package/redis-semaphore/test/src/RedlockMultiSemaphore.test.ts +0 -671
  70. package/redis-semaphore/test/src/RedlockMutex.test.ts +0 -328
  71. package/redis-semaphore/test/src/RedlockSemaphore.test.ts +0 -579
  72. package/redis-semaphore/test/src/index.test.ts +0 -22
  73. package/redis-semaphore/test/src/multiSemaphore/acquire/index.test.ts +0 -51
  74. package/redis-semaphore/test/src/multiSemaphore/acquire/internal.test.ts +0 -67
  75. package/redis-semaphore/test/src/multiSemaphore/refresh/index.test.ts +0 -52
  76. package/redis-semaphore/test/src/multiSemaphore/release/index.test.ts +0 -18
  77. package/redis-semaphore/test/src/mutex/acquire.test.ts +0 -78
  78. package/redis-semaphore/test/src/mutex/refresh.test.ts +0 -22
  79. package/redis-semaphore/test/src/mutex/release.test.ts +0 -17
  80. package/redis-semaphore/test/src/redlockMutex/acquire.test.ts +0 -90
  81. package/redis-semaphore/test/src/redlockMutex/refresh.test.ts +0 -27
  82. package/redis-semaphore/test/src/redlockMutex/release.test.ts +0 -17
  83. package/redis-semaphore/test/src/semaphore/acquire/index.test.ts +0 -49
  84. package/redis-semaphore/test/src/semaphore/acquire/internal.test.ts +0 -65
  85. package/redis-semaphore/test/src/semaphore/refresh/index.test.ts +0 -44
  86. package/redis-semaphore/test/src/semaphore/release.test.ts +0 -18
  87. package/redis-semaphore/test/src/utils/eval.test.ts +0 -22
  88. package/redis-semaphore/test/src/utils/index.test.ts +0 -19
  89. package/redis-semaphore/test/src/utils/redlock.test.ts +0 -31
  90. package/redis-semaphore/test/unhandledRejection.ts +0 -28
  91. package/redis-semaphore/tsconfig.build-commonjs.json +0 -9
  92. package/redis-semaphore/tsconfig.build-es.json +0 -9
  93. package/redis-semaphore/tsconfig.json +0 -11
  94. package/redis-semaphore/yarn.lock +0 -5338
  95. /package/examples/{index.ts → kitchen-sink.ts} +0 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Demonstrates concurrent effects competing for a distributed lock.
3
+ *
4
+ * This example runs two scenarios:
5
+ * 1. With push-based acquisition DISABLED (polling only)
6
+ * 2. With push-based acquisition ENABLED (pub/sub notifications)
7
+ *
8
+ * You'll see how push-based acquisition is faster because waiters are
9
+ * notified immediately when permits are released, rather than polling.
10
+ *
11
+ * Run with: bun run examples/concurrent.ts
12
+ * Requires REDIS_URL environment variable or local Redis at localhost:6379.
13
+ */
14
+ import { Console, Duration, Effect, Schedule } from "effect";
15
+ import Redis from "ioredis";
16
+ import { DistributedSemaphore, RedisBacking } from "../src/index.ts";
17
+
18
+ const redis = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379");
19
+
20
+ // Helper to create a task that competes for the lock
21
+ const makeTask = (
22
+ id: number,
23
+ mutex: DistributedSemaphore.DistributedSemaphore
24
+ ) =>
25
+ Effect.gen(function* () {
26
+ yield* Console.log(`[Task ${id}] Starting, waiting for lock...`);
27
+ const startWait = Date.now();
28
+
29
+ yield* mutex.withPermits(1)(
30
+ Effect.gen(function* () {
31
+ const waitTime = Date.now() - startWait;
32
+ yield* Console.log(
33
+ `[Task ${id}] 🔒 Lock acquired! (waited ${waitTime}ms)`
34
+ );
35
+
36
+ // Simulate some work
37
+ yield* Effect.sleep(Duration.millis(200));
38
+
39
+ yield* Console.log(`[Task ${id}] 🔓 Releasing lock...`);
40
+ })
41
+ );
42
+
43
+ yield* Console.log(`[Task ${id}] Done`);
44
+ });
45
+
46
+ // Run a scenario with the given configuration
47
+ const runScenario = (name: string, pushEnabled: boolean) =>
48
+ Effect.gen(function* () {
49
+ yield* Console.log(`\n${"=".repeat(60)}`);
50
+ yield* Console.log(`${name}`);
51
+ yield* Console.log(`Push-based acquisition: ${pushEnabled ? "ON" : "OFF"}`);
52
+ yield* Console.log(`${"=".repeat(60)}\n`);
53
+
54
+ const startTime = Date.now();
55
+
56
+ // Create mutex with a unique key per scenario to avoid interference
57
+ const mutex = yield* DistributedSemaphore.make(
58
+ `concurrent-example-${pushEnabled ? "push" : "poll"}`,
59
+ {
60
+ acquireRetryPolicy: Schedule.spaced(Duration.millis(500)).pipe(
61
+ Schedule.asVoid
62
+ ),
63
+ limit: 1, // Mutex - only one holder at a time
64
+ }
65
+ );
66
+
67
+ // Run 3 tasks concurrently, all competing for the same lock
68
+ yield* Effect.all(
69
+ [makeTask(1, mutex), makeTask(2, mutex), makeTask(3, mutex)],
70
+ { concurrency: 3 }
71
+ );
72
+
73
+ const totalTime = Date.now() - startTime;
74
+ yield* Console.log(`\n⏱️ Total time: ${totalTime}ms\n`);
75
+ });
76
+
77
+ // Run both scenarios
78
+ const main = Effect.gen(function* () {
79
+ yield* Console.log("🚀 Distributed Lock Concurrency Demo");
80
+ yield* Console.log(
81
+ "Showing 3 concurrent tasks competing for a mutex (limit=1)"
82
+ );
83
+
84
+ // Run WITHOUT push (polling only)
85
+ const RedisLayerNoPush = RedisBacking.layer(redis, {
86
+ keyPrefix: "concurrent-demo:",
87
+ pushBasedAcquireEnabled: false,
88
+ });
89
+ yield* runScenario("Scenario 1: Polling Only", false).pipe(
90
+ Effect.provide(RedisLayerNoPush)
91
+ );
92
+
93
+ // Run WITH push (pub/sub notifications)
94
+ const RedisLayerWithPush = RedisBacking.layer(redis, {
95
+ keyPrefix: "concurrent-demo:",
96
+ pushBasedAcquireEnabled: true,
97
+ });
98
+ yield* runScenario("Scenario 2: Push-Based (Pub/Sub)", true).pipe(
99
+ Effect.provide(RedisLayerWithPush)
100
+ );
101
+
102
+ yield* Console.log("✅ Demo complete!");
103
+ yield* Console.log(
104
+ "Notice how push-based acquisition completes faster because"
105
+ );
106
+ yield* Console.log(
107
+ "waiters are notified immediately instead of waiting for the next poll.\n"
108
+ );
109
+ }).pipe(Effect.ensuring(Effect.promise(() => redis.quit())));
110
+
111
+ Effect.runPromise(main).catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-distributed-lock",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "A distributed semaphore library for Effect with pluggable backends",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -445,6 +445,7 @@ export const make = (
445
445
  })
446
446
  : Effect.never;
447
447
 
448
+ // first to succeed (acquire permits) wins
448
449
  return yield* Effect.race(pollBasedAcquire, pushBasedAcquire);
449
450
  });
450
451
 
@@ -325,9 +325,9 @@ export const layer = (
325
325
  return { subscriber, messageHandler };
326
326
  }),
327
327
  ({ subscriber, messageHandler }) =>
328
- Effect.sync(() => {
328
+ Effect.promise(async () => {
329
329
  subscriber.off("message", messageHandler);
330
- subscriber.unsubscribe(channel);
330
+ await subscriber.unsubscribe(channel);
331
331
  subscriber.disconnect();
332
332
  })
333
333
  );
@@ -1,5 +0,0 @@
1
- version: '2'
2
- plugins:
3
- eslint:
4
- enabled: true
5
- channel: 'eslint-6'
@@ -1,14 +0,0 @@
1
- # Generated by FOSSA CLI (https://github.com/fossas/fossa-cli)
2
- # Visit https://fossa.com to learn more
3
-
4
- version: 2
5
- cli:
6
- server: https://app.fossa.com
7
- fetcher: custom
8
- project: git@github.com:swarthy/redis-semaphore.git
9
- analyze:
10
- modules:
11
- - name: .
12
- type: npm
13
- target: .
14
- path: .
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'npm'
4
- directory: '/'
5
- schedule:
6
- interval: 'monthly'
@@ -1,39 +0,0 @@
1
- name: CI (push)
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- workflow_dispatch:
8
-
9
- jobs:
10
- integration-test:
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [18.x, 20.x, 22.x]
16
-
17
- env:
18
- COVERALLS_REPO_TOKEN: '${{ secrets.COVERALLS_REPO_TOKEN }}'
19
- COVERALLS_GIT_BRANCH: '${{ github.ref }}'
20
-
21
- steps:
22
- - uses: actions/checkout@v4
23
- - name: Enable Corepack
24
- run: corepack enable
25
-
26
- - name: Use Node.js ${{ matrix.node-version }}
27
- uses: actions/setup-node@v4
28
- with:
29
- node-version: ${{ matrix.node-version }}
30
- cache: 'yarn'
31
-
32
- - run: yarn install --immutable
33
-
34
- - run: docker compose up -d redis1 redis2 redis3
35
- - run: docker compose run waiter
36
-
37
- - run: yarn build
38
- - run: yarn lint
39
- - run: yarn test-ci-with-coverage
@@ -1,35 +0,0 @@
1
- name: CI (PR)
2
-
3
- on:
4
- pull_request:
5
- branches:
6
- - master
7
- workflow_dispatch:
8
-
9
- jobs:
10
- integration-test:
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [18.x, 20.x, 22.x]
16
-
17
- steps:
18
- - uses: actions/checkout@v4
19
- - name: Enable Corepack
20
- run: corepack enable
21
-
22
- - name: Use Node.js ${{ matrix.node-version }}
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: ${{ matrix.node-version }}
26
- cache: 'yarn'
27
-
28
- - run: yarn install --immutable
29
-
30
- - run: docker compose up -d redis1 redis2 redis3
31
- - run: docker compose run waiter
32
-
33
- - run: yarn build
34
- - run: yarn lint
35
- - run: yarn test
@@ -1,6 +0,0 @@
1
- extension: ts
2
- recursive: true
3
- timeout: 5s
4
- require:
5
- - '@swc-node/register'
6
- - test/setup.ts
@@ -1,6 +0,0 @@
1
- {
2
- "semi": false,
3
- "singleQuote": true,
4
- "trailingComma": "none",
5
- "arrowParens": "avoid"
6
- }
@@ -1,4 +0,0 @@
1
- # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
2
- version: v1.13.1
3
- ignore: {}
4
- patch: {}
@@ -1,2 +0,0 @@
1
- nodeLinker: node-modules
2
- defaultSemverRangePrefix: ''
@@ -1,70 +0,0 @@
1
- ### redis-semaphore@5.6.2
2
- - Fixed implicit import from `src`
3
- - Removed `src` folder from NPM package
4
-
5
- ### redis-semaphore@5.6.1
6
- - Removed `module` field from `package.json`
7
-
8
- ### redis-semaphore@5.6.0
9
- - Added interface compatible client support (ex. `ioredis-mock`)
10
- - Removed `instanceof Redis` validation in constructor
11
- - `ioredis` marked as optional peerDependency, explicit `ioredis` install is required now
12
-
13
- ### redis-semaphore@5.5.1
14
- - Fix race condition for refresh started before release and finished after release
15
-
16
- ### redis-semaphore@5.5.0
17
-
18
- - Added `identifier` constructor option.
19
- - Added `acquiredExternally` constructor option.
20
- - Option `externallyAcquiredIdentifier` **DEPRECATED**.
21
- - Option `identifierSuffix` **DEPRECATED**.
22
-
23
- ### redis-semaphore@5.4.0
24
-
25
- - Added `identifierSuffix` option, usefull for tracing app instance which locked resource
26
-
27
- ### redis-semaphore@5.3.1
28
-
29
- - Fixed reacquire expired resource in refresh
30
-
31
- ### redis-semaphore@5.3.0
32
-
33
- - Added `stopRefresh` method
34
- - Added `externallyAcquiredIdentifier` optional constructor option
35
- - Removed `uuid` dependency
36
-
37
- ### redis-semaphore@5.2.0
38
-
39
- - Added `acquireAttemptsLimit` method
40
-
41
- ### redis-semaphore@5.1.0
42
-
43
- - Added `tryAcquire`
44
-
45
- ### redis-semaphore@5.0.0
46
-
47
- - **Breadking change:** Drop Node.js v10.x, v12.x support
48
- - Added `ioredis@5` support
49
-
50
- ### redis-semaphore@4.1.0
51
-
52
- - Added `.isAcquired` property on all locks
53
- - Added `onLostLock` constructor option. By default throws unhandled error.
54
-
55
- ### redis-semaphore@4.0.0
56
-
57
- - **Breaking change:** `Mutex`, `Semaphore`, `MultiSemaphore` not longer support `Cluster`. For multi-node case use `Redlock*` instead.
58
- - Added `RedlockMutex`, `RedlockSemaphore`, `RedlockMultiSemaphore`
59
- - Internals refactored
60
-
61
- ### redis-semaphore@3.2.0
62
-
63
- - Added `MultiSemaphore`
64
-
65
- ### redis-semaphore@3.0.0
66
-
67
- - **Breaking change:** `FairSemaphore` has been removed. Use `Semaphore` instead (has the same "fairness")
68
- - the `acquire` method in `Semaphore` no longer returns a boolean. Instead, it throws an error if it cannot acquire, and if it doesn't throw, you can assume it worked.
69
- - Internal code has been cleaned up
70
- - Added more test, include synthetic node unsynchroned clocks
@@ -1,5 +0,0 @@
1
- FROM node:alpine
2
- RUN npm i -g @swarthy/wait-for@2.0.2
3
- VOLUME /app
4
- WORKDIR /app
5
- USER node
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2018 Alexander Mochalin
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.