@bgord/bun 1.1.6 → 1.2.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 (56) hide show
  1. package/dist/checksum.service.d.ts +9 -0
  2. package/dist/checksum.service.d.ts.map +1 -0
  3. package/dist/checksum.service.js +20 -0
  4. package/dist/checksum.service.js.map +1 -0
  5. package/dist/correlation-storage.service.d.ts +1 -1
  6. package/dist/correlation-storage.service.d.ts.map +1 -1
  7. package/dist/correlation-storage.service.js +2 -2
  8. package/dist/correlation-storage.service.js.map +1 -1
  9. package/dist/healthcheck.service.d.ts +4 -16
  10. package/dist/healthcheck.service.d.ts.map +1 -1
  11. package/dist/healthcheck.service.js +2 -1
  12. package/dist/healthcheck.service.js.map +1 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/prerequisites/clock-drift.d.ts +2 -2
  18. package/dist/prerequisites/clock-drift.d.ts.map +1 -1
  19. package/dist/prerequisites/clock-drift.js +14 -9
  20. package/dist/prerequisites/clock-drift.js.map +1 -1
  21. package/dist/prerequisites/mailer.d.ts +3 -0
  22. package/dist/prerequisites/mailer.d.ts.map +1 -1
  23. package/dist/prerequisites/mailer.js +4 -1
  24. package/dist/prerequisites/mailer.js.map +1 -1
  25. package/dist/prerequisites/outside-connectivity.d.ts +5 -1
  26. package/dist/prerequisites/outside-connectivity.d.ts.map +1 -1
  27. package/dist/prerequisites/outside-connectivity.js +4 -1
  28. package/dist/prerequisites/outside-connectivity.js.map +1 -1
  29. package/dist/prerequisites.service.d.ts +3 -3
  30. package/dist/prerequisites.service.d.ts.map +1 -1
  31. package/dist/prerequisites.service.js +3 -3
  32. package/dist/prerequisites.service.js.map +1 -1
  33. package/dist/timekeeper-google.adapter.d.ts +2 -1
  34. package/dist/timekeeper-google.adapter.d.ts.map +1 -1
  35. package/dist/timekeeper-google.adapter.js +3 -2
  36. package/dist/timekeeper-google.adapter.js.map +1 -1
  37. package/dist/timekeeper.port.d.ts +1 -1
  38. package/dist/timekeeper.port.d.ts.map +1 -1
  39. package/dist/timeout.service.d.ts +11 -0
  40. package/dist/timeout.service.d.ts.map +1 -0
  41. package/dist/timeout.service.js +46 -0
  42. package/dist/timeout.service.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +2 -2
  45. package/readme.md +2 -0
  46. package/src/checksum.service.ts +23 -0
  47. package/src/correlation-storage.service.ts +2 -2
  48. package/src/healthcheck.service.ts +4 -2
  49. package/src/index.ts +2 -0
  50. package/src/prerequisites/clock-drift.ts +16 -10
  51. package/src/prerequisites/mailer.ts +5 -2
  52. package/src/prerequisites/outside-connectivity.ts +9 -2
  53. package/src/prerequisites.service.ts +6 -6
  54. package/src/timekeeper-google.adapter.ts +4 -2
  55. package/src/timekeeper.port.ts +1 -1
  56. package/src/timeout.service.ts +71 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/bun",
3
- "version": "1.1.6",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": "Bartosz Gordon",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@axiomhq/winston": "1.3.1",
41
- "@bgord/tools": "1.1.5",
41
+ "@bgord/tools": "1.1.6",
42
42
  "@hono/ua-blocker": "0.1.15",
43
43
  "better-auth": "1.3.34",
44
44
  "croner": "9.1.0",
package/readme.md CHANGED
@@ -41,6 +41,7 @@ src/
41
41
  ├── certificate-inspector-noop.adapter.ts
42
42
  ├── certificate-inspector-tls.adapter.ts
43
43
  ├── certificate-inspector.port.ts
44
+ ├── checksum.service.ts
44
45
  ├── client-from-hono.adapter.ts
