universal-lock 0.0.2 → 1.0.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/README.md CHANGED
@@ -1 +1,171 @@
1
- # UniversalLock
1
+ # universal-lock
2
+
3
+ Lightweight, isomorphic universal locking library with pluggable backends. Works in Node.js and browsers.
4
+
5
+ Part of the [`universal-lock`](https://github.com/lucasrainett/universal-lock) monorepo.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install universal-lock
11
+ ```
12
+
13
+ You also need a backend package:
14
+
15
+ ```bash
16
+ npm install @universal-lock/memory # single-process
17
+ npm install @universal-lock/redis # distributed (cross-process/server)
18
+ npm install @universal-lock/web-locks # browser (cross-tab, modern)
19
+ npm install @universal-lock/local-storage # browser (cross-tab, older)
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### ESM
25
+
26
+ ```typescript
27
+ import { lockFactory } from "universal-lock";
28
+ import { createBackend } from "@universal-lock/memory";
29
+
30
+ const lock = lockFactory(createBackend());
31
+
32
+ const release = await lock.acquire("my-resource");
33
+ try {
34
+ // critical section
35
+ } finally {
36
+ await release();
37
+ }
38
+ ```
39
+
40
+ ### CommonJS
41
+
42
+ ```javascript
43
+ const { lockFactory } = require("universal-lock");
44
+ const { createBackend } = require("@universal-lock/memory");
45
+
46
+ const lock = lockFactory(createBackend());
47
+ ```
48
+
49
+ ### Browser (IIFE)
50
+
51
+ ```html
52
+ <script src="https://unpkg.com/@universal-lock/memory/dist/index.global.js"></script>
53
+ <script src="https://unpkg.com/universal-lock/dist/index.global.js"></script>
54
+ <script>
55
+ const lock = UniversalLock.lockFactory(UniversalLockMemory.createBackend());
56
+ </script>
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### `lockFactory(backend, config?)`
62
+
63
+ Creates a lock instance with the given backend and optional configuration. Returns a `Lock` object.
64
+
65
+ ```typescript
66
+ const lock = lockFactory(backend, {
67
+ acquireInterval: 250, // retry interval in ms (default: 250)
68
+ acquireFailTimeout: 5000, // max wait before failing acquisition (default: 5000)
69
+ stale: 1000, // ignore locks older than this in ms (default: 1000)
70
+ renewInterval: 250, // lock renewal interval in ms (default: 250)
71
+ maxHoldTime: 2000, // auto-release after this duration in ms (default: 2000)
72
+ onLockLost: (name, reason) => {}, // called when lock is lost (optional)
73
+ onEvent: (event) => {}, // lifecycle events (optional)
74
+ });
75
+ ```
76
+
77
+ ### `lock.acquire(lockName)`
78
+
79
+ Acquires a named lock. Returns a `release` function with a `.signal: AbortSignal` property. Rejects if the lock cannot be acquired within `acquireFailTimeout`.
80
+
81
+ ### `lockDecoratorFactory(lock)`
82
+
83
+ Creates a decorator that wraps async functions with automatic lock acquire/release.
84
+
85
+ The first argument is either a lock name string or an options object. When a string is passed, the wrapped function keeps its original signature. Pass `{ lockName, signal: true }` to inject an `AbortSignal` as the first argument so the function can react to lock loss.
86
+
87
+ ```typescript
88
+ import { lockFactory, lockDecoratorFactory } from "universal-lock";
89
+ import { createBackend } from "@universal-lock/memory";
90
+
91
+ const lock = lockFactory(createBackend());
92
+ const withLock = lockDecoratorFactory(lock);
93
+
94
+ // Simple usage — no signal injection
95
+ const processOrder = withLock("orders", async (orderId: string) => {
96
+ return await handleOrder(orderId);
97
+ });
98
+
99
+ await processOrder("order-123");
100
+
101
+ // With signal injection for lock loss detection
102
+ const processOrderSafe = withLock({ lockName: "orders", signal: true }, async (signal: AbortSignal, orderId: string) => {
103
+ if (signal.aborted) return;
104
+ return await handleOrder(orderId);
105
+ });
106
+
107
+ await processOrderSafe("order-123");
108
+ ```
109
+
110
+ ## Lock Loss Detection
111
+
112
+ ### AbortSignal
113
+
114
+ Every `release` function has a `.signal` property that is aborted when the lock is lost:
115
+
116
+ ```typescript
117
+ const release = await lock.acquire("my-resource");
118
+
119
+ release.signal.addEventListener("abort", () => {
120
+ console.log("Lock lost! Stop critical work.");
121
+ });
122
+
123
+ await release();
124
+ ```
125
+
126
+ ### onLockLost callback
127
+
128
+ ```typescript
129
+ const lock = lockFactory(backend, {
130
+ onLockLost: (lockName, reason) => {
131
+ // reason: "renewFailed" | "timeout"
132
+ console.error(`Lock "${lockName}" lost: ${reason}`);
133
+ },
134
+ });
135
+ ```
136
+
137
+ ### Lifecycle events
138
+
139
+ ```typescript
140
+ const lock = lockFactory(backend, {
141
+ onEvent: (event) => {
142
+ // event.type: "acquired" | "renewed" | "renewFailed" | "lockLost" | "released" | "acquireTimeout"
143
+ console.log(event.type, event.lockName);
144
+ },
145
+ });
146
+ ```
147
+
148
+ ## Custom Backends
149
+
150
+ Implement the `Backend` interface to use any storage:
151
+
152
+ ```typescript
153
+ import type { Backend } from "universal-lock";
154
+
155
+ const myBackend: Backend = {
156
+ setup: async () => {},
157
+ acquire: async (lockName, stale, lockId) => {
158
+ // set lock or throw if already held
159
+ },
160
+ renew: async (lockName, lockId) => {
161
+ // extend lock TTL, verify ownership via lockId
162
+ },
163
+ release: async (lockName, lockId) => {
164
+ // delete lock, verify ownership via lockId
165
+ },
166
+ };
167
+ ```
168
+
169
+ ## License
170
+
171
+ [MIT](https://github.com/lucasrainett/universal-lock/blob/master/LICENSE)
@@ -0,0 +1,26 @@
1
+ import { Lock, Backend, LockConfiguration } from '@universal-lock/types';
2
+ export { AsyncFunction, Backend, BackendAcquireFunction, BackendFactory, BackendReleaseFunction, BackendRenewFunction, BackendSetupFunction, CallbackLockEntry, Lock, LockAcquireFunction, LockConfiguration, LockEvent, LockReleaseFunction, TimestampLockEntry } from '@universal-lock/types';
3
+
4
+ declare function lockFactory({ setup, acquire, renew, release }: Backend, configuration?: Partial<LockConfiguration>): Lock;
5
+ /**
6
+ * Creates a decorator that wraps an async function with automatic lock
7
+ * acquisition and release. The lock is always released in the finally
8
+ * block, even if fn throws.
9
+ *
10
+ * First argument is either a lock name string or an options object.
11
+ * Pass `{ lockName, signal: true }` to inject an AbortSignal as the
12
+ * first argument so the function can react to lock loss during execution.
13
+ */
14
+ declare function lockDecoratorFactory(lock: Lock): {
15
+ <A extends unknown[], R>(lockName: string, fn: (...args: A) => Promise<R>): (...args: A) => Promise<R>;
16
+ <A extends unknown[], R>(options: {
17
+ lockName: string;
18
+ signal: true;
19
+ }, fn: (signal: AbortSignal, ...args: A) => Promise<R>): (...args: A) => Promise<R>;
20
+ <A extends unknown[], R>(options: {
21
+ lockName: string;
22
+ signal?: false;
23
+ }, fn: (...args: A) => Promise<R>): (...args: A) => Promise<R>;
24
+ };
25
+
26
+ export { lockDecoratorFactory, lockFactory };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,26 @@
1
- import { lockFactory, lockDecoratoryFactory } from "./lock";
2
- import { memoryBackendFactory } from "./backends/MemoryBackend";
3
- export { memoryBackendFactory, lockFactory, lockDecoratoryFactory, };
1
+ import { Lock, Backend, LockConfiguration } from '@universal-lock/types';
2
+ export { AsyncFunction, Backend, BackendAcquireFunction, BackendFactory, BackendReleaseFunction, BackendRenewFunction, BackendSetupFunction, CallbackLockEntry, Lock, LockAcquireFunction, LockConfiguration, LockEvent, LockReleaseFunction, TimestampLockEntry } from '@universal-lock/types';
3
+
4
+ declare function lockFactory({ setup, acquire, renew, release }: Backend, configuration?: Partial<LockConfiguration>): Lock;
5
+ /**
6
+ * Creates a decorator that wraps an async function with automatic lock
7
+ * acquisition and release. The lock is always released in the finally
8
+ * block, even if fn throws.
9
+ *
10
+ * First argument is either a lock name string or an options object.
11
+ * Pass `{ lockName, signal: true }` to inject an AbortSignal as the
12
+ * first argument so the function can react to lock loss during execution.
13
+ */
14
+ declare function lockDecoratorFactory(lock: Lock): {
15
+ <A extends unknown[], R>(lockName: string, fn: (...args: A) => Promise<R>): (...args: A) => Promise<R>;
16
+ <A extends unknown[], R>(options: {
17
+ lockName: string;
18
+ signal: true;
19
+ }, fn: (signal: AbortSignal, ...args: A) => Promise<R>): (...args: A) => Promise<R>;
20
+ <A extends unknown[], R>(options: {
21
+ lockName: string;
22
+ signal?: false;
23
+ }, fn: (...args: A) => Promise<R>): (...args: A) => Promise<R>;
24
+ };
25
+
26
+ export { lockDecoratorFactory, lockFactory };
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var UniversalLock = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ lockDecoratorFactory: () => lockDecoratorFactory,
25
+ lockFactory: () => lockFactory
26
+ });
27
+
28
+ // src/util.ts
29
+ var generateId = () => Array.from(
30
+ { length: 4 },
31
+ () => Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0")
32
+ ).join("");
33
+ var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
34
+ var asyncInterval = (handler, timeout) => {
35
+ let running = true;
36
+ const loop = (async () => {
37
+ while (running) {
38
+ await handler();
39
+ if (!running) break;
40
+ await sleep(timeout);
41
+ }
42
+ })();
43
+ return async () => {
44
+ running = false;
45
+ await loop;
46
+ };
47
+ };
48
+
49
+ // src/lock.ts
50
+ var noop = () => {
51
+ };
52
+ var defaultConfiguration = {
53
+ acquireInterval: 250,
54
+ acquireFailTimeout: 5e3,
55
+ stale: 1e3,
56
+ renewInterval: 250,
57
+ maxHoldTime: 2e3,
58
+ onLockLost: noop,
59
+ onEvent: noop
60
+ };
61
+ function lockFactory({ setup, acquire, renew, release }, configuration = {}) {
62
+ const {
63
+ acquireInterval,
64
+ acquireFailTimeout,
65
+ stale,
66
+ renewInterval,
67
+ maxHoldTime,
68
+ onLockLost,
69
+ onEvent
70
+ } = { ...defaultConfiguration, ...configuration };
71
+ const setupPromise = setup();
72
+ return {
73
+ async acquire(lockName) {
74
+ await setupPromise;
75
+ return new Promise((resolve, reject) => {
76
+ const lockId = generateId();
77
+ const abortController = new AbortController();
78
+ const acquireTimeout = setTimeout(async () => {
79
+ await stopAcquireInterval();
80
+ onEvent({ type: "acquireTimeout", lockName });
81
+ reject(
82
+ new Error(
83
+ `Failed to acquire lock "${lockName}" within ${acquireFailTimeout}ms`
84
+ )
85
+ );
86
+ }, acquireFailTimeout);
87
+ const stopAcquireInterval = asyncInterval(async () => {
88
+ try {
89
+ await acquire(lockName, stale, lockId);
90
+ } catch {
91
+ return;
92
+ }
93
+ clearTimeout(acquireTimeout);
94
+ stopAcquireInterval();
95
+ let maxHoldTimer = null;
96
+ let released = false;
97
+ const doRelease = async () => {
98
+ if (released) return;
99
+ released = true;
100
+ if (maxHoldTimer) clearTimeout(maxHoldTimer);
101
+ await release(lockName, lockId);
102
+ onEvent({ type: "released", lockName });
103
+ };
104
+ const releaseLock = async () => {
105
+ if (released) return;
106
+ await stopRenewInterval();
107
+ await doRelease();
108
+ };
109
+ const stopRenewInterval = asyncInterval(async () => {
110
+ try {
111
+ await renew(lockName, lockId);
112
+ onEvent({ type: "renewed", lockName });
113
+ } catch (error) {
114
+ onEvent({
115
+ type: "renewFailed",
116
+ lockName,
117
+ error
118
+ });
119
+ onEvent({
120
+ type: "lockLost",
121
+ lockName,
122
+ reason: "renewFailed"
123
+ });
124
+ onLockLost(lockName, "renewFailed");
125
+ abortController.abort();
126
+ stopRenewInterval();
127
+ await doRelease();
128
+ }
129
+ }, renewInterval);
130
+ maxHoldTimer = setTimeout(async () => {
131
+ onEvent({
132
+ type: "lockLost",
133
+ lockName,
134
+ reason: "timeout"
135
+ });
136
+ onLockLost(lockName, "timeout");
137
+ abortController.abort();
138
+ await releaseLock();
139
+ }, maxHoldTime);
140
+ onEvent({ type: "acquired", lockName });
141
+ const releaseFn = Object.assign(releaseLock, {
142
+ signal: abortController.signal
143
+ });
144
+ resolve(releaseFn);
145
+ }, acquireInterval);
146
+ });
147
+ }
148
+ };
149
+ }
150
+ function lockDecoratorFactory(lock) {
151
+ function decorator(lockNameOrOptions, fn) {
152
+ const { lockName, signal: injectSignal } = typeof lockNameOrOptions === "string" ? { lockName: lockNameOrOptions, signal: false } : lockNameOrOptions;
153
+ return async (...args) => {
154
+ const release = await lock.acquire(lockName);
155
+ let released = false;
156
+ const safeRelease = async () => {
157
+ if (released) return;
158
+ released = true;
159
+ await release();
160
+ };
161
+ try {
162
+ if (injectSignal) {
163
+ return await fn(release.signal, ...args);
164
+ }
165
+ return await fn(...args);
166
+ } finally {
167
+ await safeRelease();
168
+ }
169
+ };
170
+ }
171
+ return decorator;
172
+ }
173
+ return __toCommonJS(index_exports);
174
+ })();
175
+ //# sourceMappingURL=index.global.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/util.ts","../src/lock.ts"],"sourcesContent":["import { lockFactory, lockDecoratorFactory } from \"./lock\";\n\nexport { lockFactory, lockDecoratorFactory };\n\nexport type {\n\tAsyncFunction,\n\tBackend,\n\tBackendFactory,\n\tBackendSetupFunction,\n\tBackendAcquireFunction,\n\tBackendRenewFunction,\n\tBackendReleaseFunction,\n\tLock,\n\tLockConfiguration,\n\tLockAcquireFunction,\n\tLockReleaseFunction,\n\tTimestampLockEntry,\n\tCallbackLockEntry,\n\tLockEvent,\n} from \"@universal-lock/types\";\n","import type { AsyncFunction } from \"@universal-lock/types\";\n\n// Generates a 32-character hex string (4 segments x 8 hex chars) used as a unique lock ownership ID\nexport const generateId = () =>\n\tArray.from({ length: 4 }, () =>\n\t\tMath.floor(Math.random() * 0x100000000)\n\t\t\t.toString(16)\n\t\t\t.padStart(8, \"0\"),\n\t).join(\"\");\n\nexport const sleep = async (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Like setInterval but awaits each async handler before scheduling the next.\n * Returns a stop function that sets the running flag to false and waits for the\n * current iteration to finish, ensuring no handler runs after stop() resolves.\n * The double check on `running` (before and after sleep) allows the loop to\n * exit promptly when stopped mid-sleep.\n */\nexport const asyncInterval = <H extends AsyncFunction<void, []>>(\n\thandler: H,\n\ttimeout: number,\n) => {\n\tlet running = true;\n\tconst loop = (async () => {\n\t\twhile (running) {\n\t\t\tawait handler();\n\t\t\tif (!running) break;\n\t\t\tawait sleep(timeout);\n\t\t}\n\t})();\n\treturn async () => {\n\t\trunning = false;\n\t\tawait loop;\n\t};\n};\n","import type {\n\tLock,\n\tBackend,\n\tLockConfiguration,\n\tLockReleaseFunction,\n} from \"@universal-lock/types\";\nimport { asyncInterval, generateId } from \"./util\";\n\nconst noop = () => {};\n\nconst defaultConfiguration: LockConfiguration = {\n\tacquireInterval: 250,\n\tacquireFailTimeout: 5000,\n\tstale: 1000,\n\trenewInterval: 250,\n\tmaxHoldTime: 2000,\n\tonLockLost: noop,\n\tonEvent: noop,\n};\n\nexport function lockFactory(\n\t{ setup, acquire, renew, release }: Backend,\n\tconfiguration: Partial<LockConfiguration> = {},\n): Lock {\n\tconst {\n\t\tacquireInterval,\n\t\tacquireFailTimeout,\n\t\tstale,\n\t\trenewInterval,\n\t\tmaxHoldTime,\n\t\tonLockLost,\n\t\tonEvent,\n\t}: LockConfiguration = { ...defaultConfiguration, ...configuration };\n\t// Eagerly invoke backend setup so it runs once, not per-acquire\n\tconst setupPromise = setup();\n\n\treturn {\n\t\tasync acquire(lockName: string) {\n\t\t\tawait setupPromise;\n\t\t\treturn new Promise<LockReleaseFunction>((resolve, reject) => {\n\t\t\t\tconst lockId = generateId();\n\t\t\t\t// AbortController lets consumers detect lock loss via release.signal\n\t\t\t\tconst abortController = new AbortController();\n\n\t\t\t\t// Reject the acquire promise if the lock isn't obtained within the timeout\n\t\t\t\tconst acquireTimeout = setTimeout(async () => {\n\t\t\t\t\tawait stopAcquireInterval();\n\t\t\t\t\tonEvent({ type: \"acquireTimeout\", lockName });\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`Failed to acquire lock \"${lockName}\" within ${acquireFailTimeout}ms`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}, acquireFailTimeout);\n\n\t\t\t\t// Retry acquisition at a fixed interval until success or timeout\n\t\t\t\tconst stopAcquireInterval = asyncInterval(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait acquire(lockName, stale, lockId);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Acquire failed (lock held by another owner) — silently retry on next interval\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lock acquired — stop retrying and cancel the timeout\n\t\t\t\t\tclearTimeout(acquireTimeout);\n\t\t\t\t\tstopAcquireInterval();\n\n\t\t\t\t\tlet maxHoldTimer: ReturnType<typeof setTimeout> | null =\n\t\t\t\t\t\tnull;\n\t\t\t\t\t// Guards against double-release from concurrent timeout and manual release\n\t\t\t\t\tlet released = false;\n\n\t\t\t\t\t// Low-level release: idempotent, clears the running timer, notifies the backend\n\t\t\t\t\tconst doRelease = async () => {\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\treleased = true;\n\t\t\t\t\t\t/* v8 ignore next */\n\t\t\t\t\t\tif (maxHoldTimer) clearTimeout(maxHoldTimer);\n\t\t\t\t\t\tawait release(lockName, lockId);\n\t\t\t\t\t\tonEvent({ type: \"released\", lockName });\n\t\t\t\t\t};\n\n\t\t\t\t\t// High-level release: stops renewals first, then releases the lock\n\t\t\t\t\tconst releaseLock = async () => {\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\tawait stopRenewInterval();\n\t\t\t\t\t\tawait doRelease();\n\t\t\t\t\t};\n\n\t\t\t\t\t// Periodically renew the lock to prevent it from going stale\n\t\t\t\t\tconst stopRenewInterval = asyncInterval(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait renew(lockName, lockId);\n\t\t\t\t\t\t\tonEvent({ type: \"renewed\", lockName });\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\t\ttype: \"renewFailed\",\n\t\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\t\ttype: \"lockLost\",\n\t\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\t\treason: \"renewFailed\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tonLockLost(lockName, \"renewFailed\");\n\t\t\t\t\t\t\t// Signal consumers that the lock is no longer held\n\t\t\t\t\t\t\tabortController.abort();\n\t\t\t\t\t\t\t// Don't await stopRenewInterval here — we're inside it\n\t\t\t\t\t\t\tstopRenewInterval();\n\t\t\t\t\t\t\tawait doRelease();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, renewInterval);\n\n\t\t\t\t\t// Safety net: auto-release if the caller holds the lock too long\n\t\t\t\t\tmaxHoldTimer = setTimeout(async () => {\n\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\ttype: \"lockLost\",\n\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\treason: \"timeout\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tonLockLost(lockName, \"timeout\");\n\t\t\t\t\t\tabortController.abort();\n\t\t\t\t\t\tawait releaseLock();\n\t\t\t\t\t}, maxHoldTime);\n\n\t\t\t\t\tonEvent({ type: \"acquired\", lockName });\n\n\t\t\t\t\t// Attach the abort signal to the release function so callers can\n\t\t\t\t\t// detect lock loss (e.g. via release.signal.aborted or addEventListener)\n\t\t\t\t\tconst releaseFn = Object.assign(releaseLock, {\n\t\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t\t}) as LockReleaseFunction;\n\t\t\t\t\tresolve(releaseFn);\n\t\t\t\t}, acquireInterval);\n\t\t\t});\n\t\t},\n\t};\n}\n\n/**\n * Creates a decorator that wraps an async function with automatic lock\n * acquisition and release. The lock is always released in the finally\n * block, even if fn throws.\n *\n * First argument is either a lock name string or an options object.\n * Pass `{ lockName, signal: true }` to inject an AbortSignal as the\n * first argument so the function can react to lock loss during execution.\n */\nexport function lockDecoratorFactory(lock: Lock) {\n\t// Overload: string lock name — fn keeps its original signature\n\tfunction decorator<A extends unknown[], R>(\n\t\tlockName: string,\n\t\tfn: (...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\t// Overload: options object with signal — fn receives AbortSignal as first arg\n\tfunction decorator<A extends unknown[], R>(\n\t\toptions: { lockName: string; signal: true },\n\t\tfn: (signal: AbortSignal, ...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\t// Overload: options object without signal — fn keeps its original signature\n\tfunction decorator<A extends unknown[], R>(\n\t\toptions: { lockName: string; signal?: false },\n\t\tfn: (...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\tfunction decorator<A extends unknown[], R>(\n\t\tlockNameOrOptions: string | { lockName: string; signal?: boolean },\n\t\tfn:\n\t\t\t| ((...args: A) => Promise<R>)\n\t\t\t| ((signal: AbortSignal, ...args: A) => Promise<R>),\n\t) {\n\t\tconst { lockName, signal: injectSignal } =\n\t\t\ttypeof lockNameOrOptions === \"string\"\n\t\t\t\t? { lockName: lockNameOrOptions, signal: false }\n\t\t\t\t: lockNameOrOptions;\n\n\t\treturn async (...args: A): Promise<R> => {\n\t\t\tconst release = await lock.acquire(lockName);\n\t\t\tlet released = false;\n\t\t\tconst safeRelease = async () => {\n\t\t\t\t/* v8 ignore next */\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait release();\n\t\t\t};\n\t\t\ttry {\n\t\t\t\t// Only inject the AbortSignal when the caller opted in\n\t\t\t\tif (injectSignal) {\n\t\t\t\t\treturn await (\n\t\t\t\t\t\tfn as (signal: AbortSignal, ...args: A) => Promise<R>\n\t\t\t\t\t)(release.signal, ...args);\n\t\t\t\t}\n\t\t\t\treturn await (fn as (...args: A) => Promise<R>)(...args);\n\t\t\t} finally {\n\t\t\t\tawait safeRelease();\n\t\t\t}\n\t\t};\n\t}\n\n\treturn decorator;\n}\n"],"mappings":"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","names":[]}
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ lockDecoratorFactory: () => lockDecoratorFactory,
24
+ lockFactory: () => lockFactory
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/util.ts
29
+ var generateId = () => Array.from(
30
+ { length: 4 },
31
+ () => Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0")
32
+ ).join("");
33
+ var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
34
+ var asyncInterval = (handler, timeout) => {
35
+ let running = true;
36
+ const loop = (async () => {
37
+ while (running) {
38
+ await handler();
39
+ if (!running) break;
40
+ await sleep(timeout);
41
+ }
42
+ })();
43
+ return async () => {
44
+ running = false;
45
+ await loop;
46
+ };
47
+ };
48
+
49
+ // src/lock.ts
50
+ var noop = () => {
51
+ };
52
+ var defaultConfiguration = {
53
+ acquireInterval: 250,
54
+ acquireFailTimeout: 5e3,
55
+ stale: 1e3,
56
+ renewInterval: 250,
57
+ maxHoldTime: 2e3,
58
+ onLockLost: noop,
59
+ onEvent: noop
60
+ };
61
+ function lockFactory({ setup, acquire, renew, release }, configuration = {}) {
62
+ const {
63
+ acquireInterval,
64
+ acquireFailTimeout,
65
+ stale,
66
+ renewInterval,
67
+ maxHoldTime,
68
+ onLockLost,
69
+ onEvent
70
+ } = { ...defaultConfiguration, ...configuration };
71
+ const setupPromise = setup();
72
+ return {
73
+ async acquire(lockName) {
74
+ await setupPromise;
75
+ return new Promise((resolve, reject) => {
76
+ const lockId = generateId();
77
+ const abortController = new AbortController();
78
+ const acquireTimeout = setTimeout(async () => {
79
+ await stopAcquireInterval();
80
+ onEvent({ type: "acquireTimeout", lockName });
81
+ reject(
82
+ new Error(
83
+ `Failed to acquire lock "${lockName}" within ${acquireFailTimeout}ms`
84
+ )
85
+ );
86
+ }, acquireFailTimeout);
87
+ const stopAcquireInterval = asyncInterval(async () => {
88
+ try {
89
+ await acquire(lockName, stale, lockId);
90
+ } catch {
91
+ return;
92
+ }
93
+ clearTimeout(acquireTimeout);
94
+ stopAcquireInterval();
95
+ let maxHoldTimer = null;
96
+ let released = false;
97
+ const doRelease = async () => {
98
+ if (released) return;
99
+ released = true;
100
+ if (maxHoldTimer) clearTimeout(maxHoldTimer);
101
+ await release(lockName, lockId);
102
+ onEvent({ type: "released", lockName });
103
+ };
104
+ const releaseLock = async () => {
105
+ if (released) return;
106
+ await stopRenewInterval();
107
+ await doRelease();
108
+ };
109
+ const stopRenewInterval = asyncInterval(async () => {
110
+ try {
111
+ await renew(lockName, lockId);
112
+ onEvent({ type: "renewed", lockName });
113
+ } catch (error) {
114
+ onEvent({
115
+ type: "renewFailed",
116
+ lockName,
117
+ error
118
+ });
119
+ onEvent({
120
+ type: "lockLost",
121
+ lockName,
122
+ reason: "renewFailed"
123
+ });
124
+ onLockLost(lockName, "renewFailed");
125
+ abortController.abort();
126
+ stopRenewInterval();
127
+ await doRelease();
128
+ }
129
+ }, renewInterval);
130
+ maxHoldTimer = setTimeout(async () => {
131
+ onEvent({
132
+ type: "lockLost",
133
+ lockName,
134
+ reason: "timeout"
135
+ });
136
+ onLockLost(lockName, "timeout");
137
+ abortController.abort();
138
+ await releaseLock();
139
+ }, maxHoldTime);
140
+ onEvent({ type: "acquired", lockName });
141
+ const releaseFn = Object.assign(releaseLock, {
142
+ signal: abortController.signal
143
+ });
144
+ resolve(releaseFn);
145
+ }, acquireInterval);
146
+ });
147
+ }
148
+ };
149
+ }
150
+ function lockDecoratorFactory(lock) {
151
+ function decorator(lockNameOrOptions, fn) {
152
+ const { lockName, signal: injectSignal } = typeof lockNameOrOptions === "string" ? { lockName: lockNameOrOptions, signal: false } : lockNameOrOptions;
153
+ return async (...args) => {
154
+ const release = await lock.acquire(lockName);
155
+ let released = false;
156
+ const safeRelease = async () => {
157
+ if (released) return;
158
+ released = true;
159
+ await release();
160
+ };
161
+ try {
162
+ if (injectSignal) {
163
+ return await fn(release.signal, ...args);
164
+ }
165
+ return await fn(...args);
166
+ } finally {
167
+ await safeRelease();
168
+ }
169
+ };
170
+ }
171
+ return decorator;
172
+ }
173
+ // Annotate the CommonJS export names for ESM import in node:
174
+ 0 && (module.exports = {
175
+ lockDecoratorFactory,
176
+ lockFactory
177
+ });
178
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/util.ts","../src/lock.ts"],"sourcesContent":["import { lockFactory, lockDecoratorFactory } from \"./lock\";\n\nexport { lockFactory, lockDecoratorFactory };\n\nexport type {\n\tAsyncFunction,\n\tBackend,\n\tBackendFactory,\n\tBackendSetupFunction,\n\tBackendAcquireFunction,\n\tBackendRenewFunction,\n\tBackendReleaseFunction,\n\tLock,\n\tLockConfiguration,\n\tLockAcquireFunction,\n\tLockReleaseFunction,\n\tTimestampLockEntry,\n\tCallbackLockEntry,\n\tLockEvent,\n} from \"@universal-lock/types\";\n","import type { AsyncFunction } from \"@universal-lock/types\";\n\n// Generates a 32-character hex string (4 segments x 8 hex chars) used as a unique lock ownership ID\nexport const generateId = () =>\n\tArray.from({ length: 4 }, () =>\n\t\tMath.floor(Math.random() * 0x100000000)\n\t\t\t.toString(16)\n\t\t\t.padStart(8, \"0\"),\n\t).join(\"\");\n\nexport const sleep = async (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Like setInterval but awaits each async handler before scheduling the next.\n * Returns a stop function that sets the running flag to false and waits for the\n * current iteration to finish, ensuring no handler runs after stop() resolves.\n * The double check on `running` (before and after sleep) allows the loop to\n * exit promptly when stopped mid-sleep.\n */\nexport const asyncInterval = <H extends AsyncFunction<void, []>>(\n\thandler: H,\n\ttimeout: number,\n) => {\n\tlet running = true;\n\tconst loop = (async () => {\n\t\twhile (running) {\n\t\t\tawait handler();\n\t\t\tif (!running) break;\n\t\t\tawait sleep(timeout);\n\t\t}\n\t})();\n\treturn async () => {\n\t\trunning = false;\n\t\tawait loop;\n\t};\n};\n","import type {\n\tLock,\n\tBackend,\n\tLockConfiguration,\n\tLockReleaseFunction,\n} from \"@universal-lock/types\";\nimport { asyncInterval, generateId } from \"./util\";\n\nconst noop = () => {};\n\nconst defaultConfiguration: LockConfiguration = {\n\tacquireInterval: 250,\n\tacquireFailTimeout: 5000,\n\tstale: 1000,\n\trenewInterval: 250,\n\tmaxHoldTime: 2000,\n\tonLockLost: noop,\n\tonEvent: noop,\n};\n\nexport function lockFactory(\n\t{ setup, acquire, renew, release }: Backend,\n\tconfiguration: Partial<LockConfiguration> = {},\n): Lock {\n\tconst {\n\t\tacquireInterval,\n\t\tacquireFailTimeout,\n\t\tstale,\n\t\trenewInterval,\n\t\tmaxHoldTime,\n\t\tonLockLost,\n\t\tonEvent,\n\t}: LockConfiguration = { ...defaultConfiguration, ...configuration };\n\t// Eagerly invoke backend setup so it runs once, not per-acquire\n\tconst setupPromise = setup();\n\n\treturn {\n\t\tasync acquire(lockName: string) {\n\t\t\tawait setupPromise;\n\t\t\treturn new Promise<LockReleaseFunction>((resolve, reject) => {\n\t\t\t\tconst lockId = generateId();\n\t\t\t\t// AbortController lets consumers detect lock loss via release.signal\n\t\t\t\tconst abortController = new AbortController();\n\n\t\t\t\t// Reject the acquire promise if the lock isn't obtained within the timeout\n\t\t\t\tconst acquireTimeout = setTimeout(async () => {\n\t\t\t\t\tawait stopAcquireInterval();\n\t\t\t\t\tonEvent({ type: \"acquireTimeout\", lockName });\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`Failed to acquire lock \"${lockName}\" within ${acquireFailTimeout}ms`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}, acquireFailTimeout);\n\n\t\t\t\t// Retry acquisition at a fixed interval until success or timeout\n\t\t\t\tconst stopAcquireInterval = asyncInterval(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait acquire(lockName, stale, lockId);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Acquire failed (lock held by another owner) — silently retry on next interval\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lock acquired — stop retrying and cancel the timeout\n\t\t\t\t\tclearTimeout(acquireTimeout);\n\t\t\t\t\tstopAcquireInterval();\n\n\t\t\t\t\tlet maxHoldTimer: ReturnType<typeof setTimeout> | null =\n\t\t\t\t\t\tnull;\n\t\t\t\t\t// Guards against double-release from concurrent timeout and manual release\n\t\t\t\t\tlet released = false;\n\n\t\t\t\t\t// Low-level release: idempotent, clears the running timer, notifies the backend\n\t\t\t\t\tconst doRelease = async () => {\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\treleased = true;\n\t\t\t\t\t\t/* v8 ignore next */\n\t\t\t\t\t\tif (maxHoldTimer) clearTimeout(maxHoldTimer);\n\t\t\t\t\t\tawait release(lockName, lockId);\n\t\t\t\t\t\tonEvent({ type: \"released\", lockName });\n\t\t\t\t\t};\n\n\t\t\t\t\t// High-level release: stops renewals first, then releases the lock\n\t\t\t\t\tconst releaseLock = async () => {\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\tawait stopRenewInterval();\n\t\t\t\t\t\tawait doRelease();\n\t\t\t\t\t};\n\n\t\t\t\t\t// Periodically renew the lock to prevent it from going stale\n\t\t\t\t\tconst stopRenewInterval = asyncInterval(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait renew(lockName, lockId);\n\t\t\t\t\t\t\tonEvent({ type: \"renewed\", lockName });\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\t\ttype: \"renewFailed\",\n\t\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\t\ttype: \"lockLost\",\n\t\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\t\treason: \"renewFailed\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tonLockLost(lockName, \"renewFailed\");\n\t\t\t\t\t\t\t// Signal consumers that the lock is no longer held\n\t\t\t\t\t\t\tabortController.abort();\n\t\t\t\t\t\t\t// Don't await stopRenewInterval here — we're inside it\n\t\t\t\t\t\t\tstopRenewInterval();\n\t\t\t\t\t\t\tawait doRelease();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, renewInterval);\n\n\t\t\t\t\t// Safety net: auto-release if the caller holds the lock too long\n\t\t\t\t\tmaxHoldTimer = setTimeout(async () => {\n\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\ttype: \"lockLost\",\n\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\treason: \"timeout\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tonLockLost(lockName, \"timeout\");\n\t\t\t\t\t\tabortController.abort();\n\t\t\t\t\t\tawait releaseLock();\n\t\t\t\t\t}, maxHoldTime);\n\n\t\t\t\t\tonEvent({ type: \"acquired\", lockName });\n\n\t\t\t\t\t// Attach the abort signal to the release function so callers can\n\t\t\t\t\t// detect lock loss (e.g. via release.signal.aborted or addEventListener)\n\t\t\t\t\tconst releaseFn = Object.assign(releaseLock, {\n\t\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t\t}) as LockReleaseFunction;\n\t\t\t\t\tresolve(releaseFn);\n\t\t\t\t}, acquireInterval);\n\t\t\t});\n\t\t},\n\t};\n}\n\n/**\n * Creates a decorator that wraps an async function with automatic lock\n * acquisition and release. The lock is always released in the finally\n * block, even if fn throws.\n *\n * First argument is either a lock name string or an options object.\n * Pass `{ lockName, signal: true }` to inject an AbortSignal as the\n * first argument so the function can react to lock loss during execution.\n */\nexport function lockDecoratorFactory(lock: Lock) {\n\t// Overload: string lock name — fn keeps its original signature\n\tfunction decorator<A extends unknown[], R>(\n\t\tlockName: string,\n\t\tfn: (...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\t// Overload: options object with signal — fn receives AbortSignal as first arg\n\tfunction decorator<A extends unknown[], R>(\n\t\toptions: { lockName: string; signal: true },\n\t\tfn: (signal: AbortSignal, ...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\t// Overload: options object without signal — fn keeps its original signature\n\tfunction decorator<A extends unknown[], R>(\n\t\toptions: { lockName: string; signal?: false },\n\t\tfn: (...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\tfunction decorator<A extends unknown[], R>(\n\t\tlockNameOrOptions: string | { lockName: string; signal?: boolean },\n\t\tfn:\n\t\t\t| ((...args: A) => Promise<R>)\n\t\t\t| ((signal: AbortSignal, ...args: A) => Promise<R>),\n\t) {\n\t\tconst { lockName, signal: injectSignal } =\n\t\t\ttypeof lockNameOrOptions === \"string\"\n\t\t\t\t? { lockName: lockNameOrOptions, signal: false }\n\t\t\t\t: lockNameOrOptions;\n\n\t\treturn async (...args: A): Promise<R> => {\n\t\t\tconst release = await lock.acquire(lockName);\n\t\t\tlet released = false;\n\t\t\tconst safeRelease = async () => {\n\t\t\t\t/* v8 ignore next */\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait release();\n\t\t\t};\n\t\t\ttry {\n\t\t\t\t// Only inject the AbortSignal when the caller opted in\n\t\t\t\tif (injectSignal) {\n\t\t\t\t\treturn await (\n\t\t\t\t\t\tfn as (signal: AbortSignal, ...args: A) => Promise<R>\n\t\t\t\t\t)(release.signal, ...args);\n\t\t\t\t}\n\t\t\t\treturn await (fn as (...args: A) => Promise<R>)(...args);\n\t\t\t} finally {\n\t\t\t\tawait safeRelease();\n\t\t\t}\n\t\t};\n\t}\n\n\treturn decorator;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,aAAa,MACzB,MAAM;AAAA,EAAK,EAAE,QAAQ,EAAE;AAAA,EAAG,MACzB,KAAK,MAAM,KAAK,OAAO,IAAI,UAAW,EACpC,SAAS,EAAE,EACX,SAAS,GAAG,GAAG;AAClB,EAAE,KAAK,EAAE;AAEH,IAAM,QAAQ,OAAO,OAC3B,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAS1C,IAAM,gBAAgB,CAC5B,SACA,YACI;AACJ,MAAI,UAAU;AACd,QAAM,QAAQ,YAAY;AACzB,WAAO,SAAS;AACf,YAAM,QAAQ;AACd,UAAI,CAAC,QAAS;AACd,YAAM,MAAM,OAAO;AAAA,IACpB;AAAA,EACD,GAAG;AACH,SAAO,YAAY;AAClB,cAAU;AACV,UAAM;AAAA,EACP;AACD;;;AC5BA,IAAM,OAAO,MAAM;AAAC;AAEpB,IAAM,uBAA0C;AAAA,EAC/C,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,OAAO;AAAA,EACP,eAAe;AAAA,EACf,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,SAAS;AACV;AAEO,SAAS,YACf,EAAE,OAAO,SAAS,OAAO,QAAQ,GACjC,gBAA4C,CAAC,GACtC;AACP,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAuB,EAAE,GAAG,sBAAsB,GAAG,cAAc;AAEnE,QAAM,eAAe,MAAM;AAE3B,SAAO;AAAA,IACN,MAAM,QAAQ,UAAkB;AAC/B,YAAM;AACN,aAAO,IAAI,QAA6B,CAAC,SAAS,WAAW;AAC5D,cAAM,SAAS,WAAW;AAE1B,cAAM,kBAAkB,IAAI,gBAAgB;AAG5C,cAAM,iBAAiB,WAAW,YAAY;AAC7C,gBAAM,oBAAoB;AAC1B,kBAAQ,EAAE,MAAM,kBAAkB,SAAS,CAAC;AAC5C;AAAA,YACC,IAAI;AAAA,cACH,2BAA2B,QAAQ,YAAY,kBAAkB;AAAA,YAClE;AAAA,UACD;AAAA,QACD,GAAG,kBAAkB;AAGrB,cAAM,sBAAsB,cAAc,YAAY;AACrD,cAAI;AACH,kBAAM,QAAQ,UAAU,OAAO,MAAM;AAAA,UACtC,QAAQ;AAEP;AAAA,UACD;AAGA,uBAAa,cAAc;AAC3B,8BAAoB;AAEpB,cAAI,eACH;AAED,cAAI,WAAW;AAGf,gBAAM,YAAY,YAAY;AAC7B,gBAAI,SAAU;AACd,uBAAW;AAEX,gBAAI,aAAc,cAAa,YAAY;AAC3C,kBAAM,QAAQ,UAAU,MAAM;AAC9B,oBAAQ,EAAE,MAAM,YAAY,SAAS,CAAC;AAAA,UACvC;AAGA,gBAAM,cAAc,YAAY;AAC/B,gBAAI,SAAU;AACd,kBAAM,kBAAkB;AACxB,kBAAM,UAAU;AAAA,UACjB;AAGA,gBAAM,oBAAoB,cAAc,YAAY;AACnD,gBAAI;AACH,oBAAM,MAAM,UAAU,MAAM;AAC5B,sBAAQ,EAAE,MAAM,WAAW,SAAS,CAAC;AAAA,YACtC,SAAS,OAAO;AACf,sBAAQ;AAAA,gBACP,MAAM;AAAA,gBACN;AAAA,gBACA;AAAA,cACD,CAAC;AACD,sBAAQ;AAAA,gBACP,MAAM;AAAA,gBACN;AAAA,gBACA,QAAQ;AAAA,cACT,CAAC;AACD,yBAAW,UAAU,aAAa;AAElC,8BAAgB,MAAM;AAEtB,gCAAkB;AAClB,oBAAM,UAAU;AAAA,YACjB;AAAA,UACD,GAAG,aAAa;AAGhB,yBAAe,WAAW,YAAY;AACrC,oBAAQ;AAAA,cACP,MAAM;AAAA,cACN;AAAA,cACA,QAAQ;AAAA,YACT,CAAC;AACD,uBAAW,UAAU,SAAS;AAC9B,4BAAgB,MAAM;AACtB,kBAAM,YAAY;AAAA,UACnB,GAAG,WAAW;AAEd,kBAAQ,EAAE,MAAM,YAAY,SAAS,CAAC;AAItC,gBAAM,YAAY,OAAO,OAAO,aAAa;AAAA,YAC5C,QAAQ,gBAAgB;AAAA,UACzB,CAAC;AACD,kBAAQ,SAAS;AAAA,QAClB,GAAG,eAAe;AAAA,MACnB,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAWO,SAAS,qBAAqB,MAAY;AAmBhD,WAAS,UACR,mBACA,IAGC;AACD,UAAM,EAAE,UAAU,QAAQ,aAAa,IACtC,OAAO,sBAAsB,WAC1B,EAAE,UAAU,mBAAmB,QAAQ,MAAM,IAC7C;AAEJ,WAAO,UAAU,SAAwB;AACxC,YAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ;AAC3C,UAAI,WAAW;AACf,YAAM,cAAc,YAAY;AAE/B,YAAI,SAAU;AACd,mBAAW;AACX,cAAM,QAAQ;AAAA,MACf;AACA,UAAI;AAEH,YAAI,cAAc;AACjB,iBAAO,MACN,GACC,QAAQ,QAAQ,GAAG,IAAI;AAAA,QAC1B;AACA,eAAO,MAAO,GAAkC,GAAG,IAAI;AAAA,MACxD,UAAE;AACD,cAAM,YAAY;AAAA,MACnB;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,150 @@
1
+ // src/util.ts
2
+ var generateId = () => Array.from(
3
+ { length: 4 },
4
+ () => Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0")
5
+ ).join("");
6
+ var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7
+ var asyncInterval = (handler, timeout) => {
8
+ let running = true;
9
+ const loop = (async () => {
10
+ while (running) {
11
+ await handler();
12
+ if (!running) break;
13
+ await sleep(timeout);
14
+ }
15
+ })();
16
+ return async () => {
17
+ running = false;
18
+ await loop;
19
+ };
20
+ };
21
+
22
+ // src/lock.ts
23
+ var noop = () => {
24
+ };
25
+ var defaultConfiguration = {
26
+ acquireInterval: 250,
27
+ acquireFailTimeout: 5e3,
28
+ stale: 1e3,
29
+ renewInterval: 250,
30
+ maxHoldTime: 2e3,
31
+ onLockLost: noop,
32
+ onEvent: noop
33
+ };
34
+ function lockFactory({ setup, acquire, renew, release }, configuration = {}) {
35
+ const {
36
+ acquireInterval,
37
+ acquireFailTimeout,
38
+ stale,
39
+ renewInterval,
40
+ maxHoldTime,
41
+ onLockLost,
42
+ onEvent
43
+ } = { ...defaultConfiguration, ...configuration };
44
+ const setupPromise = setup();
45
+ return {
46
+ async acquire(lockName) {
47
+ await setupPromise;
48
+ return new Promise((resolve, reject) => {
49
+ const lockId = generateId();
50
+ const abortController = new AbortController();
51
+ const acquireTimeout = setTimeout(async () => {
52
+ await stopAcquireInterval();
53
+ onEvent({ type: "acquireTimeout", lockName });
54
+ reject(
55
+ new Error(
56
+ `Failed to acquire lock "${lockName}" within ${acquireFailTimeout}ms`
57
+ )
58
+ );
59
+ }, acquireFailTimeout);
60
+ const stopAcquireInterval = asyncInterval(async () => {
61
+ try {
62
+ await acquire(lockName, stale, lockId);
63
+ } catch {
64
+ return;
65
+ }
66
+ clearTimeout(acquireTimeout);
67
+ stopAcquireInterval();
68
+ let maxHoldTimer = null;
69
+ let released = false;
70
+ const doRelease = async () => {
71
+ if (released) return;
72
+ released = true;
73
+ if (maxHoldTimer) clearTimeout(maxHoldTimer);
74
+ await release(lockName, lockId);
75
+ onEvent({ type: "released", lockName });
76
+ };
77
+ const releaseLock = async () => {
78
+ if (released) return;
79
+ await stopRenewInterval();
80
+ await doRelease();
81
+ };
82
+ const stopRenewInterval = asyncInterval(async () => {
83
+ try {
84
+ await renew(lockName, lockId);
85
+ onEvent({ type: "renewed", lockName });
86
+ } catch (error) {
87
+ onEvent({
88
+ type: "renewFailed",
89
+ lockName,
90
+ error
91
+ });
92
+ onEvent({
93
+ type: "lockLost",
94
+ lockName,
95
+ reason: "renewFailed"
96
+ });
97
+ onLockLost(lockName, "renewFailed");
98
+ abortController.abort();
99
+ stopRenewInterval();
100
+ await doRelease();
101
+ }
102
+ }, renewInterval);
103
+ maxHoldTimer = setTimeout(async () => {
104
+ onEvent({
105
+ type: "lockLost",
106
+ lockName,
107
+ reason: "timeout"
108
+ });
109
+ onLockLost(lockName, "timeout");
110
+ abortController.abort();
111
+ await releaseLock();
112
+ }, maxHoldTime);
113
+ onEvent({ type: "acquired", lockName });
114
+ const releaseFn = Object.assign(releaseLock, {
115
+ signal: abortController.signal
116
+ });
117
+ resolve(releaseFn);
118
+ }, acquireInterval);
119
+ });
120
+ }
121
+ };
122
+ }
123
+ function lockDecoratorFactory(lock) {
124
+ function decorator(lockNameOrOptions, fn) {
125
+ const { lockName, signal: injectSignal } = typeof lockNameOrOptions === "string" ? { lockName: lockNameOrOptions, signal: false } : lockNameOrOptions;
126
+ return async (...args) => {
127
+ const release = await lock.acquire(lockName);
128
+ let released = false;
129
+ const safeRelease = async () => {
130
+ if (released) return;
131
+ released = true;
132
+ await release();
133
+ };
134
+ try {
135
+ if (injectSignal) {
136
+ return await fn(release.signal, ...args);
137
+ }
138
+ return await fn(...args);
139
+ } finally {
140
+ await safeRelease();
141
+ }
142
+ };
143
+ }
144
+ return decorator;
145
+ }
146
+ export {
147
+ lockDecoratorFactory,
148
+ lockFactory
149
+ };
150
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util.ts","../src/lock.ts"],"sourcesContent":["import type { AsyncFunction } from \"@universal-lock/types\";\n\n// Generates a 32-character hex string (4 segments x 8 hex chars) used as a unique lock ownership ID\nexport const generateId = () =>\n\tArray.from({ length: 4 }, () =>\n\t\tMath.floor(Math.random() * 0x100000000)\n\t\t\t.toString(16)\n\t\t\t.padStart(8, \"0\"),\n\t).join(\"\");\n\nexport const sleep = async (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Like setInterval but awaits each async handler before scheduling the next.\n * Returns a stop function that sets the running flag to false and waits for the\n * current iteration to finish, ensuring no handler runs after stop() resolves.\n * The double check on `running` (before and after sleep) allows the loop to\n * exit promptly when stopped mid-sleep.\n */\nexport const asyncInterval = <H extends AsyncFunction<void, []>>(\n\thandler: H,\n\ttimeout: number,\n) => {\n\tlet running = true;\n\tconst loop = (async () => {\n\t\twhile (running) {\n\t\t\tawait handler();\n\t\t\tif (!running) break;\n\t\t\tawait sleep(timeout);\n\t\t}\n\t})();\n\treturn async () => {\n\t\trunning = false;\n\t\tawait loop;\n\t};\n};\n","import type {\n\tLock,\n\tBackend,\n\tLockConfiguration,\n\tLockReleaseFunction,\n} from \"@universal-lock/types\";\nimport { asyncInterval, generateId } from \"./util\";\n\nconst noop = () => {};\n\nconst defaultConfiguration: LockConfiguration = {\n\tacquireInterval: 250,\n\tacquireFailTimeout: 5000,\n\tstale: 1000,\n\trenewInterval: 250,\n\tmaxHoldTime: 2000,\n\tonLockLost: noop,\n\tonEvent: noop,\n};\n\nexport function lockFactory(\n\t{ setup, acquire, renew, release }: Backend,\n\tconfiguration: Partial<LockConfiguration> = {},\n): Lock {\n\tconst {\n\t\tacquireInterval,\n\t\tacquireFailTimeout,\n\t\tstale,\n\t\trenewInterval,\n\t\tmaxHoldTime,\n\t\tonLockLost,\n\t\tonEvent,\n\t}: LockConfiguration = { ...defaultConfiguration, ...configuration };\n\t// Eagerly invoke backend setup so it runs once, not per-acquire\n\tconst setupPromise = setup();\n\n\treturn {\n\t\tasync acquire(lockName: string) {\n\t\t\tawait setupPromise;\n\t\t\treturn new Promise<LockReleaseFunction>((resolve, reject) => {\n\t\t\t\tconst lockId = generateId();\n\t\t\t\t// AbortController lets consumers detect lock loss via release.signal\n\t\t\t\tconst abortController = new AbortController();\n\n\t\t\t\t// Reject the acquire promise if the lock isn't obtained within the timeout\n\t\t\t\tconst acquireTimeout = setTimeout(async () => {\n\t\t\t\t\tawait stopAcquireInterval();\n\t\t\t\t\tonEvent({ type: \"acquireTimeout\", lockName });\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`Failed to acquire lock \"${lockName}\" within ${acquireFailTimeout}ms`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}, acquireFailTimeout);\n\n\t\t\t\t// Retry acquisition at a fixed interval until success or timeout\n\t\t\t\tconst stopAcquireInterval = asyncInterval(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait acquire(lockName, stale, lockId);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Acquire failed (lock held by another owner) — silently retry on next interval\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lock acquired — stop retrying and cancel the timeout\n\t\t\t\t\tclearTimeout(acquireTimeout);\n\t\t\t\t\tstopAcquireInterval();\n\n\t\t\t\t\tlet maxHoldTimer: ReturnType<typeof setTimeout> | null =\n\t\t\t\t\t\tnull;\n\t\t\t\t\t// Guards against double-release from concurrent timeout and manual release\n\t\t\t\t\tlet released = false;\n\n\t\t\t\t\t// Low-level release: idempotent, clears the running timer, notifies the backend\n\t\t\t\t\tconst doRelease = async () => {\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\treleased = true;\n\t\t\t\t\t\t/* v8 ignore next */\n\t\t\t\t\t\tif (maxHoldTimer) clearTimeout(maxHoldTimer);\n\t\t\t\t\t\tawait release(lockName, lockId);\n\t\t\t\t\t\tonEvent({ type: \"released\", lockName });\n\t\t\t\t\t};\n\n\t\t\t\t\t// High-level release: stops renewals first, then releases the lock\n\t\t\t\t\tconst releaseLock = async () => {\n\t\t\t\t\t\tif (released) return;\n\t\t\t\t\t\tawait stopRenewInterval();\n\t\t\t\t\t\tawait doRelease();\n\t\t\t\t\t};\n\n\t\t\t\t\t// Periodically renew the lock to prevent it from going stale\n\t\t\t\t\tconst stopRenewInterval = asyncInterval(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait renew(lockName, lockId);\n\t\t\t\t\t\t\tonEvent({ type: \"renewed\", lockName });\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\t\ttype: \"renewFailed\",\n\t\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\t\ttype: \"lockLost\",\n\t\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\t\treason: \"renewFailed\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tonLockLost(lockName, \"renewFailed\");\n\t\t\t\t\t\t\t// Signal consumers that the lock is no longer held\n\t\t\t\t\t\t\tabortController.abort();\n\t\t\t\t\t\t\t// Don't await stopRenewInterval here — we're inside it\n\t\t\t\t\t\t\tstopRenewInterval();\n\t\t\t\t\t\t\tawait doRelease();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, renewInterval);\n\n\t\t\t\t\t// Safety net: auto-release if the caller holds the lock too long\n\t\t\t\t\tmaxHoldTimer = setTimeout(async () => {\n\t\t\t\t\t\tonEvent({\n\t\t\t\t\t\t\ttype: \"lockLost\",\n\t\t\t\t\t\t\tlockName,\n\t\t\t\t\t\t\treason: \"timeout\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tonLockLost(lockName, \"timeout\");\n\t\t\t\t\t\tabortController.abort();\n\t\t\t\t\t\tawait releaseLock();\n\t\t\t\t\t}, maxHoldTime);\n\n\t\t\t\t\tonEvent({ type: \"acquired\", lockName });\n\n\t\t\t\t\t// Attach the abort signal to the release function so callers can\n\t\t\t\t\t// detect lock loss (e.g. via release.signal.aborted or addEventListener)\n\t\t\t\t\tconst releaseFn = Object.assign(releaseLock, {\n\t\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t\t}) as LockReleaseFunction;\n\t\t\t\t\tresolve(releaseFn);\n\t\t\t\t}, acquireInterval);\n\t\t\t});\n\t\t},\n\t};\n}\n\n/**\n * Creates a decorator that wraps an async function with automatic lock\n * acquisition and release. The lock is always released in the finally\n * block, even if fn throws.\n *\n * First argument is either a lock name string or an options object.\n * Pass `{ lockName, signal: true }` to inject an AbortSignal as the\n * first argument so the function can react to lock loss during execution.\n */\nexport function lockDecoratorFactory(lock: Lock) {\n\t// Overload: string lock name — fn keeps its original signature\n\tfunction decorator<A extends unknown[], R>(\n\t\tlockName: string,\n\t\tfn: (...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\t// Overload: options object with signal — fn receives AbortSignal as first arg\n\tfunction decorator<A extends unknown[], R>(\n\t\toptions: { lockName: string; signal: true },\n\t\tfn: (signal: AbortSignal, ...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\t// Overload: options object without signal — fn keeps its original signature\n\tfunction decorator<A extends unknown[], R>(\n\t\toptions: { lockName: string; signal?: false },\n\t\tfn: (...args: A) => Promise<R>,\n\t): (...args: A) => Promise<R>;\n\n\tfunction decorator<A extends unknown[], R>(\n\t\tlockNameOrOptions: string | { lockName: string; signal?: boolean },\n\t\tfn:\n\t\t\t| ((...args: A) => Promise<R>)\n\t\t\t| ((signal: AbortSignal, ...args: A) => Promise<R>),\n\t) {\n\t\tconst { lockName, signal: injectSignal } =\n\t\t\ttypeof lockNameOrOptions === \"string\"\n\t\t\t\t? { lockName: lockNameOrOptions, signal: false }\n\t\t\t\t: lockNameOrOptions;\n\n\t\treturn async (...args: A): Promise<R> => {\n\t\t\tconst release = await lock.acquire(lockName);\n\t\t\tlet released = false;\n\t\t\tconst safeRelease = async () => {\n\t\t\t\t/* v8 ignore next */\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait release();\n\t\t\t};\n\t\t\ttry {\n\t\t\t\t// Only inject the AbortSignal when the caller opted in\n\t\t\t\tif (injectSignal) {\n\t\t\t\t\treturn await (\n\t\t\t\t\t\tfn as (signal: AbortSignal, ...args: A) => Promise<R>\n\t\t\t\t\t)(release.signal, ...args);\n\t\t\t\t}\n\t\t\t\treturn await (fn as (...args: A) => Promise<R>)(...args);\n\t\t\t} finally {\n\t\t\t\tawait safeRelease();\n\t\t\t}\n\t\t};\n\t}\n\n\treturn decorator;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,28 +1,43 @@
1
1
  {
2
2
  "name": "universal-lock",
3
- "version": "0.0.2",
4
- "description": "UniversalLock",
5
- "main": "dist/index.umd.js",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight, isomorphic universal locking library with pluggable backends",
5
+ "sideEffects": false,
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "main": "dist/index.js",
10
+ "module": "dist/index.mjs",
11
+ "browser": "dist/index.global.js",
6
12
  "types": "dist/index.d.ts",
7
- "type": "commonjs",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.mts",
17
+ "default": "./dist/index.mjs"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ }
24
+ },
8
25
  "files": [
9
26
  "/dist"
10
27
  ],
