@bgord/bun 1.2.5 → 1.2.7

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 (132) hide show
  1. package/dist/better-auth-logger.service.d.ts +5 -2
  2. package/dist/better-auth-logger.service.d.ts.map +1 -1
  3. package/dist/better-auth-logger.service.js +6 -6
  4. package/dist/better-auth-logger.service.js.map +1 -1
  5. package/dist/command-logger.service.d.ts +6 -2
  6. package/dist/command-logger.service.d.ts.map +1 -1
  7. package/dist/command-logger.service.js +4 -4
  8. package/dist/command-logger.service.js.map +1 -1
  9. package/dist/content-hash-noop.adapter.d.ts +7 -0
  10. package/dist/content-hash-noop.adapter.d.ts.map +1 -0
  11. package/dist/content-hash-noop.adapter.js +6 -0
  12. package/dist/content-hash-noop.adapter.js.map +1 -0
  13. package/dist/content-hash-sha256-bun.adapter.d.ts +7 -0
  14. package/dist/content-hash-sha256-bun.adapter.d.ts.map +1 -0
  15. package/dist/content-hash-sha256-bun.adapter.js +8 -0
  16. package/dist/content-hash-sha256-bun.adapter.js.map +1 -0
  17. package/dist/content-hash.port.d.ts +7 -0
  18. package/dist/content-hash.port.d.ts.map +1 -0
  19. package/dist/content-hash.port.js +2 -0
  20. package/dist/content-hash.port.js.map +1 -0
  21. package/dist/encryption-bun.adapter.d.ts +6 -2
  22. package/dist/encryption-bun.adapter.d.ts.map +1 -1
  23. package/dist/encryption-bun.adapter.js +5 -5
  24. package/dist/encryption-bun.adapter.js.map +1 -1
  25. package/dist/event-handler.service.d.ts +6 -2
  26. package/dist/event-handler.service.d.ts.map +1 -1
  27. package/dist/event-handler.service.js +4 -4
  28. package/dist/event-handler.service.js.map +1 -1
  29. package/dist/event-logger.service.d.ts +6 -2
  30. package/dist/event-logger.service.d.ts.map +1 -1
  31. package/dist/event-logger.service.js +4 -4
  32. package/dist/event-logger.service.js.map +1 -1
  33. package/dist/graceful-shutdown.service.d.ts +5 -2
  34. package/dist/graceful-shutdown.service.d.ts.map +1 -1
  35. package/dist/graceful-shutdown.service.js +18 -10
  36. package/dist/graceful-shutdown.service.js.map +1 -1
  37. package/dist/index.d.ts +6 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +6 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/jobs.service.d.ts.map +1 -1
  42. package/dist/jobs.service.js.map +1 -1
  43. package/dist/mailer-smtp-with-logger.adapter.d.ts +5 -5
  44. package/dist/mailer-smtp-with-logger.adapter.d.ts.map +1 -1
  45. package/dist/mailer-smtp-with-logger.adapter.js +8 -8
  46. package/dist/mailer-smtp-with-logger.adapter.js.map +1 -1
  47. package/dist/markdown-generator-marked.adapter.d.ts +7 -0
  48. package/dist/markdown-generator-marked.adapter.d.ts.map +1 -0
  49. package/dist/markdown-generator-marked.adapter.js +12 -0
  50. package/dist/markdown-generator-marked.adapter.js.map +1 -0
  51. package/dist/markdown-generator-noop.adapter.d.ts +5 -0
  52. package/dist/markdown-generator-noop.adapter.d.ts.map +1 -0
  53. package/dist/markdown-generator-noop.adapter.js +6 -0
  54. package/dist/markdown-generator-noop.adapter.js.map +1 -0
  55. package/dist/markdown-generator.port.d.ts +4 -0
  56. package/dist/markdown-generator.port.d.ts.map +1 -0
  57. package/dist/markdown-generator.port.js +3 -0
  58. package/dist/markdown-generator.port.js.map +1 -0
  59. package/dist/pdf-generator-noop.adapter.d.ts +6 -2
  60. package/dist/pdf-generator-noop.adapter.d.ts.map +1 -1
  61. package/dist/pdf-generator-noop.adapter.js +8 -4
  62. package/dist/pdf-generator-noop.adapter.js.map +1 -1
  63. package/dist/prerequisites/clock-drift.d.ts +2 -2
  64. package/dist/prerequisites/clock-drift.js +3 -3
  65. package/dist/prerequisites/dns.d.ts +16 -0
  66. package/dist/prerequisites/dns.d.ts.map +1 -0
  67. package/dist/prerequisites/dns.js +30 -0
  68. package/dist/prerequisites/dns.js.map +1 -0
  69. package/dist/prerequisites/index.d.ts +1 -0
  70. package/dist/prerequisites/index.d.ts.map +1 -1
  71. package/dist/prerequisites/index.js +1 -0
  72. package/dist/prerequisites/index.js.map +1 -1
  73. package/dist/prerequisites/log-file.d.ts +2 -2
  74. package/dist/prerequisites/log-file.js +3 -3
  75. package/dist/prerequisites/mailer.d.ts +2 -2
  76. package/dist/prerequisites/mailer.js +3 -3
  77. package/dist/prerequisites/space.d.ts +2 -2
  78. package/dist/prerequisites/space.d.ts.map +1 -1
  79. package/dist/prerequisites/space.js +3 -3
  80. package/dist/prerequisites/space.js.map +1 -1
  81. package/dist/prerequisites/ssl-certificate-expiry.d.ts +2 -2
  82. package/dist/prerequisites/ssl-certificate-expiry.d.ts.map +1 -1
  83. package/dist/prerequisites/ssl-certificate-expiry.js +3 -3
  84. package/dist/prerequisites/ssl-certificate-expiry.js.map +1 -1
  85. package/dist/prerequisites/translations.d.ts +4 -4
  86. package/dist/prerequisites/translations.js +5 -5
  87. package/dist/prerequisites.service.d.ts +2 -2
  88. package/dist/prerequisites.service.js +3 -3
  89. package/dist/remote-file-storage-noop.adapter.d.ts +6 -3
  90. package/dist/remote-file-storage-noop.adapter.d.ts.map +1 -1
  91. package/dist/remote-file-storage-noop.adapter.js +10 -8
  92. package/dist/remote-file-storage-noop.adapter.js.map +1 -1
  93. package/dist/shield-rate-limit.middleware.d.ts.map +1 -1
  94. package/dist/shield-rate-limit.middleware.js.map +1 -1
  95. package/dist/timekeeper-noop.adapter.d.ts +6 -2
  96. package/dist/timekeeper-noop.adapter.d.ts.map +1 -1
  97. package/dist/timekeeper-noop.adapter.js +4 -4
  98. package/dist/timekeeper-noop.adapter.js.map +1 -1
  99. package/dist/translations.service.d.ts.map +1 -1
  100. package/dist/translations.service.js.map +1 -1
  101. package/dist/tsconfig.tsbuildinfo +1 -1
  102. package/package.json +8 -6
  103. package/readme.md +7 -0
  104. package/src/better-auth-logger.service.ts +6 -4
  105. package/src/command-logger.service.ts +4 -2
  106. package/src/content-hash-noop.adapter.ts +7 -0
  107. package/src/content-hash-sha256-bun.adapter.ts +10 -0
  108. package/src/content-hash.port.ts +5 -0
  109. package/src/encryption-bun.adapter.ts +5 -3
  110. package/src/event-handler.service.ts +4 -2
  111. package/src/event-logger.service.ts +4 -2
  112. package/src/graceful-shutdown.service.ts +18 -8
  113. package/src/index.ts +6 -0
  114. package/src/jobs.service.ts +1 -0
  115. package/src/mailer-smtp-with-logger.adapter.ts +8 -7
  116. package/src/markdown-generator-marked.adapter.ts +15 -0
  117. package/src/markdown-generator-noop.adapter.ts +7 -0
  118. package/src/markdown-generator.port.ts +3 -0
  119. package/src/pdf-generator-noop.adapter.ts +8 -2
  120. package/src/prerequisites/clock-drift.ts +4 -4
  121. package/src/prerequisites/dns.ts +36 -0
  122. package/src/prerequisites/index.ts +1 -0
  123. package/src/prerequisites/log-file.ts +4 -4
  124. package/src/prerequisites/mailer.ts +4 -4
  125. package/src/prerequisites/space.ts +4 -4
  126. package/src/prerequisites/ssl-certificate-expiry.ts +4 -4
  127. package/src/prerequisites/translations.ts +7 -7
  128. package/src/prerequisites.service.ts +4 -4
  129. package/src/remote-file-storage-noop.adapter.ts +13 -9
  130. package/src/shield-rate-limit.middleware.ts +1 -0
  131. package/src/timekeeper-noop.adapter.ts +4 -2
  132. package/src/translations.service.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/bun",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": "Bartosz Gordon",
