@bgord/bun 1.15.5 → 1.16.1

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 (185) hide show
  1. package/dist/antivirus-clamav.adapter.js +1 -1
  2. package/dist/antivirus-clamav.adapter.js.map +1 -1
  3. package/dist/api-version-hono.middleware.d.ts +3 -2
  4. package/dist/api-version-hono.middleware.d.ts.map +1 -1
  5. package/dist/api-version-hono.middleware.js.map +1 -1
  6. package/dist/api-version.middleware.d.ts +4 -3
  7. package/dist/api-version.middleware.d.ts.map +1 -1
  8. package/dist/api-version.middleware.js +1 -1
  9. package/dist/api-version.middleware.js.map +1 -1
  10. package/dist/build-info.vo.d.ts +11 -0
  11. package/dist/build-info.vo.d.ts.map +1 -0
  12. package/dist/build-info.vo.js +11 -0
  13. package/dist/build-info.vo.js.map +1 -0
  14. package/dist/error-normalizer.service.js +1 -1
  15. package/dist/error-normalizer.service.js.map +1 -1
  16. package/dist/event-envelope.d.ts +0 -7
  17. package/dist/event-envelope.d.ts.map +1 -1
  18. package/dist/event-envelope.js +2 -2
  19. package/dist/event-envelope.js.map +1 -1
  20. package/dist/event-serializer-collecting.adapter.d.ts +5 -4
  21. package/dist/event-serializer-collecting.adapter.d.ts.map +1 -1
  22. package/dist/event-serializer-collecting.adapter.js.map +1 -1
  23. package/dist/event-serializer-json.adapter.d.ts +3 -2
  24. package/dist/event-serializer-json.adapter.d.ts.map +1 -1
  25. package/dist/event-serializer-json.adapter.js.map +1 -1
  26. package/dist/event-serializer.port.d.ts +3 -2
  27. package/dist/event-serializer.port.d.ts.map +1 -1
  28. package/dist/event-store.adapter.d.ts +2 -0
  29. package/dist/event-store.adapter.d.ts.map +1 -1
  30. package/dist/event-store.adapter.js +1 -0
  31. package/dist/event-store.adapter.js.map +1 -1
  32. package/dist/event-upcaster-chain.adapter.d.ts +11 -0
  33. package/dist/event-upcaster-chain.adapter.d.ts.map +1 -0
  34. package/dist/event-upcaster-chain.adapter.js +31 -0
  35. package/dist/event-upcaster-chain.adapter.js.map +1 -0
  36. package/dist/event-upcaster-noop.adapter.d.ts +6 -0
  37. package/dist/event-upcaster-noop.adapter.d.ts.map +1 -0
  38. package/dist/event-upcaster-noop.adapter.js +6 -0
  39. package/dist/event-upcaster-noop.adapter.js.map +1 -0
  40. package/dist/event-upcaster-step.vo.d.ts +11 -0
  41. package/dist/event-upcaster-step.vo.d.ts.map +1 -0
  42. package/dist/event-upcaster-step.vo.js +10 -0
  43. package/dist/event-upcaster-step.vo.js.map +1 -0
  44. package/dist/event-upcaster.port.d.ts +5 -0
  45. package/dist/event-upcaster.port.d.ts.map +1 -0
  46. package/dist/event-upcaster.port.js +2 -0
  47. package/dist/event-upcaster.port.js.map +1 -0
  48. package/dist/event.types.d.ts +1 -1
  49. package/dist/event.types.d.ts.map +1 -1
  50. package/dist/healthcheck-hono.handler.d.ts +3 -2
  51. package/dist/healthcheck-hono.handler.d.ts.map +1 -1
  52. package/dist/healthcheck-hono.handler.js.map +1 -1
  53. package/dist/healthcheck.handler.d.ts +3 -2
  54. package/dist/healthcheck.handler.d.ts.map +1 -1
  55. package/dist/healthcheck.handler.js +6 -6
  56. package/dist/healthcheck.handler.js.map +1 -1
  57. package/dist/image-alpha-sharp.adapter.js +76 -14
  58. package/dist/image-alpha-sharp.adapter.js.map +1 -1
  59. package/dist/image-blur-sharp.adapter.js +72 -10
  60. package/dist/image-blur-sharp.adapter.js.map +1 -1
  61. package/dist/image-compressor-sharp.adapter.js +73 -11
  62. package/dist/image-compressor-sharp.adapter.js.map +1 -1
  63. package/dist/image-exif-clear-sharp.adapter.js +70 -8
  64. package/dist/image-exif-clear-sharp.adapter.js.map +1 -1
  65. package/dist/image-formatter-sharp.adapter.js +75 -13
  66. package/dist/image-formatter-sharp.adapter.js.map +1 -1
  67. package/dist/image-grayscale-sharp.adapter.js +70 -8
  68. package/dist/image-grayscale-sharp.adapter.js.map +1 -1
  69. package/dist/image-info-sharp.adapter.js +73 -11
  70. package/dist/image-info-sharp.adapter.js.map +1 -1
  71. package/dist/image-processor-sharp.adapter.js +86 -24
  72. package/dist/image-processor-sharp.adapter.js.map +1 -1
  73. package/dist/image-resizer-sharp.adapter.js +81 -19
  74. package/dist/image-resizer-sharp.adapter.js.map +1 -1
  75. package/dist/index.d.ts +10 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +10 -4
  78. package/dist/index.js.map +1 -1
  79. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.d.ts.map +1 -1
  80. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.js +4 -8
  81. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.js.map +1 -1
  82. package/dist/modules/system/services/passage-of-time-hourly.service.d.ts.map +1 -1
  83. package/dist/modules/system/services/passage-of-time-hourly.service.js +5 -9
  84. package/dist/modules/system/services/passage-of-time-hourly.service.js.map +1 -1
  85. package/dist/modules/system/services/passage-of-time-minute.service.d.ts.map +1 -1
  86. package/dist/modules/system/services/passage-of-time-minute.service.js +5 -9
  87. package/dist/modules/system/services/passage-of-time-minute.service.js.map +1 -1
  88. package/dist/reactive-config-file-json.adapter.d.ts +15 -0
  89. package/dist/reactive-config-file-json.adapter.d.ts.map +1 -0
  90. package/dist/reactive-config-file-json.adapter.js +21 -0
  91. package/dist/reactive-config-file-json.adapter.js.map +1 -0
  92. package/dist/reactive-config-noop.adapter.d.ts +8 -0
  93. package/dist/reactive-config-noop.adapter.d.ts.map +1 -0
  94. package/dist/reactive-config-noop.adapter.js +18 -0
  95. package/dist/reactive-config-noop.adapter.js.map +1 -0
  96. package/dist/reactive-config-with-cache.adapter.d.ts +16 -0
  97. package/dist/reactive-config-with-cache.adapter.d.ts.map +1 -0
  98. package/dist/reactive-config-with-cache.adapter.js +15 -0
  99. package/dist/reactive-config-with-cache.adapter.js.map +1 -0
  100. package/dist/reactive-config-with-fallback.adapter.d.ts +9 -0
  101. package/dist/reactive-config-with-fallback.adapter.d.ts.map +1 -0
  102. package/dist/reactive-config-with-fallback.adapter.js +25 -0
  103. package/dist/reactive-config-with-fallback.adapter.js.map +1 -0
  104. package/dist/reactive-config.port.d.ts +9 -0
  105. package/dist/reactive-config.port.d.ts.map +1 -0
  106. package/dist/reactive-config.port.js +2 -0
  107. package/dist/reactive-config.port.js.map +1 -0
  108. package/dist/redactor-error-cause-depth-limit.strategy.d.ts.map +1 -1
  109. package/dist/redactor-error-cause-depth-limit.strategy.js +2 -2
  110. package/dist/redactor-error-cause-depth-limit.strategy.js.map +1 -1
  111. package/dist/redactor-error-stack-hide.strategy.d.ts.map +1 -1
  112. package/dist/redactor-error-stack-hide.strategy.js +2 -2
  113. package/dist/redactor-error-stack-hide.strategy.js.map +1 -1
  114. package/dist/redactor-metadata-compact-array.strategy.js +1 -1
  115. package/dist/redactor-metadata-compact-array.strategy.js.map +1 -1
  116. package/dist/redactor-metadata-compact-object.strategy.js +1 -1
  117. package/dist/redactor-metadata-compact-object.strategy.js.map +1 -1
  118. package/dist/security-countermeasure-ban.strategy.d.ts.map +1 -1
  119. package/dist/security-countermeasure-ban.strategy.js +5 -8
  120. package/dist/security-countermeasure-ban.strategy.js.map +1 -1
  121. package/dist/setup-hono.service.d.ts +3 -2
  122. package/dist/setup-hono.service.d.ts.map +1 -1
  123. package/dist/setup-hono.service.js.map +1 -1
  124. package/dist/shield-ip-whitelist.strategy.d.ts.map +1 -1
  125. package/dist/shield-ip-whitelist.strategy.js +2 -0
  126. package/dist/shield-ip-whitelist.strategy.js.map +1 -1
  127. package/dist/shield-recaptcha.strategy.js +1 -1
  128. package/dist/shield-recaptcha.strategy.js.map +1 -1
  129. package/package.json +13 -12
  130. package/readme.md +10 -4
  131. package/src/antivirus-clamav.adapter.ts +1 -1
  132. package/src/api-version-hono.middleware.ts +3 -2
  133. package/src/api-version.middleware.ts +5 -4
  134. package/src/build-info.vo.ts +14 -0
  135. package/src/error-normalizer.service.ts +1 -1
  136. package/src/event-envelope.ts +2 -2
  137. package/src/event-serializer-collecting.adapter.ts +5 -4
  138. package/src/event-serializer-json.adapter.ts +3 -2
  139. package/src/event-serializer.port.ts +4 -2
  140. package/src/event-store.adapter.ts +3 -0
  141. package/src/event-upcaster-chain.adapter.ts +49 -0
  142. package/src/event-upcaster-noop.adapter.ts +8 -0
  143. package/src/event-upcaster-step.vo.ts +21 -0
  144. package/src/event-upcaster.port.ts +5 -0
  145. package/src/event.types.ts +1 -1
  146. package/src/healthcheck-hono.handler.ts +3 -2
  147. package/src/healthcheck.handler.ts +9 -8
  148. package/src/index.ts +10 -4
  149. package/src/modules/preferences/command-handlers/handleSetUserLanguageCommand.ts +9 -9
  150. package/src/modules/system/services/passage-of-time-hourly.service.ts +5 -14
  151. package/src/modules/system/services/passage-of-time-minute.service.ts +5 -14
  152. package/src/reactive-config-file-json.adapter.ts +26 -0
  153. package/src/reactive-config-noop.adapter.ts +19 -0
  154. package/src/reactive-config-with-cache.adapter.ts +19 -0
  155. package/src/reactive-config-with-fallback.adapter.ts +24 -0
  156. package/src/reactive-config.port.ts +9 -0
  157. package/src/redactor-error-cause-depth-limit.strategy.ts +2 -3
  158. package/src/redactor-error-stack-hide.strategy.ts +2 -3
  159. package/src/redactor-metadata-compact-array.strategy.ts +1 -1
  160. package/src/redactor-metadata-compact-object.strategy.ts +1 -1
  161. package/src/security-countermeasure-ban.strategy.ts +4 -9
  162. package/src/setup-hono.service.ts +3 -2
  163. package/src/shield-ip-whitelist.strategy.ts +2 -0
  164. package/src/shield-recaptcha.strategy.ts +1 -1
  165. package/dist/build-info-repository-file.strategy.d.ts +0 -14
  166. package/dist/build-info-repository-file.strategy.d.ts.map +0 -1
  167. package/dist/build-info-repository-file.strategy.js +0 -18
  168. package/dist/build-info-repository-file.strategy.js.map +0 -1
  169. package/dist/build-info-repository-noop.strategy.d.ts +0 -12
  170. package/dist/build-info-repository-noop.strategy.d.ts.map +0 -1
  171. package/dist/build-info-repository-noop.strategy.js +0 -16
  172. package/dist/build-info-repository-noop.strategy.js.map +0 -1
  173. package/dist/build-info-repository-package-json.strategy.d.ts +0 -14
  174. package/dist/build-info-repository-package-json.strategy.d.ts.map +0 -1
  175. package/dist/build-info-repository-package-json.strategy.js +0 -21
  176. package/dist/build-info-repository-package-json.strategy.js.map +0 -1
  177. package/dist/build-info-repository.strategy.d.ts +0 -12
  178. package/dist/build-info-repository.strategy.d.ts.map +0 -1
  179. package/dist/build-info-repository.strategy.js +0 -2
  180. package/dist/build-info-repository.strategy.js.map +0 -1
  181. package/dist/tsconfig.tsbuildinfo +0 -1
  182. package/src/build-info-repository-file.strategy.ts +0 -23
  183. package/src/build-info-repository-noop.strategy.ts +0 -16
  184. package/src/build-info-repository-package-json.strategy.ts +0 -25
  185. package/src/build-info-repository.strategy.ts +0 -13