11
- "scripts": {
12
- "build": "webpack",
13
- "clean": "rm -rf dist",
14
- "format": "prettier --write --ignore-unknown .",
15
- "test": "jest --coverage",
16
- "prepare": "husky"
17
- },
18
28
  "repository": {
19
29
  "type": "git",
20
- "url": "https://github.com/lucasrainett/universal-lock"
30
+ "url": "https://github.com/lucasrainett/universal-lock",
31
+ "directory": "packages/core"
21
32
  },
22
33
  "keywords": [
23
- "UniversalLock",
24
- "Database",
25
- "nosql"
34
+ "lock",
35
+ "mutex",
36
+ "distributed-lock",
37
+ "locking",
38
+ "concurrency",
39
+ "semaphore",
40
+ "isomorphic"
26
41
  ],
27
42
  "author": {
28
43
  "name": "Lucas Rainett",
@@ -30,21 +45,11 @@
30
45
  "url": "https://github.com/lucasrainett"
31
46
  },
32
47
  "license": "MIT",
33
- "packageManager": "pnpm@10.11.1",
34
- "devDependencies": {
35
- "@types/jest": "^29.5.14",
36
- "husky": "^9.1.7",
37
- "jest": "^29.7.0",
38
- "lint-staged": "^16.1.0",
39
- "prettier": "^3.5.3",
40
- "ts-jest": "^29.3.4",
41
- "ts-loader": "^9.5.2",
42
- "ts-node": "^10.9.2",
43
- "typescript": "^5.8.3",
44
- "webpack": "^5.99.9",
45
- "webpack-cli": "^6.0.1"
46
- },
47
48
  "dependencies": {
48
- "rimraf": "^6.0.1"
49
+ "@universal-lock/types": "1.0.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "clean": "rm -rf dist"
49
54
  }
