@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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/api-key-shield.d.ts +16 -0
  4. package/dist/api-version.d.ts +8 -0
  5. package/dist/auth-shield.d.ts +32 -0
  6. package/dist/basic-auth-shield.d.ts +14 -0
  7. package/dist/bgord-bun.cjs +2 -0
  8. package/dist/bgord-bun.cjs.map +1 -0
  9. package/dist/bgord-bun.modern.js +2 -0
  10. package/dist/bgord-bun.modern.js.map +1 -0
  11. package/dist/bgord-bun.module.js +2 -0
  12. package/dist/bgord-bun.module.js.map +1 -0
  13. package/dist/bgord-bun.umd.js +2 -0
  14. package/dist/bgord-bun.umd.js.map +1 -0
  15. package/dist/build-info-repository.d.ts +8 -0
  16. package/dist/bun/api-key-shield.d.ts +16 -0
  17. package/dist/bun/api-version.d.ts +8 -0
  18. package/dist/bun/context.d.ts +12 -0
  19. package/dist/bun/etag-extractor.d.ts +17 -0
  20. package/dist/bun/graceful-shutdown.d.ts +6 -0
  21. package/dist/bun/healthcheck.d.ts +22 -0
  22. package/dist/bun/http-logger.d.ts +9 -0
  23. package/dist/bun/index.d.ts +9 -0
  24. package/dist/bun/rate-limit-shield.d.ts +11 -0
  25. package/dist/bun/time-zone-offset.d.ts +16 -0
  26. package/dist/cache-response.d.ts +15 -0
  27. package/dist/cache-static-files.d.ts +9 -0
  28. package/dist/cache.d.ts +1 -0
  29. package/dist/clock.d.ts +49 -0
  30. package/dist/context.d.ts +12 -0
  31. package/dist/credentials.d.ts +28 -0
  32. package/dist/dates.d.ts +37 -0
  33. package/dist/decorators.d.ts +8 -0
  34. package/dist/deep-merge.d.ts +7 -0
  35. package/dist/dll.d.ts +29 -0
  36. package/dist/download-file.d.ts +10 -0
  37. package/dist/email-censor.d.ts +4 -0
  38. package/dist/encryption.d.ts +20 -0
  39. package/dist/environment-validator.d.ts +23 -0
  40. package/dist/errors.d.ts +49 -0
  41. package/dist/etag-extractor.d.ts +17 -0
  42. package/dist/etag.d.ts +19 -0
  43. package/dist/event.d.ts +59 -0
  44. package/dist/express-essentials.d.ts +35 -0
  45. package/dist/feature-flag.d.ts +5 -0
  46. package/dist/file-location.d.ts +31 -0
  47. package/dist/file-uploader.d.ts +12 -0
  48. package/dist/file.d.ts +4 -0
  49. package/dist/filter.d.ts +17 -0
  50. package/dist/graceful-shutdown.d.ts +6 -0
  51. package/dist/gzip.d.ts +12 -0
  52. package/dist/handlebars.d.ts +10 -0
  53. package/dist/hcaptcha-shield.d.ts +24 -0
  54. package/dist/healthcheck.d.ts +20 -0
  55. package/dist/http-logger.d.ts +10 -0
  56. package/dist/i18n.d.ts +31 -0
  57. package/dist/image-compressor.d.ts +10 -0
  58. package/dist/image-converter.d.ts +11 -0
  59. package/dist/image-exif.d.ts +18 -0
  60. package/dist/image-resizer.d.ts +16 -0
  61. package/dist/index.d.ts +9 -0
  62. package/dist/jobs.d.ts +32 -0
  63. package/dist/leap-year-checker.d.ts +4 -0
  64. package/dist/logger.d.ts +53 -0
  65. package/dist/mailer.d.ts +16 -0
  66. package/dist/mean.d.ts +4 -0
  67. package/dist/memory-consumption.d.ts +4 -0
  68. package/dist/method-override.d.ts +4 -0
  69. package/dist/mime-types.d.ts +2 -0
  70. package/dist/mime.d.ts +10 -0
  71. package/dist/min-max-scaler.d.ts +36 -0
  72. package/dist/money.d.ts +24 -0
  73. package/dist/noop.d.ts +2 -0
  74. package/dist/open-graph.d.ts +91 -0
  75. package/dist/outlier-detector.d.ts +6 -0
  76. package/dist/package-version.d.ts +20 -0
  77. package/dist/pagination.d.ts +58 -0
  78. package/dist/percentage.d.ts +4 -0
  79. package/dist/policy.d.ts +11 -0
  80. package/dist/polyfills.d.ts +0 -0
  81. package/dist/population-standard-deviation.d.ts +4 -0
  82. package/dist/prerequisites/binary.d.ts +16 -0
  83. package/dist/prerequisites/bun.d.ts +13 -0
  84. package/dist/prerequisites/jobs.d.ts +13 -0
  85. package/dist/prerequisites/mailer.d.ts +13 -0
  86. package/dist/prerequisites/memory.d.ts +13 -0
  87. package/dist/prerequisites/node.d.ts +13 -0
  88. package/dist/prerequisites/outside-connectivity.d.ts +11 -0
  89. package/dist/prerequisites/path.d.ts +16 -0
  90. package/dist/prerequisites/port.d.ts +13 -0
  91. package/dist/prerequisites/ram.d.ts +13 -0
  92. package/dist/prerequisites/self.d.ts +11 -0
  93. package/dist/prerequisites/space.d.ts +13 -0
  94. package/dist/prerequisites/ssl-certificate-expiry.d.ts +13 -0
  95. package/dist/prerequisites/timezone-utc.d.ts +13 -0
  96. package/dist/prerequisites/translations.d.ts +14 -0
  97. package/dist/prerequisites.d.ts +44 -0
  98. package/dist/random.d.ts +8 -0
  99. package/dist/rate-limit-shield.d.ts +11 -0
  100. package/dist/rate-limiter.d.ts +18 -0
  101. package/dist/recaptcha-shield.d.ts +12 -0
  102. package/dist/redirector.d.ts +4 -0
  103. package/dist/reordering.d.ts +49 -0
  104. package/dist/request-id.d.ts +13 -0
  105. package/dist/response-body-in-locals.d.ts +4 -0
  106. package/dist/revision.d.ts +12 -0
  107. package/dist/rounding.d.ts +17 -0
  108. package/dist/schema.d.ts +189 -0
  109. package/dist/server-timing.d.ts +7 -0
  110. package/dist/simple-linear-regression.d.ts +18 -0
  111. package/dist/simulated-error.d.ts +4 -0
  112. package/dist/sitemap.d.ts +37 -0
  113. package/dist/size.d.ts +31 -0
  114. package/dist/sleep.d.ts +3 -0
  115. package/dist/slower.d.ts +8 -0
  116. package/dist/static-files.d.ts +14 -0
  117. package/dist/stepper.d.ts +23 -0
  118. package/dist/stopwatch.d.ts +14 -0
  119. package/dist/streak-calculator.d.ts +14 -0
  120. package/dist/sum.d.ts +3 -0
  121. package/dist/thousands-separator.d.ts +4 -0
  122. package/dist/time-zone-offset.d.ts +17 -0
  123. package/dist/time.d.ts +21 -0
  124. package/dist/timeout.d.ts +9 -0
  125. package/dist/ts-utils.d.ts +3 -0
  126. package/dist/uploaded-file-location.d.ts +10 -0
  127. package/dist/uptime.d.ts +9 -0
  128. package/dist/uuid.d.ts +3 -0
  129. package/dist/visually-unambiguous-characters-generator.d.ts +4 -0
  130. package/dist/z-score.d.ts +8 -0
  131. package/package.json +48 -0
  132. package/src/api-key-shield.ts +23 -0
  133. package/src/api-version.ts +19 -0
  134. package/src/context.ts +20 -0
  135. package/src/etag-extractor.ts +41 -0
  136. package/src/graceful-shutdown.ts +44 -0
  137. package/src/healthcheck.ts +58 -0
  138. package/src/http-logger.ts +123 -0
  139. package/src/index.ts +9 -0
  140. package/src/rate-limit-shield.ts +24 -0
  141. 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,3 @@
1
+ export type Constructor<T> = new (...args: any[]) => T;
2
+ export type Falsy<T> = T | null | undefined;
3
+ export type Nullable<T> = T | null;
@@ -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
+ }
@@ -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,3 @@
1
+ export declare class NewUUID {
2
+ static generate(): `${string}-${string}-${string}-${string}-${string}`;
3
+ }
@@ -0,0 +1,4 @@
1
+ export declare class VisuallyUnambiguousCharactersGenerator {
2
+ static chars: string[];
3
+ static generate(length?: number): string;
4
+ }
@@ -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
+ }