@@ -27,9 +27,9 @@
27
27
  "@types/lodash": "4.17.21",
28
28
  "@types/nodemailer": "7.0.4",
29
29
  "@types/yazl": "3.3.0",
30
- "cspell": "9.3.2",
30
+ "cspell": "9.4.0",
31
31
  "knip": "5.71.0",
32
- "lefthook": "2.0.4",
32
+ "lefthook": "2.0.7",
33
33
  "only-allow": "1.2.2",
34
34
  "shellcheck": "4.1.0",
35
35
  "typescript": "5.9.3",
@@ -37,14 +37,16 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@axiomhq/winston": "1.3.1",
40
- "@bgord/tools": "1.1.8",
41
- "@hono/ua-blocker": "0.1.19",
42
- "better-auth": "1.4.4",
40
+ "@bgord/tools": "1.1.11",
41
+ "@hono/ua-blocker": "0.1.20",
42
+ "better-auth": "1.4.5",
43
43
  "croner": "9.1.0",
44
44
  "csv": "6.4.1",
45
45
  "hcaptcha": "0.2.0",
46
46
  "hono": "4.10.7",
47
+ "isomorphic-dompurify": "2.33.0",
47
48
  "lodash": "4.17.21",
49
+ "marked": "17.0.1",
48
50
  "node-cache": "5.1.2",