50
- }
55
+ }
@@ -1,2 +0,0 @@
1
- import { Backend } from "../types";
2
- export declare const fileBackendFactory: (lockFolderPath: string) => Backend;
@@ -1,2 +0,0 @@
1
- import { BackendFactory } from "../types";
2
- export declare const memoryBackendFactory: BackendFactory;
package/dist/index.umd.js DELETED
@@ -1,182 +0,0 @@
1
- (function webpackUniversalModuleDefinition(root, factory) {
2
- if(typeof exports === 'object' && typeof module === 'object')
3
- module.exports = factory();
4
- else if(typeof define === 'function' && define.amd)
5
- define([], factory);
6
- else if(typeof exports === 'object')
7
- exports["UniversalLock"] = factory();
8
- else
9
- root["UniversalLock"] = factory();
10
- })(this, function() {
11
- return /******/ (function() { // webpackBootstrap
12
- /******/ "use strict";
13
- /******/ var __webpack_modules__ = ({
14
-
15
- /***/ 112:
16
- /***/ (function(__unused_webpack_module, exports) {
17
-
18
-
19
- Object.defineProperty(exports, "__esModule", ({ value: true }));
20
- exports.memoryBackendFactory = void 0;
21
- exports.memoryBackendFactory = ((locks = {}) => {
22
- const setup = async () => { };
23
- const acquire = async (lockName, stale) => {
24
- const now = Date.now();
25
- const lockExists = !!locks[lockName];
26
- const lockExpired = lockExists && locks[lockName] + stale < now;
27
- if (!lockExists || lockExpired) {
28
- locks[lockName] = now;
29
- }
30
- else {
31
- throw new Error(`${lockName} already locked`);
32
- }
33
- };
34
- const renew = async (lockName) => {
35
- locks[lockName] = Date.now();
36
- };
37
- const release = async (lockName) => {
38
- delete locks[lockName];
39
- };
40
- return {
41
- setup,
42
- acquire,
43
- renew,
44
- release,
45
- };
46
- });
47
-
48
-
49
- /***/ }),
50
-
51
- /***/ 340:
52
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
53
-
54
-
55
- Object.defineProperty(exports, "__esModule", ({ value: true }));
56
- exports.lockFactory = lockFactory;
57
- exports.lockDecoratoryFactory = lockDecoratoryFactory;
58
- const util_1 = __webpack_require__(639);
59
- const defaultConfiguration = {
60
- acquireInterval: 250,
61
- acquireFailTimeout: 5000,
62
- stale: 1000,
63
- renewInterval: 250,
64
- runningTimeout: 2000,
65
- };
66
- function lockFactory({ setup, acquire, renew, release }, configuration = {}) {
67
- const { acquireInterval, acquireFailTimeout, stale, renewInterval, runningTimeout, } = Object.assign(Object.assign({}, defaultConfiguration), configuration);
68
- const setupPromise = setup();
69
- return {
70
- async acquire(lockName) {
71
- await setupPromise;
72
- return new Promise((resolve) => {
73
- const stopAcquireInterval = (0, util_1.asyncInterval)(async () => {
74
- try {
75
- await acquire(lockName, stale);
76
- const stopRenewInterval = (0, util_1.asyncInterval)(async () => {
77
- await renew(lockName);
78
- }, renewInterval);
79
- const releaseLock = async () => {
80
- await stopRenewInterval();
81
- await release(lockName);
82
- };
83
- stopAcquireInterval().then(() => {
84
- resolve(releaseLock);
85
- });
86
- }
87
- catch (e) { }
88
- }, acquireInterval);
89
- });
90
- }
91
- };
92
- }
93
- function lockDecoratoryFactory(lock) {
94
- return (lockName, fn) => {
95
- return async (...args) => {
96
- const release = await lock.acquire(lockName);
97
- try {
98
- return await fn(...args);
99
- }
100
- finally {
101
- await release();
102
- }
103
- };
104
- };
105
- }
106
-
107
-
108
- /***/ }),
109
-
110
- /***/ 639:
111
- /***/ (function(__unused_webpack_module, exports) {
112
-
113
-
114
- Object.defineProperty(exports, "__esModule", ({ value: true }));
115
- exports.asyncInterval = exports.sleep = void 0;
116
- const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
117
- exports.sleep = sleep;
118
- const asyncInterval = (handler, timeout) => {
119
- let running = true;
120
- (async () => {
121
- while (running) {
122
- await handler();
123
- if (!running)
124
- break;
125
- await (0, exports.sleep)(timeout);
126
- }
127
- })();
128
- return async () => {
129
- running = false;
130
- };
131
- };
132
- exports.asyncInterval = asyncInterval;
133
-
134
-
135
- /***/ })
136
-
137
- /******/ });
138
- /************************************************************************/
139
- /******/ // The module cache
140
- /******/ var __webpack_module_cache__ = {};
141
- /******/
142
- /******/ // The require function
143
- /******/ function __webpack_require__(moduleId) {
144
- /******/ // Check if module is in cache
145
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
146
- /******/ if (cachedModule !== undefined) {
147
- /******/ return cachedModule.exports;
148
- /******/ }
149
- /******/ // Create a new module (and put it into the cache)
150
- /******/ var module = __webpack_module_cache__[moduleId] = {
151
- /******/ // no module.id needed
152
- /******/ // no module.loaded needed
153
- /******/ exports: {}
154
- /******/ };
155
- /******/
156
- /******/ // Execute the module function
157
- /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
158
- /******/
159
- /******/ // Return the exports of the module
160
- /******/ return module.exports;
161
- /******/ }
162
- /******/
163
- /************************************************************************/
164
- var __webpack_exports__ = {};
165
- // This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
166
- !function() {
167
- var exports = __webpack_exports__;
168
-
169
- Object.defineProperty(exports, "__esModule", ({ value: true }));
170
- exports.lockDecoratoryFactory = exports.lockFactory = exports.memoryBackendFactory = void 0;
171
- const lock_1 = __webpack_require__(340);
172
- Object.defineProperty(exports, "lockFactory", ({ enumerable: true, get: function () { return lock_1.lockFactory; } }));
173
- Object.defineProperty(exports, "lockDecoratoryFactory", ({ enumerable: true, get: function () { return lock_1.lockDecoratoryFactory; } }));
174
- const MemoryBackend_1 = __webpack_require__(112);
175
- Object.defineProperty(exports, "memoryBackendFactory", ({ enumerable: true, get: function () { return MemoryBackend_1.memoryBackendFactory; } }));
176
-
177
- }();
178
- /******/ return __webpack_exports__;
179
- /******/ })()
180
- ;
181
- });
182
- //# sourceMappingURL=index.umd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.umd.js","mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;ACVA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;AC5BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;ACnDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;ACnBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;ACvBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://UniversalLock/webpack/universalModuleDefinition","webpack://UniversalLock/./src/backends/MemoryBackend.ts","webpack://UniversalLock/./src/lock.ts","webpack://UniversalLock/./src/util.ts","webpack://UniversalLock/webpack/bootstrap","webpack://UniversalLock/./src/index.ts"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"UniversalLock\"] = factory();\n\telse\n\t\troot[\"UniversalLock\"] = factory();\n})(this, function() {\nreturn ","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.memoryBackendFactory = void 0;\nexports.memoryBackendFactory = ((locks = {}) => {\n const setup = async () => { };\n const acquire = async (lockName, stale) => {\n const now = Date.now();\n const lockExists = !!locks[lockName];\n const lockExpired = lockExists && locks[lockName] + stale < now;\n if (!lockExists || lockExpired) {\n locks[lockName] = now;\n }\n else {\n throw new Error(`${lockName} already locked`);\n }\n };\n const renew = async (lockName) => {\n locks[lockName] = Date.now();\n };\n const release = async (lockName) => {\n delete locks[lockName];\n };\n return {\n setup,\n acquire,\n renew,\n release,\n };\n});\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.lockFactory = lockFactory;\nexports.lockDecoratoryFactory = lockDecoratoryFactory;\nconst util_1 = require(\"./util\");\nconst defaultConfiguration = {\n acquireInterval: 250,\n acquireFailTimeout: 5000,\n stale: 1000,\n renewInterval: 250,\n runningTimeout: 2000,\n};\nfunction lockFactory({ setup, acquire, renew, release }, configuration = {}) {\n const { acquireInterval, acquireFailTimeout, stale, renewInterval, runningTimeout, } = Object.assign(Object.assign({}, defaultConfiguration), configuration);\n const setupPromise = setup();\n return {\n async acquire(lockName) {\n await setupPromise;\n return new Promise((resolve) => {\n const stopAcquireInterval = (0, util_1.asyncInterval)(async () => {\n try {\n await acquire(lockName, stale);\n const stopRenewInterval = (0, util_1.asyncInterval)(async () => {\n await renew(lockName);\n }, renewInterval);\n const releaseLock = async () => {\n await stopRenewInterval();\n await release(lockName);\n };\n stopAcquireInterval().then(() => {\n resolve(releaseLock);\n });\n }\n catch (e) { }\n }, acquireInterval);\n });\n }\n };\n}\nfunction lockDecoratoryFactory(lock) {\n return (lockName, fn) => {\n return async (...args) => {\n const release = await lock.acquire(lockName);\n try {\n return await fn(...args);\n }\n finally {\n await release();\n }\n };\n };\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.asyncInterval = exports.sleep = void 0;\nconst sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));\nexports.sleep = sleep;\nconst asyncInterval = (handler, timeout) => {\n let running = true;\n (async () => {\n while (running) {\n await handler();\n if (!running)\n break;\n await (0, exports.sleep)(timeout);\n }\n })();\n return async () => {\n running = false;\n };\n};\nexports.asyncInterval = asyncInterval;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.lockDecoratoryFactory = exports.lockFactory = exports.memoryBackendFactory = void 0;\nconst lock_1 = require(\"./lock\");\nObject.defineProperty(exports, \"lockFactory\", { enumerable: true, get: function () { return lock_1.lockFactory; } });\nObject.defineProperty(exports, \"lockDecoratoryFactory\", { enumerable: true, get: function () { return lock_1.lockDecoratoryFactory; } });\nconst MemoryBackend_1 = require(\"./backends/MemoryBackend\");\nObject.defineProperty(exports, \"memoryBackendFactory\", { enumerable: true, get: function () { return MemoryBackend_1.memoryBackendFactory; } });\n"],"names":[],"sourceRoot":""}
package/dist/lock.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import { AsyncFunction, Lock, Backend, LockConfiguration } from "./types";
2
- export declare function lockFactory({ setup, acquire, renew, release }: Backend, configuration?: Partial<LockConfiguration>): Lock;
3
- export declare function lockDecoratoryFactory(lock: Lock): <F extends AsyncFunction>(lockName: string, fn: F) => (...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>;
package/dist/types.d.ts DELETED
@@ -1,24 +0,0 @@
1
- export type AsyncFunction<R extends any = any, P extends any[] = any[]> = (...args: P) => Promise<R>;
2
- export type BackendSetupFunction = () => Promise<void>;
3
- export type BackendAcquireFunction = (lockName: string, stale: number) => Promise<void>;
4
- export type BackendRenewFunction = (lockName: string) => Promise<void>;
5
- export type BackendReleaseFunction = (lockName: string) => Promise<void>;
6
- export type Backend = {
7
- setup: BackendSetupFunction;
8
- acquire: BackendAcquireFunction;
9
- renew: BackendRenewFunction;
10
- release: BackendReleaseFunction;
11
- };
12
- export type BackendFactory = (...args: any[]) => Backend;
13
- export type LockConfiguration = {
14
- acquireInterval: number;
15
- acquireFailTimeout: number;
16
- stale: number;
17
- renewInterval: number;
18
- runningTimeout: number;
19
- };
20
- export type LockReleaseFunction = () => Promise<void>;
21
- export type LockAcquireFunction = (lockName: string) => Promise<LockReleaseFunction>;
22
- export type Lock = {
23
- acquire: LockAcquireFunction;
24
- };
package/dist/util.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import { AsyncFunction } from "./types";
2
- export declare const sleep: (ms: number) => Promise<unknown>;
3
- export declare const asyncInterval: <H extends AsyncFunction<void, []>>(handler: H, timeout: number) => () => Promise<void>;