@bgord/bun 1.23.12 → 1.24.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 (199) hide show
  1. package/dist/atomic-file-writer-collecting.adapter.d.ts +8 -0
  2. package/dist/atomic-file-writer-collecting.adapter.d.ts.map +1 -0
  3. package/dist/atomic-file-writer-collecting.adapter.js +7 -0
  4. package/dist/atomic-file-writer-collecting.adapter.js.map +1 -0
  5. package/dist/atomic-file-writer-noop.adapter.d.ts +7 -0
  6. package/dist/atomic-file-writer-noop.adapter.d.ts.map +1 -0
  7. package/dist/atomic-file-writer-noop.adapter.js +4 -0
  8. package/dist/atomic-file-writer-noop.adapter.js.map +1 -0
  9. package/dist/atomic-file-writer.adapter.d.ts +19 -0
  10. package/dist/atomic-file-writer.adapter.d.ts.map +1 -0
  11. package/dist/atomic-file-writer.adapter.js +18 -0
  12. package/dist/atomic-file-writer.adapter.js.map +1 -0
  13. package/dist/atomic-file-writer.port.d.ts +6 -0
  14. package/dist/atomic-file-writer.port.d.ts.map +1 -0
  15. package/dist/atomic-file-writer.port.js +2 -0
  16. package/dist/atomic-file-writer.port.js.map +1 -0
  17. package/dist/encryption-aes-gcm.adapter.d.ts +2 -2
  18. package/dist/encryption-aes-gcm.adapter.d.ts.map +1 -1
  19. package/dist/encryption-aes-gcm.adapter.js +2 -2
  20. package/dist/encryption-aes-gcm.adapter.js.map +1 -1
  21. package/dist/event-sourced-repository.adapter.d.ts +27 -0
  22. package/dist/event-sourced-repository.adapter.d.ts.map +1 -0
  23. package/dist/event-sourced-repository.adapter.js +16 -0
  24. package/dist/event-sourced-repository.adapter.js.map +1 -0
  25. package/dist/event-stream-accept.service.d.ts +4 -0
  26. package/dist/event-stream-accept.service.d.ts.map +1 -0
  27. package/dist/event-stream-accept.service.js +11 -0
  28. package/dist/event-stream-accept.service.js.map +1 -0
  29. package/dist/gzip.adapter.d.ts +2 -2
  30. package/dist/gzip.adapter.d.ts.map +1 -1
  31. package/dist/gzip.adapter.js +1 -1
  32. package/dist/gzip.adapter.js.map +1 -1
  33. package/dist/healthcheck.handler.d.ts +4 -2
  34. package/dist/healthcheck.handler.d.ts.map +1 -1
  35. package/dist/healthcheck.handler.js +4 -11
  36. package/dist/healthcheck.handler.js.map +1 -1
  37. package/dist/http-exception-errors.js +2 -2
  38. package/dist/http-exception-errors.js.map +1 -1
  39. package/dist/http-logger-hono.middleware.d.ts +1 -0
  40. package/dist/http-logger-hono.middleware.d.ts.map +1 -1
  41. package/dist/http-logger-hono.middleware.js +10 -6
  42. package/dist/http-logger-hono.middleware.js.map +1 -1
  43. package/dist/http-logger.middleware.d.ts.map +1 -1
  44. package/dist/http-logger.middleware.js +4 -6
  45. package/dist/http-logger.middleware.js.map +1 -1
  46. package/dist/image-blur.adapter.d.ts +2 -6
  47. package/dist/image-blur.adapter.d.ts.map +1 -1
  48. package/dist/image-blur.adapter.js +1 -4
  49. package/dist/image-blur.adapter.js.map +1 -1
  50. package/dist/image-compressor.adapter.d.ts +2 -6
  51. package/dist/image-compressor.adapter.d.ts.map +1 -1
  52. package/dist/image-compressor.adapter.js +1 -4
  53. package/dist/image-compressor.adapter.js.map +1 -1
  54. package/dist/image-exif-clear.adapter.d.ts +2 -6
  55. package/dist/image-exif-clear.adapter.d.ts.map +1 -1
  56. package/dist/image-exif-clear.adapter.js +1 -3
  57. package/dist/image-exif-clear.adapter.js.map +1 -1
  58. package/dist/image-formatter.adapter.d.ts +2 -6
  59. package/dist/image-formatter.adapter.d.ts.map +1 -1
  60. package/dist/image-formatter.adapter.js +1 -3
  61. package/dist/image-formatter.adapter.js.map +1 -1
  62. package/dist/image-grayscale.adapter.d.ts +2 -6
  63. package/dist/image-grayscale.adapter.d.ts.map +1 -1
  64. package/dist/image-grayscale.adapter.js +1 -4
  65. package/dist/image-grayscale.adapter.js.map +1 -1
  66. package/dist/image-processor.adapter.d.ts +2 -6
  67. package/dist/image-processor.adapter.d.ts.map +1 -1
  68. package/dist/image-processor.adapter.js +1 -3
  69. package/dist/image-processor.adapter.js.map +1 -1
  70. package/dist/image-resizer.adapter.d.ts +2 -6
  71. package/dist/image-resizer.adapter.d.ts.map +1 -1
  72. package/dist/image-resizer.adapter.js +1 -4
  73. package/dist/image-resizer.adapter.js.map +1 -1
  74. package/dist/index.d.ts +13 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +13 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/job-pruner-sqlite.adapter.d.ts.map +1 -1
  79. package/dist/job-pruner-sqlite.adapter.js +2 -1
  80. package/dist/job-pruner-sqlite.adapter.js.map +1 -1
  81. package/dist/job-queue-stats-provider-sqlite.adapter.js +4 -4
  82. package/dist/job-queue-stats-provider-sqlite.adapter.js.map +1 -1
  83. package/dist/job-recoverer-collecting.adapter.d.ts +7 -0
  84. package/dist/job-recoverer-collecting.adapter.d.ts.map +1 -0
  85. package/dist/job-recoverer-collecting.adapter.js +10 -0
  86. package/dist/job-recoverer-collecting.adapter.js.map +1 -0
  87. package/dist/job-recoverer-noop.adapter.d.ts +6 -0
  88. package/dist/job-recoverer-noop.adapter.d.ts.map +1 -0
  89. package/dist/job-recoverer-noop.adapter.js +7 -0
  90. package/dist/job-recoverer-noop.adapter.js.map +1 -0
  91. package/dist/job-recoverer-sqlite.adapter.d.ts +13 -0
  92. package/dist/job-recoverer-sqlite.adapter.d.ts.map +1 -0
  93. package/dist/job-recoverer-sqlite.adapter.js +14 -0
  94. package/dist/job-recoverer-sqlite.adapter.js.map +1 -0
  95. package/dist/job-recoverer.port.d.ts +5 -0
  96. package/dist/job-recoverer.port.d.ts.map +1 -0
  97. package/dist/job-recoverer.port.js +2 -0
  98. package/dist/job-recoverer.port.js.map +1 -0
  99. package/dist/memory-consumption.service.d.ts +1 -2
  100. package/dist/memory-consumption.service.d.ts.map +1 -1
  101. package/dist/memory-consumption.service.js.map +1 -1
  102. package/dist/message-bus-strict.adapter.d.ts +19 -0
  103. package/dist/message-bus-strict.adapter.d.ts.map +1 -0
  104. package/dist/message-bus-strict.adapter.js +24 -0
  105. package/dist/message-bus-strict.adapter.js.map +1 -0
  106. package/dist/path-matcher.service.d.ts +6 -0
  107. package/dist/path-matcher.service.d.ts.map +1 -0
  108. package/dist/path-matcher.service.js +13 -0
  109. package/dist/path-matcher.service.js.map +1 -0
  110. package/dist/remote-file-storage-disk.adapter.d.ts +2 -6
  111. package/dist/remote-file-storage-disk.adapter.d.ts.map +1 -1
  112. package/dist/remote-file-storage-disk.adapter.js +1 -9
  113. package/dist/remote-file-storage-disk.adapter.js.map +1 -1
  114. package/dist/request-context-hono.adapter.d.ts +1 -1
  115. package/dist/request-context-hono.adapter.d.ts.map +1 -1
  116. package/dist/request-context-hono.adapter.js +3 -3
  117. package/dist/request-context-hono.adapter.js.map +1 -1
  118. package/dist/request-context.port.d.ts +4 -0
  119. package/dist/request-context.port.d.ts.map +1 -1
  120. package/dist/request-context.port.js +4 -1
  121. package/dist/request-context.port.js.map +1 -1
  122. package/dist/runtime-stats-provider-noop.adapter.d.ts +5 -0
  123. package/dist/runtime-stats-provider-noop.adapter.d.ts.map +1 -0
  124. package/dist/runtime-stats-provider-noop.adapter.js +14 -0
  125. package/dist/runtime-stats-provider-noop.adapter.js.map +1 -0
  126. package/dist/runtime-stats-provider-system.adapter.d.ts +12 -0
  127. package/dist/runtime-stats-provider-system.adapter.d.ts.map +1 -0
  128. package/dist/runtime-stats-provider-system.adapter.js +21 -0
  129. package/dist/runtime-stats-provider-system.adapter.js.map +1 -0
  130. package/dist/runtime-stats-provider.port.d.ts +18 -0
  131. package/dist/runtime-stats-provider.port.d.ts.map +1 -0
  132. package/dist/runtime-stats-provider.port.js +2 -0
  133. package/dist/runtime-stats-provider.port.js.map +1 -0
  134. package/dist/security-rule-violation-threshold.strategy.js +1 -1
  135. package/dist/security-rule-violation-threshold.strategy.js.map +1 -1
  136. package/dist/setup-hono.service.js +1 -1
  137. package/dist/setup-hono.service.js.map +1 -1
  138. package/dist/shield-auth-hono.strategy.d.ts.map +1 -1
  139. package/dist/shield-auth-hono.strategy.js +4 -3
  140. package/dist/shield-auth-hono.strategy.js.map +1 -1
  141. package/dist/shield-auth.strategy.d.ts +1 -5
  142. package/dist/shield-auth.strategy.d.ts.map +1 -1
  143. package/dist/shield-auth.strategy.js +3 -6
  144. package/dist/shield-auth.strategy.js.map +1 -1
  145. package/dist/shield-maintenance.strategy.d.ts.map +1 -1
  146. package/dist/shield-maintenance.strategy.js +2 -5
  147. package/dist/shield-maintenance.strategy.js.map +1 -1
  148. package/dist/temporary-file-absolute.adapter.d.ts +2 -6
  149. package/dist/temporary-file-absolute.adapter.d.ts.map +1 -1
  150. package/dist/temporary-file-absolute.adapter.js +1 -9
  151. package/dist/temporary-file-absolute.adapter.js.map +1 -1
  152. package/dist/timing.middleware.d.ts +0 -1
  153. package/dist/timing.middleware.d.ts.map +1 -1
  154. package/dist/timing.middleware.js +2 -9
  155. package/dist/timing.middleware.js.map +1 -1
  156. package/package.json +5 -5
  157. package/readme.md +15 -0
  158. package/src/atomic-file-writer-collecting.adapter.ts +14 -0
  159. package/src/atomic-file-writer-noop.adapter.ts +10 -0
  160. package/src/atomic-file-writer.adapter.ts +34 -0
  161. package/src/atomic-file-writer.port.ts +6 -0
  162. package/src/encryption-aes-gcm.adapter.ts +4 -4
  163. package/src/event-sourced-repository.adapter.ts +48 -0
  164. package/src/event-stream-accept.service.ts +10 -0
  165. package/src/gzip.adapter.ts +3 -3
  166. package/src/healthcheck.handler.ts +8 -11
  167. package/src/http-exception-errors.ts +2 -2
  168. package/src/http-logger-hono.middleware.ts +12 -6
  169. package/src/http-logger.middleware.ts +4 -7
  170. package/src/image-blur.adapter.ts +3 -13
  171. package/src/image-compressor.adapter.ts +3 -12
  172. package/src/image-exif-clear.adapter.ts +3 -11
  173. package/src/image-formatter.adapter.ts +3 -12
  174. package/src/image-grayscale.adapter.ts +3 -13
  175. package/src/image-processor.adapter.ts +3 -11
  176. package/src/image-resizer.adapter.ts +3 -12
  177. package/src/index.ts +13 -0
  178. package/src/job-pruner-sqlite.adapter.ts +2 -1
  179. package/src/job-queue-stats-provider-sqlite.adapter.ts +4 -4
  180. package/src/job-recoverer-collecting.adapter.ts +14 -0
  181. package/src/job-recoverer-noop.adapter.ts +8 -0
  182. package/src/job-recoverer-sqlite.adapter.ts +20 -0
  183. package/src/job-recoverer.port.ts +5 -0
  184. package/src/memory-consumption.service.ts +4 -1
  185. package/src/message-bus-strict.adapter.ts +33 -0
  186. package/src/path-matcher.service.ts +14 -0
  187. package/src/remote-file-storage-disk.adapter.ts +3 -17
  188. package/src/request-context-hono.adapter.ts +3 -4
  189. package/src/request-context.port.ts +5 -0
  190. package/src/runtime-stats-provider-noop.adapter.ts +16 -0
  191. package/src/runtime-stats-provider-system.adapter.ts +24 -0
  192. package/src/runtime-stats-provider.port.ts +16 -0
  193. package/src/security-rule-violation-threshold.strategy.ts +2 -2
  194. package/src/setup-hono.service.ts +1 -1
  195. package/src/shield-auth-hono.strategy.ts +4 -3
  196. package/src/shield-auth.strategy.ts +3 -8
  197. package/src/shield-maintenance.strategy.ts +2 -6
  198. package/src/temporary-file-absolute.adapter.ts +3 -18
  199. package/src/timing.middleware.ts +2 -10
