@bejibun/core 0.1.52 → 0.1.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/CHANGELOG.md +35 -0
- package/builders/RateLimiterBuilder.d.ts +12 -0
- package/builders/RateLimiterBuilder.js +44 -0
- package/builders/RouterBuilder.js +1 -1
- package/builders/StorageBuilder.d.ts +10 -0
- package/builders/StorageBuilder.js +29 -0
- package/config/limiter.d.ts +2 -0
- package/config/limiter.js +5 -0
- package/exceptions/ExceptionHandler.d.ts +4 -1
- package/exceptions/ExceptionHandler.js +6 -0
- package/exceptions/RateLimiterException.d.ts +4 -0
- package/exceptions/RateLimiterException.js +14 -0
- package/facades/RateLimiter.d.ts +5 -0
- package/facades/RateLimiter.js +23 -0
- package/facades/Storage.d.ts +3 -0
- package/facades/Storage.js +12 -0
- package/middlewares/MaintenanceMiddleware.js +2 -2
- package/middlewares/RateLimiterMiddleware.d.ts +4 -0
- package/middlewares/RateLimiterMiddleware.js +20 -0
- package/middlewares/X402Middleware.js +2 -2
- package/package.json +2 -1
- package/server.js +2 -1
- package/types/router.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
## [v0.1.54](https://github.com/crenata/bejibun-core/compare/v0.1.53...v0.1.54) - 2025-11-28
|
|
7
|
+
|
|
8
|
+
### 🩹 Fixes
|
|
9
|
+
- Fix x402 middleware for optional
|
|
10
|
+
|
|
11
|
+
### 📖 Changes
|
|
12
|
+
|
|
13
|
+
### ❤️Contributors
|
|
14
|
+
- Ghulje ([@ghulje](https://github.com/ghulje))
|
|
15
|
+
|
|
16
|
+
**Full Changelog**: https://github.com/crenata/bejibun-core/blob/master/CHANGELOG.md
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [v0.1.53](https://github.com/crenata/bejibun-core/compare/v0.1.52...v0.1.53) - 2025-11-24
|
|
21
|
+
|
|
22
|
+
### 🩹 Fixes
|
|
23
|
+
|
|
24
|
+
### 📖 Changes
|
|
25
|
+
What's New :
|
|
26
|
+
- Adding `Rate Limiter` to limit any action in a certain time.
|
|
27
|
+
|
|
28
|
+
Available `Rate Limiter` functions :
|
|
29
|
+
- `.attempt(key, limit, callback, duration)` throw an error if limit reached.
|
|
30
|
+
- `.tooManyAttempts(key, limit, duration)` method to check if limit has reached.
|
|
31
|
+
- `.clear(key)` reset the counter.
|
|
32
|
+
|
|
33
|
+
### ❤️Contributors
|
|
34
|
+
- Havea Crenata ([@crenata](https://github.com/crenata))
|
|
35
|
+
- Ghulje ([@ghulje](https://github.com/ghulje))
|
|
36
|
+
|
|
37
|
+
**Full Changelog**: https://github.com/crenata/bejibun-core/blob/master/CHANGELOG.md
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
6
41
|
## [v0.1.52](https://github.com/crenata/bejibun-core/compare/v0.1.51...v0.1.52) - 2025-11-17
|
|
7
42
|
|
|
8
43
|
### 🩹 Fixes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default class RateLimiterBuilder {
|
|
2
|
+
protected key: string;
|
|
3
|
+
protected limit: number;
|
|
4
|
+
protected duration: number;
|
|
5
|
+
constructor();
|
|
6
|
+
setKey(key: string): RateLimiterBuilder;
|
|
7
|
+
setLimit(limit: number): RateLimiterBuilder;
|
|
8
|
+
setDuration(duration: number): RateLimiterBuilder;
|
|
9
|
+
attempt(callback: Function): Promise<any>;
|
|
10
|
+
tooManyAttempts(): Promise<boolean>;
|
|
11
|
+
clear(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Cache from "@bejibun/cache";
|
|
2
|
+
import { isNotEmpty } from "@bejibun/utils";
|
|
3
|
+
import RateLimiterException from "../exceptions/RateLimiterException";
|
|
4
|
+
export default class RateLimiterBuilder {
|
|
5
|
+
key;
|
|
6
|
+
limit;
|
|
7
|
+
duration; // seconds
|
|
8
|
+
constructor() {
|
|
9
|
+
this.key = "";
|
|
10
|
+
this.limit = 60;
|
|
11
|
+
this.duration = 60;
|
|
12
|
+
}
|
|
13
|
+
setKey(key) {
|
|
14
|
+
this.key = key;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
setLimit(limit) {
|
|
18
|
+
this.limit = limit;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
setDuration(duration) {
|
|
22
|
+
this.duration = duration;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
async attempt(callback) {
|
|
26
|
+
const count = Number(await Cache.increment(this.key, this.duration));
|
|
27
|
+
const canExecute = count <= this.limit;
|
|
28
|
+
if (isNotEmpty(callback) && typeof callback === "function") {
|
|
29
|
+
if (canExecute)
|
|
30
|
+
return callback();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new RateLimiterException("Invalid callback.");
|
|
34
|
+
}
|
|
35
|
+
throw new RateLimiterException("Too many attempts.");
|
|
36
|
+
}
|
|
37
|
+
async tooManyAttempts() {
|
|
38
|
+
const count = Number(await Cache.get(this.key));
|
|
39
|
+
return count > this.limit;
|
|
40
|
+
}
|
|
41
|
+
async clear() {
|
|
42
|
+
return await Cache.forget(this.key);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -4,7 +4,6 @@ import HttpMethodEnum from "@bejibun/utils/enums/HttpMethodEnum";
|
|
|
4
4
|
import Enum from "@bejibun/utils/facades/Enum";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import RouterInvalidException from "../exceptions/RouterInvalidException";
|
|
7
|
-
import X402Middleware from "../middlewares/X402Middleware";
|
|
8
7
|
export default class RouterBuilder {
|
|
9
8
|
basePath = "";
|
|
10
9
|
middlewares = [];
|
|
@@ -24,6 +23,7 @@ export default class RouterBuilder {
|
|
|
24
23
|
x402(config, facilitatorConfig, paywallConfig) {
|
|
25
24
|
if (!isModuleExists("@bejibun/x402"))
|
|
26
25
|
throw new RouterInvalidException("@bejibun/x402 is not installed.");
|
|
26
|
+
const X402Middleware = require("../middlewares/X402Middleware").default;
|
|
27
27
|
this.middlewares.push(new X402Middleware(config, facilitatorConfig, paywallConfig));
|
|
28
28
|
return this;
|
|
29
29
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default class StorageBuilder {
|
|
2
|
+
protected file: any;
|
|
3
|
+
protected directory?: string;
|
|
4
|
+
protected name?: string;
|
|
5
|
+
constructor();
|
|
6
|
+
setFile(file: any): StorageBuilder;
|
|
7
|
+
setDirectory(directory: string): StorageBuilder;
|
|
8
|
+
setName(name: string): StorageBuilder;
|
|
9
|
+
save(): Promise<any>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
2
|
+
import { isEmpty } from "@bejibun/utils";
|
|
3
|
+
import Luxon from "@bejibun/utils/facades/Luxon";
|
|
4
|
+
import path from "path";
|
|
5
|
+
export default class StorageBuilder {
|
|
6
|
+
file;
|
|
7
|
+
directory;
|
|
8
|
+
name;
|
|
9
|
+
constructor() {
|
|
10
|
+
this.name = "";
|
|
11
|
+
}
|
|
12
|
+
setFile(file) {
|
|
13
|
+
this.file = file;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
setDirectory(directory) {
|
|
17
|
+
this.directory = directory;
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
setName(name) {
|
|
21
|
+
this.name = name;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
async save() {
|
|
25
|
+
if (isEmpty(this.name))
|
|
26
|
+
this.name = `${Luxon.DateTime.now().toUnixInteger()}-${Math.random().toString(36).substring(2, 2 + 16)}`;
|
|
27
|
+
await Bun.write(App.Path.storagePath(`app/public/${this.directory}/${this.name}${path.extname(this.file?.name)}`), this.file);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { ValidationError } from "objection";
|
|
2
2
|
import ModelNotFoundException from "../exceptions/ModelNotFoundException";
|
|
3
|
+
import RateLimiterException from "../exceptions/RateLimiterException";
|
|
4
|
+
import RouterInvalidException from "../exceptions/RouterInvalidException";
|
|
5
|
+
import RuntimeException from "../exceptions/RuntimeException";
|
|
3
6
|
import ValidatorException from "../exceptions/ValidatorException";
|
|
4
7
|
export default class ExceptionHandler {
|
|
5
|
-
handle(error: Bun.ErrorLike | ModelNotFoundException | ValidatorException | ValidationError): globalThis.Response;
|
|
8
|
+
handle(error: Bun.ErrorLike | ModelNotFoundException | RateLimiterException | RouterInvalidException | RuntimeException | ValidatorException | ValidationError): globalThis.Response;
|
|
6
9
|
route(request: Bun.BunRequest): globalThis.Response;
|
|
7
10
|
}
|
|
@@ -3,12 +3,18 @@ import { defineValue } from "@bejibun/utils";
|
|
|
3
3
|
import HttpMethodEnum from "@bejibun/utils/enums/HttpMethodEnum";
|
|
4
4
|
import { ValidationError } from "objection";
|
|
5
5
|
import ModelNotFoundException from "../exceptions/ModelNotFoundException";
|
|
6
|
+
import RateLimiterException from "../exceptions/RateLimiterException";
|
|
7
|
+
import RouterInvalidException from "../exceptions/RouterInvalidException";
|
|
8
|
+
import RuntimeException from "../exceptions/RuntimeException";
|
|
6
9
|
import ValidatorException from "../exceptions/ValidatorException";
|
|
7
10
|
import Response from "../facades/Response";
|
|
8
11
|
export default class ExceptionHandler {
|
|
9
12
|
handle(error) {
|
|
10
13
|
Logger.setContext("APP").error(error.message).trace(error.stack);
|
|
11
14
|
if (error instanceof ModelNotFoundException ||
|
|
15
|
+
error instanceof RateLimiterException ||
|
|
16
|
+
error instanceof RouterInvalidException ||
|
|
17
|
+
error instanceof RuntimeException ||
|
|
12
18
|
error instanceof ValidatorException)
|
|
13
19
|
return Response
|
|
14
20
|
.setMessage(error.message)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Logger from "@bejibun/logger";
|
|
2
|
+
import { defineValue } from "@bejibun/utils";
|
|
3
|
+
export default class RateLimiterException extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "RateLimiterException";
|
|
8
|
+
this.code = defineValue(code, 429);
|
|
9
|
+
Logger.setContext(this.name).error(this.message).trace(this.stack);
|
|
10
|
+
if (Error.captureStackTrace) {
|
|
11
|
+
Error.captureStackTrace(this, RateLimiterException);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export default class RateLimiter {
|
|
2
|
+
static attempt(key: string, limit: number, callback: Function, duration?: number): Promise<any>;
|
|
3
|
+
static tooManyAttempts(key: string, limit: number, duration?: number): Promise<boolean>;
|
|
4
|
+
static clear(key: string): Promise<void>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineValue } from "@bejibun/utils";
|
|
2
|
+
import RateLimiterBuilder from "../builders/RateLimiterBuilder";
|
|
3
|
+
export default class RateLimiter {
|
|
4
|
+
static async attempt(key, limit, callback, duration) {
|
|
5
|
+
return await new RateLimiterBuilder()
|
|
6
|
+
.setKey(key)
|
|
7
|
+
.setLimit(defineValue(limit, 60))
|
|
8
|
+
.setDuration(defineValue(duration, 60))
|
|
9
|
+
.attempt(callback);
|
|
10
|
+
}
|
|
11
|
+
static async tooManyAttempts(key, limit, duration) {
|
|
12
|
+
return await new RateLimiterBuilder()
|
|
13
|
+
.setKey(key)
|
|
14
|
+
.setLimit(defineValue(limit, 60))
|
|
15
|
+
.setDuration(defineValue(duration, 60))
|
|
16
|
+
.tooManyAttempts();
|
|
17
|
+
}
|
|
18
|
+
static async clear(key) {
|
|
19
|
+
return await new RateLimiterBuilder()
|
|
20
|
+
.setKey(key)
|
|
21
|
+
.clear();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { isNotEmpty } from "@bejibun/utils";
|
|
2
|
+
import StorageBuilder from "../builders/StorageBuilder";
|
|
3
|
+
export default class Storage {
|
|
4
|
+
static async save(file, directory, name) {
|
|
5
|
+
const builder = new StorageBuilder().setFile(file);
|
|
6
|
+
if (isNotEmpty(directory))
|
|
7
|
+
builder.setDirectory(directory);
|
|
8
|
+
if (isNotEmpty(name))
|
|
9
|
+
builder.setName(name);
|
|
10
|
+
return await builder.save();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -2,7 +2,7 @@ import App from "@bejibun/app";
|
|
|
2
2
|
import Response from "../facades/Response";
|
|
3
3
|
export default class MaintenanceMiddleware {
|
|
4
4
|
handle(handler) {
|
|
5
|
-
return async (request) => {
|
|
5
|
+
return async (request, server) => {
|
|
6
6
|
if (await App.Maintenance.isMaintenanceMode()) {
|
|
7
7
|
const maintenance = await App.Maintenance.getData();
|
|
8
8
|
return Response
|
|
@@ -10,7 +10,7 @@ export default class MaintenanceMiddleware {
|
|
|
10
10
|
.setStatus(maintenance.status)
|
|
11
11
|
.send();
|
|
12
12
|
}
|
|
13
|
-
return handler(request);
|
|
13
|
+
return handler(request, server);
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
2
|
+
import { defineValue } from "@bejibun/utils";
|
|
3
|
+
import LimiterConfig from "../config/limiter";
|
|
4
|
+
import RateLimiter from "../facades/RateLimiter";
|
|
5
|
+
export default class RateLimiterMiddleware {
|
|
6
|
+
handle(handler) {
|
|
7
|
+
return async (request, server) => {
|
|
8
|
+
const configPath = App.Path.configPath("limiter.ts");
|
|
9
|
+
let config;
|
|
10
|
+
if (await Bun.file(configPath).exists())
|
|
11
|
+
config = require(configPath).default;
|
|
12
|
+
else
|
|
13
|
+
config = LimiterConfig;
|
|
14
|
+
return await RateLimiter
|
|
15
|
+
.attempt(`rate-limiter/${defineValue(server.requestIP(request)?.address, "")}`, defineValue(config?.limit, 60), () => {
|
|
16
|
+
return handler(request, server);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -9,14 +9,14 @@ export default class X402Middleware {
|
|
|
9
9
|
this.paywallConfig = paywallConfig;
|
|
10
10
|
}
|
|
11
11
|
handle(handler) {
|
|
12
|
-
return async (request) => {
|
|
12
|
+
return async (request, server) => {
|
|
13
13
|
return X402
|
|
14
14
|
.setConfig(this.config)
|
|
15
15
|
.setFacilitator(this.facilitatorConfig)
|
|
16
16
|
.setPaywall(this.paywallConfig)
|
|
17
17
|
.setRequest(request)
|
|
18
18
|
.middleware(() => {
|
|
19
|
-
return handler(request);
|
|
19
|
+
return handler(request, server);
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bejibun/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
4
4
|
"author": "Havea Crenata <havea.crenata@gmail.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"module": "index.js",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bejibun/app": "^0.1.22",
|
|
13
|
+
"@bejibun/cache": "^0.1.11",
|
|
13
14
|
"@bejibun/cors": "^0.1.16",
|
|
14
15
|
"@bejibun/database": "^0.1.19",
|
|
15
16
|
"@bejibun/logger": "^0.1.22",
|
package/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import Logger from "@bejibun/logger";
|
|
|
3
3
|
import RuntimeException from "./exceptions/RuntimeException";
|
|
4
4
|
import Router from "./facades/Router";
|
|
5
5
|
import MaintenanceMiddleware from "./middlewares/MaintenanceMiddleware";
|
|
6
|
+
import RateLimiterMiddleware from "./middlewares/RateLimiterMiddleware";
|
|
6
7
|
import(App.Path.rootPath("bootstrap.ts"));
|
|
7
8
|
const exceptionHandlerPath = App.Path.appPath("exceptions/handler.ts");
|
|
8
9
|
let ExceptionHandler;
|
|
@@ -39,7 +40,7 @@ const server = Bun.serve({
|
|
|
39
40
|
port: Bun.env.APP_PORT,
|
|
40
41
|
routes: {
|
|
41
42
|
"/": require(App.Path.publicPath("index.html")),
|
|
42
|
-
...Router.middleware(new MaintenanceMiddleware()).group([
|
|
43
|
+
...Router.middleware(new MaintenanceMiddleware(), new RateLimiterMiddleware()).group([
|
|
43
44
|
Router.namespace("app/exceptions").any("/*", "Handler@route"),
|
|
44
45
|
ApiRoutes,
|
|
45
46
|
WebRoutes
|
package/types/router.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type HandlerType = (request: Bun.BunRequest) => Promise<Response>;
|
|
1
|
+
export type HandlerType = (request: Bun.BunRequest, server: Bun.Server) => Promise<Response>;
|
|
2
2
|
export type RouterGroup = Record<string, Record<string, HandlerType>>;
|
|
3
3
|
export type ResourceAction = "index" | "store" | "show" | "update" | "destroy";
|