45
46
  ├── client.vo.ts
46
47
  ├── clock-fixed.adapter.ts
@@ -232,6 +233,7 @@ src/
232
233
  ├── timekeeper-google.adapter.ts
233
234
  ├── timekeeper-noop.adapter.ts
234
235
  ├── timekeeper.port.ts
236
+ ├── timeout.service.ts
235
237
  ├── to-event-map.types.ts
236
238
  ├── translations.service.ts
237
239
  ├── uptime.service.ts
@@ -0,0 +1,23 @@
1
+ import type { FileHashResult } from "./file-hash.port";
2
+
3
+ export enum ChecksumStrategy {
4
+ etag = "etag",
5
+ complex = "complex",
6
+ }
7
+
8
+ export class Checksum {
9
+ static compare(first: FileHashResult, second: FileHashResult, strategy: ChecksumStrategy): boolean {
10
+ switch (strategy) {
11
+ case ChecksumStrategy.etag:
12
+ return first.etag === second.etag;
13
+ case ChecksumStrategy.complex:
14
+ return (
15
+ first.etag === second.etag &&
16
+ first.size === second.size &&
17
+ first.lastModified &&
18
+ second.lastModified &&
19
+ first.mime.isSatisfiedBy(second.mime)
20
+ );
21
+ }
22
+ }
23
+ }
@@ -15,8 +15,8 @@ export class CorrelationStorage {
15
15
  ] ??=
16
16
  new AsyncLocalStorage<CorrelationContext>());
17
17
 