@@ -1,14 +1,10 @@
1
1
  // [BUN DEPENDENCY]
2
2
  import type * as tools from "@bgord/tools";
3
- import type { FileRenamerPort } from "./file-renamer.port";
4
- import type { FileWriterPort } from "./file-writer.port";
3
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
5
4
  import type { ImageBlurPort, ImageBlurStrategy } from "./image-blur.port";
6
- import type { NonceProviderPort } from "./nonce-provider.port";
7
5
 
8
6
  type Dependencies = {
9
- FileRenamer: FileRenamerPort;
10
- FileWriter: FileWriterPort;
11
- NonceProvider: NonceProviderPort;
7
+ AtomicFileWriter: AtomicFileWriterPort;
12
8
  };
13
9
 
14
10
  export class ImageBlurAdapter implements ImageBlurPort {
@@ -17,16 +13,10 @@ export class ImageBlurAdapter implements ImageBlurPort {
17
13
  async blur(recipe: ImageBlurStrategy): Promise<tools.FilePathRelative | tools.FilePathAbsolute> {
18
14
  const final = recipe.output;
19
15
 
20
- const filename = final.getFilename();
21
- const temporary = final.withFilename(
22
- filename.withSuffix(`-blurred-${this.deps.NonceProvider.generate()}`),
23
- );
24
-
25
16
  const blurred = await Bun.file(recipe.input.get()).image().placeholder();
26
17
  const bytes = Buffer.from(blurred.substring(blurred.indexOf(",") + 1), "base64");
27
18
 
28
- await this.deps.FileWriter.write(temporary.get(), bytes);
29
- await this.deps.FileRenamer.rename(temporary, final);
19
+ await this.deps.AtomicFileWriter.write(final, bytes);
30
20
 
31
21
  return final;
32
22
  }
@@ -1,15 +1,11 @@
1
1
  // [BUN DEPENDENCY]
2
2
  import type * as tools from "@bgord/tools";
3
- import type { FileRenamerPort } from "./file-renamer.port";
4
- import type { FileWriterPort } from "./file-writer.port";
3
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
5
4
  import type { ImageSupportedType } from "./image.types";
6
5
  import type { ImageCompressorPort, ImageCompressorStrategy } from "./image-compressor.port";
7
- import type { NonceProviderPort } from "./nonce-provider.port";
8
6
 
9
7
  type Dependencies = {
10
- FileRenamer: FileRenamerPort;
11
- FileWriter: FileWriterPort;
12
- NonceProvider: NonceProviderPort;
8
+ AtomicFileWriter: AtomicFileWriterPort;
13
9
  };
14
10
 
15
11
  export class ImageCompressorAdapter implements ImageCompressorPort {
@@ -21,18 +17,13 @@ export class ImageCompressorAdapter implements ImageCompressorPort {
21
17
  const quality = recipe.quality ?? ImageCompressorAdapter.DEFAULT_QUALITY;
22
18
 
23
19
  const final = recipe.strategy === "output_path" ? recipe.output : recipe.input;
24
- const filename = final.getFilename();
25
- const temporary = final.withFilename(
26
- filename.withSuffix(`-compressed-${this.deps.NonceProvider.generate()}`),
27
- );
28
20
 
29
21
  const extension = final.getFilename().getExtension();
30
22
  const format = (extension === "jpg" ? "jpeg" : extension) as ImageSupportedType;
31
23
 
32
24
  const compressed = await Bun.file(recipe.input.get()).image().rotate(0)[format]({ quality }).bytes();
33
25
 
34
- await this.deps.FileWriter.write(temporary.get(), compressed);
35
- await this.deps.FileRenamer.rename(temporary, final);
26
+ await this.deps.AtomicFileWriter.write(final, compressed);
36
27
 
37
28
  return final;
38
29
  }
@@ -1,16 +1,12 @@
1
1
  // [BUN DEPENDENCY]
2
2
  // cspell:ignore Exif
3
3
  import type * as tools from "@bgord/tools";
4
- import type { FileRenamerPort } from "./file-renamer.port";
5
- import type { FileWriterPort } from "./file-writer.port";
4
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
6
5
  import type { ImageSupportedType } from "./image.types";
7
6
  import type { ImageExifClearPort, ImageExifClearStrategy } from "./image-exif-clear.port";
8
- import type { NonceProviderPort } from "./nonce-provider.port";
9
7
 
10
8
  type Dependencies = {
11
- FileRenamer: FileRenamerPort;
12
- FileWriter: FileWriterPort;
13
- NonceProvider: NonceProviderPort;
9
+ AtomicFileWriter: AtomicFileWriterPort;
14
10
  };
15
11
 
16
12
  export class ImageExifClearAdapter implements ImageExifClearPort {
@@ -18,17 +14,13 @@ export class ImageExifClearAdapter implements ImageExifClearPort {
18
14
 
19
15
  async clear(recipe: ImageExifClearStrategy): Promise<tools.FilePathRelative | tools.FilePathAbsolute> {
20
16
  const final = recipe.strategy === "output_path" ? recipe.output : recipe.input;
21
- const temporary = final.withFilename(
22
- final.getFilename().withSuffix(`-exif-cleared-${this.deps.NonceProvider.generate()}`),
23
- );
24
17
 
25
18
  const extension = final.getFilename().getExtension();
26
19
  const format = (extension === "jpg" ? "jpeg" : extension) as ImageSupportedType;
27
20
 
28
21
  const cleared = await Bun.file(recipe.input.get()).image().rotate(0)[format]().bytes();
29
22
 
30
- await this.deps.FileWriter.write(temporary.get(), cleared);
31
- await this.deps.FileRenamer.rename(temporary, final);
23
+ await this.deps.AtomicFileWriter.write(final, cleared);
32
24
 
33
25
  return final;
34
26
  }
@@ -1,17 +1,13 @@
1
1
  // [BUN DEPENDENCY]
2
2
  import type * as tools from "@bgord/tools";
3
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
3
4
  import type { FileCleanerPort } from "./file-cleaner.port";
4
- import type { FileRenamerPort } from "./file-renamer.port";
5
- import type { FileWriterPort } from "./file-writer.port";
6
5
  import type { ImageSupportedType } from "./image.types";
7
6
  import type { ImageFormatterPort, ImageFormatterStrategy } from "./image-formatter.port";
8
- import type { NonceProviderPort } from "./nonce-provider.port";
9
7
 
10
8
  type Dependencies = {
9
+ AtomicFileWriter: AtomicFileWriterPort;
11
10
  FileCleaner: FileCleanerPort;
12
- FileRenamer: FileRenamerPort;
13
- FileWriter: FileWriterPort;
14
- NonceProvider: NonceProviderPort;
15
11
  };
16
12
 
17
13
  export class ImageFormatterAdapter implements ImageFormatterPort {
@@ -23,17 +19,12 @@ export class ImageFormatterAdapter implements ImageFormatterPort {
23
19
  ? recipe.output
24
20
  : recipe.input.withFilename(recipe.input.getFilename().withExtension(recipe.to));
25
21
 
26
- const temporary = final.withFilename(
27
- final.getFilename().withSuffix(`-formatted-${this.deps.NonceProvider.generate()}`),
28
- );
29
-
30
22
  const extension = final.getFilename().getExtension();
31
23
  const format = (extension === "jpg" ? "jpeg" : extension) as ImageSupportedType;
32
24
 
33
25
  const bytes = await Bun.file(recipe.input.get()).image().rotate(0)[format]().bytes();
34
26
 
35
- await this.deps.FileWriter.write(temporary.get(), bytes);
36
- await this.deps.FileRenamer.rename(temporary, final);
27
+ await this.deps.AtomicFileWriter.write(final, bytes);
37
28
 
38
29
  if (recipe.strategy === "in_place" && final.get() !== recipe.input.get()) {
39
30
  await this.deps.FileCleaner.delete(recipe.input.get());
@@ -1,16 +1,12 @@
1
1
  // [BUN DEPENDENCY]
2
2
  // cspell:ignore grayscaled
3
3
  import type * as tools from "@bgord/tools";
4
- import type { FileRenamerPort } from "./file-renamer.port";
5
- import type { FileWriterPort } from "./file-writer.port";
4
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
6
5
  import type { ImageSupportedType } from "./image.types";
7
6
  import type { ImageGrayscalePort, ImageGrayscaleStrategy } from "./image-grayscale.port";
8
- import type { NonceProviderPort } from "./nonce-provider.port";
9
7
 
10
8
  type Dependencies = {
11
- FileRenamer: FileRenamerPort;
12
- FileWriter: FileWriterPort;
13
- NonceProvider: NonceProviderPort;
9
+ AtomicFileWriter: AtomicFileWriterPort;
14
10
  };
15
11
 
16
12
  export class ImageGrayscaleAdapter implements ImageGrayscalePort {
@@ -19,11 +15,6 @@ export class ImageGrayscaleAdapter implements ImageGrayscalePort {
19
15
  async grayscale(recipe: ImageGrayscaleStrategy): Promise<tools.FilePathRelative | tools.FilePathAbsolute> {
20
16
  const final = recipe.strategy === "output_path" ? recipe.output : recipe.input;
21
17
 
22
- const filename = final.getFilename();
23
- const temporary = final.withFilename(
24
- filename.withSuffix(`-grayscale-${this.deps.NonceProvider.generate()}`),
25
- );
26
-
27
18
  const extension = final.getFilename().getExtension();
28
19
  const format = (extension === "jpg" ? "jpeg" : extension) as ImageSupportedType;
29
20
 
@@ -34,8 +25,7 @@ export class ImageGrayscaleAdapter implements ImageGrayscalePort {
34
25
  [format]()
35
26
  .bytes();
36
27
 
37
- await this.deps.FileWriter.write(temporary.get(), grayscaled);
38
- await this.deps.FileRenamer.rename(temporary, final);
28
+ await this.deps.AtomicFileWriter.write(final, grayscaled);
39
29
 
40
30
  return final;
41
31
  }
@@ -1,17 +1,13 @@
1
1
  // [BUN DEPENDENCY]
2
2
  import type * as tools from "@bgord/tools";
3
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
3
4
  import type { FileCleanerPort } from "./file-cleaner.port";
4
- import type { FileRenamerPort } from "./file-renamer.port";
5
- import type { FileWriterPort } from "./file-writer.port";
6
5
  import type { ImageSupportedType } from "./image.types";
7
6
  import type { ImageProcessorPort, ImageProcessorStrategy } from "./image-processor.port";
8
- import type { NonceProviderPort } from "./nonce-provider.port";
9
7
 
10
8
  type Dependencies = {
9
+ AtomicFileWriter: AtomicFileWriterPort;
11
10
  FileCleaner: FileCleanerPort;
12
- FileRenamer: FileRenamerPort;
13
- FileWriter: FileWriterPort;
14
- NonceProvider: NonceProviderPort;
15
11
  };
16
12
 
17
13
  export class ImageProcessorAdapter implements ImageProcessorPort {
@@ -25,9 +21,6 @@ export class ImageProcessorAdapter implements ImageProcessorPort {
25
21
  ? recipe.output
26
22
  : recipe.input.withFilename(recipe.input.getFilename().withExtension(recipe.to));
27
23
 
28
- const temporary = final.withFilename(
29
- final.getFilename().withSuffix(`-processed-${this.deps.NonceProvider.generate()}`),
30
- );
31
24
  const extension = final.getFilename().getExtension();
32
25
  const format = (extension === "jpg" ? "jpeg" : extension) as ImageSupportedType;
33
26
  const quality = recipe.quality ?? ImageProcessorAdapter.DEFAULT_QUALITY;
@@ -39,8 +32,7 @@ export class ImageProcessorAdapter implements ImageProcessorPort {
39
32
  [format]({ quality })
40
33
  .bytes();
41
34
 
42
- await this.deps.FileWriter.write(temporary.get(), processed);
43
- await this.deps.FileRenamer.rename(temporary, final);
35
+ await this.deps.AtomicFileWriter.write(final, processed);
44
36
 
45
37
  if (recipe.strategy === "in_place" && final.get() !== recipe.input.get()) {
46
38
  await this.deps.FileCleaner.delete(recipe.input.get());
@@ -1,16 +1,12 @@
1
1
  // [BUN DEPENDENCY]
2
2
  // cspell:ignore Resizer
3
3
  import type * as tools from "@bgord/tools";
4
- import type { FileRenamerPort } from "./file-renamer.port";
5
- import type { FileWriterPort } from "./file-writer.port";
4
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
6
5
  import type { ImageSupportedType } from "./image.types";
7
6
  import type { ImageResizerPort, ImageResizerStrategy } from "./image-resizer.port";
8
- import type { NonceProviderPort } from "./nonce-provider.port";
9
7
 
10
8
  type Dependencies = {
11
- FileWriter: FileWriterPort;
12
- FileRenamer: FileRenamerPort;
13
- NonceProvider: NonceProviderPort;
9
+ AtomicFileWriter: AtomicFileWriterPort;
14
10
  };
15
11
 
16
12
  export class ImageResizerAdapter implements ImageResizerPort {
@@ -18,10 +14,6 @@ export class ImageResizerAdapter implements ImageResizerPort {
18
14
 
19
15
  async resize(recipe: ImageResizerStrategy): Promise<tools.FilePathRelative | tools.FilePathAbsolute> {
20
16
  const final = recipe.strategy === "output_path" ? recipe.output : recipe.input;
21
- const filename = final.getFilename();
22
- const temporary = final.withFilename(
23
- filename.withSuffix(`-resized-${this.deps.NonceProvider.generate()}`),
24
- );
25
17
 
26
18
  const extension = final.getFilename().getExtension();
27
19
  const format = (extension === "jpg" ? "jpeg" : extension) as ImageSupportedType;
@@ -32,8 +24,7 @@ export class ImageResizerAdapter implements ImageResizerPort {
32
24
  [format]()
33
25
  .bytes();
34
26
 
35
- await this.deps.FileWriter.write(temporary.get(), resized);
36
- await this.deps.FileRenamer.rename(temporary, final);
27
+ await this.deps.AtomicFileWriter.write(final, resized);
37
28
 
38
29
  return final;
39
30
  }
package/src/index.ts CHANGED
@@ -36,6 +36,10 @@ export * from "./antivirus-with-semaphore.adapter";
36
36
  export * from "./antivirus-with-timeout.adapter";
37
37
  export * from "./api-version.middleware";
38
38
  export * from "./api-version-hono.middleware";
39
+ export * from "./atomic-file-writer.adapter";
40
+ export * from "./atomic-file-writer.port";
41
+ export * from "./atomic-file-writer-collecting.adapter";
42
+ export * from "./atomic-file-writer-noop.adapter";
39
43
  export * from "./auth-session-reader.port";
40
44
  export * from "./auth-session-reader-better-auth.adapter";
41
45
  export * from "./auth-session-reader-noop.adapter";
@@ -148,6 +152,7 @@ export * from "./event-loop-lag.service";
148
152
  export * from "./event-loop-utilization.service";
149
153
  export * from "./event-revision-assigner.adapter";
150
154
  export * from "./event-revision-assigner.port";
155
+ export * from "./event-sourced-repository.adapter";
151
156
  export * from "./event-store.adapter";
152
157
  export * from "./event-store.port";
153
158
  export * from "./event-store-collecting.adapter";
@@ -311,6 +316,10 @@ export * from "./job-queue-stats-provider-noop.adapter";
311
316
  export * from "./job-queue-stats-provider-sqlite.adapter";
312
317
  export * from "./job-queue-with-logger.adapter";
313
318
  export * from "./job-queue-worker.service";
319
+ export * from "./job-recoverer.port";
320
+ export * from "./job-recoverer-collecting.adapter";
321
+ export * from "./job-recoverer-noop.adapter";
322
+ export * from "./job-recoverer-sqlite.adapter";
314
323
  export * from "./job-registry.adapter";
315
324
  export * from "./job-registry.port";
316
325
  export * from "./job-requeuer.port";
@@ -359,6 +368,7 @@ export * from "./message-bus.port";
359
368
  export * from "./message-bus-collecting.adapter";
360
369
  export * from "./message-bus-emittery.adapter";
361
370
  export * from "./message-bus-noop.adapter";
371
+ export * from "./message-bus-strict.adapter";
362
372
  export * from "./message-bus-with-logger.adapter";
363
373
  export * from "./middleware-hono.port";
364
374
  export * from "./middleware-hono-noop.adapter";
@@ -441,6 +451,9 @@ export * from "./retry-backoff-exponential.strategy";
441
451
  export * from "./retry-backoff-jitter.strategy";
442
452
  export * from "./retry-backoff-linear.strategy";
443
453
  export * from "./retry-backoff-noop.strategy";
454
+ export * from "./runtime-stats-provider.port";
455
+ export * from "./runtime-stats-provider-noop.adapter";
456
+ export * from "./runtime-stats-provider-system.adapter";
444
457
  export * from "./sealer.port";
445
458
  export * from "./sealer-aes-gcm.adapter";
446
459
  export * from "./sealer-noop.adapter";
@@ -3,6 +3,7 @@ import type { Database } from "bun:sqlite";
3
3
  import * as tools from "@bgord/tools";
4
4
  import type { ClockPort } from "./clock.port";
5
5
  import type { JobPrunerPort } from "./job-pruner.port";
6
+ import { JobStatusEnum } from "./job-status.vo";
6
7
 
7
8
  type Dependencies = { db: Database; Clock: ClockPort };
8
9
 
@@ -14,7 +15,7 @@ export class JobPrunerSqliteAdapter implements JobPrunerPort {
14
15
 
15
16
  const result = this.deps.db.run<[tools.TimestampValueType]>(
16
17
  `DELETE FROM jobs
17
- WHERE status IN ('completed', 'failed')
18
+ WHERE status IN ('${JobStatusEnum.completed}', '${JobStatusEnum.failed}')
18
19
  AND createdAt <= ?`,
19
20
  [threshold],
20
21
  );
@@ -25,10 +25,10 @@ export class JobQueueStatsProviderSqliteAdapter implements JobQueueStatsProvider
25
25
  const counts = Object.fromEntries(snapshot.map((row) => [row.status, row.count]));
26
26
 
27
27
  return {
28
- pending: tools.Int.nonNegative(counts["pending"] ?? 0),
29
- claimed: tools.Int.nonNegative(counts["claimed"] ?? 0),
30
- completed: tools.Int.nonNegative(counts["completed"] ?? 0),
31
- failed: tools.Int.nonNegative(counts["failed"] ?? 0),
28
+ pending: tools.Int.nonNegative(counts[JobStatusEnum.pending] ?? 0),
29
+ claimed: tools.Int.nonNegative(counts[JobStatusEnum.claimed] ?? 0),
30
+ completed: tools.Int.nonNegative(counts[JobStatusEnum.completed] ?? 0),
31
+ failed: tools.Int.nonNegative(counts[JobStatusEnum.failed] ?? 0),
32
32
  oldestPending: oldestPending?.createdAt ? tools.Timestamp.fromNumber(oldestPending.createdAt).ms : null,
33
33
  };
34
34
  }
@@ -0,0 +1,14 @@
1
+ import * as tools from "@bgord/tools";
2
+ import type { JobRecovererPort } from "./job-recoverer.port";
3
+
4
+ export class JobRecovererCollectingAdapter implements JobRecovererPort {
5
+ readonly recovered: Array<tools.IntegerNonNegativeType> = [];
6
+
7
+ async recover(): Promise<tools.IntegerNonNegativeType> {
8
+ const count = tools.Int.nonNegative(0);
9
+
10
+ this.recovered.push(count);
11
+
12
+ return count;
13
+ }
14
+ }
@@ -0,0 +1,8 @@
1
+ import * as tools from "@bgord/tools";
2
+ import type { JobRecovererPort } from "./job-recoverer.port";
3
+
4
+ export class JobRecovererNoopAdapter implements JobRecovererPort {
5
+ async recover(): Promise<tools.IntegerNonNegativeType> {
6
+ return tools.Int.nonNegative(0);
7
+ }
8
+ }
@@ -0,0 +1,20 @@
1
+ // [BUN DEPENDENCY]
2
+ import type { Database } from "bun:sqlite";
3
+ import * as tools from "@bgord/tools";
4
+ import type { JobRecovererPort } from "./job-recoverer.port";
5
+ import { JobStatusEnum } from "./job-status.vo";
6
+
7
+ type Dependencies = { db: Database };
8
+
9
+ // Single-process only: at boot, every claimed job was left behind by a dead process.
10
+ export class JobRecovererSqliteAdapter implements JobRecovererPort {
11
+ constructor(private readonly deps: Dependencies) {}
12
+
13
+ async recover(): Promise<tools.IntegerNonNegativeType> {
14
+ const result = this.deps.db.run(
15
+ `UPDATE jobs SET status = '${JobStatusEnum.pending}' WHERE status = '${JobStatusEnum.claimed}'`,
16
+ );
17
+
18
+ return tools.Int.nonNegative(result.changes);
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ import type * as tools from "@bgord/tools";
2
+
3
+ export interface JobRecovererPort {
4
+ recover(): Promise<tools.IntegerNonNegativeType>;
5
+ }
@@ -1,6 +1,9 @@
1
1
  import * as tools from "@bgord/tools";
2
2
 
3
- type MemoryConsumptionSnapshotType = { total: tools.Size; heap: { used: tools.Size; total: tools.Size } };
3
+ export type MemoryConsumptionSnapshotType = {
4
+ total: tools.Size;
5
+ heap: { used: tools.Size; total: tools.Size };
6
+ };
4
7
 
5
8
  // RSS - resident set size - how much memory this process uses
6
9
  // Heap total - how much memory is reserved for the JS heap
@@ -0,0 +1,33 @@
1
+ import type { Message, ToMessageMap } from "./message.types";
2
+ import type { MessageBusPort } from "./message-bus.port";
3
+
4
+ export const MessageBusStrictAdapterError = {
5
+ NoHandler: "message.bus.strict.adapter.no.handler",
6
+ DuplicateHandler: "message.bus.strict.adapter.duplicate.handler",
7
+ };
8
+
9
+ type Dependencies<Messages extends Message> = { inner: MessageBusPort<Messages> };
10
+
11
+ export class MessageBusStrictAdapter<Messages extends Message> implements MessageBusPort<Messages> {
12
+ private readonly registered = new Set<PropertyKey>();
13
+
14
+ constructor(private readonly deps: Dependencies<Messages>) {}
15
+
16
+ async emit<M extends Messages>(message: M): Promise<void> {
17
+ if (!this.registered.has(message.name)) throw new Error(MessageBusStrictAdapterError.NoHandler);
18
+
19
+ await this.deps.inner.emit(message);
20
+ }
21
+
22
+ on<MessageName extends keyof ToMessageMap<Messages>>(
23
+ name: MessageName,
24
+ handler: (message: ToMessageMap<Messages>[MessageName]) => void | Promise<void>,
25
+ ): void {
26
+ if (this.registered.has(name)) throw new Error(MessageBusStrictAdapterError.DuplicateHandler);
27
+
28
+ this.registered.add(name);
29
+ this.deps.inner.on(name, handler);
30
+ }
31
+ }
32
+
33
+ export const CommandBusStrictAdapter = MessageBusStrictAdapter;
@@ -0,0 +1,14 @@
1
+ export type PathMatcherRule = string | URLPattern;
2
+
3
+ export class PathMatcher {
4
+ static matches(rules: ReadonlyArray<PathMatcherRule> | undefined, path: string): boolean {
5
+ return rules?.some((rule) => PathMatcher.matchesRule(rule, path)) ?? false;
6
+ }
7
+
8
+ private static matchesRule(rule: PathMatcherRule, path: string): boolean {
9
+ if (rule instanceof URLPattern) return rule.test({ pathname: path });
10
+ if (path === rule) return true;
11
+
12
+ return path.startsWith(rule.endsWith("/") ? rule : `${rule}/`);
13
+ }
14
+ }
@@ -1,13 +1,11 @@
1
1
  // [BUN DEPENDENCY]
2
2
  import * as tools from "@bgord/tools";
3
3
  import * as v from "valibot";
4
+ import type { AtomicFileWriterPort } from "./atomic-file-writer.port";
4
5
  import type { DirectoryEnsurerPort } from "./directory-ensurer.port";
5
6
  import type { FileCleanerPort } from "./file-cleaner.port";
6
- import type { FileCopierPort } from "./file-copier.port";
7
7
  import type { FileInspectionPort } from "./file-inspection.port";
8
- import type { FileRenamerPort } from "./file-renamer.port";
9
8
  import type { HashFilePort } from "./hash-file.port";
10
- import type { NonceProviderPort } from "./nonce-provider.port";
11
9
  import type {
12
10
  RemoteFileStoragePort,
13
11
  RemoteHeadResult,
@@ -16,13 +14,11 @@ import type {
16
14
  } from "./remote-file-storage.port";
17
15
 
18
16
  type Dependencies = {
17
+ AtomicFileWriter: AtomicFileWriterPort;
19
18
  HashFile: HashFilePort;
20
19
  FileInspection: FileInspectionPort;
21
20
  FileCleaner: FileCleanerPort;
22
- FileRenamer: FileRenamerPort;
23
- FileCopier: FileCopierPort;
24
21
  DirectoryEnsurer: DirectoryEnsurerPort;
25
- NonceProvider: NonceProviderPort;
26
22
  };
27
23
 
28
24
  type Config = { root: tools.DirectoryPathAbsoluteType };
@@ -47,19 +43,9 @@ export class RemoteFileStorageDiskAdapter implements RemoteFileStoragePort {
47
43
 
48
44
  async putFromPath(input: RemotePutFromPathInput): Promise<RemotePutFromPathResult> {
49
45
  const final = this.resolveKeyToAbsoluteFilePath(input.key);
50
- const temporary = final.withFilename(
51
- final.getFilename().withSuffix(`-part-${this.deps.NonceProvider.generate()}`),
52
- );
53
46
 
54
47
  await this.deps.DirectoryEnsurer.ensure(final.getDirectory());
55
- await this.deps.FileCopier.copy(input.path, temporary);
56
-
57
- try {
58
- await this.deps.FileRenamer.rename(temporary, final);
59
- } catch (error) {
60
- await this.deps.FileCleaner.delete(temporary);
61
- throw error;
62
- }
48
+ await this.deps.AtomicFileWriter.write(final, Bun.file(input.path.get()));
63
49
 
64
50
  return this.deps.HashFile.hash(final);
65
51
  }
@@ -4,8 +4,7 @@ import type { Context } from "hono";
4
4
  import { getConnInfo } from "hono/bun";
5
5
  import { getCookie } from "hono/cookie";
6
6
  import { HTTPException } from "hono/http-exception";
7
- import type { RequestContext } from "./request-context.port";
8
- import { ShieldAuthStrategyError } from "./shield-auth.strategy";
7
+ import { type RequestContext, RequestIdentityError } from "./request-context.port";
9
8
 
10
9
  export class RequestContextHonoAdapter implements RequestContext {
11
10
  readonly request: RequestContext["request"];
@@ -58,8 +57,8 @@ export class RequestContextHonoAdapter implements RequestContext {
58
57
  authenticatedUserId: () => {
59
58
  const user = context.get("user");
60
59
 
61
- if (user === undefined) throw new Error(ShieldAuthStrategyError.NotAttached);
62
- if (user === null) throw new HTTPException(401, { message: ShieldAuthStrategyError.Rejected });
60
+ if (user === undefined) throw new Error(RequestIdentityError.NotAttached);
61
+ if (user === null) throw new HTTPException(401, { message: RequestIdentityError.Rejected });
63
62
  return user.id;
64
63
  },
65
64
  ip: () =>
@@ -1,6 +1,11 @@
1
1
  import type * as tools from "@bgord/tools";
2
2
  import type { UUIDType } from "./uuid.vo";
3
3
 
4
+ export const RequestIdentityError = {
5
+ Rejected: "shield.auth.rejected",
6
+ NotAttached: "shield.auth.not.attached",
7
+ };
8
+
4
9
  export interface RequestContext {
5
10
  readonly request: {
6
11
  readonly path: string;
@@ -0,0 +1,16 @@
1
+ import * as tools from "@bgord/tools";
2
+ import type { RuntimeStatsProviderPort, RuntimeStatsSnapshot } from "./runtime-stats-provider.port";
3
+
4
+ export class RuntimeStatsProviderNoopAdapter implements RuntimeStatsProviderPort {
5
+ getStats(): RuntimeStatsSnapshot {
6
+ const zero = tools.Duration.Ms(0);
7
+ const empty = tools.Size.fromBytes(0);
8
+
9
+ return {
10
+ uptime: { duration: zero, formatted: "0 seconds ago" },
11
+ memory: { total: empty, heap: { used: empty, total: empty } },
12
+ eventLoop: { lag: { p50: zero, p95: zero, p99: zero }, utilization: 0 },
13
+ inFlight: tools.Int.of(0),
14
+ };
15
+ }
16
+ }
@@ -0,0 +1,24 @@
1
+ import type { ClockPort } from "./clock.port";
2
+ import { EventLoopLag } from "./event-loop-lag.service";
3
+ import { EventLoopUtilization } from "./event-loop-utilization.service";
4
+ import { InFlightRequestsTracker } from "./in-flight-requests-tracker.service";
5
+ import { MemoryConsumption } from "./memory-consumption.service";
6
+ import type { RuntimeStatsProviderPort, RuntimeStatsSnapshot } from "./runtime-stats-provider.port";
7
+ import { Uptime } from "./uptime.service";
8
+
9
+ type Dependencies = { Clock: ClockPort };
10
+
11
+ export class RuntimeStatsProviderSystemAdapter implements RuntimeStatsProviderPort {
12
+ constructor(private readonly deps: Dependencies) {
13
+ EventLoopLag.start();
14
+ }
15
+
16
+ getStats(): RuntimeStatsSnapshot {
17
+ return {
18
+ uptime: Uptime.get(this.deps.Clock),
19
+ memory: MemoryConsumption.snapshot(),
20
+ eventLoop: { lag: EventLoopLag.snapshot(), utilization: EventLoopUtilization.snapshot() },
21
+ inFlight: InFlightRequestsTracker.get(),
22
+ };
23
+ }
24
+ }
@@ -0,0 +1,16 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { EventLoopLagSnapshotType } from "./event-loop-lag.service";
3
+ import type { EventLoopUtilizationSnapshot } from "./event-loop-utilization.service";
4
+ import type { MemoryConsumptionSnapshotType } from "./memory-consumption.service";
5
+ import type { UptimeResultType } from "./uptime.service";
6
+
7
+ export type RuntimeStatsSnapshot = {
8
+ uptime: UptimeResultType;
9
+ memory: MemoryConsumptionSnapshotType;
10
+ eventLoop: { lag: EventLoopLagSnapshotType; utilization: EventLoopUtilizationSnapshot };
11
+ inFlight: tools.IntegerType;
12
+ };
13
+
14
+ export interface RuntimeStatsProviderPort {
15
+ getStats(): RuntimeStatsSnapshot;
16
+ }
@@ -31,12 +31,12 @@ export class SecurityRuleViolationThresholdStrategy implements SecurityRuleStrat
31
31
 
32
32
  // Best-effort increment, occasional lost increments are acceptable for concurrent requests.
33
33
  async isViolated(context: RequestContext): Promise<boolean> {
34
- const subject = await this.resolver.resolve(context);
35
-
36
34
  const violated = await this.rule.isViolated(context);
37
35
 
38
36
  if (!violated) return false;
39
37
 
38
+ const subject = await this.resolver.resolve(context);
39
+
40
40
  try {
41
41
  const cached = await this.deps.CacheRepository.get(subject.hex);
42
42
  const count = typeof cached === "number" ? cached : 0;
@@ -50,9 +50,9 @@ export class SetupHono {
50
50
  deps: Dependencies,
51
51
  ): Array<MiddlewareHandler> {
52
52
  return [
53
+ new CorrelationHonoMiddleware(deps).handle(),
53
54
  new ShieldMaintenanceHonoStrategy(config.maintenanceMode).handle(),
54
55
  new TrailingSlashHonoMiddleware().handle(),
55
- new CorrelationHonoMiddleware(deps).handle(),
56
56
  new TimingHonoMiddleware(deps).handle(),
57
57
  new ApiVersionHonoMiddleware(deps).handle(),
58
58
  new ShieldCsrfHonoStrategy(config.csrf).handle(),