@bgord/bun 0.1.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 +21 -0
- package/README.md +3 -0
- package/dist/api-key-shield.d.ts +16 -0
- package/dist/api-version.d.ts +8 -0
- package/dist/auth-shield.d.ts +32 -0
- package/dist/basic-auth-shield.d.ts +14 -0
- package/dist/bgord-bun.cjs +2 -0
- package/dist/bgord-bun.cjs.map +1 -0
- package/dist/bgord-bun.modern.js +2 -0
- package/dist/bgord-bun.modern.js.map +1 -0
- package/dist/bgord-bun.module.js +2 -0
- package/dist/bgord-bun.module.js.map +1 -0
- package/dist/bgord-bun.umd.js +2 -0
- package/dist/bgord-bun.umd.js.map +1 -0
- package/dist/build-info-repository.d.ts +8 -0
- package/dist/bun/api-key-shield.d.ts +16 -0
- package/dist/bun/api-version.d.ts +8 -0
- package/dist/bun/context.d.ts +12 -0
- package/dist/bun/etag-extractor.d.ts +17 -0
- package/dist/bun/graceful-shutdown.d.ts +6 -0
- package/dist/bun/healthcheck.d.ts +22 -0
- package/dist/bun/http-logger.d.ts +9 -0
- package/dist/bun/index.d.ts +9 -0
- package/dist/bun/rate-limit-shield.d.ts +11 -0
- package/dist/bun/time-zone-offset.d.ts +16 -0
- package/dist/cache-response.d.ts +15 -0
- package/dist/cache-static-files.d.ts +9 -0
- package/dist/cache.d.ts +1 -0
- package/dist/clock.d.ts +49 -0
- package/dist/context.d.ts +12 -0
- package/dist/credentials.d.ts +28 -0
- package/dist/dates.d.ts +37 -0
- package/dist/decorators.d.ts +8 -0
- package/dist/deep-merge.d.ts +7 -0
- package/dist/dll.d.ts +29 -0
- package/dist/download-file.d.ts +10 -0
- package/dist/email-censor.d.ts +4 -0
- package/dist/encryption.d.ts +20 -0
- package/dist/environment-validator.d.ts +23 -0
- package/dist/errors.d.ts +49 -0
- package/dist/etag-extractor.d.ts +17 -0
- package/dist/etag.d.ts +19 -0
- package/dist/event.d.ts +59 -0
- package/dist/express-essentials.d.ts +35 -0
- package/dist/feature-flag.d.ts +5 -0
- package/dist/file-location.d.ts +31 -0
- package/dist/file-uploader.d.ts +12 -0
- package/dist/file.d.ts +4 -0
- package/dist/filter.d.ts +17 -0
- package/dist/graceful-shutdown.d.ts +6 -0
- package/dist/gzip.d.ts +12 -0
- package/dist/handlebars.d.ts +10 -0
- package/dist/hcaptcha-shield.d.ts +24 -0
- package/dist/healthcheck.d.ts +20 -0
- package/dist/http-logger.d.ts +10 -0
- package/dist/i18n.d.ts +31 -0
- package/dist/image-compressor.d.ts +10 -0
- package/dist/image-converter.d.ts +11 -0
- package/dist/image-exif.d.ts +18 -0
- package/dist/image-resizer.d.ts +16 -0
- package/dist/index.d.ts +9 -0
- package/dist/jobs.d.ts +32 -0
- package/dist/leap-year-checker.d.ts +4 -0
- package/dist/logger.d.ts +53 -0
- package/dist/mailer.d.ts +16 -0
- package/dist/mean.d.ts +4 -0
- package/dist/memory-consumption.d.ts +4 -0
- package/dist/method-override.d.ts +4 -0
- package/dist/mime-types.d.ts +2 -0
- package/dist/mime.d.ts +10 -0
- package/dist/min-max-scaler.d.ts +36 -0
- package/dist/money.d.ts +24 -0
- package/dist/noop.d.ts +2 -0
- package/dist/open-graph.d.ts +91 -0
- package/dist/outlier-detector.d.ts +6 -0
- package/dist/package-version.d.ts +20 -0
- package/dist/pagination.d.ts +58 -0
- package/dist/percentage.d.ts +4 -0
- package/dist/policy.d.ts +11 -0
- package/dist/polyfills.d.ts +0 -0
- package/dist/population-standard-deviation.d.ts +4 -0
- package/dist/prerequisites/binary.d.ts +16 -0
- package/dist/prerequisites/bun.d.ts +13 -0
- package/dist/prerequisites/jobs.d.ts +13 -0
- package/dist/prerequisites/mailer.d.ts +13 -0
- package/dist/prerequisites/memory.d.ts +13 -0
- package/dist/prerequisites/node.d.ts +13 -0
- package/dist/prerequisites/outside-connectivity.d.ts +11 -0
- package/dist/prerequisites/path.d.ts +16 -0
- package/dist/prerequisites/port.d.ts +13 -0
- package/dist/prerequisites/ram.d.ts +13 -0
- package/dist/prerequisites/self.d.ts +11 -0
- package/dist/prerequisites/space.d.ts +13 -0
- package/dist/prerequisites/ssl-certificate-expiry.d.ts +13 -0
- package/dist/prerequisites/timezone-utc.d.ts +13 -0
- package/dist/prerequisites/translations.d.ts +14 -0
- package/dist/prerequisites.d.ts +44 -0
- package/dist/random.d.ts +8 -0
- package/dist/rate-limit-shield.d.ts +11 -0
- package/dist/rate-limiter.d.ts +18 -0
- package/dist/recaptcha-shield.d.ts +12 -0
- package/dist/redirector.d.ts +4 -0
- package/dist/reordering.d.ts +49 -0
- package/dist/request-id.d.ts +13 -0
- package/dist/response-body-in-locals.d.ts +4 -0
- package/dist/revision.d.ts +12 -0
- package/dist/rounding.d.ts +17 -0
- package/dist/schema.d.ts +189 -0
- package/dist/server-timing.d.ts +7 -0
- package/dist/simple-linear-regression.d.ts +18 -0
- package/dist/simulated-error.d.ts +4 -0
- package/dist/sitemap.d.ts +37 -0
- package/dist/size.d.ts +31 -0
- package/dist/sleep.d.ts +3 -0
- package/dist/slower.d.ts +8 -0
- package/dist/static-files.d.ts +14 -0
- package/dist/stepper.d.ts +23 -0
- package/dist/stopwatch.d.ts +14 -0
- package/dist/streak-calculator.d.ts +14 -0
- package/dist/sum.d.ts +3 -0
- package/dist/thousands-separator.d.ts +4 -0
- package/dist/time-zone-offset.d.ts +17 -0
- package/dist/time.d.ts +21 -0
- package/dist/timeout.d.ts +9 -0
- package/dist/ts-utils.d.ts +3 -0
- package/dist/uploaded-file-location.d.ts +10 -0
- package/dist/uptime.d.ts +9 -0
- package/dist/uuid.d.ts +3 -0
- package/dist/visually-unambiguous-characters-generator.d.ts +4 -0
- package/dist/z-score.d.ts +8 -0
- package/package.json +48 -0
- package/src/api-key-shield.ts +23 -0
- package/src/api-version.ts +19 -0
- package/src/context.ts +20 -0
- package/src/etag-extractor.ts +41 -0
- package/src/graceful-shutdown.ts +44 -0
- package/src/healthcheck.ts +58 -0
- package/src/http-logger.ts +123 -0
- package/src/index.ts +9 -0
- package/src/rate-limit-shield.ts +24 -0
- package/src/time-zone-offset.ts +44 -0
package/dist/time.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface TimeResultInterface {
|
|
2
|
+
readonly days: number;
|
|
3
|
+
readonly hours: number;
|
|
4
|
+
readonly minutes: number;
|
|
5
|
+
readonly seconds: number;
|
|
6
|
+
readonly ms: number;
|
|
7
|
+
isAfter(another: TimeResultInterface): boolean;
|
|
8
|
+
isBefore(another: TimeResultInterface): boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class Time {
|
|
11
|
+
static Days(value: number): TimeResultInterface;
|
|
12
|
+
static Hours(value: number): TimeResultInterface;
|
|
13
|
+
static Minutes(value: number): TimeResultInterface;
|
|
14
|
+
static Seconds(value: number): TimeResultInterface;
|
|
15
|
+
static Ms(value: number): TimeResultInterface;
|
|
16
|
+
static Now(now?: number): {
|
|
17
|
+
Minus(time: TimeResultInterface): TimeResultInterface;
|
|
18
|
+
Add(time: TimeResultInterface): TimeResultInterface;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { TimestampType } from "./schema";
|
|
3
|
+
type TimeoutConfigType = {
|
|
4
|
+
ms: TimestampType;
|
|
5
|
+
};
|
|
6
|
+
export declare class Timeout {
|
|
7
|
+
static build(config: TimeoutConfigType): (request: express.Request, response: express.Response, next: express.NextFunction) => void;
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Schema from "./schema";
|
|
2
|
+
import { FileLocation, FileLocationConfigType } from "./file-location";
|
|
3
|
+
import { Size } from "./size";
|
|
4
|
+
export declare class UploadedFileLocation {
|
|
5
|
+
readonly size: Size;
|
|
6
|
+
readonly temporary: Schema.PathType;
|
|
7
|
+
readonly handle: FileLocation;
|
|
8
|
+
constructor(file: Schema.UploadedFileType, parent: FileLocationConfigType["parent"]);
|
|
9
|
+
transfer(): Promise<void>;
|
|
10
|
+
}
|
package/dist/uptime.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DateFormatters } from "./dates";
|
|
2
|
+
import { TimestampType } from "./schema";
|
|
3
|
+
export type UptimeResultType = {
|
|
4
|
+
seconds: TimestampType;
|
|
5
|
+
formatted: ReturnType<(typeof DateFormatters)["relative"]>;
|
|
6
|
+
};
|
|
7
|
+
export declare class Uptime {
|
|
8
|
+
static get(): UptimeResultType;
|
|
9
|
+
}
|
package/dist/uuid.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RoundingStrategy } from "./rounding";
|
|
2
|
+
export declare class ZScore {
|
|
3
|
+
private readonly rounding;
|
|
4
|
+
private readonly mean;
|
|
5
|
+
private readonly standardDeviation;
|
|
6
|
+
constructor(values: number[], rounding?: RoundingStrategy);
|
|
7
|
+
calculate(value: number): number;
|
|
8
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"name": "@bgord/bun",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Bartosz Gordon",
|
|
7
|
+
"source": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"require": "./dist/bgord-bun.cjs",
|
|
10
|
+
"default": "./dist/bgord-bun.modern.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/bgord-bun.cjs",
|
|
13
|
+
"module": "./dist/bgord-bun.module.js",
|
|
14
|
+
"unpkg": "./dist/bgord-bun.umd.js",
|
|
15
|
+
"typings": "./dist/index.d.ts",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "microbundle",
|
|
18
|
+
"prepare": "husky",
|
|
19
|
+
"preinstall": "npx only-allow bun"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src"
|
|
24
|
+
],
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@biomejs/biome": "1.9.3",
|
|
27
|
+
"@commitlint/cli": "19.5.0",
|
|
28
|
+
"@commitlint/config-conventional": "19.5.0",
|
|
29
|
+
"@types/bun": "1.1.11",
|
|
30
|
+
"@types/lodash": "4.17.10",
|
|
31
|
+
"cspell": "8.14.4",
|
|
32
|
+
"husky": "9.1.6",
|
|
33
|
+
"knip": "5.33.2",
|
|
34
|
+
"microbundle": "0.15.1",
|
|
35
|
+
"only-allow": "1.2.1",
|
|
36
|
+
"shellcheck": "3.0.0",
|
|
37
|
+
"supertest": "7.0.0",
|
|
38
|
+
"typescript": "5.6.3"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@bgord/node": "0.82.0",
|
|
42
|
+
"hono": "4.6.4",
|
|
43
|
+
"lodash": "4.17.21"
|
|
44
|
+
},
|
|
45
|
+
"resolutions": {
|
|
46
|
+
"typescript": "4.7.4"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
|
|
5
|
+
type ApiKeyShieldConfigType = { API_KEY: bg.Schema.ApiKeyType };
|
|
6
|
+
|
|
7
|
+
export const AccessDeniedApiKeyError = new HTTPException(403, {
|
|
8
|
+
message: "access_denied_api_key",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export class ApiKeyShield {
|
|
12
|
+
static readonly HEADER_NAME = "bgord-api-key";
|
|
13
|
+
|
|
14
|
+
constructor(private readonly config: ApiKeyShieldConfigType) {}
|
|
15
|
+
|
|
16
|
+
verify = createMiddleware(async (c, next) => {
|
|
17
|
+
if (c.req.header(ApiKeyShield.HEADER_NAME) === this.config.API_KEY) {
|
|
18
|
+
return next();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw AccessDeniedApiKeyError;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
|
|
4
|
+
export class ApiVersion {
|
|
5
|
+
static HEADER_NAME = "api-version";
|
|
6
|
+
|
|
7
|
+
static DEFAULT_API_VERSION = "unknown";
|
|
8
|
+
|
|
9
|
+
static attach = createMiddleware(async (c, next) => {
|
|
10
|
+
const build = await bg.BuildInfoRepository.extract();
|
|
11
|
+
|
|
12
|
+
c.res.headers.set(
|
|
13
|
+
ApiVersion.HEADER_NAME,
|
|
14
|
+
build.BUILD_VERSION ?? ApiVersion.DEFAULT_API_VERSION
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
await next();
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
|
|
4
|
+
import { TimeZoneOffsetVariables } from "./time-zone-offset";
|
|
5
|
+
|
|
6
|
+
export type ContextVariables = {
|
|
7
|
+
context: bg.ContextType;
|
|
8
|
+
requestId: string;
|
|
9
|
+
} & TimeZoneOffsetVariables;
|
|
10
|
+
|
|
11
|
+
export class Context {
|
|
12
|
+
static attach = createMiddleware(async (c, next) => {
|
|
13
|
+
c.set("context", {
|
|
14
|
+
requestId: c.get("requestId") as bg.Schema.CorrelationIdType,
|
|
15
|
+
timeZoneOffset: c.get("timeZoneOffset"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await next();
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
|
|
4
|
+
export type EtagVariables = {
|
|
5
|
+
ETag: bg.ETag | null;
|
|
6
|
+
WeakETag: bg.WeakETag | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class ETagExtractor {
|
|
10
|
+
static attach = createMiddleware<{ Variables: EtagVariables }>(
|
|
11
|
+
async (c, next) => {
|
|
12
|
+
try {
|
|
13
|
+
const header = String(c.req.header(bg.ETag.IF_MATCH_HEADER_NAME));
|
|
14
|
+
|
|
15
|
+
if (!header || header === "undefined") c.set("ETag", null);
|
|
16
|
+
else c.set("ETag", bg.ETag.fromHeader(header));
|
|
17
|
+
} catch (error) {
|
|
18
|
+
c.set("ETag", null);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await next();
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class WeakETagExtractor {
|
|
27
|
+
static attach = createMiddleware<{ Variables: EtagVariables }>(
|
|
28
|
+
async (c, next) => {
|
|
29
|
+
try {
|
|
30
|
+
const header = String(c.req.header(bg.WeakETag.IF_MATCH_HEADER_NAME));
|
|
31
|
+
|
|
32
|
+
if (!header || header === "undefined") c.set("WeakETag", null);
|
|
33
|
+
else c.set("WeakETag", bg.ETag.fromHeader(header));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
c.set("WeakETag", null);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await next();
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
|
|
3
|
+
type ServerType = ReturnType<typeof Bun.serve>;
|
|
4
|
+
|
|
5
|
+
export class GracefulShutdown {
|
|
6
|
+
private static async shutdown(
|
|
7
|
+
server: ServerType,
|
|
8
|
+
callback: () => any = bg.noop
|
|
9
|
+
) {
|
|
10
|
+
server.stop();
|
|
11
|
+
await callback();
|
|
12
|
+
// biome-ignore lint: lint/suspicious/noConsoleLog
|
|
13
|
+
console.log("HTTP server closed");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static applyTo(server: ServerType, callback: () => any = bg.noop) {
|
|
17
|
+
process.on("SIGTERM", async () => {
|
|
18
|
+
// biome-ignore lint: lint/suspicious/noConsoleLog
|
|
19
|
+
console.log("SIGTERM signal received: closing HTTP server");
|
|
20
|
+
await GracefulShutdown.shutdown(server, callback);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
process.on("SIGINT", async () => {
|
|
25
|
+
// biome-ignore lint: lint/suspicious/noConsoleLog
|
|
26
|
+
console.log("SIGINT signal received: closing HTTP server");
|
|
27
|
+
await GracefulShutdown.shutdown(server, callback);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
process.on("unhandledRejection", async (event) => {
|
|
32
|
+
// biome-ignore lint: lint/suspicious/noConsoleLog
|
|
33
|
+
console.log(
|
|
34
|
+
"UnhandledPromiseRejectionWarning received: closing HTTP server"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// biome-ignore lint: lint/suspicious/noConsoleLog
|
|
38
|
+
console.log(JSON.stringify(event));
|
|
39
|
+
|
|
40
|
+
await GracefulShutdown.shutdown(server, callback);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createFactory } from "hono/factory";
|
|
3
|
+
|
|
4
|
+
const handler = createFactory();
|
|
5
|
+
|
|
6
|
+
type HealthcheckResultType = {
|
|
7
|
+
ok: bg.PrerequisiteStatusEnum;
|
|
8
|
+
version: bg.Schema.BuildVersionType;
|
|
9
|
+
details: {
|
|
10
|
+
label: bg.PrerequisiteLabelType;
|
|
11
|
+
status: bg.PrerequisiteStatusEnum;
|
|
12
|
+
}[];
|
|
13
|
+
uptime: bg.UptimeResultType;
|
|
14
|
+
memory: {
|
|
15
|
+
bytes: bg.Size["bytes"];
|
|
16
|
+
formatted: ReturnType<bg.Size["format"]>;
|
|
17
|
+
};
|
|
18
|
+
} & bg.StopwatchResultType;
|
|
19
|
+
|
|
20
|
+
export class Healthcheck {
|
|
21
|
+
static build = (
|
|
22
|
+
prerequisites: bg.AbstractPrerequisite<bg.BasePrerequisiteConfig>[]
|
|
23
|
+
) =>
|
|
24
|
+
handler.createHandlers(async (c) => {
|
|
25
|
+
const stopwatch = new bg.Stopwatch();
|
|
26
|
+
|
|
27
|
+
const build = await bg.BuildInfoRepository.extract();
|
|
28
|
+
|
|
29
|
+
const details: HealthcheckResultType["details"][number][] = [];
|
|
30
|
+
|
|
31
|
+
for (const prerequisite of prerequisites) {
|
|
32
|
+
const status = await prerequisite.verify();
|
|
33
|
+
details.push({ label: prerequisite.label, status });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ok = details.every(
|
|
37
|
+
(result) => result.status !== bg.PrerequisiteStatusEnum.failure
|
|
38
|
+
)
|
|
39
|
+
? bg.PrerequisiteStatusEnum.success
|
|
40
|
+
: bg.PrerequisiteStatusEnum.failure;
|
|
41
|
+
|
|
42
|
+
const code = ok === bg.PrerequisiteStatusEnum.success ? 200 : 424;
|
|
43
|
+
|
|
44
|
+
const result: HealthcheckResultType = {
|
|
45
|
+
ok,
|
|
46
|
+
details,
|
|
47
|
+
version: build.BUILD_VERSION ?? bg.Schema.BuildVersion.parse("unknown"),
|
|
48
|
+
uptime: bg.Uptime.get(),
|
|
49
|
+
memory: {
|
|
50
|
+
bytes: bg.MemoryConsumption.get().toBytes(),
|
|
51
|
+
formatted: bg.MemoryConsumption.get().format(bg.SizeUnit.MB),
|
|
52
|
+
},
|
|
53
|
+
...stopwatch.stop(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return c.json(result, code);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
import { getConnInfo } from "hono/bun";
|
|
4
|
+
import _ from "lodash";
|
|
5
|
+
|
|
6
|
+
export class HttpLogger {
|
|
7
|
+
private static simplify(response: unknown) {
|
|
8
|
+
const result = JSON.stringify(response, (_key, value) =>
|
|
9
|
+
Array.isArray(value) ? { type: "Array", length: value.length } : value
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return JSON.parse(result);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private static uninformativeHeaders = [
|
|
16
|
+
"accept",
|
|
17
|
+
"accept-encoding",
|
|
18
|
+
"cache-control",
|
|
19
|
+
"connection",
|
|
20
|
+
"content-length",
|
|
21
|
+
"content-type",
|
|
22
|
+
"cookie",
|
|
23
|
+
"dnt",
|
|
24
|
+
"host",
|
|
25
|
+
"origin",
|
|
26
|
+
"pragma",
|
|
27
|
+
"sec-fetch-dest",
|
|
28
|
+
"sec-fetch-mode",
|
|
29
|
+
"sec-fetch-site",
|
|
30
|
+
"sec-fetch-user",
|
|
31
|
+
"sec-gpc",
|
|
32
|
+
"upgrade-insecure-requests",
|
|
33
|
+
"user-agent",
|
|
34
|
+
"if-none-match",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
static build = (logger: bg.Logger) =>
|
|
38
|
+
createMiddleware(async (c, next) => {
|
|
39
|
+
const correlationId = c.get("requestId") as bg.Schema.CorrelationIdType;
|
|
40
|
+
const info = getConnInfo(c);
|
|
41
|
+
const url = c.req.url;
|
|
42
|
+
const method = c.req.method;
|
|
43
|
+
|
|
44
|
+
const client = {
|
|
45
|
+
ip:
|
|
46
|
+
c.req.header("x-real-ip") ||
|
|
47
|
+
c.req.header("x-forwarded-for") ||
|
|
48
|
+
info.remote.address,
|
|
49
|
+
userAgent: c.req.header("user-agent"),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let body: any;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
body = await c.req.json();
|
|
56
|
+
} catch (error) {}
|
|
57
|
+
|
|
58
|
+
const httpRequestBeforeMetadata = {
|
|
59
|
+
params: c.req.param(),
|
|
60
|
+
headers: _.omit(
|
|
61
|
+
c.req.raw.headers.toJSON(),
|
|
62
|
+
HttpLogger.uninformativeHeaders
|
|
63
|
+
),
|
|
64
|
+
body,
|
|
65
|
+
query: c.req.queries(),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
logger.http({
|
|
69
|
+
operation: "http_request_before",
|
|
70
|
+
correlationId,
|
|
71
|
+
message: "request",
|
|
72
|
+
method,
|
|
73
|
+
url,
|
|
74
|
+
client,
|
|
75
|
+
metadata: _.pickBy(
|
|
76
|
+
httpRequestBeforeMetadata,
|
|
77
|
+
(value) => !_.isEmpty(value)
|
|
78
|
+
),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await next();
|
|
82
|
+
|
|
83
|
+
const cacheHitHeader = c.res.headers.get(
|
|
84
|
+
bg.CacheResponse.CACHE_HIT_HEADER
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const cacheHit =
|
|
88
|
+
cacheHitHeader === bg.CacheHitEnum.hit
|
|
89
|
+
? bg.CacheHitEnum.hit
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
|
+
let response: any;
|
|
93
|
+
try {
|
|
94
|
+
response = await c.res.clone().json();
|
|
95
|
+
} catch (error) {}
|
|
96
|
+
|
|
97
|
+
const httpRequestAfterMetadata = {
|
|
98
|
+
response,
|
|
99
|
+
cacheHit,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const serverTimingMs = c.res.headers.get("Server-Timing");
|
|
103
|
+
|
|
104
|
+
const durationMsMatch =
|
|
105
|
+
serverTimingMs?.match(/dur=([0-9]*\.?[0-9]+)/) ?? undefined;
|
|
106
|
+
|
|
107
|
+
const durationMs = durationMsMatch?.[1]
|
|
108
|
+
? Number(durationMsMatch[1])
|
|
109
|
+
: undefined;
|
|
110
|
+
|
|
111
|
+
logger.http({
|
|
112
|
+
operation: "http_request_after",
|
|
113
|
+
correlationId,
|
|
114
|
+
message: "response",
|
|
115
|
+
method,
|
|
116
|
+
url,
|
|
117
|
+
responseCode: c.res.status,
|
|
118
|
+
durationMs,
|
|
119
|
+
client,
|
|
120
|
+
metadata: HttpLogger.simplify(httpRequestAfterMetadata),
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./graceful-shutdown";
|
|
2
|
+
export * from "./api-version";
|
|
3
|
+
export * from "./time-zone-offset";
|
|
4
|
+
export * from "./context";
|
|
5
|
+
export * from "./etag-extractor";
|
|
6
|
+
export * from "./http-logger";
|
|
7
|
+
export * from "./api-key-shield";
|
|
8
|
+
export * from "./rate-limit-shield";
|
|
9
|
+
export * from "./healthcheck";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
import { createMiddleware } from "hono/factory";
|
|
4
|
+
|
|
5
|
+
type RateLimitShieldOptionsType = { ms: bg.Schema.TimestampType };
|
|
6
|
+
|
|
7
|
+
export const TooManyRequestsError = new HTTPException(429, {
|
|
8
|
+
message: "app.too_many_requests",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const rateLimitShield = (options: RateLimitShieldOptionsType) => {
|
|
12
|
+
const rateLimiter = new bg.RateLimiter(options);
|
|
13
|
+
|
|
14
|
+
return createMiddleware(async (_c, next) => {
|
|
15
|
+
const currentTimestampMs = Date.now();
|
|
16
|
+
const check = rateLimiter.verify(currentTimestampMs);
|
|
17
|
+
|
|
18
|
+
if (!check.allowed) {
|
|
19
|
+
throw TooManyRequestsError;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return next();
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
|
|
4
|
+
export type TimeZoneOffsetVariables = {
|
|
5
|
+
timeZoneOffset: {
|
|
6
|
+
minutes: bg.Schema.TimeZoneOffsetValueType;
|
|
7
|
+
seconds: bg.Schema.TimeZoneOffsetValueType;
|
|
8
|
+
miliseconds: bg.Schema.TimeZoneOffsetValueType;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class TimeZoneOffset {
|
|
13
|
+
static TIME_ZONE_OFFSET_HEADER_NAME = "time-zone-offset";
|
|
14
|
+
|
|
15
|
+
static attach = createMiddleware(async (c, next) => {
|
|
16
|
+
const timeZoneOffsetMinutes = bg.Schema.TimeZoneOffsetHeaderValue.parse(
|
|
17
|
+
c.req.header(TimeZoneOffset.TIME_ZONE_OFFSET_HEADER_NAME)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const timeZoneOffset = {
|
|
21
|
+
minutes: timeZoneOffsetMinutes,
|
|
22
|
+
seconds: bg.Time.Minutes(timeZoneOffsetMinutes).seconds,
|
|
23
|
+
miliseconds: bg.Time.Minutes(timeZoneOffsetMinutes).ms,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
c.set("timeZoneOffset", timeZoneOffset);
|
|
27
|
+
|
|
28
|
+
await next();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
static adjustTimestamp(
|
|
32
|
+
timestamp: bg.Schema.TimestampType,
|
|
33
|
+
timeZoneOffsetMs: bg.Schema.TimeZoneOffsetValueType
|
|
34
|
+
): bg.Schema.TimestampType {
|
|
35
|
+
return timestamp - timeZoneOffsetMs;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static adjustDate(
|
|
39
|
+
timestamp: bg.Schema.TimestampType,
|
|
40
|
+
timeZoneOffsetMs: bg.Schema.TimeZoneOffsetValueType
|
|
41
|
+
): Date {
|
|
42
|
+
return new Date(timestamp - timeZoneOffsetMs);
|
|
43
|
+
}
|
|
44
|
+
}
|