49
51
  "nodemailer": "7.0.11",
50
52
  "sharp": "0.34.5",
package/readme.md CHANGED
@@ -50,6 +50,9 @@ src/
50
50
  ├── command-envelope.ts
51
51
  ├── command-logger.service.ts
52
52
  ├── command.types.ts
53
+ ├── content-hash-noop.adapter.ts
54
+ ├── content-hash-sha256-bun.adapter.ts
55
+ ├── content-hash.port.ts
53
56
  ├── context.middleware.ts
54
57
  ├── correlation-id.vo.ts
55
58
  ├── correlation-storage.service.ts
@@ -148,6 +151,9 @@ src/
148
151
  ├── mailer-smtp.adapter.ts
149
152
  ├── mailer.port.ts
150
153
  ├── mailer.vo.ts
154
+ ├── markdown-generator-marked.adapter.ts
155
+ ├── markdown-generator-noop.adapter.ts
156
+ ├── markdown-generator.port.ts
151
157
  ├── memory-consumption.service.ts
152
158
  ├── modules
153
159
  │   ├── history
@@ -194,6 +200,7 @@ src/
194
200
  │   ├── clock-drift.ts
195
201
  │   ├── dependency-vulnerabilities.ts
196
202
  │   ├── directory.ts
203
+ │   ├── dns.ts
197
204
  │   ├── external-api.ts
198
205
  │   ├── jobs.ts
199
206
  │   ├── log-file.ts
@@ -3,8 +3,10 @@ import { formatError } from "./logger-format-error.service";
3
3
 
4
4
  type LogLevel = "info" | "success" | "warn" | "error" | "debug";
5
5
 
