@bgord/bun 1.15.4 → 1.16.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 (221) hide show
  1. package/dist/alert-channel-collecting.adapter.d.ts +8 -0
  2. package/dist/alert-channel-collecting.adapter.d.ts.map +1 -0
  3. package/dist/alert-channel-collecting.adapter.js +10 -0
  4. package/dist/alert-channel-collecting.adapter.js.map +1 -0
  5. package/dist/alert-channel-composite.adapter.d.ts +13 -0
  6. package/dist/alert-channel-composite.adapter.d.ts.map +1 -0
  7. package/dist/alert-channel-composite.adapter.js +22 -0
  8. package/dist/alert-channel-composite.adapter.js.map +1 -0
  9. package/dist/alert-channel-mailer.adapter.d.ts +18 -0
  10. package/dist/alert-channel-mailer.adapter.d.ts.map +1 -0
  11. package/dist/alert-channel-mailer.adapter.js +15 -0
  12. package/dist/alert-channel-mailer.adapter.js.map +1 -0
  13. package/dist/alert-channel-noop.adapter.d.ts +7 -0
  14. package/dist/alert-channel-noop.adapter.d.ts.map +1 -0
  15. package/dist/alert-channel-noop.adapter.js +7 -0
  16. package/dist/alert-channel-noop.adapter.js.map +1 -0
  17. package/dist/alert-channel-sms.adapter.d.ts +18 -0
  18. package/dist/alert-channel-sms.adapter.d.ts.map +1 -0
  19. package/dist/alert-channel-sms.adapter.js +15 -0
  20. package/dist/alert-channel-sms.adapter.js.map +1 -0
  21. package/dist/alert-channel-with-logger.adapter.d.ts +17 -0
  22. package/dist/alert-channel-with-logger.adapter.d.ts.map +1 -0
  23. package/dist/alert-channel-with-logger.adapter.js +33 -0
  24. package/dist/alert-channel-with-logger.adapter.js.map +1 -0
  25. package/dist/alert-channel-with-retry.adapter.d.ts +19 -0
  26. package/dist/alert-channel-with-retry.adapter.d.ts.map +1 -0
  27. package/dist/alert-channel-with-retry.adapter.js +16 -0
  28. package/dist/alert-channel-with-retry.adapter.js.map +1 -0
  29. package/dist/alert-channel-with-timeout.adapter.d.ts +19 -0
  30. package/dist/alert-channel-with-timeout.adapter.d.ts.map +1 -0
  31. package/dist/alert-channel-with-timeout.adapter.js +15 -0
  32. package/dist/alert-channel-with-timeout.adapter.js.map +1 -0
  33. package/dist/alert-channel.builder.d.ts +14 -0
  34. package/dist/alert-channel.builder.d.ts.map +1 -0
  35. package/dist/alert-channel.builder.js +28 -0
  36. package/dist/alert-channel.builder.js.map +1 -0
  37. package/dist/alert-channel.port.d.ts +6 -0
  38. package/dist/alert-channel.port.d.ts.map +1 -0
  39. package/dist/alert-channel.port.js +2 -0
  40. package/dist/alert-channel.port.js.map +1 -0
  41. package/dist/alert-message.vo.d.ts +10 -0
  42. package/dist/alert-message.vo.d.ts.map +1 -0
  43. package/dist/alert-message.vo.js +12 -0
  44. package/dist/alert-message.vo.js.map +1 -0
  45. package/dist/antivirus-clamav.adapter.js +1 -1
  46. package/dist/antivirus-clamav.adapter.js.map +1 -1
  47. package/dist/api-version-hono.middleware.d.ts +3 -2
  48. package/dist/api-version-hono.middleware.d.ts.map +1 -1
  49. package/dist/api-version-hono.middleware.js.map +1 -1
  50. package/dist/api-version.middleware.d.ts +4 -3
  51. package/dist/api-version.middleware.d.ts.map +1 -1
  52. package/dist/api-version.middleware.js +1 -1
  53. package/dist/api-version.middleware.js.map +1 -1
  54. package/dist/build-info.vo.d.ts +11 -0
  55. package/dist/build-info.vo.d.ts.map +1 -0
  56. package/dist/build-info.vo.js +11 -0
  57. package/dist/build-info.vo.js.map +1 -0
  58. package/dist/error-normalizer.service.js +1 -1
  59. package/dist/error-normalizer.service.js.map +1 -1
  60. package/dist/healthcheck-hono.handler.d.ts +3 -2
  61. package/dist/healthcheck-hono.handler.d.ts.map +1 -1
  62. package/dist/healthcheck-hono.handler.js.map +1 -1
  63. package/dist/healthcheck.handler.d.ts +3 -2
  64. package/dist/healthcheck.handler.d.ts.map +1 -1
  65. package/dist/healthcheck.handler.js +6 -6
  66. package/dist/healthcheck.handler.js.map +1 -1
  67. package/dist/image-alpha-sharp.adapter.js +76 -14
  68. package/dist/image-alpha-sharp.adapter.js.map +1 -1
  69. package/dist/image-blur-sharp.adapter.js +72 -10
  70. package/dist/image-blur-sharp.adapter.js.map +1 -1
  71. package/dist/image-compressor-sharp.adapter.js +73 -11
  72. package/dist/image-compressor-sharp.adapter.js.map +1 -1
  73. package/dist/image-exif-clear-sharp.adapter.js +70 -8
  74. package/dist/image-exif-clear-sharp.adapter.js.map +1 -1
  75. package/dist/image-formatter-sharp.adapter.js +75 -13
  76. package/dist/image-formatter-sharp.adapter.js.map +1 -1
  77. package/dist/image-grayscale-sharp.adapter.js +70 -8
  78. package/dist/image-grayscale-sharp.adapter.js.map +1 -1
  79. package/dist/image-info-sharp.adapter.js +73 -11
  80. package/dist/image-info-sharp.adapter.js.map +1 -1
  81. package/dist/image-processor-sharp.adapter.js +86 -24
  82. package/dist/image-processor-sharp.adapter.js.map +1 -1
  83. package/dist/image-resizer-sharp.adapter.js +81 -19
  84. package/dist/image-resizer-sharp.adapter.js.map +1 -1
  85. package/dist/index.d.ts +26 -4
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +26 -4
  88. package/dist/index.js.map +1 -1
  89. package/dist/mailer-collecting.adapter.d.ts +8 -0
  90. package/dist/mailer-collecting.adapter.d.ts.map +1 -0
  91. package/dist/mailer-collecting.adapter.js +10 -0
  92. package/dist/mailer-collecting.adapter.js.map +1 -0
  93. package/dist/prerequisite-verifier-sms.adapter.d.ts +13 -0
  94. package/dist/prerequisite-verifier-sms.adapter.d.ts.map +1 -0
  95. package/dist/prerequisite-verifier-sms.adapter.js +20 -0
  96. package/dist/prerequisite-verifier-sms.adapter.js.map +1 -0
  97. package/dist/reactive-config-file-json.adapter.d.ts +15 -0
  98. package/dist/reactive-config-file-json.adapter.d.ts.map +1 -0
  99. package/dist/reactive-config-file-json.adapter.js +21 -0
  100. package/dist/reactive-config-file-json.adapter.js.map +1 -0
  101. package/dist/reactive-config-noop.adapter.d.ts +8 -0
  102. package/dist/reactive-config-noop.adapter.d.ts.map +1 -0
  103. package/dist/reactive-config-noop.adapter.js +18 -0
  104. package/dist/reactive-config-noop.adapter.js.map +1 -0
  105. package/dist/reactive-config-with-cache.adapter.d.ts +16 -0
  106. package/dist/reactive-config-with-cache.adapter.d.ts.map +1 -0
  107. package/dist/reactive-config-with-cache.adapter.js +15 -0
  108. package/dist/reactive-config-with-cache.adapter.js.map +1 -0
  109. package/dist/reactive-config-with-fallback.adapter.d.ts +9 -0
  110. package/dist/reactive-config-with-fallback.adapter.d.ts.map +1 -0
  111. package/dist/reactive-config-with-fallback.adapter.js +25 -0
  112. package/dist/reactive-config-with-fallback.adapter.js.map +1 -0
  113. package/dist/reactive-config.port.d.ts +9 -0
  114. package/dist/reactive-config.port.d.ts.map +1 -0
  115. package/dist/reactive-config.port.js +2 -0
  116. package/dist/reactive-config.port.js.map +1 -0
  117. package/dist/redactor-error-cause-depth-limit.strategy.d.ts.map +1 -1
  118. package/dist/redactor-error-cause-depth-limit.strategy.js +2 -2
  119. package/dist/redactor-error-cause-depth-limit.strategy.js.map +1 -1
  120. package/dist/redactor-error-stack-hide.strategy.d.ts.map +1 -1
  121. package/dist/redactor-error-stack-hide.strategy.js +2 -2
  122. package/dist/redactor-error-stack-hide.strategy.js.map +1 -1
  123. package/dist/redactor-metadata-compact-array.strategy.js +1 -1
  124. package/dist/redactor-metadata-compact-array.strategy.js.map +1 -1
  125. package/dist/redactor-metadata-compact-object.strategy.js +1 -1
  126. package/dist/redactor-metadata-compact-object.strategy.js.map +1 -1
  127. package/dist/setup-hono.service.d.ts +3 -2
  128. package/dist/setup-hono.service.d.ts.map +1 -1
  129. package/dist/setup-hono.service.js.map +1 -1
  130. package/dist/shield-recaptcha.strategy.js +1 -1
  131. package/dist/shield-recaptcha.strategy.js.map +1 -1
  132. package/dist/sms-collecting.adapter.d.ts +8 -0
  133. package/dist/sms-collecting.adapter.d.ts.map +1 -0
  134. package/dist/sms-collecting.adapter.js +10 -0
  135. package/dist/sms-collecting.adapter.js.map +1 -0
  136. package/dist/sms-noop.adapter.d.ts +7 -0
  137. package/dist/sms-noop.adapter.d.ts.map +1 -0
  138. package/dist/sms-noop.adapter.js +7 -0
  139. package/dist/sms-noop.adapter.js.map +1 -0
  140. package/dist/sms-with-logger.adapter.d.ts +17 -0
  141. package/dist/sms-with-logger.adapter.d.ts.map +1 -0
  142. package/dist/sms-with-logger.adapter.js +28 -0
  143. package/dist/sms-with-logger.adapter.js.map +1 -0
  144. package/dist/sms-with-retry.adapter.d.ts +19 -0
  145. package/dist/sms-with-retry.adapter.d.ts.map +1 -0
  146. package/dist/sms-with-retry.adapter.js +16 -0
  147. package/dist/sms-with-retry.adapter.js.map +1 -0
  148. package/dist/sms-with-timeout.adapter.d.ts +18 -0
  149. package/dist/sms-with-timeout.adapter.d.ts.map +1 -0
  150. package/dist/sms-with-timeout.adapter.js +15 -0
  151. package/dist/sms-with-timeout.adapter.js.map +1 -0
  152. package/dist/sms.builder.d.ts +14 -0
  153. package/dist/sms.builder.d.ts.map +1 -0
  154. package/dist/sms.builder.js +28 -0
  155. package/dist/sms.builder.js.map +1 -0
  156. package/dist/sms.port.d.ts +6 -0
  157. package/dist/sms.port.d.ts.map +1 -0
  158. package/dist/sms.port.js +2 -0
  159. package/dist/sms.port.js.map +1 -0
  160. package/package.json +12 -11
  161. package/readme.md +26 -4
  162. package/src/alert-channel-collecting.adapter.ts +14 -0
  163. package/src/alert-channel-composite.adapter.ts +24 -0
  164. package/src/alert-channel-mailer.adapter.ts +22 -0
  165. package/src/alert-channel-noop.adapter.ts +10 -0
  166. package/src/alert-channel-sms.adapter.ts +22 -0
  167. package/src/alert-channel-with-logger.adapter.ts +46 -0
  168. package/src/alert-channel-with-retry.adapter.ts +22 -0
  169. package/src/alert-channel-with-timeout.adapter.ts +25 -0
  170. package/src/alert-channel.builder.ts +48 -0
  171. package/src/alert-channel.port.ts +7 -0
  172. package/src/alert-message.vo.ts +10 -0
  173. package/src/antivirus-clamav.adapter.ts +1 -1
  174. package/src/api-version-hono.middleware.ts +3 -2
  175. package/src/api-version.middleware.ts +5 -4
  176. package/src/build-info.vo.ts +14 -0
  177. package/src/error-normalizer.service.ts +1 -1
  178. package/src/healthcheck-hono.handler.ts +3 -2
  179. package/src/healthcheck.handler.ts +9 -8
  180. package/src/index.ts +26 -4
  181. package/src/mailer-collecting.adapter.ts +14 -0
  182. package/src/prerequisite-verifier-sms.adapter.ts +26 -0
  183. package/src/reactive-config-file-json.adapter.ts +26 -0
  184. package/src/reactive-config-noop.adapter.ts +19 -0
  185. package/src/reactive-config-with-cache.adapter.ts +19 -0
  186. package/src/reactive-config-with-fallback.adapter.ts +24 -0
  187. package/src/reactive-config.port.ts +9 -0
  188. package/src/redactor-error-cause-depth-limit.strategy.ts +2 -3
  189. package/src/redactor-error-stack-hide.strategy.ts +2 -3
  190. package/src/redactor-metadata-compact-array.strategy.ts +1 -1
  191. package/src/redactor-metadata-compact-object.strategy.ts +1 -1
  192. package/src/setup-hono.service.ts +3 -2
  193. package/src/shield-recaptcha.strategy.ts +1 -1
  194. package/src/sms-collecting.adapter.ts +14 -0
  195. package/src/sms-noop.adapter.ts +10 -0
  196. package/src/sms-with-logger.adapter.ts +37 -0
  197. package/src/sms-with-retry.adapter.ts +22 -0
  198. package/src/sms-with-timeout.adapter.ts +21 -0
  199. package/src/sms.builder.ts +39 -0
  200. package/src/sms.port.ts +6 -0
  201. package/dist/build-info-repository-file.strategy.d.ts +0 -14
  202. package/dist/build-info-repository-file.strategy.d.ts.map +0 -1
  203. package/dist/build-info-repository-file.strategy.js +0 -18
  204. package/dist/build-info-repository-file.strategy.js.map +0 -1
  205. package/dist/build-info-repository-noop.strategy.d.ts +0 -12
  206. package/dist/build-info-repository-noop.strategy.d.ts.map +0 -1
  207. package/dist/build-info-repository-noop.strategy.js +0 -16
  208. package/dist/build-info-repository-noop.strategy.js.map +0 -1
  209. package/dist/build-info-repository-package-json.strategy.d.ts +0 -14
  210. package/dist/build-info-repository-package-json.strategy.d.ts.map +0 -1
  211. package/dist/build-info-repository-package-json.strategy.js +0 -21
  212. package/dist/build-info-repository-package-json.strategy.js.map +0 -1
  213. package/dist/build-info-repository.strategy.d.ts +0 -12
  214. package/dist/build-info-repository.strategy.d.ts.map +0 -1
  215. package/dist/build-info-repository.strategy.js +0 -2
  216. package/dist/build-info-repository.strategy.js.map +0 -1
  217. package/dist/tsconfig.tsbuildinfo +0 -1
  218. package/src/build-info-repository-file.strategy.ts +0 -23
  219. package/src/build-info-repository-noop.strategy.ts +0 -16
  220. package/src/build-info-repository-package-json.strategy.ts +0 -25
  221. package/src/build-info-repository.strategy.ts +0 -13
