universal-lock 0.0.1 → 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 +171 -1
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -2
- package/dist/index.global.js +175 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +178 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +150 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -32
- package/dist/index.umd.js +0 -67
- package/dist/index.umd.js.map +0 -1
- package/dist/util.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1 +1,171 @@
|
|
|
1
|
-
#
|
|
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)
|
package/dist/index.d.mts
ADDED
|
@@ -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,2 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
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":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,MAAM,aAAa,MACzB,MAAM;AAAA,IAAK,EAAE,QAAQ,EAAE;AAAA,IAAG,MACzB,KAAK,MAAM,KAAK,OAAO,IAAI,UAAW,EACpC,SAAS,EAAE,EACX,SAAS,GAAG,GAAG;AAAA,EAClB,EAAE,KAAK,EAAE;AAEH,MAAM,QAAQ,OAAO,OAC3B,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAS1C,MAAM,gBAAgB,CAC5B,SACA,YACI;AACJ,QAAI,UAAU;AACd,UAAM,QAAQ,YAAY;AACzB,aAAO,SAAS;AACf,cAAM,QAAQ;AACd,YAAI,CAAC,QAAS;AACd,cAAM,MAAM,OAAO;AAAA,MACpB;AAAA,IACD,GAAG;AACH,WAAO,YAAY;AAClB,gBAAU;AACV,YAAM;AAAA,IACP;AAAA,EACD;;;AC5BA,MAAM,OAAO,MAAM;AAAA,EAAC;AAEpB,MAAM,uBAA0C;AAAA,IAC/C,iBAAiB;AAAA,IACjB,oBAAoB;AAAA,IACpB,OAAO;AAAA,IACP,eAAe;AAAA,IACf,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,SAAS;AAAA,EACV;AAEO,WAAS,YACf,EAAE,OAAO,SAAS,OAAO,QAAQ,GACjC,gBAA4C,CAAC,GACtC;AACP,UAAM;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,IAAuB,EAAE,GAAG,sBAAsB,GAAG,cAAc;AAEnE,UAAM,eAAe,MAAM;AAE3B,WAAO;AAAA,MACN,MAAM,QAAQ,UAAkB;AAC/B,cAAM;AACN,eAAO,IAAI,QAA6B,CAAC,SAAS,WAAW;AAC5D,gBAAM,SAAS,WAAW;AAE1B,gBAAM,kBAAkB,IAAI,gBAAgB;AAG5C,gBAAM,iBAAiB,WAAW,YAAY;AAC7C,kBAAM,oBAAoB;AAC1B,oBAAQ,EAAE,MAAM,kBAAkB,SAAS,CAAC;AAC5C;AAAA,cACC,IAAI;AAAA,gBACH,2BAA2B,QAAQ,YAAY,kBAAkB;AAAA,cAClE;AAAA,YACD;AAAA,UACD,GAAG,kBAAkB;AAGrB,gBAAM,sBAAsB,cAAc,YAAY;AACrD,gBAAI;AACH,oBAAM,QAAQ,UAAU,OAAO,MAAM;AAAA,YACtC,QAAQ;AAEP;AAAA,YACD;AAGA,yBAAa,cAAc;AAC3B,gCAAoB;AAEpB,gBAAI,eACH;AAED,gBAAI,WAAW;AAGf,kBAAM,YAAY,YAAY;AAC7B,kBAAI,SAAU;AACd,yBAAW;AAEX,kBAAI,aAAc,cAAa,YAAY;AAC3C,oBAAM,QAAQ,UAAU,MAAM;AAC9B,sBAAQ,EAAE,MAAM,YAAY,SAAS,CAAC;AAAA,YACvC;AAGA,kBAAM,cAAc,YAAY;AAC/B,kBAAI,SAAU;AACd,oBAAM,kBAAkB;AACxB,oBAAM,UAAU;AAAA,YACjB;AAGA,kBAAM,oBAAoB,cAAc,YAAY;AACnD,kBAAI;AACH,sBAAM,MAAM,UAAU,MAAM;AAC5B,wBAAQ,EAAE,MAAM,WAAW,SAAS,CAAC;AAAA,cACtC,SAAS,OAAO;AACf,wBAAQ;AAAA,kBACP,MAAM;AAAA,kBACN;AAAA,kBACA;AAAA,gBACD,CAAC;AACD,wBAAQ;AAAA,kBACP,MAAM;AAAA,kBACN;AAAA,kBACA,QAAQ;AAAA,gBACT,CAAC;AACD,2BAAW,UAAU,aAAa;AAElC,gCAAgB,MAAM;AAEtB,kCAAkB;AAClB,sBAAM,UAAU;AAAA,cACjB;AAAA,YACD,GAAG,aAAa;AAGhB,2BAAe,WAAW,YAAY;AACrC,sBAAQ;AAAA,gBACP,MAAM;AAAA,gBACN;AAAA,gBACA,QAAQ;AAAA,cACT,CAAC;AACD,yBAAW,UAAU,SAAS;AAC9B,8BAAgB,MAAM;AACtB,oBAAM,YAAY;AAAA,YACnB,GAAG,WAAW;AAEd,oBAAQ,EAAE,MAAM,YAAY,SAAS,CAAC;AAItC,kBAAM,YAAY,OAAO,OAAO,aAAa;AAAA,cAC5C,QAAQ,gBAAgB;AAAA,YACzB,CAAC;AACD,oBAAQ,SAAS;AAAA,UAClB,GAAG,eAAe;AAAA,QACnB,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAWO,WAAS,qBAAqB,MAAY;AAmBhD,aAAS,UACR,mBACA,IAGC;AACD,YAAM,EAAE,UAAU,QAAQ,aAAa,IACtC,OAAO,sBAAsB,WAC1B,EAAE,UAAU,mBAAmB,QAAQ,MAAM,IAC7C;AAEJ,aAAO,UAAU,SAAwB;AACxC,cAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ;AAC3C,YAAI,WAAW;AACf,cAAM,cAAc,YAAY;AAE/B,cAAI,SAAU;AACd,qBAAW;AACX,gBAAM,QAAQ;AAAA,QACf;AACA,YAAI;AAEH,cAAI,cAAc;AACjB,mBAAO,MACN,GACC,QAAQ,QAAQ,GAAG,IAAI;AAAA,UAC1B;AACA,iBAAO,MAAO,GAAkC,GAAG,IAAI;AAAA,QACxD,UAAE;AACD,gBAAM,YAAY;AAAA,QACnB;AAAA,MACD;AAAA,IACD;AAEA,WAAO;AAAA,EACR;","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":"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","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,29 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-lock",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
34
|
+
"lock",
|
|
35
|
+
"mutex",
|
|
36
|
+
"distributed-lock",
|
|
37
|
+
"locking",
|
|
38
|
+
"concurrency",
|
|
39
|
+
"semaphore",
|
|
40
|
+
"isomorphic"
|
|
27
41
|
],
|
|
28
42
|
"author": {
|
|
29
43
|
"name": "Lucas Rainett",
|
|
@@ -31,21 +45,11 @@
|
|
|
31
45
|
"url": "https://github.com/lucasrainett"
|
|
32
46
|
},
|
|
33
47
|
"license": "MIT",
|
|
34
|
-
"packageManager": "pnpm@10.11.1",
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/jest": "^29.5.14",
|
|
37
|
-
"husky": "^9.1.7",
|
|
38
|
-
"jest": "^29.7.0",
|
|
39
|
-
"lint-staged": "^16.1.0",
|
|
40
|
-
"prettier": "^3.5.3",
|
|
41
|
-
"ts-jest": "^29.3.4",
|
|
42
|
-
"ts-loader": "^9.5.2",
|
|
43
|
-
"ts-node": "^10.9.2",
|
|
44
|
-
"typescript": "^5.8.3",
|
|
45
|
-
"webpack": "^5.99.9",
|
|
46
|
-
"webpack-cli": "^6.0.1"
|
|
47
|
-
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"
|
|
49
|
+
"@universal-lock/types": "1.0.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"clean": "rm -rf dist"
|
|
50
54
|
}
|
|
51
|
-
}
|
|
55
|
+
}
|
package/dist/index.umd.js
DELETED
|
@@ -1,67 +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["SnapshotDB"] = factory();
|
|
8
|
-
else
|
|
9
|
-
root["SnapshotDB"] = factory();
|
|
10
|
-
})(this, function() {
|
|
11
|
-
return /******/ (function() { // webpackBootstrap
|
|
12
|
-
/******/ "use strict";
|
|
13
|
-
/******/ var __webpack_modules__ = ({
|
|
14
|
-
|
|
15
|
-
/***/ 639:
|
|
16
|
-
/***/ (function(__unused_webpack_module, exports) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
20
|
-
exports.test = test;
|
|
21
|
-
function test() { }
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/***/ })
|
|
25
|
-
|
|
26
|
-
/******/ });
|
|
27
|
-
/************************************************************************/
|
|
28
|
-
/******/ // The module cache
|
|
29
|
-
/******/ var __webpack_module_cache__ = {};
|
|
30
|
-
/******/
|
|
31
|
-
/******/ // The require function
|
|
32
|
-
/******/ function __webpack_require__(moduleId) {
|
|
33
|
-
/******/ // Check if module is in cache
|
|
34
|
-
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
35
|
-
/******/ if (cachedModule !== undefined) {
|
|
36
|
-
/******/ return cachedModule.exports;
|
|
37
|
-
/******/ }
|
|
38
|
-
/******/ // Create a new module (and put it into the cache)
|
|
39
|
-
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
40
|
-
/******/ // no module.id needed
|
|
41
|
-
/******/ // no module.loaded needed
|
|
42
|
-
/******/ exports: {}
|
|
43
|
-
/******/ };
|
|
44
|
-
/******/
|
|
45
|
-
/******/ // Execute the module function
|
|
46
|
-
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
47
|
-
/******/
|
|
48
|
-
/******/ // Return the exports of the module
|
|
49
|
-
/******/ return module.exports;
|
|
50
|
-
/******/ }
|
|
51
|
-
/******/
|
|
52
|
-
/************************************************************************/
|
|
53
|
-
var __webpack_exports__ = {};
|
|
54
|
-
// This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
|
|
55
|
-
!function() {
|
|
56
|
-
var exports = __webpack_exports__;
|
|
57
|
-
|
|
58
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
59
|
-
const util_1 = __webpack_require__(639);
|
|
60
|
-
exports["default"] = util_1.test;
|
|
61
|
-
|
|
62
|
-
}();
|
|
63
|
-
/******/ return __webpack_exports__;
|
|
64
|
-
/******/ })()
|
|
65
|
-
;
|
|
66
|
-
});
|
|
67
|
-
//# sourceMappingURL=index.umd.js.map
|
package/dist/index.umd.js.map
DELETED
|
@@ -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;;;;;;;ACHA;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","sources":["webpack://UniversalLock/webpack/universalModuleDefinition","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.test = test;\nfunction test() { }\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 });\nconst util_1 = require(\"./util\");\nexports.default = util_1.test;\n"],"names":[],"sourceRoot":""}
|
package/dist/util.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function test(): void;
|