@lowerdeck/lock 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/.turbo/turbo-build.log +12 -0
- package/.turbo/turbo-test.log +11 -0
- package/README.md +63 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +40 -0
- package/src/index.ts +118 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mmicrobundle[0m
|
|
3
|
+
No name was provided for external module '@lowerdeck/redis' in output.globals – guessing 'redis'
|
|
4
|
+
[34mBuild "@lowerdeck/lock" to dist:[39m
|
|
5
|
+
[32m1418 B[39m: [37mindex.cjs[39m.gz
|
|
6
|
+
[32m1275 B[39m: [37mindex.cjs[39m.br
|
|
7
|
+
[32m654 B[39m: [37mindex.module.js[39m.gz
|
|
8
|
+
[32m592 B[39m: [37mindex.module.js[39m.br
|
|
9
|
+
[32m1390 B[39m: [37mindex.module.js[39m.gz
|
|
10
|
+
[32m1263 B[39m: [37mindex.module.js[39m.br
|
|
11
|
+
[32m1509 B[39m: [37mindex.umd.js[39m.gz
|
|
12
|
+
[32m1371 B[39m: [37mindex.umd.js[39m.br
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mvitest run --passWithNoTests[0m
|
|
3
|
+
[?25l
|
|
4
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/Users/tobias/code/metorial/metorial-enterprise/oss/src/packages/backend/lock[39m
|
|
5
|
+
|
|
6
|
+
No test files found, exiting with code 0
|
|
7
|
+
|
|
8
|
+
[2minclude: [22m[33m**/*.{test,spec}.?(c|m)[jt]s?(x)[39m
|
|
9
|
+
[2mexclude: [22m[33m**/node_modules/**[2m, [22m**/dist/**[2m, [22m**/cypress/**[2m, [22m**/.{idea,git,cache,output,temp}/**[2m, [22m**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*[39m
|
|
10
|
+
|
|
11
|
+
[?25h
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# `@lowerdeck/lock`
|
|
2
|
+
|
|
3
|
+
Distributed locking using Redlock for Redis. Ensures atomic operations across multiple instances with automatic lock extension and retry logic.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lowerdeck/lock
|
|
9
|
+
yarn add @lowerdeck/lock
|
|
10
|
+
bun add @lowerdeck/lock
|
|
11
|
+
pnpm add @lowerdeck/lock
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { createLock } from '@lowerdeck/lock';
|
|
18
|
+
|
|
19
|
+
const lock = createLock({
|
|
20
|
+
name: 'my-service',
|
|
21
|
+
redisUrl: 'redis://localhost:6379'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Use lock to ensure only one instance runs a critical section
|
|
25
|
+
await lock.usingLock('resource-key', async ({ passForNow }) => {
|
|
26
|
+
console.log('Inside critical section');
|
|
27
|
+
|
|
28
|
+
// If you want to pass on this execution and retry later
|
|
29
|
+
if (shouldSkip) {
|
|
30
|
+
passForNow();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await performCriticalOperation();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Ensure a function runs only once across all instances
|
|
38
|
+
await lock.doOnce('unique-task-id', async () => {
|
|
39
|
+
console.log('This will run on only one instance');
|
|
40
|
+
await oneTimeSetup();
|
|
41
|
+
});
|
|
42
|
+
// Other instances will wait and return null
|
|
43
|
+
|
|
44
|
+
// Run once and share the result with other instances
|
|
45
|
+
const result = await lock.doOnceAndReturn('compute-key', async () => {
|
|
46
|
+
console.log('Computing expensive result once');
|
|
47
|
+
return await expensiveComputation();
|
|
48
|
+
});
|
|
49
|
+
// First instance computes and caches, others get cached result
|
|
50
|
+
|
|
51
|
+
// Lock multiple resources at once
|
|
52
|
+
await lock.usingLock(['user:123', 'account:456'], async () => {
|
|
53
|
+
await transferFunds();
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
This project is licensed under the Apache License 2.0.
|
|
60
|
+
|
|
61
|
+
<div align="center">
|
|
62
|
+
<sub>Built with ❤️ by <a href="https://metorial.com">Metorial</a></sub>
|
|
63
|
+
</div>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var n=require("@lowerdeck/redis"),e=require("ioredis"),r=require("superjson"),t=require("redlock");function i(n){return n&&"object"==typeof n&&"default"in n?n:{default:n}}var u=/*#__PURE__*/i(r),o=/*#__PURE__*/i(t),c=function(n){return new Promise(function(e){return setTimeout(e,n)})};function f(n,e,r){if(!n.s){if(r instanceof s){if(!r.s)return void(r.o=f.bind(null,n,e));1&e&&(e=r.s),r=r.v}if(r&&r.then)return void r.then(f.bind(null,n,e),f.bind(null,n,2));n.s=e,n.v=r;const t=n.o;t&&t(n)}}var s=/*#__PURE__*/function(){function n(){}return n.prototype.then=function(e,r){var t=new n,i=this.s;if(i){var u=1&i?e:r;if(u){try{f(t,1,u(this.v))}catch(n){f(t,2,n)}return t}return this}return this.o=function(n){try{var i=n.v;1&n.s?f(t,1,e?e(i):i):r?f(t,1,r(i)):f(t,2,i)}catch(n){f(t,2,n)}},t},n}();function h(n){return n instanceof s&&1&n.s}exports.createLock=function(r){var t=r.redisUrl,i=Bun.hash.cityHash32(r.name),a=new e.Redis(n.parseRedisUrl(t)),v=new o.default([a],{driftFactor:.01,retryCount:50,retryDelay:200,retryJitter:200,automaticExtensionThreshold:500}),l=function(n,e){try{var r=(Array.isArray(n)?n:[n]).map(function(n){return"l:"+i+":"+n}),t=function(){try{var n=!1,i=function(){n=!0};return Promise.resolve(v.using(r,1e4,function(){return e({passForNow:i})})).then(function(e){var r,i=function(){if(n)return Promise.resolve(c(100)).then(function(){var n=t();return r=1,n})}();return i&&i.then?i.then(function(n){return r?n:e}):r?i:e})}catch(n){return Promise.reject(n)}};return t()}catch(n){return Promise.reject(n)}},d=function(n,e){try{var r=i+":"+Math.random()+":"+Date.now(),t="luniq:"+i+":"+n;return Promise.resolve(a.setnx(t,r)).then(function(i){return Promise.resolve(a.expire(t,300)).then(function(){var u;function o(n){return u?n:Promise.resolve(a.get(t)).then(function(n){var e;if(!n)return null;var r=0,t=function(n,e,r){for(var t;;){var i=n();if(h(i)&&(i=i.v),!i)return u;if(i.then){t=0;break}var u=r();if(u&&u.then){if(!h(u)){t=1;break}u=u.s}if(e){var o=e();if(o&&o.then&&!h(o)){t=2;break}}}var c=new s,a=f.bind(null,c,2);return(0===t?i.then(l):1===t?u.then(v):o.then(d)).then(void 0,a),c;function v(t){u=t;do{if(e&&(o=e())&&o.then&&!h(o))return void o.then(d).then(void 0,a);if(!(i=n())||h(i)&&!i.v)return void f(c,1,u);if(i.then)return void i.then(l).then(void 0,a);h(u=r())&&(u=u.v)}while(!u||!u.then);u.then(v).then(void 0,a)}function l(n){n?(u=r())&&u.then?u.then(v).then(void 0,a):v(u):f(c,1,u)}function d(){(i=n())?i.then?i.then(l).then(void 0,a):l(i):f(c,1,u)}}(function(){return!e&&r<25},function(){return r++},function(){function t(){return Promise.resolve(a.get("luniq:"+n+":done")).then(function(n){if(n)return e=1,null})}var i=function(){if(r>0)return Promise.resolve(c(25)).then(function(){})}();return i&&i.then?i.then(t):t()});return t&&t.then?t.then(function(n){return e?n:null}):e?t:null})}var v=function(){if(i)return Promise.resolve(l(n,function(){try{return Promise.resolve(function(n,e){try{var r=n()}catch(n){return e(!0,n)}return r&&r.then?r.then(e.bind(null,!1),e.bind(null,!0)):e(!1,r)}(e,function(n,e){return Promise.resolve(a.set("luniq:"+r+":done","1","EX",10)).then(function(){return Promise.resolve(a.expire(t,5)).then(function(){if(n)throw e;return e})})}))}catch(n){return Promise.reject(n)}})).then(function(n){return u=1,n})}();return v&&v.then?v.then(o):o(v)})})}catch(n){return Promise.reject(n)}};return{usingLock:l,doOnce:d,doOnceAndReturn:function(n,e){var r="doa:"+i+":"+n;return Promise.resolve(d(n,function(){try{return Promise.resolve(e()).then(function(n){return Promise.resolve(a.set(r,u.default.stringify(n),"EX",60)).then(function(){return n})})}catch(n){return Promise.reject(n)}})).then(function(n){var e,t=function(){if(null==n){var t=u.default.parse;return Promise.resolve(a.get(r)).then(function(n){var r=t.call(u.default,n);return e=1,r})}}();return t&&t.then?t.then(function(r){return e?r:n}):e?t:n})}}};
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../delay/dist/index.module.js","../src/index.ts"],"sourcesContent":["var e=function(e){return new Promise(function(n){return setTimeout(n,e)})};export{e as delay};\n//# sourceMappingURL=index.module.js.map\n","import { delay } from '@lowerdeck/delay';\nimport { parseRedisUrl } from '@lowerdeck/redis';\nimport { Redis } from 'ioredis';\n\n// @ts-ignore\nimport SuperJSON from 'superjson';\n\n// @ts-ignore\nimport Redlock_ from 'redlock';\n\nexport let createLock = ({ name, redisUrl }: { name: string; redisUrl: string }) => {\n let nameHash = Bun.hash.cityHash32(name);\n let redis = new Redis(parseRedisUrl(redisUrl));\n\n let redlock = new Redlock_([redis as any], {\n // The expected clock drift; for more details see:\n // http://redis.io/topics/distlock\n driftFactor: 0.01, // multiplied by lock ttl to determine drift time\n\n // The max number of times Redlock will attempt to lock a resource\n // before erroring.\n retryCount: 50,\n\n // the time in ms between attempts\n retryDelay: 200, // time in ms\n\n // the max time in ms randomly added to retries\n // to improve performance under high contention\n // see https://www.awsarchitectureblog.com/2015/03/backoff.html\n retryJitter: 200, // time in ms\n\n // The minimum remaining time on a lock before an extension is automatically\n // attempted with the `using` API.\n automaticExtensionThreshold: 500\n } as any);\n\n let usingLock = async <T>(\n key: string | string[],\n fn: (controller: { passForNow: () => void }) => Promise<T>\n ): Promise<T> => {\n let keyArray = (Array.isArray(key) ? key : [key]).map(k => `l:${nameHash}:${k}`);\n\n let runLock = async () => {\n let passingForNow = false;\n let passForNow = () => {\n passingForNow = true;\n };\n\n let result = await redlock.using(keyArray, 10_000, () => fn({ passForNow }));\n\n if (passingForNow) {\n await delay(100);\n return runLock();\n }\n\n return result;\n };\n\n return runLock();\n };\n\n let doOnce = async <T>(key: string, fn: () => Promise<T>): Promise<T | null> => {\n let id = `${nameHash}:${Math.random()}:${Date.now()}`;\n let uniquenessHashKey = `luniq:${nameHash}:${key}`;\n\n let keyWasSet = await redis.setnx(uniquenessHashKey, id);\n await redis.expire(uniquenessHashKey, 60 * 5);\n\n if (keyWasSet) {\n return await usingLock(key, async () => {\n try {\n return await fn();\n } finally {\n await redis.set(`luniq:${id}:done`, '1', 'EX', 10);\n await redis.expire(uniquenessHashKey, 5);\n }\n });\n }\n\n let winnerId = await redis.get(uniquenessHashKey);\n if (!winnerId) return null;\n\n // If we lost, we'll wait for the winner to finish.\n for (let i = 0; i < 25; i++) {\n if (i > 0) await delay(25);\n\n if (await redis.get(`luniq:${winnerId}:done`)) {\n return null;\n }\n }\n\n return null;\n };\n\n let doOnceAndReturn = async <T>(key: string, fn: () => Promise<T>): Promise<T> => {\n let redisKey = `doa:${nameHash}:${key}`;\n\n let res = await doOnce(key, async () => {\n let res = await fn();\n\n await redis.set(redisKey, SuperJSON.stringify(res), 'EX', 60);\n\n return res;\n });\n\n if (res == null) {\n return SuperJSON.parse((await redis.get(redisKey)) as string);\n }\n\n return res;\n };\n\n return {\n usingLock,\n doOnce,\n doOnceAndReturn\n };\n};\n"],"names":["e","Promise","n","setTimeout","_settle","state","value","pact","s","_Pact","o","v","then","bind","observer","prototype","onFulfilled","onRejected","result","this","callback","_this","_isSettledPact","thenable","_ref","redisUrl","nameHash","Bun","hash","cityHash32","name","redis","Redis","parseRedisUrl","redlock","Redlock_","driftFactor","retryCount","retryDelay","retryJitter","automaticExtensionThreshold","usingLock","key","fn","keyArray","Array","isArray","map","k","runLock","passingForNow","passForNow","resolve","using","_exit","_temp","delay","_runLock","_result","reject","doOnce","id","Math","random","Date","now","uniquenessHashKey","setnx","keyWasSet","expire","_exit2","_temp7","_result2","get","winnerId","_exit3","i","_temp5","_for","_temp4","_redis$get","_temp3","_result3","_temp6","_finallyRethrows","_wasThrown","_result4","set","_await$usingLock","doOnceAndReturn","redisKey","res","SuperJSON","stringify","_exit4","_temp8","_parse","parse","_redis$get2","_SuperJSON$parse","call","_result5"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare let createLock: ({ name, redisUrl }: {
|
|
2
|
+
name: string;
|
|
3
|
+
redisUrl: string;
|
|
4
|
+
}) => {
|
|
5
|
+
usingLock: <T>(key: string | string[], fn: (controller: {
|
|
6
|
+
passForNow: () => void;
|
|
7
|
+
}) => Promise<T>) => Promise<T>;
|
|
8
|
+
doOnce: <T>(key: string, fn: () => Promise<T>) => Promise<T | null>;
|
|
9
|
+
doOnceAndReturn: <T>(key: string, fn: () => Promise<T>) => Promise<T>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,eAAO,IAAI,UAAU,GAAI,oBAAoB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;gBA0BtD,CAAC,OACjB,MAAM,GAAG,MAAM,EAAE,MAClB,CAAC,UAAU,EAAE;QAAE,UAAU,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,KACzD,OAAO,CAAC,CAAC,CAAC;aAsBO,CAAC,OAAO,MAAM,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;sBAiC/C,CAAC,OAAO,MAAM,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC;CAuB/E,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{parseRedisUrl as n}from"@lowerdeck/redis";import{Redis as r}from"ioredis";import t from"superjson";import e from"redlock";var i=function(n){return new Promise(function(r){return setTimeout(r,n)})};function o(n,r,t){if(!n.s){if(t instanceof u){if(!t.s)return void(t.o=o.bind(null,n,r));1&r&&(r=t.s),t=t.v}if(t&&t.then)return void t.then(o.bind(null,n,r),o.bind(null,n,2));n.s=r,n.v=t;const e=n.o;e&&e(n)}}var u=/*#__PURE__*/function(){function n(){}return n.prototype.then=function(r,t){var e=new n,i=this.s;if(i){var u=1&i?r:t;if(u){try{o(e,1,u(this.v))}catch(n){o(e,2,n)}return e}return this}return this.o=function(n){try{var i=n.v;1&n.s?o(e,1,r?r(i):i):t?o(e,1,t(i)):o(e,2,i)}catch(n){o(e,2,n)}},e},n}();function c(n){return n instanceof u&&1&n.s}var f=function(f){var h=f.redisUrl,s=Bun.hash.cityHash32(f.name),v=new r(n(h)),a=new e([v],{driftFactor:.01,retryCount:50,retryDelay:200,retryJitter:200,automaticExtensionThreshold:500}),l=function(n,r){try{var t=(Array.isArray(n)?n:[n]).map(function(n){return"l:"+s+":"+n}),e=function(){try{var n=!1,o=function(){n=!0};return Promise.resolve(a.using(t,1e4,function(){return r({passForNow:o})})).then(function(r){var t,o=function(){if(n)return Promise.resolve(i(100)).then(function(){var n=e();return t=1,n})}();return o&&o.then?o.then(function(n){return t?n:r}):t?o:r})}catch(n){return Promise.reject(n)}};return e()}catch(n){return Promise.reject(n)}},m=function(n,r){try{var t=s+":"+Math.random()+":"+Date.now(),e="luniq:"+s+":"+n;return Promise.resolve(v.setnx(e,t)).then(function(f){return Promise.resolve(v.expire(e,300)).then(function(){var h;function s(n){return h?n:Promise.resolve(v.get(e)).then(function(n){var r;if(!n)return null;var t=0,e=function(n,r,t){for(var e;;){var i=n();if(c(i)&&(i=i.v),!i)return f;if(i.then){e=0;break}var f=t();if(f&&f.then){if(!c(f)){e=1;break}f=f.s}if(r){var h=r();if(h&&h.then&&!c(h)){e=2;break}}}var s=new u,v=o.bind(null,s,2);return(0===e?i.then(l):1===e?f.then(a):h.then(m)).then(void 0,v),s;function a(e){f=e;do{if(r&&(h=r())&&h.then&&!c(h))return void h.then(m).then(void 0,v);if(!(i=n())||c(i)&&!i.v)return void o(s,1,f);if(i.then)return void i.then(l).then(void 0,v);c(f=t())&&(f=f.v)}while(!f||!f.then);f.then(a).then(void 0,v)}function l(n){n?(f=t())&&f.then?f.then(a).then(void 0,v):a(f):o(s,1,f)}function m(){(i=n())?i.then?i.then(l).then(void 0,v):l(i):o(s,1,f)}}(function(){return!r&&t<25},function(){return t++},function(){function e(){return Promise.resolve(v.get("luniq:"+n+":done")).then(function(n){if(n)return r=1,null})}var o=function(){if(t>0)return Promise.resolve(i(25)).then(function(){})}();return o&&o.then?o.then(e):e()});return e&&e.then?e.then(function(n){return r?n:null}):r?e:null})}var a=function(){if(f)return Promise.resolve(l(n,function(){try{return Promise.resolve(function(n,r){try{var t=n()}catch(n){return r(!0,n)}return t&&t.then?t.then(r.bind(null,!1),r.bind(null,!0)):r(!1,t)}(r,function(n,r){return Promise.resolve(v.set("luniq:"+t+":done","1","EX",10)).then(function(){return Promise.resolve(v.expire(e,5)).then(function(){if(n)throw r;return r})})}))}catch(n){return Promise.reject(n)}})).then(function(n){return h=1,n})}();return a&&a.then?a.then(s):s(a)})})}catch(n){return Promise.reject(n)}};return{usingLock:l,doOnce:m,doOnceAndReturn:function(n,r){var e="doa:"+s+":"+n;return Promise.resolve(m(n,function(){try{return Promise.resolve(r()).then(function(n){return Promise.resolve(v.set(e,t.stringify(n),"EX",60)).then(function(){return n})})}catch(n){return Promise.reject(n)}})).then(function(n){var r,i=function(){if(null==n){var i=t.parse;return Promise.resolve(v.get(e)).then(function(n){var e=i.call(t,n);return r=1,e})}}();return i&&i.then?i.then(function(t){return r?t:n}):r?i:n})}}};export{f as createLock};
|
|
2
|
+
//# sourceMappingURL=index.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.module.js","sources":["../../delay/dist/index.module.js","../src/index.ts"],"sourcesContent":["var e=function(e){return new Promise(function(n){return setTimeout(n,e)})};export{e as delay};\n//# sourceMappingURL=index.module.js.map\n","import { delay } from '@lowerdeck/delay';\nimport { parseRedisUrl } from '@lowerdeck/redis';\nimport { Redis } from 'ioredis';\n\n// @ts-ignore\nimport SuperJSON from 'superjson';\n\n// @ts-ignore\nimport Redlock_ from 'redlock';\n\nexport let createLock = ({ name, redisUrl }: { name: string; redisUrl: string }) => {\n let nameHash = Bun.hash.cityHash32(name);\n let redis = new Redis(parseRedisUrl(redisUrl));\n\n let redlock = new Redlock_([redis as any], {\n // The expected clock drift; for more details see:\n // http://redis.io/topics/distlock\n driftFactor: 0.01, // multiplied by lock ttl to determine drift time\n\n // The max number of times Redlock will attempt to lock a resource\n // before erroring.\n retryCount: 50,\n\n // the time in ms between attempts\n retryDelay: 200, // time in ms\n\n // the max time in ms randomly added to retries\n // to improve performance under high contention\n // see https://www.awsarchitectureblog.com/2015/03/backoff.html\n retryJitter: 200, // time in ms\n\n // The minimum remaining time on a lock before an extension is automatically\n // attempted with the `using` API.\n automaticExtensionThreshold: 500\n } as any);\n\n let usingLock = async <T>(\n key: string | string[],\n fn: (controller: { passForNow: () => void }) => Promise<T>\n ): Promise<T> => {\n let keyArray = (Array.isArray(key) ? key : [key]).map(k => `l:${nameHash}:${k}`);\n\n let runLock = async () => {\n let passingForNow = false;\n let passForNow = () => {\n passingForNow = true;\n };\n\n let result = await redlock.using(keyArray, 10_000, () => fn({ passForNow }));\n\n if (passingForNow) {\n await delay(100);\n return runLock();\n }\n\n return result;\n };\n\n return runLock();\n };\n\n let doOnce = async <T>(key: string, fn: () => Promise<T>): Promise<T | null> => {\n let id = `${nameHash}:${Math.random()}:${Date.now()}`;\n let uniquenessHashKey = `luniq:${nameHash}:${key}`;\n\n let keyWasSet = await redis.setnx(uniquenessHashKey, id);\n await redis.expire(uniquenessHashKey, 60 * 5);\n\n if (keyWasSet) {\n return await usingLock(key, async () => {\n try {\n return await fn();\n } finally {\n await redis.set(`luniq:${id}:done`, '1', 'EX', 10);\n await redis.expire(uniquenessHashKey, 5);\n }\n });\n }\n\n let winnerId = await redis.get(uniquenessHashKey);\n if (!winnerId) return null;\n\n // If we lost, we'll wait for the winner to finish.\n for (let i = 0; i < 25; i++) {\n if (i > 0) await delay(25);\n\n if (await redis.get(`luniq:${winnerId}:done`)) {\n return null;\n }\n }\n\n return null;\n };\n\n let doOnceAndReturn = async <T>(key: string, fn: () => Promise<T>): Promise<T> => {\n let redisKey = `doa:${nameHash}:${key}`;\n\n let res = await doOnce(key, async () => {\n let res = await fn();\n\n await redis.set(redisKey, SuperJSON.stringify(res), 'EX', 60);\n\n return res;\n });\n\n if (res == null) {\n return SuperJSON.parse((await redis.get(redisKey)) as string);\n }\n\n return res;\n };\n\n return {\n usingLock,\n doOnce,\n doOnceAndReturn\n };\n};\n"],"names":["e","Promise","n","setTimeout","_settle","state","value","pact","s","_Pact","o","v","then","bind","observer","prototype","onFulfilled","onRejected","result","this","callback","_this","_isSettledPact","thenable","createLock","_ref","redisUrl","nameHash","Bun","hash","cityHash32","name","redis","Redis","parseRedisUrl","redlock","Redlock_","driftFactor","retryCount","retryDelay","retryJitter","automaticExtensionThreshold","usingLock","key","fn","keyArray","Array","isArray","map","k","runLock","passingForNow","passForNow","resolve","using","_exit","_temp","delay","_runLock","_result","reject","doOnce","id","Math","random","Date","now","uniquenessHashKey","setnx","keyWasSet","expire","_exit2","_temp7","_result2","get","winnerId","_exit3","i","_temp5","_for","_temp4","_redis$get","_temp3","_result3","_temp6","_finallyRethrows","_wasThrown","_result4","set","_await$usingLock","doOnceAndReturn","redisKey","res","SuperJSON","stringify","_exit4","_temp8","_parse","parse","_redis$get2","_SuperJSON$parse","call","_result5"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@lowerdeck/redis"),require("ioredis"),require("superjson"),require("redlock")):"function"==typeof define&&define.amd?define(["exports","@lowerdeck/redis","ioredis","superjson","redlock"],e):e((n||self).lock={},n.redis,n.ioredis,n.superjson,n.redlock)}(this,function(n,e,r,t,i){function o(n){return n&&"object"==typeof n&&"default"in n?n:{default:n}}var u=/*#__PURE__*/o(t),f=/*#__PURE__*/o(i),c=function(n){return new Promise(function(e){return setTimeout(e,n)})};function s(n,e,r){if(!n.s){if(r instanceof h){if(!r.s)return void(r.o=s.bind(null,n,e));1&e&&(e=r.s),r=r.v}if(r&&r.then)return void r.then(s.bind(null,n,e),s.bind(null,n,2));n.s=e,n.v=r;const t=n.o;t&&t(n)}}var h=/*#__PURE__*/function(){function n(){}return n.prototype.then=function(e,r){var t=new n,i=this.s;if(i){var o=1&i?e:r;if(o){try{s(t,1,o(this.v))}catch(n){s(t,2,n)}return t}return this}return this.o=function(n){try{var i=n.v;1&n.s?s(t,1,e?e(i):i):r?s(t,1,r(i)):s(t,2,i)}catch(n){s(t,2,n)}},t},n}();function a(n){return n instanceof h&&1&n.s}n.createLock=function(n){var t=n.redisUrl,i=Bun.hash.cityHash32(n.name),o=new r.Redis(e.parseRedisUrl(t)),l=new f.default([o],{driftFactor:.01,retryCount:50,retryDelay:200,retryJitter:200,automaticExtensionThreshold:500}),v=function(n,e){try{var r=(Array.isArray(n)?n:[n]).map(function(n){return"l:"+i+":"+n}),t=function(){try{var n=!1,i=function(){n=!0};return Promise.resolve(l.using(r,1e4,function(){return e({passForNow:i})})).then(function(e){var r,i=function(){if(n)return Promise.resolve(c(100)).then(function(){var n=t();return r=1,n})}();return i&&i.then?i.then(function(n){return r?n:e}):r?i:e})}catch(n){return Promise.reject(n)}};return t()}catch(n){return Promise.reject(n)}},d=function(n,e){try{var r=i+":"+Math.random()+":"+Date.now(),t="luniq:"+i+":"+n;return Promise.resolve(o.setnx(t,r)).then(function(i){return Promise.resolve(o.expire(t,300)).then(function(){var u;function f(n){return u?n:Promise.resolve(o.get(t)).then(function(n){var e;if(!n)return null;var r=0,t=function(n,e,r){for(var t;;){var i=n();if(a(i)&&(i=i.v),!i)return o;if(i.then){t=0;break}var o=r();if(o&&o.then){if(!a(o)){t=1;break}o=o.s}if(e){var u=e();if(u&&u.then&&!a(u)){t=2;break}}}var f=new h,c=s.bind(null,f,2);return(0===t?i.then(v):1===t?o.then(l):u.then(d)).then(void 0,c),f;function l(t){o=t;do{if(e&&(u=e())&&u.then&&!a(u))return void u.then(d).then(void 0,c);if(!(i=n())||a(i)&&!i.v)return void s(f,1,o);if(i.then)return void i.then(v).then(void 0,c);a(o=r())&&(o=o.v)}while(!o||!o.then);o.then(l).then(void 0,c)}function v(n){n?(o=r())&&o.then?o.then(l).then(void 0,c):l(o):s(f,1,o)}function d(){(i=n())?i.then?i.then(v).then(void 0,c):v(i):s(f,1,o)}}(function(){return!e&&r<25},function(){return r++},function(){function t(){return Promise.resolve(o.get("luniq:"+n+":done")).then(function(n){if(n)return e=1,null})}var i=function(){if(r>0)return Promise.resolve(c(25)).then(function(){})}();return i&&i.then?i.then(t):t()});return t&&t.then?t.then(function(n){return e?n:null}):e?t:null})}var l=function(){if(i)return Promise.resolve(v(n,function(){try{return Promise.resolve(function(n,e){try{var r=n()}catch(n){return e(!0,n)}return r&&r.then?r.then(e.bind(null,!1),e.bind(null,!0)):e(!1,r)}(e,function(n,e){return Promise.resolve(o.set("luniq:"+r+":done","1","EX",10)).then(function(){return Promise.resolve(o.expire(t,5)).then(function(){if(n)throw e;return e})})}))}catch(n){return Promise.reject(n)}})).then(function(n){return u=1,n})}();return l&&l.then?l.then(f):f(l)})})}catch(n){return Promise.reject(n)}};return{usingLock:v,doOnce:d,doOnceAndReturn:function(n,e){var r="doa:"+i+":"+n;return Promise.resolve(d(n,function(){try{return Promise.resolve(e()).then(function(n){return Promise.resolve(o.set(r,u.default.stringify(n),"EX",60)).then(function(){return n})})}catch(n){return Promise.reject(n)}})).then(function(n){var e,t=function(){if(null==n){var t=u.default.parse;return Promise.resolve(o.get(r)).then(function(n){var r=t.call(u.default,n);return e=1,r})}}();return t&&t.then?t.then(function(r){return e?r:n}):e?t:n})}}}});
|
|
2
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../../delay/dist/index.module.js","../src/index.ts"],"sourcesContent":["var e=function(e){return new Promise(function(n){return setTimeout(n,e)})};export{e as delay};\n//# sourceMappingURL=index.module.js.map\n","import { delay } from '@lowerdeck/delay';\nimport { parseRedisUrl } from '@lowerdeck/redis';\nimport { Redis } from 'ioredis';\n\n// @ts-ignore\nimport SuperJSON from 'superjson';\n\n// @ts-ignore\nimport Redlock_ from 'redlock';\n\nexport let createLock = ({ name, redisUrl }: { name: string; redisUrl: string }) => {\n let nameHash = Bun.hash.cityHash32(name);\n let redis = new Redis(parseRedisUrl(redisUrl));\n\n let redlock = new Redlock_([redis as any], {\n // The expected clock drift; for more details see:\n // http://redis.io/topics/distlock\n driftFactor: 0.01, // multiplied by lock ttl to determine drift time\n\n // The max number of times Redlock will attempt to lock a resource\n // before erroring.\n retryCount: 50,\n\n // the time in ms between attempts\n retryDelay: 200, // time in ms\n\n // the max time in ms randomly added to retries\n // to improve performance under high contention\n // see https://www.awsarchitectureblog.com/2015/03/backoff.html\n retryJitter: 200, // time in ms\n\n // The minimum remaining time on a lock before an extension is automatically\n // attempted with the `using` API.\n automaticExtensionThreshold: 500\n } as any);\n\n let usingLock = async <T>(\n key: string | string[],\n fn: (controller: { passForNow: () => void }) => Promise<T>\n ): Promise<T> => {\n let keyArray = (Array.isArray(key) ? key : [key]).map(k => `l:${nameHash}:${k}`);\n\n let runLock = async () => {\n let passingForNow = false;\n let passForNow = () => {\n passingForNow = true;\n };\n\n let result = await redlock.using(keyArray, 10_000, () => fn({ passForNow }));\n\n if (passingForNow) {\n await delay(100);\n return runLock();\n }\n\n return result;\n };\n\n return runLock();\n };\n\n let doOnce = async <T>(key: string, fn: () => Promise<T>): Promise<T | null> => {\n let id = `${nameHash}:${Math.random()}:${Date.now()}`;\n let uniquenessHashKey = `luniq:${nameHash}:${key}`;\n\n let keyWasSet = await redis.setnx(uniquenessHashKey, id);\n await redis.expire(uniquenessHashKey, 60 * 5);\n\n if (keyWasSet) {\n return await usingLock(key, async () => {\n try {\n return await fn();\n } finally {\n await redis.set(`luniq:${id}:done`, '1', 'EX', 10);\n await redis.expire(uniquenessHashKey, 5);\n }\n });\n }\n\n let winnerId = await redis.get(uniquenessHashKey);\n if (!winnerId) return null;\n\n // If we lost, we'll wait for the winner to finish.\n for (let i = 0; i < 25; i++) {\n if (i > 0) await delay(25);\n\n if (await redis.get(`luniq:${winnerId}:done`)) {\n return null;\n }\n }\n\n return null;\n };\n\n let doOnceAndReturn = async <T>(key: string, fn: () => Promise<T>): Promise<T> => {\n let redisKey = `doa:${nameHash}:${key}`;\n\n let res = await doOnce(key, async () => {\n let res = await fn();\n\n await redis.set(redisKey, SuperJSON.stringify(res), 'EX', 60);\n\n return res;\n });\n\n if (res == null) {\n return SuperJSON.parse((await redis.get(redisKey)) as string);\n }\n\n return res;\n };\n\n return {\n usingLock,\n doOnce,\n doOnceAndReturn\n };\n};\n"],"names":["e","Promise","n","setTimeout","_settle","state","value","pact","s","_Pact","o","v","then","bind","observer","prototype","onFulfilled","onRejected","result","this","callback","_this","_isSettledPact","thenable","_ref","redisUrl","nameHash","Bun","hash","cityHash32","name","redis","Redis","parseRedisUrl","redlock","Redlock_","driftFactor","retryCount","retryDelay","retryJitter","automaticExtensionThreshold","usingLock","key","fn","keyArray","Array","isArray","map","k","runLock","passingForNow","passForNow","resolve","using","_exit","_temp","delay","_runLock","_result","reject","doOnce","id","Math","random","Date","now","uniquenessHashKey","setnx","keyWasSet","expire","_exit2","_temp7","_result2","get","winnerId","_exit3","i","_temp5","_for","_temp4","_redis$get","_temp3","_result3","_temp6","_finallyRethrows","_wasThrown","_result4","set","_await$usingLock","doOnceAndReturn","redisKey","res","SuperJSON","stringify","_exit4","_temp8","_parse","parse","_redis$get2","_SuperJSON$parse","call","_result5"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lowerdeck/lock",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"author": "Tobias Herber",
|
|
8
|
+
"license": "Apache 2",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"source": "src/index.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"require": "./dist/index.cjs",
|
|
14
|
+
"import": "./dist/index.module.js",
|
|
15
|
+
"default": "./dist/index.modern.js"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.module.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"unpkg": "./dist/index.umd.js",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "vitest run --passWithNoTests",
|
|
23
|
+
"lint": "prettier src/**/*.ts --check",
|
|
24
|
+
"build": "microbundle"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lowerdeck/redis": "^1.0.0",
|
|
28
|
+
"@lowerdeck/delay": "^1.0.0",
|
|
29
|
+
"@types/bun": "^1.2.11",
|
|
30
|
+
"ioredis": "^5.4.1",
|
|
31
|
+
"redlock": "^5.0.0-beta.2",
|
|
32
|
+
"superjson": "^2.2.5"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@lowerdeck/tsconfig": "^1.0.0",
|
|
36
|
+
"typescript": "5.8.2",
|
|
37
|
+
"microbundle": "^0.15.1",
|
|
38
|
+
"vitest": "^3.1.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { delay } from '@lowerdeck/delay';
|
|
2
|
+
import { parseRedisUrl } from '@lowerdeck/redis';
|
|
3
|
+
import { Redis } from 'ioredis';
|
|
4
|
+
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import SuperJSON from 'superjson';
|
|
7
|
+
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import Redlock_ from 'redlock';
|
|
10
|
+
|
|
11
|
+
export let createLock = ({ name, redisUrl }: { name: string; redisUrl: string }) => {
|
|
12
|
+
let nameHash = Bun.hash.cityHash32(name);
|
|
13
|
+
let redis = new Redis(parseRedisUrl(redisUrl));
|
|
14
|
+
|
|
15
|
+
let redlock = new Redlock_([redis as any], {
|
|
16
|
+
// The expected clock drift; for more details see:
|
|
17
|
+
// http://redis.io/topics/distlock
|
|
18
|
+
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
|
|
19
|
+
|
|
20
|
+
// The max number of times Redlock will attempt to lock a resource
|
|
21
|
+
// before erroring.
|
|
22
|
+
retryCount: 50,
|
|
23
|
+
|
|
24
|
+
// the time in ms between attempts
|
|
25
|
+
retryDelay: 200, // time in ms
|
|
26
|
+
|
|
27
|
+
// the max time in ms randomly added to retries
|
|
28
|
+
// to improve performance under high contention
|
|
29
|
+
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
30
|
+
retryJitter: 200, // time in ms
|
|
31
|
+
|
|
32
|
+
// The minimum remaining time on a lock before an extension is automatically
|
|
33
|
+
// attempted with the `using` API.
|
|
34
|
+
automaticExtensionThreshold: 500
|
|
35
|
+
} as any);
|
|
36
|
+
|
|
37
|
+
let usingLock = async <T>(
|
|
38
|
+
key: string | string[],
|
|
39
|
+
fn: (controller: { passForNow: () => void }) => Promise<T>
|
|
40
|
+
): Promise<T> => {
|
|
41
|
+
let keyArray = (Array.isArray(key) ? key : [key]).map(k => `l:${nameHash}:${k}`);
|
|
42
|
+
|
|
43
|
+
let runLock = async () => {
|
|
44
|
+
let passingForNow = false;
|
|
45
|
+
let passForNow = () => {
|
|
46
|
+
passingForNow = true;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let result = await redlock.using(keyArray, 10_000, () => fn({ passForNow }));
|
|
50
|
+
|
|
51
|
+
if (passingForNow) {
|
|
52
|
+
await delay(100);
|
|
53
|
+
return runLock();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return runLock();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let doOnce = async <T>(key: string, fn: () => Promise<T>): Promise<T | null> => {
|
|
63
|
+
let id = `${nameHash}:${Math.random()}:${Date.now()}`;
|
|
64
|
+
let uniquenessHashKey = `luniq:${nameHash}:${key}`;
|
|
65
|
+
|
|
66
|
+
let keyWasSet = await redis.setnx(uniquenessHashKey, id);
|
|
67
|
+
await redis.expire(uniquenessHashKey, 60 * 5);
|
|
68
|
+
|
|
69
|
+
if (keyWasSet) {
|
|
70
|
+
return await usingLock(key, async () => {
|
|
71
|
+
try {
|
|
72
|
+
return await fn();
|
|
73
|
+
} finally {
|
|
74
|
+
await redis.set(`luniq:${id}:done`, '1', 'EX', 10);
|
|
75
|
+
await redis.expire(uniquenessHashKey, 5);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let winnerId = await redis.get(uniquenessHashKey);
|
|
81
|
+
if (!winnerId) return null;
|
|
82
|
+
|
|
83
|
+
// If we lost, we'll wait for the winner to finish.
|
|
84
|
+
for (let i = 0; i < 25; i++) {
|
|
85
|
+
if (i > 0) await delay(25);
|
|
86
|
+
|
|
87
|
+
if (await redis.get(`luniq:${winnerId}:done`)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
let doOnceAndReturn = async <T>(key: string, fn: () => Promise<T>): Promise<T> => {
|
|
96
|
+
let redisKey = `doa:${nameHash}:${key}`;
|
|
97
|
+
|
|
98
|
+
let res = await doOnce(key, async () => {
|
|
99
|
+
let res = await fn();
|
|
100
|
+
|
|
101
|
+
await redis.set(redisKey, SuperJSON.stringify(res), 'EX', 60);
|
|
102
|
+
|
|
103
|
+
return res;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (res == null) {
|
|
107
|
+
return SuperJSON.parse((await redis.get(redisKey)) as string);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return res;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
usingLock,
|
|
115
|
+
doOnce,
|
|
116
|
+
doOnceAndReturn
|
|
117
|
+
};
|
|
118
|
+
};
|
package/tsconfig.json
ADDED