6
+ type Dependencies = { Logger: LoggerPort };
7
+
6
8
  export class BetterAuthLogger {
7
- constructor(private readonly logger: LoggerPort) {}
9
+ constructor(private readonly deps: Dependencies) {}
8
10
 
9
11
  attach() {
10
12
  return {
@@ -16,18 +18,18 @@ export class BetterAuthLogger {
16
18
 
17
19
  switch (level) {
18
20
  case LogLevelEnum.error: {
19
- this.logger.error({
21
+ this.deps.Logger.error({
20
22
  ...base,
21
23
  error: formatError(args.find((a) => a instanceof Error) ?? new Error(message)),
22
24
  });
23
25
  break;
24
26
  }
25
27
  case LogLevelEnum.warn: {
26
- this.logger.warn(base);
28
+ this.deps.Logger.warn(base);
27
29
  break;
28
30
  }
29
31
  default: {
30
- this.logger.info(base);
32
+ this.deps.Logger.info(base);
31
33
  break;
32
34
  }
33
35
  }
@@ -1,9 +1,11 @@
1
1
  import type { LoggerPort } from "./logger.port";
2
2
 
3
+ type Dependencies = { Logger: LoggerPort };
4
+
3
5
  export class CommandLogger {
4
6
  private readonly base = { component: "infra", operation: "command_emitted" };
5
7
 
6
- constructor(private readonly logger: LoggerPort) {}
8
+ constructor(private readonly deps: Dependencies) {}
7
9
 
8
10
  private _handle(
9
11
  type: string,
@@ -14,7 +16,7 @@ export class CommandLogger {
14
16
  if (type === "subscribe") return;
15
17
  if (typeof commandName === "symbol") return;
16
18
 
17
- this.logger.info({ message: `${commandName} emitted`, metadata: commandData, ...this.base });
19
+ this.deps.Logger.info({ message: `${commandName} emitted`, metadata: commandData, ...this.base });
18
20
  }
19
21
 
20
22
  handle = this._handle.bind(this);
@@ -0,0 +1,7 @@
1
+ import type { ContentHashPort } from "./content-hash.port";
2
+
3
+ export class ContentHashNoopAdapter implements ContentHashPort {
4
+ async hash(_content: string) {
5
+ return { etag: "noop" };
6
+ }
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { ContentHashPort } from "./content-hash.port";
2
+
3
+ export class ContentHashSha256BunAdapter implements ContentHashPort {
4
+ async hash(content: string) {
5
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
6
+ const etag = Buffer.from(digest).toString("hex");
7
+
8
+ return { etag };
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ export type ContentHashResult = { etag: string };
2
+
3
+ export interface ContentHashPort {
4
+ hash(content: string): Promise<ContentHashResult>;
5
+ }
@@ -4,11 +4,13 @@ import { EncryptionIV } from "./encryption-iv.vo";
4
4
 
5
5
  export const EncryptionBunAdapterError = { InvalidPayload: "encryption.bun.adapter.invalid.payload" };
6
6
 
7
+ type Dependencies = { CryptoKeyProvider: CryptoKeyProviderPort };
8
+
7
9
  export class EncryptionBunAdapter implements EncryptionPort {
8
- constructor(private readonly cryptoKeyProvider: CryptoKeyProviderPort) {}
10
+ constructor(private readonly deps: Dependencies) {}
9
11
 
10
12
  async encrypt(recipe: EncryptionRecipe) {
11
- const key = await this.cryptoKeyProvider.get();
13
+ const key = await this.deps.CryptoKeyProvider.get();
12
14
  const iv = EncryptionIV.generate();
13
15
 
14
16
  const plaintext = await Bun.file(recipe.input.get()).arrayBuffer();
@@ -30,7 +32,7 @@ export class EncryptionBunAdapter implements EncryptionPort {
30
32
  }
31
33
 
32
34
  async decrypt(recipe: EncryptionRecipe) {
33
- const key = await this.cryptoKeyProvider.get();
35
+ const key = await this.deps.CryptoKeyProvider.get();
34
36
 
35
37
  const bytes = new Uint8Array(await Bun.file(recipe.input.get()).arrayBuffer());
36
38
  if (bytes.length < EncryptionIV.LENGTH + 1) throw new Error(EncryptionBunAdapterError.InvalidPayload);
@@ -3,15 +3,17 @@ import type { GenericEventSchema } from "./event.types";
3
3
  import type { LoggerPort } from "./logger.port";
4
4
  import { formatError } from "./logger-format-error.service";
5
5
 
6
+ type Dependencies = { Logger: LoggerPort };
7
+
6
8
  export class EventHandler {
7
- constructor(private readonly logger: LoggerPort) {}
9
+ constructor(private readonly deps: Dependencies) {}
8
10
 
9
11
  handle<T extends { name: z.infer<GenericEventSchema["shape"]["name"]> }>(fn: (event: T) => Promise<void>) {
10
12
  return async (event: T) => {
11
13
  try {
12
14
  await fn(event);
13
15
  } catch (error) {
14
- this.logger.error({
16
+ this.deps.Logger.error({
15
17
  message: `Unknown ${event.name} event handler error`,
16
18
  component: "infra",
17
19
  operation: "unknown_event_handler_error",
@@ -1,9 +1,11 @@
1
1
  import type { LoggerPort } from "./logger.port";
2
2
 
3
+ type Dependencies = { Logger: LoggerPort };
4
+
3
5
  export class EventLogger {
4
6
  private readonly base = { component: "infra", operation: "event_emitted" };
5
7
 
6
- constructor(private readonly logger: LoggerPort) {}
8
+ constructor(private readonly deps: Dependencies) {}
7
9
 
8
10
  private _handle(
9
11
  type: string,
@@ -14,7 +16,7 @@ export class EventLogger {
14
16
  if (type === "subscribe") return;
15
17
  if (typeof eventName === "symbol") return;
16
18
 
17
- this.logger.info({ message: `${eventName} emitted`, metadata: eventData, ...this.base });
19
+ this.deps.Logger.info({ message: `${eventName} emitted`, metadata: eventData, ...this.base });
18
20
  }
19
21
 
20
22
  handle = this._handle.bind(this);
@@ -4,12 +4,14 @@ import { formatError } from "./logger-format-error.service";
4
4
 
5
5
  type ServerType = ReturnType<typeof Bun.serve>;
6
6
 
7
+ type Dependencies = { Logger: LoggerPort };
8
+
7
9
  export class GracefulShutdown {
8
10
  private readonly base = { operation: "shutdown", component: "infra" } as const;
9
11
  private isShuttingDown = false;
10
12
 
11
13
  constructor(
12
- private readonly logger: LoggerPort,
14
+ private readonly deps: Dependencies,
13
15
  private readonly exitFn: (code: number) => never = ((code: number) => process.exit(code)) as never,
14
16
  ) {}
15
17
 
@@ -20,36 +22,44 @@ export class GracefulShutdown {
20
22
  try {
21
23
  server.stop();
22
24
  } catch (error) {
23
- this.logger.error({ message: "Server stop failed", error: formatError(error), ...this.base });
25
+ this.deps.Logger.error({ message: "Server stop failed", error: formatError(error), ...this.base });
24
26
  }
25
27
 
26
28
  Promise.resolve()
27
29
  .then(() => cleanup())
28
- .then(() => this.logger.info({ message: "HTTP server closed", ...this.base }))
30
+ .then(() => this.deps.Logger.info({ message: "HTTP server closed", ...this.base }))
29
31
  .catch((error) =>
30
- this.logger.error({ message: "Cleanup hook failed", error: formatError(error), ...this.base }),
32
+ this.deps.Logger.error({ message: "Cleanup hook failed", error: formatError(error), ...this.base }),
31
33
  )
32
34
  .finally(() => this.exitFn(exitCode));
33
35
  }
34
36
 
35
37
  applyTo(server: ServerType, cleanup: () => any = tools.noop) {
36
38
  process.once("SIGTERM", () => {
37
- this.logger.info({ message: "SIGTERM received", ...this.base });
39
+ this.deps.Logger.info({ message: "SIGTERM received", ...this.base });
38
40
  this.shutdown(server, cleanup, 0);
39
41
  });
40
42
 
41
43
  process.once("SIGINT", () => {
42
- this.logger.info({ message: "SIGINT received", ...this.base });
44
+ this.deps.Logger.info({ message: "SIGINT received", ...this.base });
43
45
  this.shutdown(server, cleanup, 0);
44
46
  });
45
47
 
46
48
  process.once("unhandledRejection", (reason) => {
47
- this.logger.error({ message: "UnhandledRejection received", error: formatError(reason), ...this.base });
49
+ this.deps.Logger.error({
50
+ message: "UnhandledRejection received",
51
+ error: formatError(reason),
52
+ ...this.base,
53
+ });
48
54
  this.shutdown(server, cleanup, 1);
49
55
  });
50
56
 
51
57
  process.once("uncaughtException", (error) => {
52
- this.logger.error({ message: "UncaughtException received", error: formatError(error), ...this.base });
58
+ this.deps.Logger.error({
59
+ message: "UncaughtException received",
60
+ error: formatError(error),
61
+ ...this.base,
62
+ });
53
63
  this.shutdown(server, cleanup, 1);
54
64
  });
55
65
  }
package/src/index.ts CHANGED
@@ -23,6 +23,9 @@ export * from "./clock-system.adapter";
23
23
  export * from "./command.types";
24
24
  export * from "./command-envelope";
25
25
  export * from "./command-logger.service";
26
+ export * from "./content-hash.port";
27
+ export * from "./content-hash-noop.adapter";
28
+ export * from "./content-hash-sha256-bun.adapter";
26
29
  export * from "./context.middleware";
27
30
  export * from "./correlation-id.vo";
28
31
  export * from "./correlation-storage.service";
@@ -124,6 +127,9 @@ export * from "./mailer.vo";
124
127
  export * from "./mailer-noop.adapter";
125
128
  export * from "./mailer-smtp.adapter";
126
129
  export * from "./mailer-smtp-with-logger.adapter";
130
+ export * from "./markdown-generator.port";
131
+ export * from "./markdown-generator-marked.adapter";
132
+ export * from "./markdown-generator-noop.adapter";
127
133
  export * from "./memory-consumption.service";
128
134
  export * as History from "./modules/history";
129
135
  export * as Preferences from "./modules/preferences";
@@ -9,6 +9,7 @@ import { formatError } from "./logger-format-error.service";
9
9
  export type JobNameType = string;
10
10
  export type MultipleJobsType = Record<JobNameType, Cron>;
11
11
  export type JobProcessorType = { cron: string; label: JobNameType; process: () => Promise<void> };
12
+
12
13
  type Dependencies = { Logger: LoggerPort; IdProvider: IdProviderPort; Clock: ClockPort };
13
14
 
14
15
  export class Jobs {
@@ -5,28 +5,29 @@ import type { MailerPort } from "./mailer.port";
5
5
  import type { MailerSmtpAdapter } from "./mailer-smtp.adapter";
6
6
 
7
7
  type MailerSendOptionsType = SendMailOptions;
8
- type SmtpMailerWithLoggerConfigType = { smtpMailer: MailerSmtpAdapter; logger: LoggerPort };
8
+
9
+ type Dependencies = { MailerSmtp: MailerSmtpAdapter; Logger: LoggerPort };
9
10
 
10
11
  export class MailerSmtpWithLoggerAdapter implements MailerPort {
11
12
  private readonly base = { component: "infra", operation: "mailer" };
12
13
 
13
- constructor(private readonly config: SmtpMailerWithLoggerConfigType) {}
14
+ constructor(private readonly deps: Dependencies) {}
14
15
 
15
16
  async send(message: MailerSendOptionsType): Promise<unknown> {
16
17
  try {
17
- this.config.logger.info({ message: "Mailer attempt", metadata: message, ...this.base });
18
- const result = await this.config.smtpMailer.send(message);
19
- this.config.logger.info({ message: "Mailer success", metadata: { message, result }, ...this.base });
18
+ this.deps.Logger.info({ message: "Mailer attempt", metadata: message, ...this.base });
19
+ const result = await this.deps.MailerSmtp.send(message);
20
+ this.deps.Logger.info({ message: "Mailer success", metadata: { message, result }, ...this.base });
20
21
 
21
22
  return result;
22
23
  } catch (error) {
23
- this.config.logger.error({ message: "Mailer error", error: formatError(error), ...this.base });
24
+ this.deps.Logger.error({ message: "Mailer error", error: formatError(error), ...this.base });
24
25
 
25
26
  throw error;
26
27
  }
27
28
  }
28
29
 
29
30
  async verify() {
30
- return this.config.smtpMailer.verify();
31
+ return this.deps.MailerSmtp.verify();
31
32
  }
32
33
  }
@@ -0,0 +1,15 @@
1
+ import DOMPurify from "isomorphic-dompurify";
2
+ import { Marked } from "marked";
3
+ import type { MarkdownGeneratorPort } from "./markdown-generator.port";
4
+
5
+ export class MarkdownGeneratorMarkedAdapter implements MarkdownGeneratorPort {
6
+ private readonly instance: Marked;
7
+
8
+ constructor() {
9
+ this.instance = new Marked();
10
+ }
11
+
12
+ async generate(content: string) {
13
+ return DOMPurify.sanitize(await this.instance.parse(content));
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ import type { MarkdownGeneratorPort } from "./markdown-generator.port";
2
+
3
+ export class MarkdownGeneratorNoopAdapter implements MarkdownGeneratorPort {
4
+ async generate(_content: string) {
5
+ return "<p>noop</p>";
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ export abstract class MarkdownGeneratorPort {
2
+ abstract generate(input: string): Promise<string>;
3
+ }
@@ -4,13 +4,19 @@ import type { PdfGeneratorPort, PdfGeneratorTemplateType } from "./pdf-generator
4
4
  export const PLACEHOLDER_PDF_BASE64 =
5
5
  "JVBERi0xLjQKMSAwIG9iago8PC9UeXBlIC9DYXRhbG9nCi9QYWdlcyAyIDAgUgo+PgplbmRvYmoK MiAwIG9iago8PC9UeXBlIC9QYWdlcwovS2lkcyBbMyAwIFJdCi9Db3VudCAxCj4+CmVuZG9iagoz IDAgb2JqCjw8L1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA1OTUgODQy XQovQ29udGVudHMgNSAwIFIKL1Jlc291cmNlcyA8PC9Qcm9jU2V0IFsvUERGIC9UZXh0XQovRm9u dCA8PC9GMSA0IDAgUj4+Cj4+Cj4+CmVuZG9iago0IDAgb2JqCjw8L1R5cGUgL0ZvbnQKL1N1YnR5 cGUgL1R5cGUxCi9OYW1lIC9GMQovQmFzZUZvbnQgL0hlbHZldGljYQovRW5jb2RpbmcgL01hY1Jv bWFuRW5jb2RpbmcKPj4KZW5kb2JqCjUgMCBvYmoKPDwvTGVuZ3RoIDUzCj4+CnN0cmVhbQpCVAov RjEgMjAgVGYKMjIwIDQwMCBUZAooRHVtbXkgUERGKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhy ZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZgowMDAwMDAwMDA5IDAwMDAwIG4KMDAwMDAwMDA2MyAw MDAwMCBuCjAwMDAwMDAxMjQgMDAwMDAgbgowMDAwMDAwMjc3IDAwMDAwIG4KMDAwMDAwMDM5MiAw MDAwMCBuCnRyYWlsZXIKPDwvU2l6ZSA2Ci9Sb290IDEgMCBSCj4+CnN0YXJ0eHJlZgo0OTUKJSVF T0YK";
6
6
 
7
+ type Dependencies = { Logger: LoggerPort };
8
+
7
9
  export class PdfGeneratorNoopAdapter implements PdfGeneratorPort {
8
10
  private readonly base = { component: "infra", operation: "pdf_generator" };
9
11
 
10
- constructor(private readonly logger: LoggerPort) {}
12
+ constructor(private readonly deps: Dependencies) {}
11
13
 
12
14
  async request(template: PdfGeneratorTemplateType, data: Record<string, unknown>) {
13
- this.logger.info({ message: "[NOOP] PDF generator adapter", metadata: { template, data }, ...this.base });
15
+ this.deps.Logger.info({
16
+ message: "[NOOP] PDF generator adapter",
17
+ metadata: { template, data },
18
+ ...this.base,
19
+ });
14
20
 
15
21
  return Buffer.from(PLACEHOLDER_PDF_BASE64, "base64");
16
22
  }
@@ -11,13 +11,13 @@ export class PrerequisiteClockDrift implements prereqs.Prerequisite {
11
11
  readonly enabled?: boolean = true;
12
12
 
13
13
  readonly skew: tools.Duration;
14
- readonly timekeeper: TimekeeperPort;
14
+ readonly Timekeeper: TimekeeperPort;
15
15
  readonly timeout: tools.Duration;
16
16
 
17
17
  constructor(
18
18
  config: prereqs.PrerequisiteConfigType & {
19
19
  skew: tools.Duration;
20
- timekeeper?: TimekeeperPort;
20
+ Timekeeper?: TimekeeperPort;
21
21
  timeout?: tools.Duration;
22
22
  },
23
23
  ) {
@@ -25,7 +25,7 @@ export class PrerequisiteClockDrift implements prereqs.Prerequisite {
25
25
  this.enabled = config.enabled === undefined ? true : config.enabled;
26
26
 
27
27
  this.skew = config.skew;
28
- this.timekeeper = config.timekeeper ?? new TimekeeperGoogleAdapter();
28
+ this.Timekeeper = config.Timekeeper ?? new TimekeeperGoogleAdapter();
29
29
  this.timeout = config.timeout ?? tools.Duration.Seconds(2);
30
30
  }
31
31
 
@@ -36,7 +36,7 @@ export class PrerequisiteClockDrift implements prereqs.Prerequisite {
36
36
 
37
37
  try {
38
38
  const timestamp = await Timeout.cancellable(
39
- (signal: AbortSignal) => this.timekeeper.get(signal),
39
+ (signal: AbortSignal) => this.Timekeeper.get(signal),
40
40
  this.timeout,
41
41
  );
42
42
  if (!timestamp) return prereqs.Verification.undetermined(stopwatch.stop());
@@ -0,0 +1,36 @@
1
+ import dns from "dns/promises";
2
+ import * as tools from "@bgord/tools";
3
+ import type { ClockPort } from "../clock.port";
4
+ import * as prereqs from "../prerequisites.service";
5
+ import { Timeout } from "../timeout.service";
6
+
7
+ export class PrerequisiteDNS implements prereqs.Prerequisite {
8
+ readonly kind = "dns";
9
+ readonly label: prereqs.PrerequisiteLabelType;
10
+ readonly enabled?: boolean = true;
11
+
12
+ private readonly hostname: string;
13
+ readonly timeout: tools.Duration;
14
+
15
+ constructor(config: prereqs.PrerequisiteConfigType & { hostname: string; timeout?: tools.Duration }) {
16
+ this.label = config.label;
17
+ this.enabled = config.enabled === undefined ? true : config.enabled;
18
+
19
+ this.hostname = config.hostname;
20
+ this.timeout = config.timeout ?? tools.Duration.Seconds(1);
21
+ }
22
+
23
+ async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
24
+ const stopwatch = new tools.Stopwatch(clock.now());
25
+
26
+ if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
27
+
28
+ try {
29
+ await Timeout.run(dns.lookup(this.hostname), this.timeout);
30
+
31
+ return prereqs.Verification.success(stopwatch.stop());
32
+ } catch (error) {
33
+ return prereqs.Verification.failure(stopwatch.stop(), error as Error);
34
+ }
35
+ }
36
+ }
@@ -3,6 +3,7 @@ export * from "./bun";
3
3
  export * from "./clock-drift";
4
4
  export * from "./dependency-vulnerabilities";
5
5
  export * from "./directory";
6
+ export * from "./dns";
6
7
  export * from "./external-api";
7
8
  export * from "./jobs";
8
9
  export * from "./log-file";
@@ -8,13 +8,13 @@ export class PrerequisiteLogFile implements prereqs.Prerequisite {
8
8
  readonly label: prereqs.PrerequisiteLabelType;
9
9
  readonly enabled?: boolean = true;
10
10
 
11
- private readonly logger: LoggerWinstonProductionAdapter;
11
+ private readonly Logger: LoggerWinstonProductionAdapter;
12
12
 
13
- constructor(config: prereqs.PrerequisiteConfigType & { logger: LoggerWinstonProductionAdapter }) {
13
+ constructor(config: prereqs.PrerequisiteConfigType & { Logger: LoggerWinstonProductionAdapter }) {
14
14
  this.label = config.label;
15
15
  this.enabled = config.enabled === undefined ? true : config.enabled;
16
16
 
17
- this.logger = config.logger;
17
+ this.Logger = config.Logger;
18
18
  }
19
19
 
20
20
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -23,7 +23,7 @@ export class PrerequisiteLogFile implements prereqs.Prerequisite {
23
23
  if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
24
24
 
25
25
  try {
26
- const path = this.logger.prodLogFile;
26
+ const path = this.Logger.prodLogFile;
27
27
  const result = await Bun.file(path).exists();
28
28
 
29
29
  if (result) return prereqs.Verification.success(stopwatch.stop());
@@ -9,14 +9,14 @@ export class PrerequisiteMailer implements prereqs.Prerequisite {
9
9
  readonly label: prereqs.PrerequisiteLabelType;
10
10
  readonly enabled?: boolean = true;
11
11
 
12
- private readonly mailer: MailerPort;
12
+ private readonly Mailer: MailerPort;
13
13
  readonly timeout: tools.Duration;
14
14
 
15
- constructor(config: prereqs.PrerequisiteConfigType & { mailer: MailerPort; timeout?: tools.Duration }) {
15
+ constructor(config: prereqs.PrerequisiteConfigType & { Mailer: MailerPort; timeout?: tools.Duration }) {
16
16
  this.label = config.label;
17
17
  this.enabled = config.enabled === undefined ? true : config.enabled;
18
18
 
19
- this.mailer = config.mailer;
19
+ this.Mailer = config.Mailer;
20
20
  this.timeout = config.timeout ?? tools.Duration.Seconds(2);
21
21
  }
22
22
 
@@ -26,7 +26,7 @@ export class PrerequisiteMailer implements prereqs.Prerequisite {
26
26
  if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
27
27
 
28
28
  try {
29
- await Timeout.run(this.mailer.verify(), this.timeout);
29
+ await Timeout.run(this.Mailer.verify(), this.timeout);
30
30
  return prereqs.Verification.success(stopwatch.stop());
31
31
  } catch (error) {
32
32
  return prereqs.Verification.failure(stopwatch.stop(), error as Error);
@@ -11,16 +11,16 @@ export class PrerequisiteSpace implements prereqs.Prerequisite {
11
11
  readonly enabled?: boolean = true;
12
12
 
13
13
  private readonly minimum: tools.Size;
14
- private readonly checker: DiskSpaceCheckerPort;
14
+ private readonly DiskSpaceChecker: DiskSpaceCheckerPort;
15
15
 
16
16
  constructor(
17
- config: prereqs.PrerequisiteConfigType & { minimum: tools.Size; checker?: DiskSpaceCheckerPort },
17
+ config: prereqs.PrerequisiteConfigType & { minimum: tools.Size; DiskSpaceChecker?: DiskSpaceCheckerPort },
18
18
  ) {
19
19
  this.label = config.label;
20
20
  this.enabled = config.enabled === undefined ? true : config.enabled;
21
21
 
22
22
  this.minimum = config.minimum;
23
- this.checker = config.checker ?? new DiskSpaceCheckerBunAdapter();
23
+ this.DiskSpaceChecker = config.DiskSpaceChecker ?? new DiskSpaceCheckerBunAdapter();
24
24
  }
25
25
 
26
26
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -30,7 +30,7 @@ export class PrerequisiteSpace implements prereqs.Prerequisite {
30
30
 
31
31
  try {
32
32
  const root = path.sep;
33
- const freeDiskSpace = await this.checker.get(root);
33
+ const freeDiskSpace = await this.DiskSpaceChecker.get(root);
34
34
 
35
35
  if (freeDiskSpace.isGreaterThan(this.minimum)) return prereqs.Verification.success(stopwatch.stop());
36
36
  return prereqs.Verification.failure(stopwatch.stop(), {
@@ -10,13 +10,13 @@ export class PrerequisiteSSLCertificateExpiry implements prereqs.Prerequisite {
10
10
 
11
11
  private readonly host: string;
12
12
  private readonly days: number;
13
- private readonly inspector: CertificateInspectorPort;
13
+ private readonly CertificateInspector: CertificateInspectorPort;
14
14
 
15
15
  constructor(
16
16
  config: prereqs.PrerequisiteConfigType & {
17
17
  host: string;
18
18
  days: number;
19
- inspector: CertificateInspectorPort;
19
+ CertificateInspector: CertificateInspectorPort;
20
20
  },
21
21
  ) {
22
22
  this.label = config.label;
@@ -24,7 +24,7 @@ export class PrerequisiteSSLCertificateExpiry implements prereqs.Prerequisite {
24
24
 
25
25
  this.host = config.host;
26
26
  this.days = config.days;
27
- this.inspector = config.inspector;
27
+ this.CertificateInspector = config.CertificateInspector;
28
28
  }
29
29
 
30
30
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -32,7 +32,7 @@ export class PrerequisiteSSLCertificateExpiry implements prereqs.Prerequisite {
32
32
 
33
33
  if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
34
34
 
35
- const result = await this.inspector.inspect(this.host);
35
+ const result = await this.CertificateInspector.inspect(this.host);
36
36
 
37
37
  if (!result.success)
38
38
  return prereqs.Verification.failure(stopwatch.stop(), { message: "Certificate unavailable" });
@@ -23,15 +23,15 @@ export class PrerequisiteTranslations implements prereqs.Prerequisite {
23
23
  private readonly translationsPath?: typeof I18n.DEFAULT_TRANSLATIONS_PATH;
24
24
  private readonly supportedLanguages: types.I18nConfigType["supportedLanguages"];
25
25
 
26
- private readonly logger: LoggerPort;
27
- private readonly jsonFileReader: JsonFileReaderPort;
26
+ private readonly Logger: LoggerPort;
27
+ private readonly JsonFileReader: JsonFileReaderPort;
28
28
 
29
29
  constructor(
30
30
  config: prereqs.PrerequisiteConfigType & {
31
31
  translationsPath?: typeof I18n.DEFAULT_TRANSLATIONS_PATH;
32
32
  supportedLanguages: types.I18nConfigType["supportedLanguages"];
33
- logger: LoggerPort;
34
- jsonFileReader?: JsonFileReaderPort;
33
+ Logger: LoggerPort;
34
+ JsonFileReader?: JsonFileReaderPort;
35
35
  },
36
36
  ) {
37
37
  this.label = config.label;
@@ -40,8 +40,8 @@ export class PrerequisiteTranslations implements prereqs.Prerequisite {
40
40
  this.translationsPath = config.translationsPath;
41
41
  this.supportedLanguages = config.supportedLanguages;
42
42
 
43
- this.logger = config.logger;
44
- this.jsonFileReader = config.jsonFileReader ?? new JsonFileReaderBunForgivingAdapter();
43
+ this.Logger = config.Logger;
44
+ this.JsonFileReader = config.JsonFileReader ?? new JsonFileReaderBunForgivingAdapter();
45
45
  }
46
46
 
47
47
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -52,7 +52,7 @@ export class PrerequisiteTranslations implements prereqs.Prerequisite {
52
52
  const translationsPath = this.translationsPath ?? I18n.DEFAULT_TRANSLATIONS_PATH;
53
53
 
54
54
  const supportedLanguages = Object.keys(this.supportedLanguages);
55
- const i18n = new I18n({ Logger: this.logger, JsonFileReader: this.jsonFileReader });
55
+ const i18n = new I18n({ Logger: this.Logger, JsonFileReader: this.JsonFileReader });
56
56
 
57
57
  try {
58
58
  await fsp.access(translationsPath, constants.R_OK);