@universal-lock/web-locks 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Lucas Rainett
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @universal-lock/web-locks
2
+
3
+ Web Locks API backend for [`universal-lock`](https://github.com/lucasrainett/universal-lock). Provides cross-tab locking using the native browser [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install universal-lock @universal-lock/web-locks
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### ESM
14
+
15
+ ```typescript
16
+ import { lockFactory } from "universal-lock";
17
+ import { createBackend } from "@universal-lock/web-locks";
18
+
19
+ const lock = lockFactory(createBackend());
20
+
21
+ const release = await lock.acquire("my-resource");
22
+ try {
23
+ // critical section — safe across browser tabs
24
+ } finally {
25
+ await release();
26
+ }
27
+ ```
28
+
29
+ ### CommonJS
30
+
31
+ ```javascript
32
+ const { lockFactory } = require("universal-lock");
33
+ const { createBackend } = require("@universal-lock/web-locks");
34
+
35
+ const lock = lockFactory(createBackend());
36
+ ```
37
+
38
+ ### Browser (IIFE)
39
+
40
+ ```html
41
+ <script src="https://unpkg.com/@universal-lock/web-locks/dist/index.global.js"></script>
42
+ <script src="https://unpkg.com/universal-lock/dist/index.global.js"></script>
43
+ <script>
44
+ const lock = UniversalLock.lockFactory(UniversalLockWebLocks.createBackend());
45
+ </script>
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### `createBackend()`
51
+
52
+ Creates a Web Locks API backend instance. No arguments required.
53
+
54
+ ```typescript
55
+ import { createBackend } from "@universal-lock/web-locks";
56
+
57
+ const backend = createBackend();
58
+ ```
59
+
60
+ ## How It Works
61
+
62
+ Uses `navigator.locks.request()` with `ifAvailable: true` mode. Locks are managed natively by the browser — no external storage is needed. Lock references and release callbacks are stored in memory.
63
+
64
+ ## Advantages
65
+
66
+ - **Automatic cleanup** — locks are released if a tab crashes or navigates away
67
+ - **True atomicity** — OS-level lock guarantees, no race conditions
68
+ - **No storage needed** — no localStorage or cookies involved
69
+
70
+ ## When to Use
71
+
72
+ - Cross-tab locking in modern browsers (recommended over localStorage backend)
73
+ - Applications that need reliable lock cleanup on tab crash
74
+
75
+ ## Limitations
76
+
77
+ - Throws during `setup` if the Web Locks API is unavailable. For older browsers, use [`@universal-lock/local-storage`](https://www.npmjs.com/package/@universal-lock/local-storage) as a fallback.
78
+ - Browser-only — not available in Node.js.
79
+
80
+ ## Browser Support
81
+
82
+ The Web Locks API is supported in Chrome 69+, Firefox 96+, Safari 15.4+, and Edge 79+. See [Can I use](https://caniuse.com/web-locks) for details.
83
+
84
+ ## License
85
+
86
+ [MIT](https://github.com/lucasrainett/universal-lock/blob/master/LICENSE)
@@ -0,0 +1,5 @@
1
+ import { BackendFactory } from '@universal-lock/types';
2
+
3
+ declare const createBackend: BackendFactory;
4
+
5
+ export { createBackend };
@@ -0,0 +1,5 @@
1
+ import { BackendFactory } from '@universal-lock/types';
2
+
3
+ declare const createBackend: BackendFactory;
4
+
5
+ export { createBackend };
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var UniversalLockWebLocks = (() => {
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
+ createBackend: () => createBackend
25
+ });
26
+ var createBackend = () => {
27
+ const locks = {};
28
+ const setup = async () => {
29
+ if (typeof navigator === "undefined" || !navigator.locks) {
30
+ throw new Error("Web Locks API is not available");
31
+ }
32
+ };
33
+ const acquire = async (lockName, _stale, lockId) => {
34
+ await new Promise((resolve, reject) => {
35
+ navigator.locks.request(lockName, { ifAvailable: true }, (lock) => {
36
+ if (!lock) {
37
+ reject(new Error(`${lockName} already locked`));
38
+ return Promise.resolve();
39
+ }
40
+ return new Promise((releaseResolve) => {
41
+ locks[lockName] = { lockId, release: releaseResolve };
42
+ resolve();
43
+ });
44
+ });
45
+ });
46
+ };
47
+ const renew = async (lockName, lockId) => {
48
+ const existing = locks[lockName];
49
+ if (!existing) throw new Error(`${lockName} not locked`);
50
+ if (existing.lockId !== lockId)
51
+ throw new Error(`${lockName} not owned by caller`);
52
+ };
53
+ const release = async (lockName, lockId) => {
54
+ const existing = locks[lockName];
55
+ if (!existing) throw new Error(`${lockName} not locked`);
56
+ if (existing.lockId !== lockId)
57
+ throw new Error(`${lockName} not owned by caller`);
58
+ existing.release();
59
+ delete locks[lockName];
60
+ };
61
+ return {
62
+ setup,
63
+ acquire,
64
+ renew,
65
+ release
66
+ };
67
+ };
68
+ return __toCommonJS(index_exports);
69
+ })();
70
+ //# sourceMappingURL=index.global.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBackend,\n\tBackendAcquireFunction,\n\tBackendFactory,\n\tBackendReleaseFunction,\n\tBackendRenewFunction,\n\tBackendSetupFunction,\n\tCallbackLockEntry,\n} from \"@universal-lock/types\";\n\nexport const createBackend: BackendFactory = (): Backend => {\n\tconst locks: Record<string, CallbackLockEntry> = {};\n\n\tconst setup: BackendSetupFunction = async () => {\n\t\tif (typeof navigator === \"undefined\" || !navigator.locks) {\n\t\t\tthrow new Error(\"Web Locks API is not available\");\n\t\t}\n\t};\n\n\t// The Web Locks API holds a lock for the duration of the callback's returned promise.\n\t// To control when the lock is released, we return a promise whose resolve function\n\t// (releaseResolve) is stored and called later in the release() method.\n\t// ifAvailable: true makes this non-blocking — fails immediately if the lock is held.\n\tconst acquire: BackendAcquireFunction = async (\n\t\tlockName,\n\t\t_stale,\n\t\tlockId,\n\t) => {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tnavigator.locks.request(lockName, { ifAvailable: true }, (lock) => {\n\t\t\t\tif (!lock) {\n\t\t\t\t\treject(new Error(`${lockName} already locked`));\n\t\t\t\t\t// Must return a resolved promise to complete the lock request callback\n\t\t\t\t\treturn Promise.resolve();\n\t\t\t\t}\n\n\t\t\t\t// Return an unresolved promise — the browser holds the lock until this resolves\n\t\t\t\treturn new Promise<void>((releaseResolve) => {\n\t\t\t\t\tlocks[lockName] = { lockId, release: releaseResolve };\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\t// Renew is a no-op for Web Locks — the browser holds the lock until the promise resolves,\n\t// so there is no TTL to refresh. We only validate ownership here.\n\tconst renew: BackendRenewFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t};\n\n\tconst release: BackendReleaseFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t\texisting.release();\n\t\tdelete locks[lockName];\n\t};\n\n\treturn {\n\t\tsetup,\n\t\tacquire,\n\t\trenew,\n\t\trelease,\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,gBAAgC,MAAe;AAC3D,UAAM,QAA2C,CAAC;AAElD,UAAM,QAA8B,YAAY;AAC/C,UAAI,OAAO,cAAc,eAAe,CAAC,UAAU,OAAO;AACzD,cAAM,IAAI,MAAM,gCAAgC;AAAA,MACjD;AAAA,IACD;AAMA,UAAM,UAAkC,OACvC,UACA,QACA,WACI;AACJ,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,kBAAU,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,GAAG,CAAC,SAAS;AAClE,cAAI,CAAC,MAAM;AACV,mBAAO,IAAI,MAAM,GAAG,QAAQ,iBAAiB,CAAC;AAE9C,mBAAO,QAAQ,QAAQ;AAAA,UACxB;AAGA,iBAAO,IAAI,QAAc,CAAC,mBAAmB;AAC5C,kBAAM,QAAQ,IAAI,EAAE,QAAQ,SAAS,eAAe;AACpD,oBAAQ;AAAA,UACT,CAAC;AAAA,QACF,CAAC;AAAA,MACF,CAAC;AAAA,IACF;AAIA,UAAM,QAA8B,OAAO,UAAU,WAAW;AAC/D,YAAM,WAAW,MAAM,QAAQ;AAC/B,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,UAAI,SAAS,WAAW;AACvB,cAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAAA,IACnD;AAEA,UAAM,UAAkC,OAAO,UAAU,WAAW;AACnE,YAAM,WAAW,MAAM,QAAQ;AAC/B,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,UAAI,SAAS,WAAW;AACvB,cAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAClD,eAAS,QAAQ;AACjB,aAAO,MAAM,QAAQ;AAAA,IACtB;AAEA,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;","names":[]}
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
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
+ createBackend: () => createBackend
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var createBackend = () => {
27
+ const locks = {};
28
+ const setup = async () => {
29
+ if (typeof navigator === "undefined" || !navigator.locks) {
30
+ throw new Error("Web Locks API is not available");
31
+ }
32
+ };
33
+ const acquire = async (lockName, _stale, lockId) => {
34
+ await new Promise((resolve, reject) => {
35
+ navigator.locks.request(lockName, { ifAvailable: true }, (lock) => {
36
+ if (!lock) {
37
+ reject(new Error(`${lockName} already locked`));
38
+ return Promise.resolve();
39
+ }
40
+ return new Promise((releaseResolve) => {
41
+ locks[lockName] = { lockId, release: releaseResolve };
42
+ resolve();
43
+ });
44
+ });
45
+ });
46
+ };
47
+ const renew = async (lockName, lockId) => {
48
+ const existing = locks[lockName];
49
+ if (!existing) throw new Error(`${lockName} not locked`);
50
+ if (existing.lockId !== lockId)
51
+ throw new Error(`${lockName} not owned by caller`);
52
+ };
53
+ const release = async (lockName, lockId) => {
54
+ const existing = locks[lockName];
55
+ if (!existing) throw new Error(`${lockName} not locked`);
56
+ if (existing.lockId !== lockId)
57
+ throw new Error(`${lockName} not owned by caller`);
58
+ existing.release();
59
+ delete locks[lockName];
60
+ };
61
+ return {
62
+ setup,
63
+ acquire,
64
+ renew,
65
+ release
66
+ };
67
+ };
68
+ // Annotate the CommonJS export names for ESM import in node:
69
+ 0 && (module.exports = {
70
+ createBackend
71
+ });
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBackend,\n\tBackendAcquireFunction,\n\tBackendFactory,\n\tBackendReleaseFunction,\n\tBackendRenewFunction,\n\tBackendSetupFunction,\n\tCallbackLockEntry,\n} from \"@universal-lock/types\";\n\nexport const createBackend: BackendFactory = (): Backend => {\n\tconst locks: Record<string, CallbackLockEntry> = {};\n\n\tconst setup: BackendSetupFunction = async () => {\n\t\tif (typeof navigator === \"undefined\" || !navigator.locks) {\n\t\t\tthrow new Error(\"Web Locks API is not available\");\n\t\t}\n\t};\n\n\t// The Web Locks API holds a lock for the duration of the callback's returned promise.\n\t// To control when the lock is released, we return a promise whose resolve function\n\t// (releaseResolve) is stored and called later in the release() method.\n\t// ifAvailable: true makes this non-blocking — fails immediately if the lock is held.\n\tconst acquire: BackendAcquireFunction = async (\n\t\tlockName,\n\t\t_stale,\n\t\tlockId,\n\t) => {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tnavigator.locks.request(lockName, { ifAvailable: true }, (lock) => {\n\t\t\t\tif (!lock) {\n\t\t\t\t\treject(new Error(`${lockName} already locked`));\n\t\t\t\t\t// Must return a resolved promise to complete the lock request callback\n\t\t\t\t\treturn Promise.resolve();\n\t\t\t\t}\n\n\t\t\t\t// Return an unresolved promise — the browser holds the lock until this resolves\n\t\t\t\treturn new Promise<void>((releaseResolve) => {\n\t\t\t\t\tlocks[lockName] = { lockId, release: releaseResolve };\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\t// Renew is a no-op for Web Locks — the browser holds the lock until the promise resolves,\n\t// so there is no TTL to refresh. We only validate ownership here.\n\tconst renew: BackendRenewFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t};\n\n\tconst release: BackendReleaseFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t\texisting.release();\n\t\tdelete locks[lockName];\n\t};\n\n\treturn {\n\t\tsetup,\n\t\tacquire,\n\t\trenew,\n\t\trelease,\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,IAAM,gBAAgC,MAAe;AAC3D,QAAM,QAA2C,CAAC;AAElD,QAAM,QAA8B,YAAY;AAC/C,QAAI,OAAO,cAAc,eAAe,CAAC,UAAU,OAAO;AACzD,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACjD;AAAA,EACD;AAMA,QAAM,UAAkC,OACvC,UACA,QACA,WACI;AACJ,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,gBAAU,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,GAAG,CAAC,SAAS;AAClE,YAAI,CAAC,MAAM;AACV,iBAAO,IAAI,MAAM,GAAG,QAAQ,iBAAiB,CAAC;AAE9C,iBAAO,QAAQ,QAAQ;AAAA,QACxB;AAGA,eAAO,IAAI,QAAc,CAAC,mBAAmB;AAC5C,gBAAM,QAAQ,IAAI,EAAE,QAAQ,SAAS,eAAe;AACpD,kBAAQ;AAAA,QACT,CAAC;AAAA,MACF,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAIA,QAAM,QAA8B,OAAO,UAAU,WAAW;AAC/D,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAAA,EACnD;AAEA,QAAM,UAAkC,OAAO,UAAU,WAAW;AACnE,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAClD,aAAS,QAAQ;AACjB,WAAO,MAAM,QAAQ;AAAA,EACtB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,47 @@
1
+ // src/index.ts
2
+ var createBackend = () => {
3
+ const locks = {};
4
+ const setup = async () => {
5
+ if (typeof navigator === "undefined" || !navigator.locks) {
6
+ throw new Error("Web Locks API is not available");
7
+ }
8
+ };
9
+ const acquire = async (lockName, _stale, lockId) => {
10
+ await new Promise((resolve, reject) => {
11
+ navigator.locks.request(lockName, { ifAvailable: true }, (lock) => {
12
+ if (!lock) {
13
+ reject(new Error(`${lockName} already locked`));
14
+ return Promise.resolve();
15
+ }
16
+ return new Promise((releaseResolve) => {
17
+ locks[lockName] = { lockId, release: releaseResolve };
18
+ resolve();
19
+ });
20
+ });
21
+ });
22
+ };
23
+ const renew = async (lockName, lockId) => {
24
+ const existing = locks[lockName];
25
+ if (!existing) throw new Error(`${lockName} not locked`);
26
+ if (existing.lockId !== lockId)
27
+ throw new Error(`${lockName} not owned by caller`);
28
+ };
29
+ const release = async (lockName, lockId) => {
30
+ const existing = locks[lockName];
31
+ if (!existing) throw new Error(`${lockName} not locked`);
32
+ if (existing.lockId !== lockId)
33
+ throw new Error(`${lockName} not owned by caller`);
34
+ existing.release();
35
+ delete locks[lockName];
36
+ };
37
+ return {
38
+ setup,
39
+ acquire,
40
+ renew,
41
+ release
42
+ };
43
+ };
44
+ export {
45
+ createBackend
46
+ };
47
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBackend,\n\tBackendAcquireFunction,\n\tBackendFactory,\n\tBackendReleaseFunction,\n\tBackendRenewFunction,\n\tBackendSetupFunction,\n\tCallbackLockEntry,\n} from \"@universal-lock/types\";\n\nexport const createBackend: BackendFactory = (): Backend => {\n\tconst locks: Record<string, CallbackLockEntry> = {};\n\n\tconst setup: BackendSetupFunction = async () => {\n\t\tif (typeof navigator === \"undefined\" || !navigator.locks) {\n\t\t\tthrow new Error(\"Web Locks API is not available\");\n\t\t}\n\t};\n\n\t// The Web Locks API holds a lock for the duration of the callback's returned promise.\n\t// To control when the lock is released, we return a promise whose resolve function\n\t// (releaseResolve) is stored and called later in the release() method.\n\t// ifAvailable: true makes this non-blocking — fails immediately if the lock is held.\n\tconst acquire: BackendAcquireFunction = async (\n\t\tlockName,\n\t\t_stale,\n\t\tlockId,\n\t) => {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tnavigator.locks.request(lockName, { ifAvailable: true }, (lock) => {\n\t\t\t\tif (!lock) {\n\t\t\t\t\treject(new Error(`${lockName} already locked`));\n\t\t\t\t\t// Must return a resolved promise to complete the lock request callback\n\t\t\t\t\treturn Promise.resolve();\n\t\t\t\t}\n\n\t\t\t\t// Return an unresolved promise — the browser holds the lock until this resolves\n\t\t\t\treturn new Promise<void>((releaseResolve) => {\n\t\t\t\t\tlocks[lockName] = { lockId, release: releaseResolve };\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\t// Renew is a no-op for Web Locks — the browser holds the lock until the promise resolves,\n\t// so there is no TTL to refresh. We only validate ownership here.\n\tconst renew: BackendRenewFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t};\n\n\tconst release: BackendReleaseFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t\texisting.release();\n\t\tdelete locks[lockName];\n\t};\n\n\treturn {\n\t\tsetup,\n\t\tacquire,\n\t\trenew,\n\t\trelease,\n\t};\n};\n"],"mappings":";AAUO,IAAM,gBAAgC,MAAe;AAC3D,QAAM,QAA2C,CAAC;AAElD,QAAM,QAA8B,YAAY;AAC/C,QAAI,OAAO,cAAc,eAAe,CAAC,UAAU,OAAO;AACzD,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACjD;AAAA,EACD;AAMA,QAAM,UAAkC,OACvC,UACA,QACA,WACI;AACJ,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,gBAAU,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,GAAG,CAAC,SAAS;AAClE,YAAI,CAAC,MAAM;AACV,iBAAO,IAAI,MAAM,GAAG,QAAQ,iBAAiB,CAAC;AAE9C,iBAAO,QAAQ,QAAQ;AAAA,QACxB;AAGA,eAAO,IAAI,QAAc,CAAC,mBAAmB;AAC5C,gBAAM,QAAQ,IAAI,EAAE,QAAQ,SAAS,eAAe;AACpD,kBAAQ;AAAA,QACT,CAAC;AAAA,MACF,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAIA,QAAM,QAA8B,OAAO,UAAU,WAAW;AAC/D,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAAA,EACnD;AAEA,QAAM,UAAkC,OAAO,UAAU,WAAW;AACnE,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAClD,aAAS,QAAQ;AACjB,WAAO,MAAM,QAAQ;AAAA,EACtB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@universal-lock/web-locks",
3
+ "version": "1.0.0",
4
+ "description": "Web Locks API backend for universal-lock",
5
+ "sideEffects": false,
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "browser": "dist/index.global.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.mts",
14
+ "default": "./dist/index.mjs"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "/dist"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/lucasrainett/universal-lock",
28
+ "directory": "packages/web-locks"
29
+ },
30
+ "keywords": [
31
+ "universal-lock",
32
+ "lock",
33
+ "mutex",
34
+ "web-locks",
35
+ "browser",
36
+ "backend"
37
+ ],
38
+ "author": {
39
+ "name": "Lucas Rainett",
40
+ "email": "lucas@rainett.dev",
41
+ "url": "https://github.com/lucasrainett"
42
+ },
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@universal-lock/types": "1.0.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "clean": "rm -rf dist"
50
+ }
51
+ }