@bgord/bun 0.6.0 → 0.8.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 (238) hide show
  1. package/dist/basic-auth.d.ts +14 -0
  2. package/dist/basic-auth.d.ts.map +1 -0
  3. package/dist/basic-auth.js +13 -0
  4. package/dist/basic-auth.js.map +1 -0
  5. package/dist/correlation-id.d.ts.map +1 -1
  6. package/dist/decorators.d.ts +4 -0
  7. package/dist/decorators.d.ts.map +1 -0
  8. package/dist/decorators.js +62 -0
  9. package/dist/decorators.js.map +1 -0
  10. package/dist/encryption.d.ts +8 -3
  11. package/dist/encryption.d.ts.map +1 -0
  12. package/dist/encryption.js +37 -0
  13. package/dist/encryption.js.map +1 -0
  14. package/dist/env-validator.d.ts +21 -0
  15. package/dist/env-validator.d.ts.map +1 -0
  16. package/dist/env-validator.js +28 -0
  17. package/dist/env-validator.js.map +1 -0
  18. package/dist/event-handler.d.ts +8 -0
  19. package/dist/event-handler.d.ts.map +1 -0
  20. package/dist/event-handler.js +21 -0
  21. package/dist/event-handler.js.map +1 -0
  22. package/dist/event-logger.d.ts +8 -0
  23. package/dist/event-logger.d.ts.map +1 -0
  24. package/dist/event-logger.js +19 -0
  25. package/dist/event-logger.js.map +1 -0
  26. package/dist/event.d.ts +7 -46
  27. package/dist/event.d.ts.map +1 -0
  28. package/dist/event.js +23 -0
  29. package/dist/event.js.map +1 -0
  30. package/dist/file-location.d.ts +10 -9
  31. package/dist/file-location.d.ts.map +1 -0
  32. package/dist/file-location.js +46 -0
  33. package/dist/file-location.js.map +1 -0
  34. package/dist/gzip.d.ts +1 -0
  35. package/dist/gzip.d.ts.map +1 -0
  36. package/dist/gzip.js +18 -0
  37. package/dist/gzip.js.map +1 -0
  38. package/dist/hcaptcha-shield.d.ts +12 -13
  39. package/dist/hcaptcha-shield.d.ts.map +1 -0
  40. package/dist/hcaptcha-shield.js +34 -0
  41. package/dist/hcaptcha-shield.js.map +1 -0
  42. package/dist/healthcheck.d.ts +5 -5
  43. package/dist/healthcheck.d.ts.map +1 -1
  44. package/dist/healthcheck.js +5 -5
  45. package/dist/healthcheck.js.map +1 -1
  46. package/dist/i18n.d.ts.map +1 -1
  47. package/dist/i18n.js.map +1 -1
  48. package/dist/image-compressor.d.ts +9 -4
  49. package/dist/image-compressor.d.ts.map +1 -0
  50. package/dist/image-compressor.js +13 -0
  51. package/dist/image-compressor.js.map +1 -0
  52. package/dist/image-exif.d.ts +10 -9
  53. package/dist/image-exif.d.ts.map +1 -0
  54. package/dist/image-exif.js +21 -0
  55. package/dist/image-exif.js.map +1 -0
  56. package/dist/index.d.ts +19 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +19 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/jobs.d.ts.map +1 -1
  61. package/dist/jobs.js.map +1 -1
  62. package/dist/logger.d.ts.map +1 -1
  63. package/dist/logger.js.map +1 -1
  64. package/dist/mailer.d.ts +13 -0
  65. package/dist/mailer.d.ts.map +1 -1
  66. package/dist/mailer.js +6 -0
  67. package/dist/mailer.js.map +1 -1
  68. package/dist/node-env.vo.d.ts.map +1 -1
  69. package/dist/node-env.vo.js.map +1 -1
  70. package/dist/open-graph.d.ts +31 -12
  71. package/dist/open-graph.d.ts.map +1 -0
  72. package/dist/open-graph.js +172 -0
  73. package/dist/open-graph.js.map +1 -0
  74. package/dist/passwords.d.ts.map +1 -1
  75. package/dist/passwords.js.map +1 -1
  76. package/dist/path.d.ts.map +1 -1
  77. package/dist/policy.service.d.ts +12 -0
  78. package/dist/policy.service.d.ts.map +1 -0
  79. package/dist/policy.service.js +14 -0
  80. package/dist/policy.service.js.map +1 -0
  81. package/dist/port.d.ts.map +1 -1
  82. package/dist/prerequisites/binary.d.ts +5 -5
  83. package/dist/prerequisites/binary.d.ts.map +1 -1
  84. package/dist/prerequisites/binary.js +4 -4
  85. package/dist/prerequisites/binary.js.map +1 -1
  86. package/dist/prerequisites/bun.d.ts +5 -5
  87. package/dist/prerequisites/bun.d.ts.map +1 -1
  88. package/dist/prerequisites/bun.js +5 -3
  89. package/dist/prerequisites/bun.js.map +1 -1
  90. package/dist/prerequisites/dependency-vulnerabilities.d.ts +13 -0
  91. package/dist/prerequisites/dependency-vulnerabilities.d.ts.map +1 -0
  92. package/dist/prerequisites/dependency-vulnerabilities.js +30 -0
  93. package/dist/prerequisites/dependency-vulnerabilities.js.map +1 -0
  94. package/dist/prerequisites/external-api.d.ts +14 -0
  95. package/dist/prerequisites/external-api.d.ts.map +1 -0
  96. package/dist/prerequisites/external-api.js +21 -0
  97. package/dist/prerequisites/external-api.js.map +1 -0
  98. package/dist/prerequisites/index.d.ts +2 -0
  99. package/dist/prerequisites/index.d.ts.map +1 -1
  100. package/dist/prerequisites/index.js +2 -0
  101. package/dist/prerequisites/index.js.map +1 -1
  102. package/dist/prerequisites/jobs.d.ts +5 -5
  103. package/dist/prerequisites/jobs.d.ts.map +1 -1
  104. package/dist/prerequisites/jobs.js +4 -4
  105. package/dist/prerequisites/jobs.js.map +1 -1
  106. package/dist/prerequisites/log-file.d.ts +5 -5
  107. package/dist/prerequisites/log-file.d.ts.map +1 -1
  108. package/dist/prerequisites/log-file.js +4 -4
  109. package/dist/prerequisites/log-file.js.map +1 -1
  110. package/dist/prerequisites/mailer.d.ts +5 -5
  111. package/dist/prerequisites/mailer.d.ts.map +1 -1
  112. package/dist/prerequisites/mailer.js +4 -4
  113. package/dist/prerequisites/mailer.js.map +1 -1
  114. package/dist/prerequisites/memory.d.ts +5 -5
  115. package/dist/prerequisites/memory.d.ts.map +1 -1
  116. package/dist/prerequisites/memory.js +4 -4
  117. package/dist/prerequisites/memory.js.map +1 -1
  118. package/dist/prerequisites/node.d.ts +5 -5
  119. package/dist/prerequisites/node.d.ts.map +1 -1
  120. package/dist/prerequisites/node.js +4 -4
  121. package/dist/prerequisites/node.js.map +1 -1
  122. package/dist/prerequisites/outside-connectivity.d.ts +5 -5
  123. package/dist/prerequisites/outside-connectivity.d.ts.map +1 -1
  124. package/dist/prerequisites/outside-connectivity.js +4 -4
  125. package/dist/prerequisites/outside-connectivity.js.map +1 -1
  126. package/dist/prerequisites/path.d.ts +5 -5
  127. package/dist/prerequisites/path.d.ts.map +1 -1
  128. package/dist/prerequisites/path.js +4 -4
  129. package/dist/prerequisites/path.js.map +1 -1
  130. package/dist/prerequisites/port.d.ts +5 -5
  131. package/dist/prerequisites/port.d.ts.map +1 -1
  132. package/dist/prerequisites/port.js +6 -6
  133. package/dist/prerequisites/port.js.map +1 -1
  134. package/dist/prerequisites/ram.d.ts +5 -5
  135. package/dist/prerequisites/ram.d.ts.map +1 -1
  136. package/dist/prerequisites/ram.js +4 -4
  137. package/dist/prerequisites/ram.js.map +1 -1
  138. package/dist/prerequisites/self.d.ts +5 -5
  139. package/dist/prerequisites/self.d.ts.map +1 -1
  140. package/dist/prerequisites/self.js +4 -4
  141. package/dist/prerequisites/self.js.map +1 -1
  142. package/dist/prerequisites/space.d.ts +5 -5
  143. package/dist/prerequisites/space.d.ts.map +1 -1
  144. package/dist/prerequisites/space.js +4 -4
  145. package/dist/prerequisites/space.js.map +1 -1
  146. package/dist/prerequisites/ssl-certificate-expiry.d.ts +5 -5
  147. package/dist/prerequisites/ssl-certificate-expiry.d.ts.map +1 -1
  148. package/dist/prerequisites/ssl-certificate-expiry.js +5 -5
  149. package/dist/prerequisites/ssl-certificate-expiry.js.map +1 -1
  150. package/dist/prerequisites/timezone-utc.d.ts +7 -5
  151. package/dist/prerequisites/timezone-utc.d.ts.map +1 -1
  152. package/dist/prerequisites/timezone-utc.js +6 -5
  153. package/dist/prerequisites/timezone-utc.js.map +1 -1
  154. package/dist/prerequisites/translations.d.ts +8 -8
  155. package/dist/prerequisites/translations.d.ts.map +1 -1
  156. package/dist/prerequisites/translations.js +8 -8
  157. package/dist/prerequisites/translations.js.map +1 -1
  158. package/dist/prerequisites.d.ts +2 -0
  159. package/dist/prerequisites.d.ts.map +1 -1
  160. package/dist/prerequisites.js +2 -0
  161. package/dist/prerequisites.js.map +1 -1
  162. package/dist/recaptcha-shield.d.ts +13 -5
  163. package/dist/recaptcha-shield.d.ts.map +1 -0
  164. package/dist/recaptcha-shield.js +50 -0
  165. package/dist/recaptcha-shield.js.map +1 -0
  166. package/dist/setup.js +1 -1
  167. package/dist/setup.js.map +1 -1
  168. package/dist/simulated-error.d.ts +2 -2
  169. package/dist/simulated-error.d.ts.map +1 -0
  170. package/dist/simulated-error.js +7 -0
  171. package/dist/simulated-error.js.map +1 -0
  172. package/dist/sitemap.d.ts +19 -27
  173. package/dist/sitemap.d.ts.map +1 -0
  174. package/dist/sitemap.js +57 -0
  175. package/dist/sitemap.js.map +1 -0
  176. package/dist/slower.d.ts +3 -6
  177. package/dist/slower.d.ts.map +1 -0
  178. package/dist/slower.js +8 -0
  179. package/dist/slower.js.map +1 -0
  180. package/dist/tsconfig.tsbuildinfo +1 -1
  181. package/dist/url-wo-trailing-slash.d.ts +4 -0
  182. package/dist/url-wo-trailing-slash.d.ts.map +1 -0
  183. package/dist/url-wo-trailing-slash.js +9 -0
  184. package/dist/url-wo-trailing-slash.js.map +1 -0
  185. package/dist/uuid.d.ts.map +1 -1
  186. package/package.json +5 -2
  187. package/src/basic-auth.ts +26 -0
  188. package/src/correlation-id.ts +1 -0
  189. package/src/decorators.ts +78 -0
  190. package/src/encryption.ts +60 -0
  191. package/src/env-validator.ts +43 -0
  192. package/src/event-handler.ts +20 -0
  193. package/src/event-logger.ts +24 -0
  194. package/src/event.ts +29 -0
  195. package/src/file-location.ts +70 -0
  196. package/src/gzip.ts +23 -0
  197. package/src/hcaptcha-shield.ts +57 -0
  198. package/src/healthcheck.ts +9 -14
  199. package/src/i18n.ts +4 -0
  200. package/src/image-compressor.ts +26 -0
  201. package/src/image-exif.ts +38 -0
  202. package/src/index.ts +19 -0
  203. package/src/jobs.ts +1 -0
  204. package/src/logger.ts +6 -0
  205. package/src/mailer.ts +25 -0
  206. package/src/node-env.vo.ts +1 -0
  207. package/src/open-graph.ts +259 -0
  208. package/src/passwords.ts +1 -0
  209. package/src/path.ts +1 -0
  210. package/src/policy.service.ts +25 -0
  211. package/src/port.ts +1 -0
  212. package/src/prerequisites/binary.ts +6 -10
  213. package/src/prerequisites/bun.ts +7 -10
  214. package/src/prerequisites/dependency-vulnerabilities.ts +54 -0
  215. package/src/prerequisites/external-api.ts +27 -0
  216. package/src/prerequisites/index.ts +2 -0
  217. package/src/prerequisites/jobs.ts +6 -12
  218. package/src/prerequisites/log-file.ts +6 -11
  219. package/src/prerequisites/mailer.ts +6 -11
  220. package/src/prerequisites/memory.ts +7 -12
  221. package/src/prerequisites/node.ts +6 -11
  222. package/src/prerequisites/outside-connectivity.ts +6 -11
  223. package/src/prerequisites/path.ts +6 -11
  224. package/src/prerequisites/port.ts +8 -14
  225. package/src/prerequisites/ram.ts +6 -11
  226. package/src/prerequisites/self.ts +6 -11
  227. package/src/prerequisites/space.ts +6 -11
  228. package/src/prerequisites/ssl-certificate-expiry.ts +7 -12
  229. package/src/prerequisites/timezone-utc.ts +9 -12
  230. package/src/prerequisites/translations.ts +14 -19
  231. package/src/prerequisites.ts +2 -0
  232. package/src/recaptcha-shield.ts +76 -0
  233. package/src/setup.ts +1 -1
  234. package/src/simulated-error.ts +8 -0
  235. package/src/sitemap.ts +98 -0
  236. package/src/slower.ts +10 -0
  237. package/src/url-wo-trailing-slash.ts +11 -0
  238. package/src/uuid.ts +1 -0
