@ya-accelerators/web-backend-framework 0.0.52 → 0.0.54
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/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lock/index.d.ts +27 -0
- package/dist/lock/index.js +62 -0
- package/dist/lock/index.js.map +1 -0
- package/dist/rate-limit/index.d.ts +16 -0
- package/dist/rate-limit/index.js +38 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/redis/index.d.ts +6 -0
- package/dist/redis/index.js +20 -0
- package/dist/redis/index.js.map +1 -1
- package/dist/throttle/index.d.ts +19 -0
- package/dist/throttle/index.js +21 -0
- package/dist/throttle/index.js.map +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export * from './app/index.ts';
|
|
2
2
|
export * from './env.ts';
|
|
3
|
+
export * from './lock/index.ts';
|
|
3
4
|
export * from './logger/index.ts';
|
|
4
5
|
export * from './prisma/index.ts';
|
|
6
|
+
export * from './rate-limit/index.ts';
|
|
5
7
|
export * from './redis/index.ts';
|
|
6
8
|
export * from './s3/index.ts';
|
|
9
|
+
export * from './throttle/index.ts';
|
|
7
10
|
export * from './util/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export * from "./app/index.js";
|
|
2
2
|
export * from "./env.js";
|
|
3
|
+
export * from "./lock/index.js";
|
|
3
4
|
export * from "./logger/index.js";
|
|
4
5
|
export * from "./prisma/index.js";
|
|
6
|
+
export * from "./rate-limit/index.js";
|
|
5
7
|
export * from "./redis/index.js";
|
|
6
8
|
export * from "./s3/index.js";
|
|
9
|
+
export * from "./throttle/index.js";
|
|
7
10
|
export * from "./util/index.js";
|
|
8
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const lock: {
|
|
2
|
+
acquire: ({ key, ttlSec }: {
|
|
3
|
+
key: string;
|
|
4
|
+
ttlSec: number;
|
|
5
|
+
}) => Promise<string | null>;
|
|
6
|
+
release: ({ key, token }: {
|
|
7
|
+
key: string;
|
|
8
|
+
token: string;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Run `fn` while holding the lock. Returns `{ acquired: false }` without
|
|
12
|
+
* invoking `fn` if the lock could not be acquired. Always releases the lock,
|
|
13
|
+
* even when `fn` throws.
|
|
14
|
+
*
|
|
15
|
+
* The discriminated return lets callers tell "lock not acquired" from "fn
|
|
16
|
+
* ran and returned undefined" — a plain `T | undefined` would collide.
|
|
17
|
+
*/
|
|
18
|
+
withLock: <T>({ key, ttlSec }: {
|
|
19
|
+
key: string;
|
|
20
|
+
ttlSec: number;
|
|
21
|
+
}, fn: () => Promise<T>) => Promise<{
|
|
22
|
+
acquired: true;
|
|
23
|
+
value: T;
|
|
24
|
+
} | {
|
|
25
|
+
acquired: false;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Script } from '@valkey/valkey-glide';
|
|
2
|
+
import { redis } from "../redis/index.js";
|
|
3
|
+
import { Util } from "../util/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Distributed mutex built on Redis SET NX + check-and-delete.
|
|
6
|
+
*
|
|
7
|
+
* Use to serialize work that must not run concurrently across processes —
|
|
8
|
+
* payout execution, batch jobs, anything where two concurrent runs would
|
|
9
|
+
* double-spend or corrupt state.
|
|
10
|
+
*
|
|
11
|
+
* `acquire` returns a unique token on success (null when the lock is held).
|
|
12
|
+
* `release` only deletes the key when the stored token still matches, so a
|
|
13
|
+
* TTL-expired-then-reacquired lock is never deleted by the original holder.
|
|
14
|
+
*
|
|
15
|
+
* Redis errors propagate as throws — callers decide whether to fail-closed
|
|
16
|
+
* via `.catch(() => null)` (acquire) or surface the failure.
|
|
17
|
+
*
|
|
18
|
+
* Note: even with the token check, a critical section that overruns `ttlSec`
|
|
19
|
+
* no longer holds the lock; another worker can enter while `fn` is still
|
|
20
|
+
* running. Pick `ttlSec` larger than the worst-case body duration.
|
|
21
|
+
*/
|
|
22
|
+
const RELEASE_SCRIPT = new Script(`
|
|
23
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
24
|
+
return redis.call("DEL", KEYS[1])
|
|
25
|
+
else
|
|
26
|
+
return 0
|
|
27
|
+
end
|
|
28
|
+
`);
|
|
29
|
+
export const lock = {
|
|
30
|
+
acquire: async ({ key, ttlSec }) => {
|
|
31
|
+
const token = Util.createULID();
|
|
32
|
+
const ok = await redis.setNx(key, token, ttlSec);
|
|
33
|
+
return ok != null ? token : null;
|
|
34
|
+
},
|
|
35
|
+
release: async ({ key, token }) => {
|
|
36
|
+
await redis.invokeScript(RELEASE_SCRIPT, { keys: [key], args: [token] });
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Run `fn` while holding the lock. Returns `{ acquired: false }` without
|
|
40
|
+
* invoking `fn` if the lock could not be acquired. Always releases the lock,
|
|
41
|
+
* even when `fn` throws.
|
|
42
|
+
*
|
|
43
|
+
* The discriminated return lets callers tell "lock not acquired" from "fn
|
|
44
|
+
* ran and returned undefined" — a plain `T | undefined` would collide.
|
|
45
|
+
*/
|
|
46
|
+
withLock: async ({ key, ttlSec }, fn) => {
|
|
47
|
+
const token = await lock.acquire({ key, ttlSec });
|
|
48
|
+
if (token === null) {
|
|
49
|
+
return { acquired: false };
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
return { acquired: true, value: await fn() };
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
// Swallow release failures so a Redis blip in cleanup doesn't mask
|
|
56
|
+
// `fn`'s original exception. The framework-level redis.invokeScript
|
|
57
|
+
// already logs the failure, and the lock TTL guarantees recovery.
|
|
58
|
+
await lock.release({ key, token }).catch(() => undefined);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lock/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC;;;;;;CAMjC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAmC,EAA0B,EAAE;QAC1F,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,CAAA;QAC/B,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAChD,OAAO,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IAClC,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAkC,EAAiB,EAAE;QAC/E,MAAM,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC1E,CAAC;IACD;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,EACb,EAAE,GAAG,EAAE,MAAM,EAAmC,EAChD,EAAoB,EACyC,EAAE;QAC/D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;QACjD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QAC5B,CAAC;QACD,IAAI,CAAC;YACH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;QAC9C,CAAC;gBAAS,CAAC;YACT,mEAAmE;YACnE,oEAAoE;YACpE,kEAAkE;YAClE,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const rateLimit: {
|
|
2
|
+
/**
|
|
3
|
+
* Increment the counter for `key` and decide whether the current attempt is
|
|
4
|
+
* allowed. Returns the post-increment count and how many attempts remain in
|
|
5
|
+
* the current window.
|
|
6
|
+
*/
|
|
7
|
+
attempt: ({ key, max, windowSec, }: {
|
|
8
|
+
key: string;
|
|
9
|
+
max: number;
|
|
10
|
+
windowSec: number;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
count: number;
|
|
14
|
+
remaining: number;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Script } from '@valkey/valkey-glide';
|
|
2
|
+
import { redis } from "../redis/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fixed-window rate limiter built on a single atomic Lua script
|
|
5
|
+
* (INCR + EXPIRE on the first hit of a window).
|
|
6
|
+
*
|
|
7
|
+
* Use to cap the number of attempts against a key inside a rolling window —
|
|
8
|
+
* unauthenticated endpoints (password reset, signup, OTP request, etc.) that
|
|
9
|
+
* would otherwise be abusable by brute-force or spam.
|
|
10
|
+
*
|
|
11
|
+
* The INCR and EXPIRE run inside a Lua script, so the key is guaranteed to
|
|
12
|
+
* have a TTL whenever its value > 0 — an interrupt between the two ops is
|
|
13
|
+
* impossible.
|
|
14
|
+
*
|
|
15
|
+
* Redis errors propagate as throws — callers decide whether to fail-closed
|
|
16
|
+
* via `.catch(...)` or surface the failure.
|
|
17
|
+
*/
|
|
18
|
+
const ATTEMPT_SCRIPT = new Script(`
|
|
19
|
+
local n = redis.call("INCR", KEYS[1])
|
|
20
|
+
if n == 1 then redis.call("EXPIRE", KEYS[1], ARGV[1]) end
|
|
21
|
+
return n
|
|
22
|
+
`);
|
|
23
|
+
export const rateLimit = {
|
|
24
|
+
/**
|
|
25
|
+
* Increment the counter for `key` and decide whether the current attempt is
|
|
26
|
+
* allowed. Returns the post-increment count and how many attempts remain in
|
|
27
|
+
* the current window.
|
|
28
|
+
*/
|
|
29
|
+
attempt: async ({ key, max, windowSec, }) => {
|
|
30
|
+
const count = Number(await redis.invokeScript(ATTEMPT_SCRIPT, { keys: [key], args: [String(windowSec)] }));
|
|
31
|
+
return {
|
|
32
|
+
allowed: count <= max,
|
|
33
|
+
count,
|
|
34
|
+
remaining: Math.max(0, max - count),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rate-limit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEzC;;;;;;;;;;;;;;GAcG;AACH,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC;;;;CAIjC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;OAIG;IACH,OAAO,EAAE,KAAK,EAAE,EACd,GAAG,EACH,GAAG,EACH,SAAS,GAKV,EAAmE,EAAE;QACpE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAC1G,OAAO;YACL,OAAO,EAAE,KAAK,IAAI,GAAG;YACrB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC;SACpC,CAAA;IACH,CAAC;CACF,CAAA"}
|
package/dist/redis/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Script } from '@valkey/valkey-glide';
|
|
1
2
|
export declare const redis: {
|
|
2
3
|
health: () => Promise<"NG" | "OK">;
|
|
3
4
|
set: (key: string, value: string, expirationSeconds?: number) => Promise<void>;
|
|
@@ -5,5 +6,10 @@ export declare const redis: {
|
|
|
5
6
|
get: (key: string) => Promise<string | undefined>;
|
|
6
7
|
remove: (key: string) => Promise<void>;
|
|
7
8
|
incr: (key: string) => Promise<number>;
|
|
9
|
+
expire: (key: string, expirationSeconds: number) => Promise<boolean>;
|
|
8
10
|
decr: (key: string) => Promise<number>;
|
|
11
|
+
invokeScript: (script: Script, opts: {
|
|
12
|
+
keys: string[];
|
|
13
|
+
args: string[];
|
|
14
|
+
}) => Promise<import("@valkey/valkey-glide").GlideReturnType>;
|
|
9
15
|
};
|
package/dist/redis/index.js
CHANGED
|
@@ -84,6 +84,16 @@ export const redis = {
|
|
|
84
84
|
throw e;
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
|
+
expire: async (key, expirationSeconds) => {
|
|
88
|
+
try {
|
|
89
|
+
const client = await getClient();
|
|
90
|
+
return await client.expire(key, expirationSeconds);
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
logger.error('cache', { op: 'expire', key, error: e });
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
87
97
|
decr: async (key) => {
|
|
88
98
|
try {
|
|
89
99
|
const client = await getClient();
|
|
@@ -94,5 +104,15 @@ export const redis = {
|
|
|
94
104
|
throw e;
|
|
95
105
|
}
|
|
96
106
|
},
|
|
107
|
+
invokeScript: async (script, opts) => {
|
|
108
|
+
try {
|
|
109
|
+
const client = await getClient();
|
|
110
|
+
return await client.invokeScript(script, opts);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
logger.error('cache', { op: 'invokeScript', key: opts.keys[0], error: e });
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
97
117
|
};
|
|
98
118
|
//# sourceMappingURL=index.js.map
|
package/dist/redis/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/redis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/redis/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* At-most-once-per-window throttle built on Redis SET NX.
|
|
3
|
+
*
|
|
4
|
+
* Use when an action should run at most once per `windowSec` — sanctions
|
|
5
|
+
* evaluation that should not re-fire on every call-log ingest, single-use
|
|
6
|
+
* token claims that must succeed exactly once across concurrent attempts.
|
|
7
|
+
*
|
|
8
|
+
* Unlike `lock`, there is no explicit release: the slot opens again only
|
|
9
|
+
* when the TTL expires.
|
|
10
|
+
*
|
|
11
|
+
* Redis errors propagate as throws — callers decide whether to fail-closed
|
|
12
|
+
* via `.catch(() => false)` or surface the failure.
|
|
13
|
+
*/
|
|
14
|
+
export declare const throttle: {
|
|
15
|
+
tryAcquire: ({ key, windowSec }: {
|
|
16
|
+
key: string;
|
|
17
|
+
windowSec: number;
|
|
18
|
+
}) => Promise<boolean>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { redis } from "../redis/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* At-most-once-per-window throttle built on Redis SET NX.
|
|
4
|
+
*
|
|
5
|
+
* Use when an action should run at most once per `windowSec` — sanctions
|
|
6
|
+
* evaluation that should not re-fire on every call-log ingest, single-use
|
|
7
|
+
* token claims that must succeed exactly once across concurrent attempts.
|
|
8
|
+
*
|
|
9
|
+
* Unlike `lock`, there is no explicit release: the slot opens again only
|
|
10
|
+
* when the TTL expires.
|
|
11
|
+
*
|
|
12
|
+
* Redis errors propagate as throws — callers decide whether to fail-closed
|
|
13
|
+
* via `.catch(() => false)` or surface the failure.
|
|
14
|
+
*/
|
|
15
|
+
export const throttle = {
|
|
16
|
+
tryAcquire: async ({ key, windowSec }) => {
|
|
17
|
+
const ok = await redis.setNx(key, '1', windowSec);
|
|
18
|
+
return ok != null;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/throttle/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEzC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,UAAU,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,SAAS,EAAsC,EAAoB,EAAE;QAC7F,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAA;QACjD,OAAO,EAAE,IAAI,IAAI,CAAA;IACnB,CAAC;CACF,CAAA"}
|