@@ -0,0 +1,22 @@
1
+ import type { AlertChannelPort } from "./alert-channel.port";
2
+ import type { AlertMessage } from "./alert-message.vo";
3
+ import { Retry, type RetryConfig } from "./retry.service";
4
+ import type { SleeperPort } from "./sleeper.port";
5
+
6
+ export type AlertChannelWithRetryAdapterDependencies = { Sleeper: SleeperPort; inner: AlertChannelPort };
7
+ export type AlertChannelWithRetryAdapterConfig = { retry: RetryConfig };
8
+
9
+ export class AlertChannelWithRetryAdapter implements AlertChannelPort {
10
+ constructor(
11
+ private readonly config: AlertChannelWithRetryAdapterConfig,
12
+ private readonly deps: AlertChannelWithRetryAdapterDependencies,
13
+ ) {}
14
+
15
+ async send(alert: AlertMessage): Promise<void> {
16
+ await new Retry(this.deps).run(async () => this.deps.inner.send(alert), this.config.retry);
17
+ }
18
+
19
+ async verify(): Promise<boolean> {
20
+ return this.deps.inner.verify();
21
+ }
22
+ }
@@ -0,0 +1,25 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { AlertChannelPort } from "./alert-channel.port";
3
+ import type { AlertMessage } from "./alert-message.vo";
4
+ import type { TimeoutRunnerPort } from "./timeout-runner.port";
5
+
6
+ export type AlertChannelWithTimeoutAdapterDependencies = {
7
+ TimeoutRunner: TimeoutRunnerPort;
8
+ inner: AlertChannelPort;
9
+ };
10
+ export type AlertChannelWithTimeoutAdapterConfig = { timeout: tools.Duration };
11
+
12
+ export class AlertChannelWithTimeoutAdapter implements AlertChannelPort {
13
+ constructor(
14
+ private readonly config: AlertChannelWithTimeoutAdapterConfig,
15
+ private readonly deps: AlertChannelWithTimeoutAdapterDependencies,
16
+ ) {}
17
+
18
+ async send(alert: AlertMessage): Promise<void> {
19
+ await this.deps.TimeoutRunner.run(this.deps.inner.send(alert), this.config.timeout);
20
+ }
21
+
22
+ async verify(): Promise<boolean> {
23
+ return this.deps.inner.verify();
24
+ }
25
+ }
@@ -0,0 +1,48 @@
1
+ import type { AlertChannelPort } from "./alert-channel.port";
2
+ import {
3
+ AlertChannelWithLoggerAdapter,
4
+ type AlertChannelWithLoggerAdapterDependencies,
5
+ } from "./alert-channel-with-logger.adapter";
6
+ import {
7
+ AlertChannelWithRetryAdapter,
8
+ type AlertChannelWithRetryAdapterConfig,
9
+ type AlertChannelWithRetryAdapterDependencies,
10
+ } from "./alert-channel-with-retry.adapter";
11
+ import {
12
+ AlertChannelWithTimeoutAdapter,
13
+ type AlertChannelWithTimeoutAdapterConfig,
14
+ type AlertChannelWithTimeoutAdapterDependencies,
15
+ } from "./alert-channel-with-timeout.adapter";
16
+
17
+ export class AlertChannelBuilder {
18
+ constructor(private inner: AlertChannelPort) {}
19
+
20
+ static of(channel: AlertChannelPort): AlertChannelBuilder {
21
+ return new AlertChannelBuilder(channel);
22
+ }
23
+
24
+ withLogger(deps: Omit<AlertChannelWithLoggerAdapterDependencies, "inner">) {
25
+ this.inner = new AlertChannelWithLoggerAdapter({ ...deps, inner: this.inner });
26
+ return this;
27
+ }
28
+
29
+ withRetry(
30
+ config: AlertChannelWithRetryAdapterConfig,
31
+ deps: Omit<AlertChannelWithRetryAdapterDependencies, "inner">,
32
+ ) {
33
+ this.inner = new AlertChannelWithRetryAdapter(config, { ...deps, inner: this.inner });
34
+ return this;
35
+ }
36
+
37
+ withTimeout(
38
+ config: AlertChannelWithTimeoutAdapterConfig,
39
+ deps: Omit<AlertChannelWithTimeoutAdapterDependencies, "inner">,
40
+ ) {
41
+ this.inner = new AlertChannelWithTimeoutAdapter(config, { ...deps, inner: this.inner });
42
+ return this;
43
+ }
44
+
45
+ build(): AlertChannelPort {
46
+ return this.inner;
47
+ }
48
+ }
@@ -0,0 +1,7 @@
1
+ import type { AlertMessage } from "./alert-message.vo";
2
+
3
+ export interface AlertChannelPort {
4
+ send(alert: AlertMessage): Promise<void>;
5
+
6
+ verify(): Promise<boolean>;
7
+ }
@@ -0,0 +1,10 @@
1
+ export class AlertMessage {
2
+ constructor(
3
+ readonly message: string,
4
+ readonly error?: unknown,
5
+ ) {}
6
+
7
+ toJSON() {
8
+ return { message: this.message, error: this.error };
9
+ }
10
+ }
@@ -29,7 +29,7 @@ export class AntivirusClamavAdapter implements AntivirusPort {
29
29
  const signature = stdout.match(this.signature) ?? stderr.match(this.signature);
30
30
 
31
31
  // Stryker disable all
32
- return { clean: false, signature: signature?.groups?.signature?.trim() ?? "unknown" };
32
+ return { clean: false, signature: signature?.groups?.["signature"]?.trim() ?? "unknown" };
33
33
  // Stryker restore all
34
34
  }