@@ -0,0 +1,4 @@
1
+ import { z } from "zod/v4";
2
+ export declare const UrlWithoutTrailingSlash: z.ZodURL;
3
+ export type UrlWithoutTrailingSlashType = z.infer<typeof UrlWithoutTrailingSlash>;
4
+ //# sourceMappingURL=url-wo-trailing-slash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-wo-trailing-slash.d.ts","sourceRoot":"","sources":["../src/url-wo-trailing-slash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B,eAAO,MAAM,uBAAuB,UAMhC,CAAC;AAEL,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { z } from "zod/v4";
2
+ export const UrlWithoutTrailingSlash = z
3
+ .url()
4
+ .trim()
5
+ .min(1)
6
+ .refine((value) => !value.endsWith("/"), {
7
+ message: "url_cannot_end_with_trailing_slash",
8
+ });
9
+ //# sourceMappingURL=url-wo-trailing-slash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-wo-trailing-slash.js","sourceRoot":"","sources":["../src/url-wo-trailing-slash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,GAAG,EAAE;KACL,IAAI,EAAE;KACN,GAAG,CAAC,CAAC,CAAC;KACN,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;IACvC,OAAO,EAAE,oCAAoC;CAC9C,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAI3B,eAAO,MAAM,IAAI,yBAA6C,CAAC;AAC/D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAI3B,eAAO,MAAM,IAAI,yBAA6C,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/bun",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": "Bartosz Gordon",
@@ -22,6 +22,7 @@
22
22
  "@commitlint/config-conventional": "19.8.1",