@@ -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 {
@@ -17,13 +17,12 @@ export const EventEnvelopeSchema = {
17
17
  revision: v.optional(tools.RevisionValue),
18
18
  };
19
19
 
20
- export const createEventEnvelope = (stream: EventStreamType, deps: Dependencies) =>
20
+ const createEventEnvelope = (stream: EventStreamType, deps: Dependencies) =>
21
21
  ({
22
22
  id: deps.IdProvider.generate(),
23
23
  correlationId: CorrelationStorage.get(),
24
24
  createdAt: deps.Clock.now().ms,
25
25
  stream,
26
- version: 1,
27
26
  }) as const;
28
27
 
29
28
  export function event<Schema extends v.ObjectSchema<any, any>>(
@@ -35,6 +34,7 @@ export function event<Schema extends v.ObjectSchema<any, any>>(
35
34
  return v.parse(schema, {
36
35
  ...createEventEnvelope(stream, deps),
37
36
  name: schema.entries.name.literal,
37
+ version: schema.entries.version.literal,
38
38
  payload,
39
39
  });
40
40
  }
@@ -1,17 +1,18 @@
1
+ import type { GenericEvent, GenericEventSerialized } from "./event.types";
1
2
  import type { EventSerializerPort } from "./event-serializer.port";
2
3
 
3
4
  export class EventSerializerCollectingAdapter implements EventSerializerPort {
4
- readonly serialized: Array<unknown> = [];
5
+ readonly serialized: Array<GenericEvent["payload"]> = [];
5
6
 
6
- readonly deserialized: Array<string> = [];
7
+ readonly deserialized: Array<GenericEventSerialized["payload"]> = [];
7
8
 
8
- serialize(payload: unknown): string {
9
+ serialize(payload: GenericEvent["payload"]): GenericEventSerialized["payload"] {
9
10
  this.serialized.push(payload);
10
11
 
11
12
  return JSON.stringify(payload);
12
13
  }
13
14
 
14
- deserialize(raw: string): unknown {
15
+ deserialize(raw: GenericEventSerialized["payload"]): GenericEvent["payload"] {
15
16
  this.deserialized.push(raw);
16
17
 
17
18
  return JSON.parse(raw);
@@ -1,11 +1,12 @@
1
+ import type { GenericEvent, GenericEventSerialized } from "./event.types";
1
2
  import type { EventSerializerPort } from "./event-serializer.port";
2
3
 
3
4
  export class EventSerializerJsonAdapter implements EventSerializerPort {
4
- serialize(payload: unknown): string {
5
+ serialize(payload: GenericEvent["payload"]): GenericEventSerialized["payload"] {
5
6
  return JSON.stringify(payload);
6
7
  }
7
8
 
8
- deserialize(raw: string): unknown {
9
+ deserialize(raw: GenericEventSerialized["payload"]): GenericEvent["payload"] {
9
10
  return JSON.parse(raw);
10
11
  }
11
12
  }
@@ -1,4 +1,6 @@
1
+ import type { GenericEvent, GenericEventSerialized } from "./event.types";
2
+
1
3
  export interface EventSerializerPort {
2
- serialize(payload: unknown): string;
3
- deserialize(raw: string): unknown;
4
+ serialize(payload: GenericEvent["payload"]): GenericEventSerialized["payload"];
5
+ deserialize(raw: GenericEventSerialized["payload"]): GenericEvent["payload"];
4
6
  }
@@ -4,12 +4,14 @@ import type { EventInserterPort } from "./event-inserter.port";
4
4
  import type { EventSerializerPort } from "./event-serializer.port";
5
5
  import type { EventStorePort } from "./event-store.port";
6
6
  import type { EventStreamType } from "./event-stream.vo";
7
+ import type { EventUpcasterPort } from "./event-upcaster.port";
7
8
  import type { EventValidatorRegistryPort } from "./event-validator-registry.port";
8
9
 
9
10
  type Config = {
10
11
  finder: EventFinderPort;
11
12
  inserter: EventInserterPort;
12
13
  serializer: EventSerializerPort;
14
+ upcaster?: EventUpcasterPort;
13
15
  };
14
16
 
15
17
  const EventStoreAdapterError = { UniqueStream: "event.store.adapter.error.unique.stream" };
@@ -25,6 +27,7 @@ export class EventStoreAdapter<Event extends GenericEvent> implements EventStore
25
27
 
26
28
  return events
27
29
  .map((event) => ({ ...event, payload: this.config.serializer.deserialize(event.payload) }))
30
+ .map((event) => (this.config.upcaster ? this.config.upcaster.upcast(event) : event))
28
31
  .map((event) => registry.validate(event));
29
32
  }
30
33
 
@@ -0,0 +1,49 @@
1
+ import type { GenericEvent } from "./event.types";
2
+ import type { EventUpcasterPort } from "./event-upcaster.port";
3
+ import type { EventUpcasterStep } from "./event-upcaster-step.vo";
4
+
5
+ const EventUpcasterChainAdapterError = {
6
+ DuplicateStep: "event.upcaster.chain.duplicate.step",
7
+ GapInChain: "event.upcaster.chain.gap",
8
+ };
9
+
10
+ type EventUpcasterChainConfig = Record<GenericEvent["name"], ReadonlyArray<EventUpcasterStep<any, any>>>;
11
+
12
+ export class EventUpcasterChainAdapter implements EventUpcasterPort {
13
+ private readonly upcasters: Record<GenericEvent["name"], ReadonlyArray<EventUpcasterStep<any, any>>>;
14
+
15
+ constructor(config: EventUpcasterChainConfig) {
16
+ this.upcasters = Object.fromEntries(
17
+ Object.entries(config).map(([name, chain]) => [
18
+ name,
19
+ [...chain].sort((a, b) => a.config.fromVersion - b.config.fromVersion),
20
+ ]),
21
+ );
22
+
23
+ for (const chain of Object.values(this.upcasters)) {
24
+ const versions = chain.map((step) => step.config.fromVersion);
25
+
26
+ if (new Set(versions).size !== versions.length) {
27
+ throw new Error(EventUpcasterChainAdapterError.DuplicateStep);
28
+ }
29
+
30
+ if (chain.some((step, i) => i > 0 && step.config.fromVersion !== chain[i - 1]!.config.toVersion)) {
31
+ throw new Error(EventUpcasterChainAdapterError.GapInChain);
32
+ }
33
+ }
34
+ }
35
+
36
+ upcast(event: GenericEvent): GenericEvent {
37
+ const chain = this.upcasters[event.name];
38
+
39
+ if (!chain) return event;
40
+
41
+ return chain.reduce(
42
+ (result, step) =>
43
+ result.version === step.config.fromVersion
44
+ ? { ...result, version: step.config.toVersion, payload: step.config.upcast(result.payload) }
45
+ : result,
46
+ event,
47
+ );
48
+ }
49
+ }
@@ -0,0 +1,8 @@
1
+ import type { GenericEvent } from "./event.types";
2
+ import type { EventUpcasterPort } from "./event-upcaster.port";
3
+
4
+ export class EventUpcasterNoopAdapter implements EventUpcasterPort {
5
+ upcast(event: GenericEvent): GenericEvent {
6
+ return event;
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ import type { GenericEvent } from "./event.types";
2
+
3
+ export type EventUpcasterStepConfig<
4
+ From extends GenericEvent = GenericEvent,
5
+ To extends GenericEvent = GenericEvent,
6
+ > = {
7
+ fromVersion: GenericEvent["version"];
8
+ toVersion: GenericEvent["version"];
9
+ upcast: (payload: From["payload"]) => To["payload"];
10
+ };
11
+
12
+ const EventUpcasterStepError = { VersionIncrement: "event.upcaster.step.version.increment" };
13
+
14
+ export class EventUpcasterStep<
15
+ From extends GenericEvent = GenericEvent,
16
+ To extends GenericEvent = GenericEvent,
17
+ > {
18
+ constructor(readonly config: EventUpcasterStepConfig<From, To>) {
19
+ if (config.toVersion !== config.fromVersion + 1) throw new Error(EventUpcasterStepError.VersionIncrement);
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ import type { GenericEvent } from "./event.types";
2
+
3
+ export interface EventUpcasterPort {
4
+ upcast(event: GenericEvent): GenericEvent;
5
+ }
@@ -10,7 +10,7 @@ export type GenericEvent = {
10
10
  revision?: tools.RevisionValueType;
11
11
  name: string;
12
12
  version: number;
13
- payload: unknown;
13
+ payload: Record<string, unknown>;
14
14
  };
15
15
 
16
16
  export type GenericEventSerialized = Omit<GenericEvent, "payload"> & { payload: string };
@@ -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
@@ -35,10 +35,7 @@ export * from "./basic-auth-username.vo";
35
35
  export * from "./better-auth-logger.service";
36
36
  export * from "./binary.vo";
37
37
  export * from "./bots.vo";
38
- export * from "./build-info-repository.strategy";
39
- export * from "./build-info-repository-file.strategy";
40
- export * from "./build-info-repository-noop.strategy";
41
- export * from "./build-info-repository-package-json.strategy";
38
+ export * from "./build-info.vo";
42
39
  export * from "./cache-file.service";
43
40
  export * from "./cache-repository.port";
44
41
  export * from "./cache-repository-node-cache.adapter";
@@ -114,6 +111,10 @@ export * from "./event-store-dispatching.adapter";
114
111
  export * from "./event-store-noop.adapter";
115
112
  export * from "./event-store-with-logger.adapter";
116
113
  export * from "./event-stream.vo";
114
+ export * from "./event-upcaster.port";
115
+ export * from "./event-upcaster-chain.adapter";
116
+ export * from "./event-upcaster-noop.adapter";
117
+ export * from "./event-upcaster-step.vo";
117
118
  export * from "./event-validator-registry.adapter";
118
119
  export * from "./event-validator-registry.port";
119
120
  export * from "./file-cleaner.adapter";
@@ -303,6 +304,11 @@ export * from "./randomness.strategy";
303
304
  export * from "./randomness-crypto.strategy";
304
305
  export * from "./randomness-math.strategy";
305
306
  export * from "./randomness-noop.strategy";
307
+ export * from "./reactive-config.port";
308
+ export * from "./reactive-config-file-json.adapter";
309
+ export * from "./reactive-config-noop.adapter";
310
+ export * from "./reactive-config-with-cache.adapter";
311
+ export * from "./reactive-config-with-fallback.adapter";
306
312
  export * from "./readiness.handler";
307
313
  export * from "./readiness-hono.handler";
308
314
  export * from "./recaptcha-secret-key.vo";
@@ -1,7 +1,6 @@
1
1
  import type * as tools from "@bgord/tools";
2
- import * as v from "valibot";
3
2
  import type { ClockPort } from "../../../clock.port";
4
- import { createEventEnvelope } from "../../../event-envelope";
3
+ import { event } from "../../../event-envelope";
5
4
  import type { EventStorePort } from "../../../event-store.port";
6
5
  import type { IdProviderPort } from "../../../id-provider.port";
7
6
  import type { Languages } from "../../../languages.vo";
@@ -34,11 +33,12 @@ export const handleSetUserLanguageCommand =
34
33
 
35
34
  if (!Invariants.UserLanguageHasChanged.passes({ current, candidate })) return;
36
35
 
37
- const event = v.parse(Events.UserLanguageSetEvent, {
38
- ...createEventEnvelope(`preferences_${command.payload.userId}`, deps),
39
- name: Events.USER_LANGUAGE_SET_EVENT,
40
- payload: { userId: command.payload.userId, language: candidate },
41
- } satisfies Events.UserLanguageSetEventType);
42
-
43
- await deps.EventStore.save([event]);
36
+ await deps.EventStore.save([
37
+ event(
38
+ Events.UserLanguageSetEvent,
39
+ `preferences_${command.payload.userId}`,
40
+ { userId: command.payload.userId, language: candidate },
41
+ deps,
42
+ ),
43
+ ]);
44
44
  };
@@ -1,15 +1,10 @@
1
- import * as v from "valibot";
2
1
  import type { ClockPort } from "../../../clock.port";
3
- import { createEventEnvelope } from "../../../event-envelope";
2
+ import { event } from "../../../event-envelope";
4
3
  import type { EventStorePort } from "../../../event-store.port";
5
4
  import type { IdProviderPort } from "../../../id-provider.port";
6
5
  import type { UnitOfWork } from "../../../job-handler.strategy";
7
6
  import { Jobs } from "../../../jobs.service";
8
- import {
9
- HOUR_HAS_PASSED_EVENT,
10
- HourHasPassedEvent,
11
- type HourHasPassedEventType,
12
- } from "../events/HOUR_HAS_PASSED_EVENT";
7
+ import { HourHasPassedEvent, type HourHasPassedEventType } from "../events/HOUR_HAS_PASSED_EVENT";
13
8
 
14
9
  type Dependencies = {
15
10
  EventStore: EventStorePort<HourHasPassedEventType>;
@@ -25,12 +20,8 @@ export class PassageOfTimeHourly implements UnitOfWork {
25
20
  label = "PassageOfTime";
26
21
 
27
22
  async process() {
28
- const event = v.parse(HourHasPassedEvent, {
29
- ...createEventEnvelope("passage_of_time", this.deps),
30
- name: HOUR_HAS_PASSED_EVENT,
31
- payload: { timestamp: this.deps.Clock.now().ms },
32
- } satisfies HourHasPassedEventType);
33
-
34
- await this.deps.EventStore.save([event]);
23
+ await this.deps.EventStore.save([
24
+ event(HourHasPassedEvent, "passage_of_time", { timestamp: this.deps.Clock.now().ms }, this.deps),
25
+ ]);
35
26
  }
36
27
  }
@@ -1,15 +1,10 @@
1
- import * as v from "valibot";
2
1
  import type { ClockPort } from "../../../clock.port";
3
- import { createEventEnvelope } from "../../../event-envelope";
2
+ import { event } from "../../../event-envelope";
4
3
  import type { EventStorePort } from "../../../event-store.port";
5
4
  import type { IdProviderPort } from "../../../id-provider.port";
6
5
  import type { UnitOfWork } from "../../../job-handler.strategy";
7
6
  import { Jobs } from "../../../jobs.service";
8
- import {
9
- MINUTE_HAS_PASSED_EVENT,
10
- MinuteHasPassedEvent,
11
- type MinuteHasPassedEventType,
12
- } from "../events/MINUTE_HAS_PASSED_EVENT";
7
+ import { MinuteHasPassedEvent, type MinuteHasPassedEventType } from "../events/MINUTE_HAS_PASSED_EVENT";
13
8
 
14
9
  type Dependencies = {
15
10
  EventStore: EventStorePort<MinuteHasPassedEventType>;
@@ -25,12 +20,8 @@ export class PassageOfTimeMinute implements UnitOfWork {
25
20
  label = "PassageOfTime";
26
21
 
27
22
  async process() {
28
- const event = v.parse(MinuteHasPassedEvent, {
29
- ...createEventEnvelope("passage_of_time", this.deps),
30
- name: MINUTE_HAS_PASSED_EVENT,
31
- payload: { timestamp: this.deps.Clock.now().ms },
32
- } satisfies MinuteHasPassedEventType);
33
-
34
- await this.deps.EventStore.save([event]);
23
+ await this.deps.EventStore.save([
24
+ event(MinuteHasPassedEvent, "passage_of_time", { timestamp: this.deps.Clock.now().ms }, this.deps),
25
+ ]);
35
26
  }
36
27
  }
@@ -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