18
- static run<T>(correlationId: CorrelationIdType, fn: () => T | Promise<T>): T | Promise<T> {
19
- return CorrelationStorage.als.run({ correlationId }, fn);
18
+ static run<T>(correlationId: CorrelationIdType, action: () => T | Promise<T>): T | Promise<T> {
19
+ return CorrelationStorage.als.run({ correlationId }, action);
20
20
  }
21
21
 
22
22
  static get(): CorrelationIdType {
@@ -14,7 +14,7 @@ type HealthcheckResultType = {
14
14
  ok: prereqs.PrerequisiteStatusEnum;
15
15
  version: string;
16
16
  details: { label: prereqs.PrerequisiteLabelType; outcome: prereqs.VerifyOutcome }[];
17
- uptime: UptimeResultType;
17
+ uptime: Omit<UptimeResultType, "duration"> & { durationMs: tools.DurationMsType };
18
18
  memory: { bytes: tools.Size["bytes"]; formatted: ReturnType<tools.Size["format"]> };
19
19
  durationMs: tools.Duration["ms"];
20
20
  };
@@ -40,11 +40,13 @@ export class Healthcheck {
40
40
 
41
41
  const code = ok === prereqs.PrerequisiteStatusEnum.success ? 200 : 424;
42
42
 
43
+ const uptime = Uptime.get(deps.Clock);
44
+
43
45
  const result: HealthcheckResultType = {
44
46
  ok,
45
47
  details,
46
48
  version: buildInfo.BUILD_VERSION ?? "unknown",
47
- uptime: Uptime.get(deps.Clock),
49
+ uptime: { durationMs: uptime.duration.ms, formatted: uptime.formatted },
48
50
  memory: {
49
51
  bytes: MemoryConsumption.get().toBytes(),
50
52
  formatted: MemoryConsumption.get().format(tools.Size.unit.MB),
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./cache-response.middleware";
14
14
  export * from "./certificate-inspector.port";
15
15
  export * from "./certificate-inspector-noop.adapter";
16
16
  export * from "./certificate-inspector-tls.adapter";
17
+ export * from "./checksum.service";
17
18
  export * from "./client.vo";
18
19
  export * from "./client-from-hono.adapter";
19
20
  export * from "./clock.port";
@@ -155,6 +156,7 @@ export * from "./time-zone-offset.middleware";
155
156
  export * from "./timekeeper.port";
156
157
  export * from "./timekeeper-google.adapter";
157
158
  export * from "./timekeeper-noop.adapter";
159
+ export * from "./timeout.service";
158
160
  export * from "./to-event-map.types";
159
161
  export * from "./translations.service";
160
162
  export * from "./uptime.service";
@@ -2,6 +2,7 @@ import * as tools from "@bgord/tools";
2
2
  import type { ClockPort } from "../clock.port";
3
3
  import * as prereqs from "../prerequisites.service";
4
4
  import type { TimekeeperPort } from "../timekeeper.port";
5
+ import { Timeout } from "../timeout.service";
5
6
 
6
7
  export class PrerequisiteClockDrift implements prereqs.Prerequisite {
7
8
  readonly kind = "clock-drift";
@@ -10,13 +11,13 @@ export class PrerequisiteClockDrift implements prereqs.Prerequisite {
10
11
 
11
12
  readonly skew: tools.Duration;
12
13
  readonly timekeeper: TimekeeperPort;
13
- readonly clock: ClockPort;
14
+ readonly timeout: tools.Duration;
14
15
 
15
16
  constructor(
16
17
  config: prereqs.PrerequisiteConfigType & {
17
18
  skew: tools.Duration;
18
19
  timekeeper: TimekeeperPort;
19
- clock: ClockPort;
20
+ timeout?: tools.Duration;
20
21
  },
21
22
  ) {
22
23
  this.label = config.label;
@@ -24,7 +25,7 @@ export class PrerequisiteClockDrift implements prereqs.Prerequisite {
24
25
 
25
26
  this.skew = config.skew;
26
27
  this.timekeeper = config.timekeeper;
27
- this.clock = config.clock;
28
+ this.timeout = config.timeout ?? tools.Duration.Seconds(2);
28
29
  }
29
30
 
30
31
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -32,14 +33,19 @@ export class PrerequisiteClockDrift implements prereqs.Prerequisite {
32
33
 
33
34
  if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
34
35
 
35
- const now = this.clock.now();
36
+ try {
37
+ const timestamp = await Timeout.cancellable(
38
+ (signal: AbortSignal) => this.timekeeper.get(signal),
39
+ this.timeout,
40
+ );
41
+ if (!timestamp) return prereqs.Verification.undetermined(stopwatch.stop());
36
42
 
37
- const timestamp = await this.timekeeper.get();
38
- if (!timestamp) return prereqs.Verification.undetermined(stopwatch.stop());
43
+ const duration = clock.now().difference(timestamp).toAbsolute();
39
44
 
40
- const duration = now.difference(timestamp).toAbolute();
41
-
42
- if (duration.isShorterThan(this.skew)) return prereqs.Verification.success(stopwatch.stop());
43
- return prereqs.Verification.failure(stopwatch.stop(), { message: `Difference: ${duration.seconds}s` });
45
+ if (duration.isShorterThan(this.skew)) return prereqs.Verification.success(stopwatch.stop());
46
+ return prereqs.Verification.failure(stopwatch.stop(), { message: `Difference: ${duration.seconds}s` });
47
+ } catch (error) {
48
+ return prereqs.Verification.undetermined(stopwatch.stop());
49
+ }
44
50
  }
45
51
  }
@@ -2,6 +2,7 @@ import * as tools from "@bgord/tools";
2
2
  import type { ClockPort } from "../clock.port";
3
3
  import type { MailerPort } from "../mailer.port";
4
4
  import * as prereqs from "../prerequisites.service";
5
+ import { Timeout } from "../timeout.service";
5
6
 
6
7
  export class PrerequisiteMailer implements prereqs.Prerequisite {
7
8
  readonly kind = "mailer";
@@ -9,12 +10,14 @@ export class PrerequisiteMailer implements prereqs.Prerequisite {
9
10
  readonly enabled?: boolean = true;
10
11
 
11
12
  private readonly mailer: MailerPort;
13
+ readonly timeout: tools.Duration;
12
14
 
13
- constructor(config: prereqs.PrerequisiteConfigType & { mailer: MailerPort }) {
15
+ constructor(config: prereqs.PrerequisiteConfigType & { mailer: MailerPort; timeout?: tools.Duration }) {
14
16
  this.label = config.label;
15
17
  this.enabled = config.enabled === undefined ? true : config.enabled;
16
18
 
17
19
  this.mailer = config.mailer;
20
+ this.timeout = config.timeout ?? tools.Duration.Seconds(2);
18
21
  }
19
22
 
20
23
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -23,7 +26,7 @@ export class PrerequisiteMailer implements prereqs.Prerequisite {
23
26
  if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
24
27
 
25
28
  try {
26
- await this.mailer.verify();
29
+ await Timeout.run(this.mailer.verify(), this.timeout);
27
30
  return prereqs.Verification.success(stopwatch.stop());
28
31
  } catch (error) {
29
32
  return prereqs.Verification.failure(stopwatch.stop(), error as Error);
@@ -1,6 +1,7 @@
1
1
  import * as tools from "@bgord/tools";
2
2
  import type { ClockPort } from "../clock.port";
3
3
  import * as prereqs from "../prerequisites.service";
4
+ import { Timeout } from "../timeout.service";
4
5
 
5
6
  export class PrerequisiteOutsideConnectivity implements prereqs.Prerequisite {
6
7
  readonly kind = "outside-connectivity";
@@ -8,10 +9,13 @@ export class PrerequisiteOutsideConnectivity implements prereqs.Prerequisite {
8
9
  readonly enabled?: boolean = true;
9
10
 
10
11
  private readonly url = "https://google.com";
12
+ readonly timeout: tools.Duration;
11
13
 
12
- constructor(config: prereqs.PrerequisiteConfigType) {
14
+ constructor(config: prereqs.PrerequisiteConfigType & { timeout?: tools.Duration }) {
13
15
  this.label = config.label;
14
16
  this.enabled = config.enabled === undefined ? true : config.enabled;
17
+
18
+ this.timeout = config.timeout ?? tools.Duration.Seconds(2);
15
19
  }
16
20
 
17
21
  async verify(clock: ClockPort): Promise<prereqs.VerifyOutcome> {
@@ -20,7 +24,10 @@ export class PrerequisiteOutsideConnectivity implements prereqs.Prerequisite {
20
24
  try {
21
25
  if (!this.enabled) return prereqs.Verification.undetermined(stopwatch.stop());
22
26
 
23
- const response = await fetch(this.url, { method: "HEAD" });
27
+ const response = await Timeout.cancellable(
28
+ (signal: AbortSignal) => fetch(this.url, { method: "HEAD", signal }),
29
+ this.timeout,
30
+ );
24
31
 
25
32
  if (response.ok) return prereqs.Verification.success(stopwatch.stop());
26
33
  return prereqs.Verification.failure(stopwatch.stop(), { message: `HTTP ${response.status}` });
@@ -11,15 +11,15 @@ export enum PrerequisiteStatusEnum {
11
11
  undetermined = "undetermined",
12
12
  }
13
13
 
14
- export type VerifySuccess = { status: PrerequisiteStatusEnum.success; duration: tools.DurationMsType };
14
+ export type VerifySuccess = { status: PrerequisiteStatusEnum.success; durationMs: tools.DurationMsType };
15
15
  export type VerifyFailure = {
16
16
  status: PrerequisiteStatusEnum.failure;
17
- duration: tools.DurationMsType;
17
+ durationMs: tools.DurationMsType;
18
18
  error?: ErrorInfo;
19
19
  };
20
20
  export type VerifyUndetermined = {
21
21
  status: PrerequisiteStatusEnum.undetermined;
22
- duration: tools.DurationMsType;
22
+ durationMs: tools.DurationMsType;
23
23
  };
24
24
  export type VerifyOutcome = VerifySuccess | VerifyFailure | VerifyUndetermined;
25
25
 
@@ -39,17 +39,17 @@ export type PrerequisiteResult = {
39
39
 
40
40
  export class Verification {
41
41
  static success(duration: tools.Duration): VerifySuccess {
42
- return { status: PrerequisiteStatusEnum.success, duration: duration.ms };
42
+ return { status: PrerequisiteStatusEnum.success, durationMs: duration.ms };
43
43
  }
44
44
  static failure(duration: tools.Duration, meta?: Error | ErrorInfo): VerifyFailure {
45
45
  return {
46
46
  status: PrerequisiteStatusEnum.failure,
47
- duration: duration.ms,
47
+ durationMs: duration.ms,
48
48
  error: meta instanceof Error ? formatError(meta) : meta,
49
49
  };
50
50
  }
51
51
  static undetermined(duration: tools.Duration): VerifyUndetermined {
52
- return { status: PrerequisiteStatusEnum.undetermined, duration: duration.ms };
52
+ return { status: PrerequisiteStatusEnum.undetermined, durationMs: duration.ms };
53
53
  }
54
54
  }
55
55
 
@@ -2,9 +2,11 @@ import * as tools from "@bgord/tools";
2
2
  import type { TimekeeperPort } from "./timekeeper.port";
3
3
 
4
4
  export class TimekeeperGoogleAdapter implements TimekeeperPort {
5
- async get() {
5
+ static URL = "https://www.google.com/generate_204";
6
+
7
+ async get(signal?: AbortSignal) {
6
8
  try {
7
- const response = await fetch("https://www.google.com/generate_204");
9
+ const response = await fetch(TimekeeperGoogleAdapter.URL, { signal });
8
10
  if (!response.ok) return null;
9
11
 
10
12
  const date = response.headers.get("Date");
@@ -1,5 +1,5 @@
1
1
  import type * as tools from "@bgord/tools";
2
2
 
3
3
  export interface TimekeeperPort {
4
- get(): Promise<tools.Timestamp | null>;
4
+ get(signal?: AbortSignal): Promise<tools.Timestamp | null>;
5
5
  }
@@ -0,0 +1,71 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { LoggerPort } from "../src/logger.port";
3
+
4
+ export const TimeoutError = { Exceeded: "timeout.exceeded" };
5
+
6
+ export class Timeout {
7
+ static async run<T>(action: Promise<T>, timeout: tools.Duration): Promise<T> {
8
+ return new Promise<T>((resolve, reject) => {
9
+ const reason = new Error(TimeoutError.Exceeded);
10
+
11
+ const canceller = setTimeout(() => reject(reason), timeout.ms);
12
+
13
+ action.then(
14
+ (value) => {
15
+ clearTimeout(canceller);
16
+ resolve(value);
17
+ },
18
+ (error) => {
19
+ clearTimeout(canceller);
20
+ reject(error);
21
+ },
22
+ );
23
+ });
24
+ }
25
+
26
+ static async monitor<T>(action: Promise<T>, timeout: tools.Duration, logger: LoggerPort): Promise<T> {
27
+ const monitor = setTimeout(
28
+ () =>
29
+ logger.warn({
30
+ message: "Timeout",
31
+ component: "infra",
32
+ operation: "timeout_monitor",
33
+ metadata: { timeoutMs: timeout.ms },
34
+ }),
35
+ timeout.ms,
36
+ );
37
+
38
+ return action.finally(() => clearTimeout(monitor));
39
+ }
40
+
41
+ static async cancellable<T>(
42
+ action: (signal: AbortSignal) => Promise<T>,
43
+ timeout: tools.Duration,
44
+ ): Promise<T> {
45
+ return new Promise<T>((resolve, reject) => {
46
+ const controller = new AbortController();
47
+
48
+ const reason = new Error(TimeoutError.Exceeded);
49
+
50
+ const canceller = setTimeout(() => {
51
+ controller.abort(reason);
52
+ reject(reason);
53
+ }, timeout.ms);
54
+
55
+ // Promise.resolve.then used to prevent the initial action(controller.signal) call
56
+ // from throwing before the resulting work-promise is run by promise.then.
57
+ const promise: Promise<T> = Promise.resolve().then(() => action(controller.signal));
58
+
59
+ promise.then(
60
+ (value) => {
61
+ clearTimeout(canceller);
62
+ resolve(value);
63
+ },
64
+ (error) => {
65
+ clearTimeout(canceller);
66
+ reject(error);
67
+ },
68
+ );
69
+ });
70
+ }
71
+ }