35
35
 
@@ -1,14 +1,15 @@
1
1
  import type { MiddlewareHandler } from "hono";
2
2
  import { ApiVersionMiddleware } from "./api-version.middleware";
3
- import type { BuildInfoRepositoryStrategy } from "./build-info-repository.strategy";
3
+ import type { BuildInfoType } from "./build-info.vo";
4
4
  import type { CacheResolverStrategy } from "./cache-resolver.strategy";
5
5
  import type { HashContentStrategy } from "./hash-content.strategy";
6
6
  import type { MiddlewareHonoPort } from "./middleware-hono.port";
7
+ import type { ReactiveConfigPort } from "./reactive-config.port";
7
8
 
8
9
  type Dependencies = {
9
10
  CacheResolver: CacheResolverStrategy;
10
11
  HashContent: HashContentStrategy;
11
- BuildInfoRepository: BuildInfoRepositoryStrategy;
12
+ BuildInfoConfig: ReactiveConfigPort<BuildInfoType>;
12
13
  };
13
14
 
14
15
  export class ApiVersionHonoMiddleware implements MiddlewareHonoPort {
@@ -1,14 +1,15 @@
1
1
  import type * as tools from "@bgord/tools";
2
- import type { BuildInfoRepositoryStrategy } from "./build-info-repository.strategy";
2
+ import type { BuildInfoType } from "./build-info.vo";
3
3
  import type { CacheResolverStrategy } from "./cache-resolver.strategy";
4
4
  import type { HashContentStrategy } from "./hash-content.strategy";
5
+ import type { ReactiveConfigPort } from "./reactive-config.port";
5
6
  import { SubjectApplicationResolver } from "./subject-application-resolver.vo";
6
7
  import { SubjectSegmentFixedStrategy } from "./subject-segment-fixed.strategy";
7
8
 
8
9
  type Dependencies = {
9
10
  CacheResolver: CacheResolverStrategy;
10
11
  HashContent: HashContentStrategy;
11
- BuildInfoRepository: BuildInfoRepositoryStrategy;
12
+ BuildInfoConfig: ReactiveConfigPort<BuildInfoType>;
12
13
  };
13
14
 
14
15
  export class ApiVersionMiddleware {
@@ -16,7 +17,7 @@ export class ApiVersionMiddleware {
16
17
 
17
18
  constructor(private readonly deps: Dependencies) {}
18
19
 
19
- async evaluate(): Promise<tools.PackageVersion> {
20
+ async evaluate(): Promise<tools.PackageVersionSchemaType> {
20
21
  const resolver = new SubjectApplicationResolver(
21
22
  [new SubjectSegmentFixedStrategy("api-version")],
22
23
  this.deps,
@@ -24,7 +25,7 @@ export class ApiVersionMiddleware {
24
25
  const subject = await resolver.resolve();
25
26
 
26
27
  const build = await this.deps.CacheResolver.resolve(subject.hex, async () =>
27
- this.deps.BuildInfoRepository.extract(),
28
+ this.deps.BuildInfoConfig.get(),
28
29
  );
29
30
 
30
31
  return build.version;
@@ -0,0 +1,14 @@
1
+ import * as tools from "@bgord/tools";
2
+ import * as v from "valibot";
3
+ import { CommitShaValue } from "./commit-sha-value.vo";
4
+
5
+ export const BuildInfo = v.object({
6
+ timestamp: tools.TimestampValue,
7
+ version: tools.PackageVersionSchema,
8
+ sha: CommitShaValue,
9
+ size: tools.SizeBytes,
10
+ });
11
+
12
+ export type BuildInfoType = v.InferOutput<typeof BuildInfo>;
13
+
14
+ export const BUILD_INFO_FILE_PATH = tools.FilePathRelative.fromString("infra/build-info.json");
@@ -8,7 +8,7 @@ export class ErrorNormalizer {
8
8
  }
9
9
 
10
10
  static isNormalizedError(value: unknown): value is NormalizedError {
11
- return isPlainObject(value) && "message" in value && typeof value.message === "string";
11
+ return isPlainObject(value) && "message" in value && typeof value["message"] === "string";
12
12
  }
13
13
 
14
14
  private static normalizeWithGuard(error: unknown, seen: WeakSet<object>): NormalizedError {
@@ -1,13 +1,14 @@
1
1
  import { createFactory } from "hono/factory";
2
- import type { BuildInfoRepositoryStrategy } from "./build-info-repository.strategy";
2
+ import type { BuildInfoType } from "./build-info.vo";
3
3
  import type { ClockPort } from "./clock.port";
4
4
  import type { HandlerHonoPort } from "./handler-hono.port";
5
5
  import { type HealthcheckConfig, HealthcheckHandler } from "./healthcheck.handler";
6
6
  import type { LoggerStatsProviderPort } from "./logger-stats-provider.port";
7
+ import type { ReactiveConfigPort } from "./reactive-config.port";
7
8
 
8
9
  type Dependencies = {
9
10
  Clock: ClockPort;
10
- BuildInfoRepository: BuildInfoRepositoryStrategy;
11
+ BuildInfoConfig: ReactiveConfigPort<BuildInfoType>;
11
12
  LoggerStatsProvider?: LoggerStatsProviderPort;
12
13
  };
13
14
 
@@ -1,6 +1,6 @@
1
1
  import os from "node:os";
2
2
  import * as tools from "@bgord/tools";
3
- import type { BuildInfoRepositoryStrategy } from "./build-info-repository.strategy";
3
+ import type { BuildInfoType } from "./build-info.vo";
4
4
  import type { ClockPort } from "./clock.port";
5
5
  import type { CommitShaValueType } from "./commit-sha-value.vo";
6
6
  import { EventLoopLag } from "./event-loop-lag.service";
@@ -15,6 +15,7 @@ import {
15
15
  type PrerequisiteVerificationResult,
16
16
  } from "./prerequisite-verifier.port";
17
17
  import { PrerequisiteVerifierSelfAdapter } from "./prerequisite-verifier-self.adapter";
18
+ import type { ReactiveConfigPort } from "./reactive-config.port";
18
19
  import type { RedactorStrategy } from "./redactor.strategy";
19
20
  import { Stopwatch } from "./stopwatch.service";
20
21
  import { Uptime, type UptimeResultType } from "./uptime.service";
@@ -27,7 +28,7 @@ export enum HealthcheckStatusEnum {
27
28
 
28
29
  type Dependencies = {
29
30
  Clock: ClockPort;
30
- BuildInfoRepository: BuildInfoRepositoryStrategy;
31
+ BuildInfoConfig: ReactiveConfigPort<BuildInfoType>;
31
32
  LoggerStatsProvider?: LoggerStatsProviderPort;
32
33
  };
33
34
 
@@ -125,7 +126,7 @@ export class HealthcheckHandler {
125
126
  ? HealthcheckStatusEnum.degraded
126
127
  : HealthcheckStatusEnum.healthy;
127
128
 
128
- const build = await this.deps.BuildInfoRepository.extract();
129
+ const build = await this.deps.BuildInfoConfig.get();
129
130
  const uptime = Uptime.get(this.deps.Clock);
130
131
  const histogram = EventLoopLag.snapshot();
131
132
  const memory = MemoryConsumption.snapshot();
@@ -135,11 +136,11 @@ export class HealthcheckHandler {
135
136
  code: HealthcheckStatusCode[status],
136
137
  details,
137
138
  deployment: {
138
- version: build.version.toString(),
139
- timestamp: build.timestamp.ms,
140
- date: new Date(build.timestamp.ms).toISOString(),
141
- sha: build.sha.toString(),
142
- size: build.size.format(tools.Size.unit.MB),
139
+ version: build.version,
140
+ timestamp: build.timestamp,
141
+ date: new Date(build.timestamp).toISOString(),
142
+ sha: build.sha,
143
+ size: tools.Size.fromBytes(build.size).format(tools.Size.unit.MB),
143
144
  environment: this.config.Env,
144
145
  },
145
146
  server: {
package/src/index.ts CHANGED
@@ -10,6 +10,17 @@ export * from "./ab-variant.vo";
10
10
  export * from "./ab-variant-selector.service";
11
11
  export * from "./ab-variant-weight.vo";
12
12
  export * from "./ab-variants.vo";
13
+ export * from "./alert-channel.builder";
14
+ export * from "./alert-channel.port";
15
+ export * from "./alert-channel-collecting.adapter";
16
+ export * from "./alert-channel-composite.adapter";
17
+ export * from "./alert-channel-mailer.adapter";
18
+ export * from "./alert-channel-noop.adapter";
19
+ export * from "./alert-channel-sms.adapter";
20
+ export * from "./alert-channel-with-logger.adapter";
21
+ export * from "./alert-channel-with-retry.adapter";
22
+ export * from "./alert-channel-with-timeout.adapter";
23
+ export * from "./alert-message.vo";
13
24
  export * from "./antivirus.port";
14
25
  export * from "./antivirus-clamav.adapter";
15
26
  export * from "./antivirus-noop.adapter";
@@ -24,10 +35,7 @@ export * from "./basic-auth-username.vo";
24
35
  export * from "./better-auth-logger.service";
25
36
  export * from "./binary.vo";
26
37
  export * from "./bots.vo";
27
- export * from "./build-info-repository.strategy";
28
- export * from "./build-info-repository-file.strategy";
29
- export * from "./build-info-repository-noop.strategy";
30
- export * from "./build-info-repository-package-json.strategy";
38
+ export * from "./build-info.vo";
31
39
  export * from "./cache-file.service";
32
40
  export * from "./cache-repository.port";
33
41
  export * from "./cache-repository-node-cache.adapter";
@@ -222,6 +230,7 @@ export * from "./logger-stats-provider-noop.adapter";
222
230
  export * from "./mailer.builder";
223
231
  export * from "./mailer.builder";
224
232
  export * from "./mailer.port";
233
+ export * from "./mailer-collecting.adapter";
225
234
  export * from "./mailer-content-html.vo";
226
235
  export * from "./mailer-noop.adapter";
227
236
  export * from "./mailer-resend.adapter";
@@ -281,6 +290,7 @@ export * from "./prerequisite-verifier-port.adapter";
281
290
  export * from "./prerequisite-verifier-ram.adapter";
282
291
  export * from "./prerequisite-verifier-running-user.adapter";
283
292
  export * from "./prerequisite-verifier-self.adapter";
293
+ export * from "./prerequisite-verifier-sms.adapter";
284
294
  export * from "./prerequisite-verifier-space.adapter";
285
295
  export * from "./prerequisite-verifier-sqlite.adapter";
286
296
  export * from "./prerequisite-verifier-ssl-certificate-expiry.adapter";
@@ -290,6 +300,11 @@ export * from "./randomness.strategy";
290
300
  export * from "./randomness-crypto.strategy";
291
301
  export * from "./randomness-math.strategy";
292
302
  export * from "./randomness-noop.strategy";
303
+ export * from "./reactive-config.port";
304
+ export * from "./reactive-config-file-json.adapter";
305
+ export * from "./reactive-config-noop.adapter";
306
+ export * from "./reactive-config-with-cache.adapter";
307
+ export * from "./reactive-config-with-fallback.adapter";
293
308
  export * from "./readiness.handler";
294
309
  export * from "./readiness-hono.handler";
295
310
  export * from "./recaptcha-secret-key.vo";
@@ -373,6 +388,13 @@ export * from "./sleeper-noop.adapter";
373
388
  export * from "./sleeper-system.adapter";
374
389
  export * from "./slower.middleware";
375
390
  export * from "./slower-hono.middleware";
391
+ export * from "./sms.builder";
392
+ export * from "./sms.port";
393
+ export * from "./sms-collecting.adapter";
394
+ export * from "./sms-noop.adapter";
395
+ export * from "./sms-with-logger.adapter";
396
+ export * from "./sms-with-retry.adapter";
397
+ export * from "./sms-with-timeout.adapter";
376
398
  export * from "./smtp-host.vo";
377
399
  export * from "./smtp-pass.vo";
378
400
  export * from "./smtp-port.vo";
@@ -0,0 +1,14 @@
1
+ import type { MailerPort } from "./mailer.port";
2
+ import type { MailerTemplate } from "./mailer-template.vo";
3
+
4
+ export class MailerCollectingAdapter implements MailerPort {
5
+ messages: Array<MailerTemplate> = [];
6
+
7
+ async send(template: MailerTemplate): Promise<void> {
8
+ this.messages.push(template);
9
+ }
10
+
11
+ async verify(): Promise<boolean> {
12
+ return true;
13
+ }
14
+ }
@@ -0,0 +1,26 @@
1
+ import {
2
+ PrerequisiteVerification,
3
+ type PrerequisiteVerificationResult,
4
+ type PrerequisiteVerifierPort,
5
+ } from "./prerequisite-verifier.port";
6
+ import type { SmsPort } from "./sms.port";
7
+
8
+ type Dependencies = { Sms: SmsPort };
9
+
10
+ export class PrerequisiteVerifierSmsAdapter implements PrerequisiteVerifierPort {
11
+ constructor(private readonly deps: Dependencies) {}
12
+
13
+ async verify(): Promise<PrerequisiteVerificationResult> {
14
+ try {
15
+ await this.deps.Sms.verify();
16
+
17
+ return PrerequisiteVerification.success;
18
+ } catch (error) {
19
+ return PrerequisiteVerification.failure(error);
20
+ }
21
+ }
22
+
23
+ get kind(): string {
24
+ return "sms";
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { FileReaderJsonPort } from "./file-reader-json.port";
3
+ import {
4
+ ReactiveConfigError,
5
+ type ReactiveConfigPort,
6
+ type ReactiveConfigSchema,
7
+ } from "./reactive-config.port";
8
+
9
+ type Dependencies = { FileReaderJson: FileReaderJsonPort };
10
+
11
+ export class ReactiveConfigFileJsonAdapter<T extends object> implements ReactiveConfigPort<T> {
12
+ constructor(
13
+ private readonly path: tools.FilePathAbsolute | tools.FilePathRelative,
14
+ private readonly schema: ReactiveConfigSchema<T>,
15
+ private readonly deps: Dependencies,
16
+ ) {}
17
+
18
+ async get(): Promise<Readonly<T>> {
19
+ const raw = await this.deps.FileReaderJson.read(this.path);
20
+
21
+ const result = this.schema["~standard"].validate(raw);
22
+ if (result instanceof Promise) throw new Error(ReactiveConfigError.NoAsyncSchema);
23
+ if (result.issues) throw new Error(result.issues[0]?.message);
24
+ return Object.freeze(result.value);
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ import {
2
+ ReactiveConfigError,
3
+ type ReactiveConfigPort,
4
+ type ReactiveConfigSchema,
5
+ } from "./reactive-config.port";
6
+
7
+ export class ReactiveConfigNoopAdapter<T extends object> implements ReactiveConfigPort<T> {
8
+ constructor(
9
+ private readonly schema: ReactiveConfigSchema<T>,
10
+ private readonly value: T,
11
+ ) {}
12
+
13
+ async get(): Promise<Readonly<T>> {
14
+ const result = this.schema["~standard"].validate(this.value);
15
+ if (result instanceof Promise) throw new Error(ReactiveConfigError.NoAsyncSchema);
16
+ if (result.issues) throw new Error(result.issues[0]?.message);
17
+ return Object.freeze(result.value);
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ import type { CacheResolverStrategy } from "./cache-resolver.strategy";
2
+ import type { HashContentStrategy } from "./hash-content.strategy";
3
+ import type { ReactiveConfigPort } from "./reactive-config.port";
4
+
5
+ type Dependencies = { CacheResolver: CacheResolverStrategy; HashContent: HashContentStrategy };
6
+
7
+ export class ReactiveConfigWithCacheAdapter<T extends object> implements ReactiveConfigPort<T> {
8
+ constructor(
9
+ private readonly inner: ReactiveConfigPort<T>,
10
+ private readonly subject: string,
11
+ private readonly deps: Dependencies,
12
+ ) {}
13
+
14
+ async get(): Promise<Readonly<T>> {
15
+ const key = await this.deps.HashContent.hash(this.subject);
16
+
17
+ return this.deps.CacheResolver.resolve(key, () => this.inner.get());
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ ReactiveConfigError,
3
+ type ReactiveConfigPort,
4
+ type ReactiveConfigSchema,
5
+ } from "./reactive-config.port";
6
+
7
+ export class ReactiveConfigWithFallbackAdapter<T extends object> implements ReactiveConfigPort<T> {
8
+ constructor(
9
+ private readonly inner: ReactiveConfigPort<T>,
10
+ private readonly schema: ReactiveConfigSchema<T>,
11
+ private readonly fallback: T,
12
+ ) {}
13
+
14
+ async get(): Promise<Readonly<T>> {
15
+ try {
16
+ return await this.inner.get();
17
+ } catch {
18
+ const result = this.schema["~standard"].validate(this.fallback);
19
+ if (result instanceof Promise) throw new Error(ReactiveConfigError.NoAsyncSchema);
20
+ if (result.issues) throw new Error(result.issues[0]?.message);
21
+ return Object.freeze(result.value);
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ export const ReactiveConfigError = { NoAsyncSchema: "reactive.config.no.async.schema" };
4
+
5
+ export type ReactiveConfigSchema<T extends object> = StandardSchemaV1<unknown, T>;
6
+
7
+ export interface ReactiveConfigPort<T extends object> {
8
+ get(): Promise<Readonly<T>>;
9
+ }
@@ -10,9 +10,8 @@ export class RedactorErrorCauseDepthLimit implements RedactorStrategy {
10
10
  // Stryker disable all
11
11
  if (!isPlainObject(input)) return input;
12
12
  // Stryker restore all
13
- if (!ErrorNormalizer.isNormalizedError(input.error)) return input;
14
-
15
- return { ...input, error: this.limit(input.error, tools.Int.nonNegative(0)) };
13
+ if (!ErrorNormalizer.isNormalizedError(input["error"])) return input;
14
+ return { ...input, error: this.limit(input["error"], tools.Int.nonNegative(0)) };
16
15
  }
17
16
 
18
17
  private limit(error: NormalizedError, depth: tools.IntegerNonNegativeType): NormalizedError {
@@ -7,9 +7,8 @@ export class RedactorErrorStackHide implements RedactorStrategy {
7
7
  // Stryker disable all
8
8
  if (!isPlainObject(input)) return input;
9
9
  // Stryker restore all
10
- if (!ErrorNormalizer.isNormalizedError(input.error)) return input;
11
-
12
- return { ...input, error: this.hide(input.error) };
10
+ if (!ErrorNormalizer.isNormalizedError(input["error"])) return input;
11
+ return { ...input, error: this.hide(input["error"]) };
13
12
  }
14
13
 
15
14
  private hide(error: NormalizedError): NormalizedError {
@@ -20,7 +20,7 @@ export class RedactorMetadataCompactArray implements RedactorStrategy {
20
20
  return {
21
21
  ...input,
22
22
  metadata: deepCloneWith(
23
- input.metadata,
23
+ input["metadata"],
24
24
  (value) => {
25
25
  if (!Array.isArray(value)) return undefined;
26
26
  if (value.length <= this.maxItems) return undefined;
@@ -20,7 +20,7 @@ export class RedactorMetadataCompactObject implements RedactorStrategy {
20
20
  return {
21
21
  ...input,
22
22
  metadata: deepCloneWith(
23
- input.metadata,
23
+ input["metadata"],
24
24
  (value) => {
25
25
  if (!isPlainObject(value) || Array.isArray(value)) return undefined;
26
26
 
@@ -3,7 +3,7 @@ import type { MiddlewareHandler } from "hono";
3
3
  import { cors } from "hono/cors";
4
4
  import { secureHeaders } from "hono/secure-headers";
5
5
  import { ApiVersionHonoMiddleware } from "./api-version-hono.middleware";
6
- import type { BuildInfoRepositoryStrategy } from "./build-info-repository.strategy";
6
+ import type { BuildInfoType } from "./build-info.vo";
7
7
  import type { CacheResolverStrategy } from "./cache-resolver.strategy";
8
8
  import type { ClockPort } from "./clock.port";
9
9
  import { CorrelationHonoMiddleware } from "./correlation-hono.middleware";
@@ -15,6 +15,7 @@ import type { IdProviderPort } from "./id-provider.port";
15
15
  import type { LanguageDetectorMiddlewareConfig } from "./language-detector.middleware";
16
16
  import { LanguageDetectorHonoMiddleware } from "./language-detector-hono.middleware";
17
17
  import type { LoggerPort } from "./logger.port";
18
+ import type { ReactiveConfigPort } from "./reactive-config.port";
18
19
  import type { ShieldCsrfConfig } from "./shield-csrf.strategy";
19
20
  import { ShieldCsrfHonoStrategy } from "./shield-csrf-hono.strategy";
20
21
  import type { ShieldMaintenanceConfig } from "./shield-maintenance.strategy";
@@ -30,7 +31,7 @@ type Dependencies = {
30
31
  Clock: ClockPort;
31
32
  CacheResolver: CacheResolverStrategy;
32
33
  HashContent: HashContentStrategy;
33
- BuildInfoRepository: BuildInfoRepositoryStrategy;
34
+ BuildInfoConfig: ReactiveConfigPort<BuildInfoType>;
34
35
  };
35
36
 
36
37
  type Config<T extends tools.LanguageType> = {
@@ -19,7 +19,7 @@ export class ShieldRecaptchaStrategy {
19
19
  async evaluate(context: HasRequestHeader & HasRequestQuery, formToken: string | null): Promise<boolean> {
20
20
  try {
21
21
  const header = context.request.header("x-recaptcha-token");
22
- const query = context.request.query().recaptchaToken;
22
+ const query = context.request.query()["recaptchaToken"];
23
23
  const remoteip = context.request.header("x-forwarded-for") ?? "";
24
24
 
25
25
  const token = header ?? query ?? formToken;
@@ -0,0 +1,14 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { SmsPort } from "./sms.port";
3
+
4
+ export class SmsCollectingAdapter implements SmsPort {
5
+ readonly messages: Array<tools.SmsMessage> = [];
6
+
7
+ async send(message: tools.SmsMessage): Promise<void> {
8
+ this.messages.push(message);
9
+ }
10
+
11
+ async verify(): Promise<boolean> {
12
+ return true;
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { SmsPort } from "./sms.port";
3
+
4
+ export class SmsNoopAdapter implements SmsPort {
5
+ async send(_message: tools.SmsMessage): Promise<void> {}
6
+
7
+ async verify(): Promise<boolean> {
8
+ return true;
9
+ }
10
+ }
@@ -0,0 +1,37 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { ClockPort } from "./clock.port";
3
+ import type { LoggerPort } from "./logger.port";
4
+ import type { SmsPort } from "./sms.port";
5
+ import { Stopwatch } from "./stopwatch.service";
6
+
7
+ export type SmsWithLoggerAdapterDependencies = { inner: SmsPort; Logger: LoggerPort; Clock: ClockPort };
8
+
9
+ export class SmsWithLoggerAdapter implements SmsPort {
10
+ private readonly base = { component: "infra", operation: "sms" };
11
+
12
+ constructor(private readonly deps: SmsWithLoggerAdapterDependencies) {}
13
+
14
+ async send(message: tools.SmsMessage): Promise<void> {
15
+ const duration = new Stopwatch(this.deps);
16
+
17
+ try {
18
+ this.deps.Logger.info({ message: "SMS attempt", metadata: message.toJSON(), ...this.base });
19
+
20
+ await this.deps.inner.send(message);
21
+
22
+ this.deps.Logger.info({
23
+ message: "SMS success",
24
+ metadata: { message: message.toJSON(), duration: duration.stop() },
25
+ ...this.base,
26
+ });
27
+ } catch (error) {
28
+ this.deps.Logger.error({ message: "SMS error", error, metadata: duration.stop(), ...this.base });
29
+
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async verify(): Promise<boolean> {
35
+ return this.deps.inner.verify();
36
+ }
37
+ }