better-ship 0.27.0 → 0.28.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.
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Xt as AppError } from "../index-DUFS0SJ_.js";
|
|
2
|
+
//#region src/infrastructure/cloudflare/rate-limiter.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Caller limits through a Workers Rate Limiting binding. Ported from the `consume` path of
|
|
5
|
+
* `better-auth/rate-limit.ts` and Porte `auth-rate-limit.ts`. This module does not log.
|
|
6
|
+
*/
|
|
7
|
+
/** The binding threw instead of answering. A refused call is `RateLimitError`, never this error. */
|
|
8
|
+
export declare class RateLimiterUnavailableError extends AppError {
|
|
9
|
+
readonly scope: string;
|
|
10
|
+
readonly _tag = "RateLimiterUnavailableError";
|
|
11
|
+
readonly classification = "unknown";
|
|
12
|
+
constructor(scope: string, cause: unknown);
|
|
13
|
+
}
|
|
14
|
+
/** One feature's limit on a binding. The binding's `simple.limit` sets how many calls a window allows. */
|
|
15
|
+
export type RateLimiterOptions = {
|
|
16
|
+
readonly binding: Pick<RateLimit, 'limit'>;
|
|
17
|
+
/** Names the feature. Two scopes on one binding keep separate counters. */
|
|
18
|
+
readonly scope: string;
|
|
19
|
+
/** The binding's `simple.period` in seconds. Cloudflare accepts only 10 or 60. */
|
|
20
|
+
readonly window: 10 | 60;
|
|
21
|
+
};
|
|
22
|
+
/** Counts calls for each caller key inside one scope. */
|
|
23
|
+
export type RateLimiter = {
|
|
24
|
+
/**
|
|
25
|
+
* Count one call for `key`, and resolve when the binding allows it.
|
|
26
|
+
* A refused call throws `RateLimitError` with `retryAfter` set to the window.
|
|
27
|
+
* A binding failure throws `RateLimiterUnavailableError`. The call does not retry, because a retry counts again.
|
|
28
|
+
*/
|
|
29
|
+
readonly limit: (key: string) => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
/** Bind one scope and window to a Rate Limiting binding. Build it once in the composition root. */
|
|
32
|
+
export declare function createRateLimiter(options: RateLimiterOptions): RateLimiter;
|
|
33
|
+
//#endregion
|
|
34
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","names":[],"sources":["../../src/infrastructure/cloudflare/rate-limiter.ts"],"mappings":";;;;;;;qBAQa,oCAAoC;WAKpC;WAJF;WACA;EAET,YACW,eACT;;;YAOQ;WACD,SAAS,KAAK;;WAEd;;WAEA;;;YAIC;;;;;;WAMD,QAAQ,gBAAgB;;;wBAInB,kBAAkB,SAAS,qBAAqB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { U as RateLimitError, nt as AppError } from "../core-BiN6S_cm.js";
|
|
2
|
+
|
|
3
|
+
//#region src/infrastructure/cloudflare/rate-limiter.ts
|
|
4
|
+
/**
|
|
5
|
+
* Caller limits through a Workers Rate Limiting binding. Ported from the `consume` path of
|
|
6
|
+
* `better-auth/rate-limit.ts` and Porte `auth-rate-limit.ts`. This module does not log.
|
|
7
|
+
*/
|
|
8
|
+
/** The binding threw instead of answering. A refused call is `RateLimitError`, never this error. */
|
|
9
|
+
var RateLimiterUnavailableError = class extends AppError {
|
|
10
|
+
scope;
|
|
11
|
+
_tag = "RateLimiterUnavailableError";
|
|
12
|
+
classification = "unknown";
|
|
13
|
+
constructor(scope, cause) {
|
|
14
|
+
super("Rate limiter is unavailable", { cause });
|
|
15
|
+
this.scope = scope;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/** Bind one scope and window to a Rate Limiting binding. Build it once in the composition root. */
|
|
19
|
+
function createRateLimiter(options) {
|
|
20
|
+
return { async limit(key) {
|
|
21
|
+
const { success } = await consume(options, key);
|
|
22
|
+
if (!success) throw new RateLimitError(options.window);
|
|
23
|
+
} };
|
|
24
|
+
}
|
|
25
|
+
async function consume(options, key) {
|
|
26
|
+
try {
|
|
27
|
+
return await options.binding.limit({ key: JSON.stringify([options.scope, key]) });
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
throw new RateLimiterUnavailableError(options.scope, cause);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { RateLimiterUnavailableError, createRateLimiter };
|
|
35
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","names":[],"sources":["../../src/infrastructure/cloudflare/rate-limiter.ts"],"sourcesContent":["import { AppError, RateLimitError } from '@/core'\n\n/**\n * Caller limits through a Workers Rate Limiting binding. Ported from the `consume` path of\n * `better-auth/rate-limit.ts` and Porte `auth-rate-limit.ts`. This module does not log.\n */\n\n/** The binding threw instead of answering. A refused call is `RateLimitError`, never this error. */\nexport class RateLimiterUnavailableError extends AppError {\n readonly _tag = 'RateLimiterUnavailableError'\n readonly classification = 'unknown'\n\n constructor(\n readonly scope: string,\n cause: unknown,\n ) {\n super('Rate limiter is unavailable', { cause })\n }\n}\n\n/** One feature's limit on a binding. The binding's `simple.limit` sets how many calls a window allows. */\nexport type RateLimiterOptions = {\n readonly binding: Pick<RateLimit, 'limit'>\n /** Names the feature. Two scopes on one binding keep separate counters. */\n readonly scope: string\n /** The binding's `simple.period` in seconds. Cloudflare accepts only 10 or 60. */\n readonly window: 10 | 60\n}\n\n/** Counts calls for each caller key inside one scope. */\nexport type RateLimiter = {\n /**\n * Count one call for `key`, and resolve when the binding allows it.\n * A refused call throws `RateLimitError` with `retryAfter` set to the window.\n * A binding failure throws `RateLimiterUnavailableError`. The call does not retry, because a retry counts again.\n */\n readonly limit: (key: string) => Promise<void>\n}\n\n/** Bind one scope and window to a Rate Limiting binding. Build it once in the composition root. */\nexport function createRateLimiter(options: RateLimiterOptions): RateLimiter {\n return {\n async limit(key) {\n const { success } = await consume(options, key)\n if (!success) throw new RateLimitError(options.window)\n },\n }\n}\n\nasync function consume(options: RateLimiterOptions, key: string): Promise<RateLimitOutcome> {\n try {\n // A JSON pair keeps `a:b` + `c` apart from `a` + `b:c`.\n return await options.binding.limit({ key: JSON.stringify([options.scope, key]) })\n } catch (cause) {\n throw new RateLimiterUnavailableError(options.scope, cause)\n }\n}\n"],"mappings":";;;;;;;;AAQA,IAAa,8BAAb,cAAiD,SAAS;CAK7C;CAJX,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,YACE,AAAS,OACT,OACA;EACA,MAAM,+BAA+B,EAAE,MAAM,CAAC;EAHrC;CAIX;AACF;;AAsBA,SAAgB,kBAAkB,SAA0C;CAC1E,OAAO,EACL,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,YAAY,MAAM,QAAQ,SAAS,GAAG;EAC9C,IAAI,CAAC,SAAS,MAAM,IAAI,eAAe,QAAQ,MAAM;CACvD,EACF;AACF;AAEA,eAAe,QAAQ,SAA6B,KAAwC;CAC1F,IAAI;EAEF,OAAO,MAAM,QAAQ,QAAQ,MAAM,EAAE,KAAK,KAAK,UAAU,CAAC,QAAQ,OAAO,GAAG,CAAC,EAAE,CAAC;CAClF,SAAS,OAAO;EACd,MAAM,IAAI,4BAA4B,QAAQ,OAAO,KAAK;CAC5D;AACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "better-ship",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.28.0",
|
|
5
5
|
"description": "Foundation for applications on Cloudflare Workers: logger, errors, message bus, infrastructure adapters, and UI primitives",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"./cloudflare/queue": "./dist/cloudflare/queue.js",
|
|
37
37
|
"./cloudflare/r2-client": "./dist/cloudflare/r2-client.js",
|
|
38
38
|
"./cloudflare/r2-errors": "./dist/cloudflare/r2-errors.js",
|
|
39
|
+
"./cloudflare/rate-limiter": "./dist/cloudflare/rate-limiter.js",
|
|
39
40
|
"./cloudflare/scheduled": "./dist/cloudflare/scheduled.js",
|
|
40
41
|
"./cloudflare/tracing": "./dist/cloudflare/tracing.js",
|
|
41
42
|
"./cloudflare/turnstile": "./dist/cloudflare/turnstile.js",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AppError, RateLimitError } from '@/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Caller limits through a Workers Rate Limiting binding. Ported from the `consume` path of
|
|
5
|
+
* `better-auth/rate-limit.ts` and Porte `auth-rate-limit.ts`. This module does not log.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The binding threw instead of answering. A refused call is `RateLimitError`, never this error. */
|
|
9
|
+
export class RateLimiterUnavailableError extends AppError {
|
|
10
|
+
readonly _tag = 'RateLimiterUnavailableError'
|
|
11
|
+
readonly classification = 'unknown'
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly scope: string,
|
|
15
|
+
cause: unknown,
|
|
16
|
+
) {
|
|
17
|
+
super('Rate limiter is unavailable', { cause })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One feature's limit on a binding. The binding's `simple.limit` sets how many calls a window allows. */
|
|
22
|
+
export type RateLimiterOptions = {
|
|
23
|
+
readonly binding: Pick<RateLimit, 'limit'>
|
|
24
|
+
/** Names the feature. Two scopes on one binding keep separate counters. */
|
|
25
|
+
readonly scope: string
|
|
26
|
+
/** The binding's `simple.period` in seconds. Cloudflare accepts only 10 or 60. */
|
|
27
|
+
readonly window: 10 | 60
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Counts calls for each caller key inside one scope. */
|
|
31
|
+
export type RateLimiter = {
|
|
32
|
+
/**
|
|
33
|
+
* Count one call for `key`, and resolve when the binding allows it.
|
|
34
|
+
* A refused call throws `RateLimitError` with `retryAfter` set to the window.
|
|
35
|
+
* A binding failure throws `RateLimiterUnavailableError`. The call does not retry, because a retry counts again.
|
|
36
|
+
*/
|
|
37
|
+
readonly limit: (key: string) => Promise<void>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Bind one scope and window to a Rate Limiting binding. Build it once in the composition root. */
|
|
41
|
+
export function createRateLimiter(options: RateLimiterOptions): RateLimiter {
|
|
42
|
+
return {
|
|
43
|
+
async limit(key) {
|
|
44
|
+
const { success } = await consume(options, key)
|
|
45
|
+
if (!success) throw new RateLimitError(options.window)
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function consume(options: RateLimiterOptions, key: string): Promise<RateLimitOutcome> {
|
|
51
|
+
try {
|
|
52
|
+
// A JSON pair keeps `a:b` + `c` apart from `a` + `b:c`.
|
|
53
|
+
return await options.binding.limit({ key: JSON.stringify([options.scope, key]) })
|
|
54
|
+
} catch (cause) {
|
|
55
|
+
throw new RateLimiterUnavailableError(options.scope, cause)
|
|
56
|
+
}
|
|
57
|
+
}
|