23
23
  "@types/bun": "1.2.15",
24
24
  "@types/lodash": "4.17.17",
25
+ "@types/mime-types": "3.0.1",
25
26
  "@types/nodemailer": "6.4.17",
26
27
  "cspell": "9.0.2",
27
28
  "knip": "5.60.2",
@@ -31,12 +32,14 @@
31
32
  "typescript": "5.8.3"
32
33
  },
33
34
  "dependencies": {
34
- "@bgord/tools": "0.6.0",
35
+ "@bgord/tools": "0.7.0",
35
36
  "check-disk-space": "3.4.0",
36
37
  "croner": "9.0.0",
38
+ "hcaptcha": "0.2.0",
37
39
  "hono": "4.7.11",
38
40
  "lodash": "4.17.21",
39
41
  "lucia": "3.2.2",
42
+ "mime-types": "3.0.1",
40
43
  "node-cache": "5.1.2",
41
44
  "nodemailer": "7.0.3",
42
45
  "sharp": "0.34.2",
@@ -0,0 +1,26 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const BasicAuthUsername = z.string().min(1).max(128);
4
+
5
+ export type BasicAuthUsernameType = z.infer<typeof BasicAuthUsername>;
6
+
7
+ export const BasicAuthPassword = z.string().min(1).max(128);
8
+
9
+ export type BasicAuthPasswordType = z.infer<typeof BasicAuthPassword>;
10
+
11
+ type BasicAuthHeaderValueType = { authorization: string };
12
+
13
+ export class BasicAuth {
14
+ static toHeaderValue(
15
+ username: BasicAuthUsernameType,
16
+ password: BasicAuthPasswordType,
17
+ ): BasicAuthHeaderValueType {
18
+ const credentials = btoa(`${username}:${password}`);
19
+
20
+ return { authorization: `Basic ${credentials}` };
21
+ }
22
+
23
+ static toHeader(username: BasicAuthUsernameType, password: BasicAuthPasswordType): Headers {
24
+ return new Headers(BasicAuth.toHeaderValue(username, password));
25
+ }
26
+ }
@@ -3,4 +3,5 @@ import { z } from "zod/v4";
3
3
  import { UUID } from "./uuid";
4
4
 
5
5
  export const CorrelationId = UUID;
6
+
6
7
  export type CorrelationIdType = z.infer<typeof CorrelationId>;
@@ -0,0 +1,78 @@
1
+ import * as tools from "@bgord/tools";
2
+
3
+ import { Logger } from "./logger";
4
+
5
+ export class DecoratorTimeoutError extends Error {}
6
+
7
+ export class Decorators {
8
+ private readonly rounding = new tools.RoundToDecimal(2);
9
+
10
+ constructor(private readonly logger: Logger) {}
11
+
12
+ duration() {
13
+ const that = this;
14
+
15
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
16
+ const className =
17
+ // @ts-expect-error
18
+ this?.constructor?.name || target?.name || target?.constructor?.name || "UnknownClass";
19
+
20
+ const original: (...args: unknown[]) => unknown = descriptor.value;
21
+
22
+ const label = `${className}.${propertyKey}`;
23
+
24
+ descriptor.value = function (...args: unknown[]) {
25
+ const before = performance.now();
26
+ const value = original.apply(this, args);
27
+ const after = performance.now();
28
+
29
+ that.logger.info({
30
+ message: `${label} duration`,
31
+ operation: "decorators_duration_ms",
32
+ metadata: { durationMs: that.rounding.round(after - before) },
33
+ });
34
+
35
+ return value;
36
+ };
37
+ };
38
+ }
39
+
40
+ inspector() {
41
+ const that = this;
42
+
43
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
44
+ const className =
45
+ // @ts-expect-error
46
+ this?.constructor?.name || target?.name || target?.constructor?.name || "UnknownClass";
47
+
48
+ const original: (...args: unknown[]) => unknown = descriptor.value;
49
+
50
+ const label = `${className}.${propertyKey}`;
51
+
52
+ descriptor.value = async function (...args: unknown[]) {
53
+ const value = await original.apply(this, args);
54
+
55
+ that.logger.info({
56
+ message: `${label} inspector`,
57
+ operation: "decorators_inspector",
58
+ metadata: { arguments: args, output: value },
59
+ });
60
+
61
+ return value;
62
+ };
63
+ };
64
+ }
65
+
66
+ timeout(ms: number) {
67
+ return (_target: any, _key: string, descriptor: PropertyDescriptor) => {
68
+ const original: (...args: unknown[]) => unknown = descriptor.value;
69
+
70
+ descriptor.value = async function (...args: any[]) {
71
+ return await Promise.race([
72
+ original.apply(this, args),
73
+ new Promise((_, reject) => setTimeout(() => reject(new DecoratorTimeoutError()), ms)),
74
+ ]);
75
+ };
76
+ };
77
+ }
78
+ }
@@ -0,0 +1,60 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import { pipeline } from "node:stream/promises";
4
+ import * as util from "node:util";
5
+ import { z } from "zod/v4";
6
+
7
+ export const EncryptionIV = z
8
+ .string()
9
+ .min(1)
10
+ .transform((value) => Buffer.from(value.split(",").map(Number)));
11
+
12
+ export type EncryptionIVType = z.infer<typeof EncryptionIV>;
13
+
14
+ export const EncryptionSecret = z.string().length(64);
15
+
16
+ export type EncryptionSecretType = z.infer<typeof EncryptionSecret>;
17
+
18
+ export type EncryptionConfig = {
19
+ secret: EncryptionSecretType;
20
+ iv: EncryptionIVType;
21
+ };
22
+
23
+ export type EncryptionOperationConfig = {
24
+ input: fs.PathLike;
25
+ output: fs.PathLike;
26
+ };
27
+
28
+ const scrypt = util.promisify(crypto.scrypt);
29
+
30
+ export class Encryption {
31
+ private readonly algorithm = "aes-192-cbc";
32
+
33
+ constructor(private readonly config: EncryptionConfig) {}
34
+
35
+ async createEncrypt() {
36
+ const key = (await scrypt(this.config.secret, "salt", 24)) as Buffer;
37
+
38
+ // @ts-ignore
39
+ return crypto.createCipheriv(this.algorithm, key, this.config.iv);
40
+ }
41
+
42
+ async createDecrypt() {
43
+ const key = (await scrypt(this.config.secret, "salt", 24)) as Buffer;
44
+
45
+ // @ts-ignore
46
+ return crypto.createDecipheriv(this.algorithm, key, this.config.iv);
47
+ }
48
+
49
+ async encrypt(config: EncryptionOperationConfig) {
50
+ const encrypt = await this.createEncrypt();
51
+
52
+ return pipeline(fs.createReadStream(config.input), encrypt, fs.createWriteStream(config.output));
53
+ }
54
+
55
+ async decrypt(config: EncryptionOperationConfig) {
56
+ const decrypt = await this.createDecrypt();
57
+
58
+ return pipeline(fs.createReadStream(config.input), decrypt, fs.createWriteStream(config.output));
59
+ }
60
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from "zod/v4";
2
+
3
+ import { NodeEnvironment } from "../src/node-env.vo";
4
+
5
+ type NodeEnvironmentEnumType = z.infer<typeof NodeEnvironment>;
6
+
7
+ type AnyZodSchema = z.ZodSchema<any, any>;
8
+ type QuitType = boolean;
9
+
10
+ type EnvironmentValidatorConfig = {
11
+ type: unknown;
12
+ schema: AnyZodSchema;
13
+ quit?: QuitType;
14
+ };
15
+
16
+ export class EnvironmentValidator<SchemaType> {
17
+ type: NodeEnvironmentEnumType;
18
+ schema: z.Schema<SchemaType>;
19
+ quit: QuitType;
20
+
21
+ constructor(config: EnvironmentValidatorConfig) {
22
+ this.schema = config.schema;
23
+ this.quit = config?.quit ?? true;
24
+
25
+ const result = NodeEnvironment.safeParse(config.type);
26
+
27
+ if (result.success) {
28
+ this.type = result.data;
29
+ } else if (this.quit) {
30
+ // biome-ignore lint: lint/suspicious/noConsoleLog
31
+ console.log(`Invalid EnvironmentType: ${config.type}`);
32
+ process.exit(1);
33
+ } else {
34
+ throw new NodeEnvironmentError();
35
+ }
36
+ }
37
+
38
+ load(): SchemaType & { type: NodeEnvironmentEnumType } {
39
+ return { ...this.schema.parse(process.env), type: this.type };
40
+ }
41
+ }
42
+
43
+ class NodeEnvironmentError extends Error {}
@@ -0,0 +1,20 @@
1
+ import { EventType } from "./event";
2
+ import { Logger } from "./logger";
3
+
4
+ export class EventHandler {
5
+ constructor(private readonly logger: Logger) {}
6
+
7
+ handle<T extends Pick<EventType, "name">>(fn: (event: T) => Promise<void>) {
8
+ return async (event: T) => {
9
+ try {
10
+ await fn(event);
11
+ } catch (error) {
12
+ this.logger.error({
13
+ message: `Unknown ${event.name} event handler error`,
14
+ operation: "unknown_event_handler_error",
15
+ metadata: this.logger.formatError(error),
16
+ });
17
+ }
18
+ };
19
+ }
20
+ }
@@ -0,0 +1,24 @@
1
+ import { Logger } from "./logger";
2
+
3
+ export class EventLogger {
4
+ constructor(private readonly logger: Logger) {}
5
+
6
+ private _handle(
7
+ type: string,
8
+ _debugName: string,
9
+ eventName: string | undefined,
10
+ eventData: Record<string, any> | undefined,
11
+ ) {
12
+ if (type === "subscribe") return;
13
+
14
+ if (typeof eventName === "symbol") return;
15
+
16
+ this.logger.info({
17
+ message: `${eventName} emitted`,
18
+ operation: "event_emitted",
19
+ metadata: eventData,
20
+ });
21
+ }
22
+
23
+ handle = this._handle.bind(this);
24
+ }
package/src/event.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { z } from "zod/v4";
2
+
3
+ import { UUID } from "./uuid";
4
+
5
+ export const Event = z.object({
6
+ id: UUID,
7
+ createdAt: z.date(),
8
+
9
+ stream: z.string().min(1),
10
+
11
+ name: z.string().min(1),
12
+ version: z.number().int().positive(),
13
+ payload: z
14
+ .record(z.string(), z.any())
15
+ .refine((value) => {
16
+ try {
17
+ JSON.parse(JSON.stringify(value));
18
+ return true;
19
+ } catch (error) {
20
+ return false;
21
+ }
22
+ })
23
+ .transform((value) => JSON.stringify(value)),
24
+ });
25
+
26
+ export const ParsedEvent = Event.merge(z.object({ payload: z.record(z.string(), z.any()) }));
27
+
28
+ export type EventType = z.infer<typeof Event>;
29
+ export type ParsedEventType = z.infer<typeof ParsedEvent>;
@@ -0,0 +1,70 @@
1
+ import * as path from "node:path";
2
+
3
+ import { Path, PathType } from "./path";
4
+
5
+ type FileLocationBasenameType = string;
6
+
7
+ type FileLocationExtensionType = string;
8
+
9
+ type FileLocationParentType = string;
10
+
11
+ export type FileLocationConfigType = {
12
+ parent: FileLocationParentType;
13
+ basename: FileLocationBasenameType;
14
+ extension: FileLocationExtensionType;
15
+ };
16
+
17
+ export class FileLocation {
18
+ readonly parent: PathType; // /parent/dir
19
+
20
+ private basename: FileLocationBasenameType; // index
21
+
22
+ private extension: FileLocationExtensionType; // .html
23
+
24
+ constructor(config: FileLocationConfigType) {
25
+ this.parent = Path.parse(config.parent);
26
+ this.basename = config.basename;
27
+ this.extension = config.extension;
28
+ }
29
+
30
+ getBasename(): FileLocationBasenameType {
31
+ return this.basename; // index
32
+ }
33
+
34
+ getExtension(): FileLocationExtensionType {
35
+ return this.extension; // .html
36
+ }
37
+
38
+ getFilename(extension?: FileLocationExtensionType): PathType {
39
+ return Path.parse(`${this.basename}${extension ?? this.extension}`); // index.html
40
+ }
41
+
42
+ getPath(extension?: FileLocationExtensionType): PathType {
43
+ const filename = this.getFilename(extension);
44
+ return Path.parse(path.join(this.parent, filename)); // parent/index.html
45
+ }
46
+
47
+ getGzippedPath(): PathType {
48
+ return Path.parse(`${this.getPath()}.gz`); // parent/index.html.gz
49
+ }
50
+
51
+ summary() {
52
+ return {
53
+ parent: this.parent,
54
+ basename: this.getBasename(),
55
+ extension: this.getExtension(),
56
+ filename: this.getFilename(),
57
+ path: this.getPath(),
58
+ gzipped: this.getGzippedPath(),
59
+ };
60
+ }
61
+
62
+ setBasename(basename: FileLocationBasenameType): FileLocation {
63
+ this.basename = basename;
64
+ return this;
65
+ }
66
+
67
+ async delete(): Promise<void> {
68
+ return Bun.file(this.getPath()).unlink();
69
+ }
70
+ }
package/src/gzip.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { PathLike, createReadStream, createWriteStream } from "node:fs";
2
+ import { pipeline } from "node:stream/promises";
3
+ import { createGunzip, createGzip } from "node:zlib";
4
+
5
+ type GzipCompressConfigType = { input: PathLike; output: PathLike };
6
+
7
+ export class Gzip {
8
+ static createCompress() {
9
+ return createGzip();
10
+ }
11
+
12
+ static createDecompress() {
13
+ return createGunzip();
14
+ }
15
+
16
+ static async compress(config: GzipCompressConfigType) {
17
+ return pipeline(createReadStream(config.input), createGzip(), createWriteStream(config.output));
18
+ }
19
+
20
+ static async uncompress(config: GzipCompressConfigType) {
21
+ return pipeline(createReadStream(config.input), createGunzip(), createWriteStream(config.output));
22
+ }
23
+ }
@@ -0,0 +1,57 @@
1
+ import hcaptcha from "hcaptcha";
2
+ import { createMiddleware } from "hono/factory";
3
+ import { HTTPException } from "hono/http-exception";
4
+ import { z } from "zod/v4";
5
+
6
+ export const HCaptchaSecretKey = z.string().trim().length(42);
7
+
8
+ export type HCaptchaSecretKeyType = z.infer<typeof HCaptchaSecretKey>;
9
+
10
+ export const HCaptchaSiteKey = z.string().trim().length(36);
11
+ export type HCaptchaSiteKeyType = z.infer<typeof HCaptchaSiteKey>;
12
+
13
+ export const HCaptchaResponseToken = z.string().trim();
14
+ export type HCaptchaResponseTokenType = z.infer<typeof HCaptchaResponseToken>;
15
+
16
+ type HCaptchaVerifierModeType = "local" | "production";
17
+
18
+ export const AccessDeniedHcaptchaError = new HTTPException(403, {
19
+ message: "access_denied_recaptcha",
20
+ });
21
+
22
+ export type HCaptchaVerifierConfigType = {
23
+ secretKey: HCaptchaSecretKeyType;
24
+ mode: HCaptchaVerifierModeType;
25
+ };
26
+
27
+ export class HcaptchaShield {
28
+ private readonly secretKey: HCaptchaSecretKeyType;
29
+ private readonly mode: HCaptchaVerifierModeType;
30
+
31
+ private readonly LOCAL_HCAPTCHA_RESPONSE_PLACEHOLDER = "10000000-aaaa-bbbb-cccc-000000000001";
32
+
33
+ constructor(config: HCaptchaVerifierConfigType) {
34
+ this.mode = config.mode;
35
+ this.secretKey = config.secretKey;
36
+ }
37
+
38
+ build = createMiddleware(async (c, next) => {
39
+ try {
40
+ const form = await c.req.formData();
41
+
42
+ const hcaptchaTokenFormData = form.get("h-captcha-response")?.toString() as HCaptchaResponseTokenType;
43
+
44
+ const result = await hcaptcha.verify(
45
+ this.secretKey,
46
+ this.mode === "production" ? hcaptchaTokenFormData : this.LOCAL_HCAPTCHA_RESPONSE_PLACEHOLDER,
47
+ );
48
+
49
+ if (!result?.success) {
50
+ throw AccessDeniedHcaptchaError;
51
+ }
52
+ return next();
53
+ } catch (error) {
54
+ throw AccessDeniedHcaptchaError;
55
+ }
56
+ });
57
+ }
@@ -3,22 +3,17 @@ import { createFactory } from "hono/factory";
3
3
 
4
4
  import { BuildInfoRepository } from "./build-info-repository";
5
5
  import { MemoryConsumption } from "./memory-consumption";
6
- import {
7
- AbstractPrerequisite,
8
- BasePrerequisiteConfig,
9
- PrerequisiteLabelType,
10
- PrerequisiteStatusEnum,
11
- } from "./prerequisites";
6
+ import * as prereqs from "./prerequisites";
12
7
  import { Uptime, UptimeResultType } from "./uptime";
13
8
 
14
9
  const handler = createFactory();
15
10
 
16
11
  type HealthcheckResultType = {
17
- ok: PrerequisiteStatusEnum;
12
+ ok: prereqs.PrerequisiteStatusEnum;
18
13
  version: tools.BuildVersionType;
19
14
  details: {
20
- label: PrerequisiteLabelType;
21
- status: PrerequisiteStatusEnum;
15
+ label: prereqs.PrerequisiteLabelType;
16
+ status: prereqs.PrerequisiteStatusEnum;
22
17
  }[];
23
18
  uptime: UptimeResultType;
24
19
  memory: {
@@ -28,7 +23,7 @@ type HealthcheckResultType = {
28
23
  } & tools.StopwatchResultType;
29
24
 
30
25
  export class Healthcheck {
31
- static build = (prerequisites: AbstractPrerequisite<BasePrerequisiteConfig>[]) =>
26
+ static build = (prerequisites: prereqs.AbstractPrerequisite<prereqs.BasePrerequisiteConfig>[]) =>
32
27
  handler.createHandlers(async (c) => {
33
28
  const stopwatch = new tools.Stopwatch();
34
29
 
@@ -41,11 +36,11 @@ export class Healthcheck {
41
36
  details.push({ label: prerequisite.label, status });
42
37
  }
43
38
 
44
- const ok = details.every((result) => result.status !== PrerequisiteStatusEnum.failure)
45
- ? PrerequisiteStatusEnum.success
46
- : PrerequisiteStatusEnum.failure;
39
+ const ok = details.every((result) => result.status !== prereqs.PrerequisiteStatusEnum.failure)
40
+ ? prereqs.PrerequisiteStatusEnum.success
41
+ : prereqs.PrerequisiteStatusEnum.failure;
47
42
 
48
- const code = ok === PrerequisiteStatusEnum.success ? 200 : 424;
43
+ const code = ok === prereqs.PrerequisiteStatusEnum.success ? 200 : 424;
49
44
 
50
45
  const result: HealthcheckResultType = {
51
46
  ok,
package/src/i18n.ts CHANGED
@@ -4,11 +4,15 @@ import * as tools from "@bgord/tools";
4
4
  import { Path, PathType } from "./path";
5
5
 
6
6
  export type TranslationsKeyType = string;
7
+
7
8
  export type TranslationsValueType = string;
9
+
8
10
  export type TranslationsType = Record<TranslationsKeyType, TranslationsValueType>;
9
11
 
10
12
  export type TranslationPlaceholderType = string;
13
+
11
14
  export type TranslationPlaceholderValueType = string | number;
15
+
12
16
  export type TranslationVariableType = Record<TranslationPlaceholderType, TranslationPlaceholderValueType>;
13
17
 
14
18
  export type I18nConfigType = {
@@ -0,0 +1,26 @@
1
+ import sharp from "sharp";
2
+ import { z } from "zod/v4";
3
+
4
+ import { PathType } from "../src/path";
5
+
6
+ export const ImageCompressionQuality = z.number().int().min(1).max(100).default(85);
7
+
8
+ type ImageCompressionQualityType = z.infer<typeof ImageCompressionQuality>;
9
+
10
+ export type ImageCompressorConfigType = {
11
+ input: PathType;
12
+ output: PathType;
13
+ quality?: ImageCompressionQualityType;
14
+ };
15
+
16
+ export class ImageCompressor {
17
+ static async compress(config: ImageCompressorConfigType): Promise<sharp.OutputInfo> {
18
+ const quality = config.quality ?? 85;
19
+
20
+ const image = sharp(config.input);
21
+ const metadata = await image.metadata();
22
+ const format = metadata.format as keyof sharp.FormatEnum;
23
+
24
+ return image.toFormat(format, { quality }).toFile(config.output);
25
+ }
26
+ }
@@ -0,0 +1,38 @@
1
+ import path from "node:path";
2
+ import * as tools from "@bgord/tools";
3
+ import mime from "mime-types";
4
+ import sharp from "sharp";
5
+
6
+ import { PathType } from "./path";
7
+
8
+ export type ImageExifOutputType = {
9
+ width: tools.WidthType;
10
+ height: tools.HeightType;
11
+ name: path.ParsedPath["base"];
12
+ mimeType: tools.MimeRawType;
13
+ };
14
+
15
+ export type ImageExifClearConfigType = {
16
+ input: PathType;
17
+ output: PathType;
18
+ };
19
+
20
+ export class ImageEXIF {
21
+ static async read(input: PathType): Promise<ImageExifOutputType> {
22
+ const image = sharp(input);
23
+ const metadata = await image.metadata();
24
+
25
+ const name = path.parse(input).base;
26
+
27
+ return {
28
+ width: tools.Width.parse(metadata.width),
29
+ height: tools.Height.parse(metadata.height),
30
+ name,
31
+ mimeType: String(mime.contentType(String(metadata.format))),
32
+ };
33
+ }
34
+
35
+ static async clear(config: ImageExifClearConfigType): Promise<sharp.OutputInfo> {
36
+ return sharp(config.input).rotate().toFile(config.output);
37
+ }
38
+ }