@satorio/machinery 0.2.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 (341) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dictionary/all.json +4656 -0
  4. package/dictionary/all.toon +639 -0
  5. package/dictionary/core.json +221 -0
  6. package/dictionary/core.toon +33 -0
  7. package/dictionary/graphql.json +212 -0
  8. package/dictionary/graphql.toon +32 -0
  9. package/dictionary/http.json +113 -0
  10. package/dictionary/http.toon +21 -0
  11. package/dictionary/identifiers.json +266 -0
  12. package/dictionary/identifiers.toon +38 -0
  13. package/dictionary/index.json +96 -0
  14. package/dictionary/index.toon +18 -0
  15. package/dictionary/integration.json +131 -0
  16. package/dictionary/integration.toon +23 -0
  17. package/dictionary/money.json +383 -0
  18. package/dictionary/money.toon +51 -0
  19. package/dictionary/ports.json +392 -0
  20. package/dictionary/ports.toon +52 -0
  21. package/dictionary/postgres.json +473 -0
  22. package/dictionary/postgres.toon +61 -0
  23. package/dictionary/publishing.json +131 -0
  24. package/dictionary/publishing.toon +23 -0
  25. package/dictionary/queue.json +365 -0
  26. package/dictionary/queue.toon +49 -0
  27. package/dictionary/reference/api.mdx +843 -0
  28. package/dictionary/reference/uk/api.mdx +844 -0
  29. package/dictionary/storage.json +194 -0
  30. package/dictionary/storage.toon +30 -0
  31. package/dictionary/testing.json +707 -0
  32. package/dictionary/testing.toon +87 -0
  33. package/dictionary/time.json +311 -0
  34. package/dictionary/time.toon +43 -0
  35. package/dictionary/vitest.json +356 -0
  36. package/dictionary/vitest.toon +48 -0
  37. package/dictionary/workflow.json +185 -0
  38. package/dictionary/workflow.toon +29 -0
  39. package/dist/bin/command.d.ts +45 -0
  40. package/dist/bin/command.js +113 -0
  41. package/dist/bin.d.ts +2 -0
  42. package/dist/bin.js +11 -0
  43. package/dist/core/bigint-json.d.ts +6 -0
  44. package/dist/core/bigint-json.js +6 -0
  45. package/dist/core/closed-set.d.ts +14 -0
  46. package/dist/core/closed-set.js +8 -0
  47. package/dist/core/domain-error.d.ts +8 -0
  48. package/dist/core/domain-error.js +11 -0
  49. package/dist/core/environment.d.ts +124 -0
  50. package/dist/core/environment.js +115 -0
  51. package/dist/core/index.d.ts +13 -0
  52. package/dist/core/index.js +13 -0
  53. package/dist/core/ulid.d.ts +27 -0
  54. package/dist/core/ulid.js +27 -0
  55. package/dist/graphql/builder.d.ts +36 -0
  56. package/dist/graphql/builder.js +35 -0
  57. package/dist/graphql/context.d.ts +36 -0
  58. package/dist/graphql/context.js +69 -0
  59. package/dist/graphql/failure-classification.d.ts +11 -0
  60. package/dist/graphql/failure-classification.js +1 -0
  61. package/dist/graphql/failures.d.ts +62 -0
  62. package/dist/graphql/failures.js +30 -0
  63. package/dist/graphql/graphiql.d.ts +45 -0
  64. package/dist/graphql/graphiql.js +56 -0
  65. package/dist/graphql/index.d.ts +15 -0
  66. package/dist/graphql/index.js +14 -0
  67. package/dist/graphql/layer.d.ts +15 -0
  68. package/dist/graphql/layer.js +23 -0
  69. package/dist/graphql/request-handler.d.ts +26 -0
  70. package/dist/graphql/request-handler.js +33 -0
  71. package/dist/graphql/server.d.ts +19 -0
  72. package/dist/graphql/server.js +16 -0
  73. package/dist/graphql/settings.d.ts +55 -0
  74. package/dist/graphql/settings.js +82 -0
  75. package/dist/http/health.d.ts +70 -0
  76. package/dist/http/health.js +60 -0
  77. package/dist/http/index.d.ts +5 -0
  78. package/dist/http/index.js +5 -0
  79. package/dist/identifiers/client-code.d.ts +37 -0
  80. package/dist/identifiers/client-code.js +41 -0
  81. package/dist/identifiers/creditor-reference.d.ts +38 -0
  82. package/dist/identifiers/creditor-reference.js +32 -0
  83. package/dist/identifiers/damm.d.ts +22 -0
  84. package/dist/identifiers/damm.js +50 -0
  85. package/dist/identifiers/document-number.d.ts +68 -0
  86. package/dist/identifiers/document-number.js +73 -0
  87. package/dist/identifiers/document-numbers.d.ts +27 -0
  88. package/dist/identifiers/document-numbers.js +16 -0
  89. package/dist/identifiers/failures.d.ts +46 -0
  90. package/dist/identifiers/failures.js +48 -0
  91. package/dist/identifiers/index.d.ts +14 -0
  92. package/dist/identifiers/index.js +13 -0
  93. package/dist/identifiers/next-document-number.d.ts +26 -0
  94. package/dist/identifiers/next-document-number.js +31 -0
  95. package/dist/identifiers/ulid.d.ts +28 -0
  96. package/dist/identifiers/ulid.js +71 -0
  97. package/dist/integration/external-http-client.d.ts +22 -0
  98. package/dist/integration/external-http-client.js +74 -0
  99. package/dist/integration/index.d.ts +12 -0
  100. package/dist/integration/index.js +12 -0
  101. package/dist/integration/layer.d.ts +19 -0
  102. package/dist/integration/layer.js +30 -0
  103. package/dist/integration/retry.d.ts +65 -0
  104. package/dist/integration/retry.js +54 -0
  105. package/dist/integration/settings.d.ts +73 -0
  106. package/dist/integration/settings.js +92 -0
  107. package/dist/money/amount.d.ts +60 -0
  108. package/dist/money/amount.js +67 -0
  109. package/dist/money/arithmetic.d.ts +60 -0
  110. package/dist/money/arithmetic.js +73 -0
  111. package/dist/money/currency.d.ts +25 -0
  112. package/dist/money/currency.js +24 -0
  113. package/dist/money/decimal.d.ts +29 -0
  114. package/dist/money/decimal.js +39 -0
  115. package/dist/money/exchange-rate.d.ts +32 -0
  116. package/dist/money/exchange-rate.js +28 -0
  117. package/dist/money/failures.d.ts +86 -0
  118. package/dist/money/failures.js +97 -0
  119. package/dist/money/index.d.ts +15 -0
  120. package/dist/money/index.js +15 -0
  121. package/dist/money/percent.d.ts +35 -0
  122. package/dist/money/percent.js +50 -0
  123. package/dist/money/schema.d.ts +25 -0
  124. package/dist/money/schema.js +41 -0
  125. package/dist/ports/blob-store.d.ts +42 -0
  126. package/dist/ports/blob-store.js +13 -0
  127. package/dist/ports/document-number-counter.d.ts +36 -0
  128. package/dist/ports/document-number-counter.js +12 -0
  129. package/dist/ports/event-publisher.d.ts +29 -0
  130. package/dist/ports/event-publisher.js +13 -0
  131. package/dist/ports/external-http-client.d.ts +60 -0
  132. package/dist/ports/external-http-client.js +25 -0
  133. package/dist/ports/index.d.ts +17 -0
  134. package/dist/ports/index.js +17 -0
  135. package/dist/ports/integration-event-outbox.d.ts +44 -0
  136. package/dist/ports/integration-event-outbox.js +13 -0
  137. package/dist/ports/port-unavailable.d.ts +21 -0
  138. package/dist/ports/port-unavailable.js +30 -0
  139. package/dist/ports/processed-event-registry.d.ts +57 -0
  140. package/dist/ports/processed-event-registry.js +24 -0
  141. package/dist/ports/queue-message-producer.d.ts +26 -0
  142. package/dist/ports/queue-message-producer.js +12 -0
  143. package/dist/ports/queue-message-source.d.ts +39 -0
  144. package/dist/ports/queue-message-source.js +13 -0
  145. package/dist/ports/readiness-probe.d.ts +27 -0
  146. package/dist/ports/readiness-probe.js +13 -0
  147. package/dist/ports/transaction-boundary.d.ts +28 -0
  148. package/dist/ports/transaction-boundary.js +13 -0
  149. package/dist/postgres/client.d.ts +51 -0
  150. package/dist/postgres/client.js +81 -0
  151. package/dist/postgres/document-number-counter.d.ts +14 -0
  152. package/dist/postgres/document-number-counter.js +27 -0
  153. package/dist/postgres/failures.d.ts +28 -0
  154. package/dist/postgres/failures.js +18 -0
  155. package/dist/postgres/index.d.ts +18 -0
  156. package/dist/postgres/index.js +18 -0
  157. package/dist/postgres/integration-event-outbox.d.ts +15 -0
  158. package/dist/postgres/integration-event-outbox.js +60 -0
  159. package/dist/postgres/jsonb-value.d.ts +10 -0
  160. package/dist/postgres/jsonb-value.js +9 -0
  161. package/dist/postgres/layer.d.ts +57 -0
  162. package/dist/postgres/layer.js +72 -0
  163. package/dist/postgres/migrations/advisory-lock.d.ts +47 -0
  164. package/dist/postgres/migrations/advisory-lock.js +59 -0
  165. package/dist/postgres/migrations/runner.d.ts +56 -0
  166. package/dist/postgres/migrations/runner.js +69 -0
  167. package/dist/postgres/migrations/sql-file-loader.d.ts +59 -0
  168. package/dist/postgres/migrations/sql-file-loader.js +99 -0
  169. package/dist/postgres/processed-event-registry.d.ts +13 -0
  170. package/dist/postgres/processed-event-registry.js +44 -0
  171. package/dist/postgres/readiness-probe.d.ts +27 -0
  172. package/dist/postgres/readiness-probe.js +33 -0
  173. package/dist/postgres/settings.d.ts +134 -0
  174. package/dist/postgres/settings.js +210 -0
  175. package/dist/postgres/sql/0001_processed_event.sql +4 -0
  176. package/dist/postgres/sql/0002_integration_event_outbox.sql +13 -0
  177. package/dist/postgres/sql/0003_document_number_counter.sql +6 -0
  178. package/dist/postgres/sql-failure.d.ts +19 -0
  179. package/dist/postgres/sql-failure.js +22 -0
  180. package/dist/postgres/store-failure.d.ts +17 -0
  181. package/dist/postgres/store-failure.js +16 -0
  182. package/dist/postgres/transaction-boundary.d.ts +13 -0
  183. package/dist/postgres/transaction-boundary.js +21 -0
  184. package/dist/publishing/event-publisher.sns.d.ts +66 -0
  185. package/dist/publishing/event-publisher.sns.js +75 -0
  186. package/dist/publishing/event-publisher.stdout.d.ts +15 -0
  187. package/dist/publishing/event-publisher.stdout.js +24 -0
  188. package/dist/publishing/index.d.ts +10 -0
  189. package/dist/publishing/index.js +10 -0
  190. package/dist/publishing/layer.d.ts +25 -0
  191. package/dist/publishing/layer.js +39 -0
  192. package/dist/publishing/settings.d.ts +60 -0
  193. package/dist/publishing/settings.js +85 -0
  194. package/dist/queue/consumer.d.ts +91 -0
  195. package/dist/queue/consumer.js +92 -0
  196. package/dist/queue/envelope.d.ts +39 -0
  197. package/dist/queue/envelope.js +39 -0
  198. package/dist/queue/failures.d.ts +33 -0
  199. package/dist/queue/failures.js +15 -0
  200. package/dist/queue/index.d.ts +14 -0
  201. package/dist/queue/index.js +14 -0
  202. package/dist/queue/layer.d.ts +33 -0
  203. package/dist/queue/layer.js +58 -0
  204. package/dist/queue/message-codec.d.ts +43 -0
  205. package/dist/queue/message-codec.js +33 -0
  206. package/dist/queue/queue-message-producer.d.ts +61 -0
  207. package/dist/queue/queue-message-producer.js +52 -0
  208. package/dist/queue/queue-message-source.d.ts +93 -0
  209. package/dist/queue/queue-message-source.js +86 -0
  210. package/dist/queue/readiness.d.ts +78 -0
  211. package/dist/queue/readiness.js +86 -0
  212. package/dist/queue/settings.d.ts +106 -0
  213. package/dist/queue/settings.js +160 -0
  214. package/dist/storage/blob-store.d.ts +84 -0
  215. package/dist/storage/blob-store.js +109 -0
  216. package/dist/storage/index.d.ts +9 -0
  217. package/dist/storage/index.js +9 -0
  218. package/dist/storage/layer.d.ts +17 -0
  219. package/dist/storage/layer.js +30 -0
  220. package/dist/storage/settings.d.ts +48 -0
  221. package/dist/storage/settings.js +65 -0
  222. package/dist/testing/blob-store.d.ts +27 -0
  223. package/dist/testing/blob-store.js +45 -0
  224. package/dist/testing/clock.d.ts +29 -0
  225. package/dist/testing/clock.js +41 -0
  226. package/dist/testing/dna-perturbator/attention.d.ts +55 -0
  227. package/dist/testing/dna-perturbator/attention.js +49 -0
  228. package/dist/testing/dna-perturbator/chains.d.ts +31 -0
  229. package/dist/testing/dna-perturbator/chains.js +53 -0
  230. package/dist/testing/dna-perturbator/report.d.ts +12 -0
  231. package/dist/testing/dna-perturbator/report.js +22 -0
  232. package/dist/testing/dna-perturbator/verdict.d.ts +26 -0
  233. package/dist/testing/dna-perturbator/verdict.js +28 -0
  234. package/dist/testing/dna-perturbator.d.ts +48 -0
  235. package/dist/testing/dna-perturbator.js +145 -0
  236. package/dist/testing/document-number-counter.d.ts +29 -0
  237. package/dist/testing/document-number-counter.js +39 -0
  238. package/dist/testing/event-publisher.d.ts +28 -0
  239. package/dist/testing/event-publisher.js +33 -0
  240. package/dist/testing/external-http-client.d.ts +29 -0
  241. package/dist/testing/external-http-client.js +49 -0
  242. package/dist/testing/index.d.ts +24 -0
  243. package/dist/testing/index.js +24 -0
  244. package/dist/testing/integration-event-outbox.d.ts +37 -0
  245. package/dist/testing/integration-event-outbox.js +67 -0
  246. package/dist/testing/mutation-settings.d.ts +71 -0
  247. package/dist/testing/mutation-settings.js +57 -0
  248. package/dist/testing/postgres-rows.d.ts +12 -0
  249. package/dist/testing/postgres-rows.js +19 -0
  250. package/dist/testing/processed-event-registry.d.ts +25 -0
  251. package/dist/testing/processed-event-registry.js +39 -0
  252. package/dist/testing/queue-message-producer.d.ts +27 -0
  253. package/dist/testing/queue-message-producer.js +41 -0
  254. package/dist/testing/queue-message-source.d.ts +54 -0
  255. package/dist/testing/queue-message-source.js +71 -0
  256. package/dist/testing/quiet-reporter.d.ts +86 -0
  257. package/dist/testing/quiet-reporter.js +81 -0
  258. package/dist/testing/readiness-probe.d.ts +27 -0
  259. package/dist/testing/readiness-probe.js +33 -0
  260. package/dist/testing/scenario.d.ts +94 -0
  261. package/dist/testing/scenario.js +81 -0
  262. package/dist/testing/transaction-boundary.d.ts +30 -0
  263. package/dist/testing/transaction-boundary.js +36 -0
  264. package/dist/testing/typed-failure.d.ts +8 -0
  265. package/dist/testing/typed-failure.js +15 -0
  266. package/dist/time/calendar-day.d.ts +83 -0
  267. package/dist/time/calendar-day.js +112 -0
  268. package/dist/time/failures.d.ts +51 -0
  269. package/dist/time/failures.js +56 -0
  270. package/dist/time/index.d.ts +11 -0
  271. package/dist/time/index.js +11 -0
  272. package/dist/time/instant.d.ts +64 -0
  273. package/dist/time/instant.js +67 -0
  274. package/dist/time/period.d.ts +45 -0
  275. package/dist/time/period.js +55 -0
  276. package/dist/vitest/container-reuse.d.ts +14 -0
  277. package/dist/vitest/container-reuse.js +15 -0
  278. package/dist/vitest/dotenv.d.ts +16 -0
  279. package/dist/vitest/dotenv.js +48 -0
  280. package/dist/vitest/effect-single-instance.d.ts +36 -0
  281. package/dist/vitest/effect-single-instance.js +125 -0
  282. package/dist/vitest/fast-check-single-instance.d.ts +11 -0
  283. package/dist/vitest/fast-check-single-instance.js +26 -0
  284. package/dist/vitest/index.d.ts +31 -0
  285. package/dist/vitest/index.js +30 -0
  286. package/dist/vitest/localstack-container.d.ts +32 -0
  287. package/dist/vitest/localstack-container.js +55 -0
  288. package/dist/vitest/port-contract-tester.d.ts +8 -0
  289. package/dist/vitest/port-contract-tester.js +1 -0
  290. package/dist/vitest/port-contracts/blob-store.contract.d.ts +22 -0
  291. package/dist/vitest/port-contracts/blob-store.contract.js +108 -0
  292. package/dist/vitest/port-contracts/document-number-counter.contract.d.ts +18 -0
  293. package/dist/vitest/port-contracts/document-number-counter.contract.js +90 -0
  294. package/dist/vitest/port-contracts/event-publisher.contract.d.ts +35 -0
  295. package/dist/vitest/port-contracts/event-publisher.contract.js +67 -0
  296. package/dist/vitest/port-contracts/external-http-client.contract.d.ts +35 -0
  297. package/dist/vitest/port-contracts/external-http-client.contract.js +52 -0
  298. package/dist/vitest/port-contracts/integration-event-outbox.contract.d.ts +23 -0
  299. package/dist/vitest/port-contracts/integration-event-outbox.contract.js +75 -0
  300. package/dist/vitest/port-contracts/processed-event-registry.contract.d.ts +18 -0
  301. package/dist/vitest/port-contracts/processed-event-registry.contract.js +84 -0
  302. package/dist/vitest/port-contracts/queue-message-producer.contract.d.ts +26 -0
  303. package/dist/vitest/port-contracts/queue-message-producer.contract.js +48 -0
  304. package/dist/vitest/port-contracts/queue-message-source.contract.d.ts +29 -0
  305. package/dist/vitest/port-contracts/queue-message-source.contract.js +71 -0
  306. package/dist/vitest/port-contracts/readiness-probe.contract.d.ts +18 -0
  307. package/dist/vitest/port-contracts/readiness-probe.contract.js +41 -0
  308. package/dist/vitest/port-contracts/transaction-boundary.contract.d.ts +21 -0
  309. package/dist/vitest/port-contracts/transaction-boundary.contract.js +64 -0
  310. package/dist/vitest/postgres-container.d.ts +57 -0
  311. package/dist/vitest/postgres-container.js +118 -0
  312. package/dist/vitest/reader-port-contract.d.ts +41 -0
  313. package/dist/vitest/reader-port-contract.js +43 -0
  314. package/dist/vitest/repo-source-files.d.ts +37 -0
  315. package/dist/vitest/repo-source-files.js +61 -0
  316. package/dist/vitest/reset-tables.d.ts +18 -0
  317. package/dist/vitest/reset-tables.js +30 -0
  318. package/dist/vitest/settings-contract.d.ts +38 -0
  319. package/dist/vitest/settings-contract.js +77 -0
  320. package/dist/vitest/stub-server.d.ts +43 -0
  321. package/dist/vitest/stub-server.js +60 -0
  322. package/dist/vitest/term-registry.json +730 -0
  323. package/dist/vitest/testclock-fork-isolated-files.d.ts +13 -0
  324. package/dist/vitest/testclock-fork-isolated-files.js +18 -0
  325. package/dist/vitest/testclock-fork-isolation.d.ts +77 -0
  326. package/dist/vitest/testclock-fork-isolation.js +188 -0
  327. package/dist/workflow/claim-once.d.ts +16 -0
  328. package/dist/workflow/claim-once.js +9 -0
  329. package/dist/workflow/failures.d.ts +47 -0
  330. package/dist/workflow/failures.js +22 -0
  331. package/dist/workflow/index.d.ts +12 -0
  332. package/dist/workflow/index.js +12 -0
  333. package/dist/workflow/on-claim.d.ts +17 -0
  334. package/dist/workflow/on-claim.js +8 -0
  335. package/dist/workflow/publish-pending-events.d.ts +31 -0
  336. package/dist/workflow/publish-pending-events.js +31 -0
  337. package/dist/workflow/transactionally.d.ts +8 -0
  338. package/dist/workflow/transactionally.js +8 -0
  339. package/dist/workflow/versioned-payload.d.ts +35 -0
  340. package/dist/workflow/versioned-payload.js +43 -0
  341. package/package.json +309 -0
@@ -0,0 +1,106 @@
1
+ import { type EnvironmentEntry, type Refusal } from '@satorio/machinery/core';
2
+ import { Config, Option } from 'effect';
3
+ /**
4
+ * The most messages SQS hands back from one receive call. The service
5
+ * asks for no more than this, because SQS returns no more than this.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare const SQS_HIGHEST_MESSAGES_PER_RECEIVE = 10;
10
+ /**
11
+ * The longest long poll SQS allows. Anything above it is a value SQS
12
+ * itself refuses.
13
+ *
14
+ * @internal
15
+ */
16
+ export declare const SQS_HIGHEST_WAIT_TIME_SECONDS = 20;
17
+ /**
18
+ * The longest SQS keeps a received message hidden from other consumers:
19
+ * twelve hours.
20
+ *
21
+ * @internal
22
+ */
23
+ export declare const SQS_HIGHEST_VISIBILITY_TIMEOUT_SECONDS = 43200;
24
+ /**
25
+ * How long a received message stays hidden while a handler works on it,
26
+ * when nobody names a number. A minute is long enough for an ordinary
27
+ * handler and short enough that a crashed one redelivers soon.
28
+ */
29
+ export declare const SQS_DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 60;
30
+ /**
31
+ * Builds the environment variable name from the prefix the application
32
+ * gives at mount time. One process serves any number of queues this way,
33
+ * with readable names: `INBOUND_QUEUE_URL`, `RETRY_QUEUE_URL`, and so on.
34
+ */
35
+ export declare const queueUrlVariable: (variablePrefix: string) => string;
36
+ /**
37
+ * What a consumer needs before it can receive a single message.
38
+ *
39
+ * What a consumer needs before it can receive: the queue to poll, how it
40
+ * polls, and the two variables the AWS SDK reads for itself when nobody names
41
+ * them.
42
+ */
43
+ export interface QueueConsumerSettings {
44
+ readonly queueUrl: string;
45
+ readonly maxMessagesPerReceive: number;
46
+ readonly waitTimeSeconds: number;
47
+ readonly visibilityTimeoutSeconds: number;
48
+ readonly region: Option.Option<string>;
49
+ readonly endpoint: Option.Option<string>;
50
+ }
51
+ /**
52
+ * What a producer needs before it can send: the queue, and the same two
53
+ * variables the SDK decides for itself.
54
+ */
55
+ export interface QueueProducerSettings {
56
+ readonly queueUrl: string;
57
+ readonly region: Option.Option<string>;
58
+ readonly endpoint: Option.Option<string>;
59
+ }
60
+ /**
61
+ * The three parts a reader meets when the queue URL is not set. The
62
+ * mount desk logs this text and every operation of the loud stub fails
63
+ * with it, so an operator reads one sentence rather than two.
64
+ *
65
+ * @internal
66
+ */
67
+ export declare const queueUrlRefusal: (variablePrefix: string) => Refusal;
68
+ /**
69
+ * Reads the queue URL without refusing, so the mount desk can put up the
70
+ * loud stub instead of failing service startup.
71
+ *
72
+ * @internal
73
+ */
74
+ export declare const configuredQueueUrl: (variablePrefix: string) => Config.Config<Option.Option<string>>;
75
+ /**
76
+ * Every variable this adapter reads, and the one state each of them
77
+ * stands in. `<PREFIX>` is the prefix the application passes at mount
78
+ * time; the three poll settings share that prefix and keep their own
79
+ * suffixes.
80
+ */
81
+ export declare const ENVIRONMENT: readonly EnvironmentEntry[];
82
+ /**
83
+ * Reads every variable a consumer needs under one prefix. A missing or
84
+ * blank queue URL refuses in three parts; the three poll settings fall
85
+ * back to their defaults; region and endpoint stay `Option.none`, and
86
+ * the SDK decides.
87
+ */
88
+ export declare const queueConsumerSettings: (variablePrefix: string) => Config.Config<QueueConsumerSettings>;
89
+ /**
90
+ * Reads every variable a producer needs under one prefix. A producer
91
+ * only sends, so it reads the queue URL and nothing about polling.
92
+ */
93
+ export declare const queueProducerSettings: (variablePrefix: string) => Config.Config<QueueProducerSettings>;
94
+ /**
95
+ * The client configuration shape both transports and the readiness probe
96
+ * share: region and endpoint appear in it only when they are set.
97
+ *
98
+ * @internal
99
+ */
100
+ export declare const sqsClientConfigurationOf: (settings: {
101
+ readonly region: Option.Option<string>;
102
+ readonly endpoint: Option.Option<string>;
103
+ }) => {
104
+ readonly region?: string;
105
+ readonly endpoint?: string;
106
+ };
@@ -0,0 +1,160 @@
1
+ import { fallsBackTo, handedOn, optionalText, refuses, requiredText, wholeNumberFallingBackTo, } from '@satorio/machinery/core';
2
+ import { Config, Option } from 'effect';
3
+ const QUEUE_URL_SUFFIX = '_QUEUE_URL';
4
+ const MAX_MESSAGES_SUFFIX = '_MAX_MESSAGES_PER_RECEIVE';
5
+ const WAIT_TIME_SUFFIX = '_WAIT_TIME_SECONDS';
6
+ const VISIBILITY_TIMEOUT_SUFFIX = '_VISIBILITY_TIMEOUT_SECONDS';
7
+ const REGION = 'AWS_REGION';
8
+ const ENDPOINT = 'AWS_ENDPOINT_URL';
9
+ const LOWEST = 1;
10
+ /**
11
+ * The most messages SQS hands back from one receive call. The service
12
+ * asks for no more than this, because SQS returns no more than this.
13
+ *
14
+ * @internal
15
+ */
16
+ export const SQS_HIGHEST_MESSAGES_PER_RECEIVE = 10;
17
+ /**
18
+ * The longest long poll SQS allows. Anything above it is a value SQS
19
+ * itself refuses.
20
+ *
21
+ * @internal
22
+ */
23
+ export const SQS_HIGHEST_WAIT_TIME_SECONDS = 20;
24
+ /**
25
+ * The longest SQS keeps a received message hidden from other consumers:
26
+ * twelve hours.
27
+ *
28
+ * @internal
29
+ */
30
+ export const SQS_HIGHEST_VISIBILITY_TIMEOUT_SECONDS = 43_200;
31
+ /**
32
+ * How long a received message stays hidden while a handler works on it,
33
+ * when nobody names a number. A minute is long enough for an ordinary
34
+ * handler and short enough that a crashed one redelivers soon.
35
+ */
36
+ export const SQS_DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 60;
37
+ /**
38
+ * Builds the environment variable name from the prefix the application
39
+ * gives at mount time. One process serves any number of queues this way,
40
+ * with readable names: `INBOUND_QUEUE_URL`, `RETRY_QUEUE_URL`, and so on.
41
+ */
42
+ export const queueUrlVariable = (variablePrefix) => `${variablePrefix}${QUEUE_URL_SUFFIX}`;
43
+ /**
44
+ * The three parts a reader meets when the queue URL is not set. The
45
+ * mount desk logs this text and every operation of the loud stub fails
46
+ * with it, so an operator reads one sentence rather than two.
47
+ *
48
+ * @internal
49
+ */
50
+ export const queueUrlRefusal = (variablePrefix) => ({
51
+ what: `${queueUrlVariable(variablePrefix)} is not set, so this queue has no address.`,
52
+ why: 'One process serves several queues, each under its own prefix, so the machinery cannot guess which one you meant.',
53
+ whatToDo: `Set ${queueUrlVariable(variablePrefix)} to the full queue URL from the AWS console or your .env, then start the service again.`,
54
+ });
55
+ const rangeRefusal = (name, what, highest) => ({
56
+ what: `${name} is set to something this consumer cannot poll with.`,
57
+ why: `${what} It has to be a whole number from ${LOWEST} to ${highest}, which is the range SQS itself accepts.`,
58
+ whatToDo: `Set ${name} to a whole number from ${LOWEST} to ${highest}, or leave it unset and take the default.`,
59
+ });
60
+ /**
61
+ * Reads the queue URL without refusing, so the mount desk can put up the
62
+ * loud stub instead of failing service startup.
63
+ *
64
+ * @internal
65
+ */
66
+ export const configuredQueueUrl = (variablePrefix) => optionalText(queueUrlVariable(variablePrefix));
67
+ /**
68
+ * Every variable this adapter reads, and the one state each of them
69
+ * stands in. `<PREFIX>` is the prefix the application passes at mount
70
+ * time; the three poll settings share that prefix and keep their own
71
+ * suffixes.
72
+ */
73
+ export const ENVIRONMENT = [
74
+ {
75
+ name: QUEUE_URL_SUFFIX,
76
+ prefixed: true,
77
+ what: 'The queue a consumer receives from and a producer sends to.',
78
+ state: refuses(queueUrlRefusal('<PREFIX>')),
79
+ },
80
+ {
81
+ name: MAX_MESSAGES_SUFFIX,
82
+ prefixed: true,
83
+ what: 'How many messages one receive call asks for.',
84
+ state: fallsBackTo(String(SQS_HIGHEST_MESSAGES_PER_RECEIVE)),
85
+ },
86
+ {
87
+ name: WAIT_TIME_SUFFIX,
88
+ prefixed: true,
89
+ what: 'How many seconds a receive call waits for a message before it comes back empty.',
90
+ state: fallsBackTo(String(SQS_HIGHEST_WAIT_TIME_SECONDS)),
91
+ },
92
+ {
93
+ name: VISIBILITY_TIMEOUT_SUFFIX,
94
+ prefixed: true,
95
+ what: 'How many seconds a received message stays hidden from other consumers.',
96
+ state: fallsBackTo(String(SQS_DEFAULT_VISIBILITY_TIMEOUT_SECONDS)),
97
+ },
98
+ {
99
+ name: REGION,
100
+ what: 'The region the SQS client talks to.',
101
+ state: handedOn('the AWS SDK finds its own region from the machine it runs on.'),
102
+ },
103
+ {
104
+ name: ENDPOINT,
105
+ what: 'A local stand to talk to instead of real SQS, such as localstack.',
106
+ state: handedOn('the client goes to the real SQS for the region it found.'),
107
+ },
108
+ ];
109
+ const queueUrl = (variablePrefix) => requiredText(queueUrlVariable(variablePrefix), queueUrlRefusal(variablePrefix));
110
+ const maxMessagesPerReceive = (variablePrefix) => {
111
+ const name = `${variablePrefix}${MAX_MESSAGES_SUFFIX}`;
112
+ return wholeNumberFallingBackTo(name, LOWEST, SQS_HIGHEST_MESSAGES_PER_RECEIVE, SQS_HIGHEST_MESSAGES_PER_RECEIVE, rangeRefusal(name, 'It says how many messages one receive call asks for.', SQS_HIGHEST_MESSAGES_PER_RECEIVE));
113
+ };
114
+ const waitTimeSeconds = (variablePrefix) => {
115
+ const name = `${variablePrefix}${WAIT_TIME_SUFFIX}`;
116
+ return wholeNumberFallingBackTo(name, LOWEST, SQS_HIGHEST_WAIT_TIME_SECONDS, SQS_HIGHEST_WAIT_TIME_SECONDS, rangeRefusal(name, 'Zero turns long polling off and makes the consumer a hot loop.', SQS_HIGHEST_WAIT_TIME_SECONDS));
117
+ };
118
+ const visibilityTimeoutSeconds = (variablePrefix) => {
119
+ const name = `${variablePrefix}${VISIBILITY_TIMEOUT_SUFFIX}`;
120
+ return wholeNumberFallingBackTo(name, LOWEST, SQS_HIGHEST_VISIBILITY_TIMEOUT_SECONDS, SQS_DEFAULT_VISIBILITY_TIMEOUT_SECONDS, rangeRefusal(name, 'It says how long a handler has before the message comes back to another consumer.', SQS_HIGHEST_VISIBILITY_TIMEOUT_SECONDS));
121
+ };
122
+ /**
123
+ * Reads every variable a consumer needs under one prefix. A missing or
124
+ * blank queue URL refuses in three parts; the three poll settings fall
125
+ * back to their defaults; region and endpoint stay `Option.none`, and
126
+ * the SDK decides.
127
+ */
128
+ export const queueConsumerSettings = (variablePrefix) => Config.all({
129
+ queueUrl: queueUrl(variablePrefix),
130
+ maxMessagesPerReceive: maxMessagesPerReceive(variablePrefix),
131
+ waitTimeSeconds: waitTimeSeconds(variablePrefix),
132
+ visibilityTimeoutSeconds: visibilityTimeoutSeconds(variablePrefix),
133
+ region: optionalText(REGION),
134
+ endpoint: optionalText(ENDPOINT),
135
+ });
136
+ /**
137
+ * Reads every variable a producer needs under one prefix. A producer
138
+ * only sends, so it reads the queue URL and nothing about polling.
139
+ */
140
+ export const queueProducerSettings = (variablePrefix) => Config.all({
141
+ queueUrl: queueUrl(variablePrefix),
142
+ region: optionalText(REGION),
143
+ endpoint: optionalText(ENDPOINT),
144
+ });
145
+ /**
146
+ * The client configuration shape both transports and the readiness probe
147
+ * share: region and endpoint appear in it only when they are set.
148
+ *
149
+ * @internal
150
+ */
151
+ export const sqsClientConfigurationOf = (settings) => ({
152
+ ...Option.match(settings.region, {
153
+ onNone: () => ({}),
154
+ onSome: (region) => ({ region }),
155
+ }),
156
+ ...Option.match(settings.endpoint, {
157
+ onNone: () => ({}),
158
+ onSome: (endpoint) => ({ endpoint }),
159
+ }),
160
+ });
@@ -0,0 +1,84 @@
1
+ import { type Refusal } from '@satorio/machinery/core';
2
+ import { type BlobStoreShape } from '@satorio/machinery/ports';
3
+ import { Effect, type Scope } from 'effect';
4
+ import type { BlobStoreSettings } from './settings.js';
5
+ /**
6
+ * The narrowest slice of the SDK the store uses: put, get, check
7
+ * existence, delete. A test can substitute its own object of this shape
8
+ * without a network or a mock of the whole client.
9
+ */
10
+ export interface S3PutRequest {
11
+ readonly Bucket: string;
12
+ readonly Key: string;
13
+ readonly Body: Uint8Array;
14
+ readonly ContentType: string;
15
+ readonly Metadata: Readonly<Record<string, string>>;
16
+ }
17
+ /**
18
+ * One object named: the bucket it sits in and the key it sits under.
19
+ */
20
+ export interface S3KeyRequest {
21
+ readonly Bucket: string;
22
+ readonly Key: string;
23
+ }
24
+ /**
25
+ * One object as the store hands it back: its bytes, and the content type and
26
+ * metadata it carries when it carries any.
27
+ */
28
+ export interface S3FetchedObject {
29
+ readonly body: Uint8Array;
30
+ readonly contentType: string | undefined;
31
+ readonly metadata: Readonly<Record<string, string>> | undefined;
32
+ }
33
+ /**
34
+ * The narrow slice of the object store a blob store calls: put, get, check,
35
+ * delete, and nothing else.
36
+ */
37
+ export interface S3ObjectTransport {
38
+ readonly putObject: (request: S3PutRequest, signal?: AbortSignal) => Promise<unknown>;
39
+ readonly getObject: (request: S3KeyRequest, signal?: AbortSignal) => Promise<S3FetchedObject>;
40
+ readonly headObject: (request: S3KeyRequest, signal?: AbortSignal) => Promise<unknown>;
41
+ readonly deleteObject: (request: S3KeyRequest, signal?: AbortSignal) => Promise<unknown>;
42
+ }
43
+ /**
44
+ * A transport that also closes, so the client behind it is released when the
45
+ * scope around it ends.
46
+ */
47
+ export interface ClosableS3Transport extends S3ObjectTransport {
48
+ readonly close: () => void;
49
+ }
50
+ /**
51
+ * S3 signals "no such key" as an ERROR, not an empty response: `GetObject`
52
+ * throws `NoSuchKey`, `HeadObject` throws `NotFound`. The port is more
53
+ * honest than the SDK: absence is `Option.none` and `false`, not a failure.
54
+ */
55
+ export declare const signalsAbsentKey: (error: unknown) => boolean;
56
+ /**
57
+ * What an object is read as when the store hands back no content type of its
58
+ * own: plain bytes.
59
+ */
60
+ export declare const DEFAULT_CONTENT_TYPE = "application/octet-stream";
61
+ /**
62
+ * Builds the blob store over one transport and one bucket, turning the store's
63
+ * own way of signalling a missing key into an absent value.
64
+ */
65
+ export declare const makeS3BlobStore: (transport: S3ObjectTransport, settings: BlobStoreSettings) => BlobStoreShape;
66
+ /**
67
+ * Opens a transport that closes with the scope around it, so no client
68
+ * outlives the work that opened it.
69
+ */
70
+ export declare const scopedS3Transport: (openTransport: () => ClosableS3Transport) => Effect.Effect<S3ObjectTransport, never, Scope.Scope>;
71
+ /**
72
+ * Opens the real object-store client against the settings handed in, and is
73
+ * the one place that client is constructed.
74
+ */
75
+ export declare const openS3Transport: (settings: BlobStoreSettings) => ClosableS3Transport;
76
+ /**
77
+ * Without a bucket set, the store does not fail service startup: it mounts
78
+ * a stub that fails every operation loudly. The rest of the service keeps
79
+ * running, and the use case reports the missing configuration honestly
80
+ * instead of silently losing what it stored. The stub carries the same
81
+ * `Refusal` the mount desk logs, so the operator reads one text on both
82
+ * paths.
83
+ */
84
+ export declare const blobStoreUnavailableWithoutConfiguration: (refusal: Refusal) => BlobStoreShape;
@@ -0,0 +1,109 @@
1
+ import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3';
2
+ import { refusalText } from '@satorio/machinery/core';
3
+ import { BlobStoreUnavailableError, } from '@satorio/machinery/ports';
4
+ import { Effect, Option } from 'effect';
5
+ /**
6
+ * S3 signals "no such key" as an ERROR, not an empty response: `GetObject`
7
+ * throws `NoSuchKey`, `HeadObject` throws `NotFound`. The port is more
8
+ * honest than the SDK: absence is `Option.none` and `false`, not a failure.
9
+ */
10
+ export const signalsAbsentKey = (error) => error instanceof Error &&
11
+ (error.name === 'NoSuchKey' || error.name === 'NotFound');
12
+ /**
13
+ * What an object is read as when the store hands back no content type of its
14
+ * own: plain bytes.
15
+ */
16
+ export const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
17
+ const storedBlobOf = (fetched) => ({
18
+ body: fetched.body,
19
+ contentType: fetched.contentType ?? DEFAULT_CONTENT_TYPE,
20
+ metadata: fetched.metadata ?? {},
21
+ });
22
+ /**
23
+ * Builds the blob store over one transport and one bucket, turning the store's
24
+ * own way of signalling a missing key into an absent value.
25
+ */
26
+ export const makeS3BlobStore = (transport, settings) => ({
27
+ put: (key, blob) => Effect.tryPromise({
28
+ try: (signal) => transport.putObject({
29
+ Bucket: settings.bucket,
30
+ Key: key,
31
+ Body: blob.body,
32
+ ContentType: blob.contentType,
33
+ Metadata: blob.metadata,
34
+ }, signal),
35
+ catch: (cause) => new BlobStoreUnavailableError({ operation: 'put', cause }),
36
+ }).pipe(Effect.asVoid),
37
+ get: (key) => Effect.tryPromise({
38
+ try: (signal) => transport.getObject({ Bucket: settings.bucket, Key: key }, signal),
39
+ catch: (cause) => new BlobStoreUnavailableError({ operation: 'get', cause }),
40
+ }).pipe(Effect.map((fetched) => Option.some(storedBlobOf(fetched))), Effect.catchIf((error) => signalsAbsentKey(error.cause), () => Effect.succeed(Option.none()))),
41
+ exists: (key) => Effect.tryPromise({
42
+ try: (signal) => transport.headObject({ Bucket: settings.bucket, Key: key }, signal),
43
+ catch: (cause) => new BlobStoreUnavailableError({ operation: 'exists', cause }),
44
+ }).pipe(Effect.as(true), Effect.catchIf((error) => signalsAbsentKey(error.cause), () => Effect.succeed(false))),
45
+ delete: (key) => Effect.tryPromise({
46
+ try: (signal) => transport.deleteObject({ Bucket: settings.bucket, Key: key }, signal),
47
+ catch: (cause) => new BlobStoreUnavailableError({ operation: 'delete', cause }),
48
+ }).pipe(Effect.asVoid),
49
+ });
50
+ /**
51
+ * Opens a transport that closes with the scope around it, so no client
52
+ * outlives the work that opened it.
53
+ */
54
+ export const scopedS3Transport = (openTransport) => Effect.acquireRelease(Effect.sync(openTransport), (transport) => Effect.sync(() => transport.close()));
55
+ const clientConfigurationOf = (settings) => ({
56
+ ...Option.match(settings.region, {
57
+ onNone: () => ({}),
58
+ onSome: (region) => ({ region }),
59
+ }),
60
+ ...Option.match(settings.endpoint, {
61
+ onNone: () => ({}),
62
+ onSome: (endpoint) => ({ endpoint, forcePathStyle: true }),
63
+ }),
64
+ });
65
+ const abortOptions = (signal) => signal === undefined ? {} : { abortSignal: signal };
66
+ /**
67
+ * Opens the real object-store client against the settings handed in, and is
68
+ * the one place that client is constructed.
69
+ */
70
+ export const openS3Transport = (settings) => {
71
+ const client = new S3Client(clientConfigurationOf(settings));
72
+ return {
73
+ putObject: (request, signal) => client.send(new PutObjectCommand(request), abortOptions(signal)),
74
+ getObject: async (request, signal) => {
75
+ const result = await client.send(new GetObjectCommand(request), abortOptions(signal));
76
+ if (result.Body === undefined) {
77
+ throw new Error('S3 returned an object without a body');
78
+ }
79
+ return {
80
+ body: await result.Body.transformToByteArray(),
81
+ contentType: result.ContentType,
82
+ metadata: result.Metadata,
83
+ };
84
+ },
85
+ headObject: (request, signal) => client.send(new HeadObjectCommand(request), abortOptions(signal)),
86
+ deleteObject: (request, signal) => client.send(new DeleteObjectCommand(request), abortOptions(signal)),
87
+ close: () => client.destroy(),
88
+ };
89
+ };
90
+ /**
91
+ * Without a bucket set, the store does not fail service startup: it mounts
92
+ * a stub that fails every operation loudly. The rest of the service keeps
93
+ * running, and the use case reports the missing configuration honestly
94
+ * instead of silently losing what it stored. The stub carries the same
95
+ * `Refusal` the mount desk logs, so the operator reads one text on both
96
+ * paths.
97
+ */
98
+ export const blobStoreUnavailableWithoutConfiguration = (refusal) => {
99
+ const failure = (operation) => new BlobStoreUnavailableError({
100
+ operation,
101
+ cause: refusalText(refusal),
102
+ });
103
+ return {
104
+ put: () => failure('put'),
105
+ get: () => failure('get'),
106
+ exists: () => failure('exists'),
107
+ delete: () => failure('delete'),
108
+ };
109
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public entry to the storage mechanism: whole objects stored under a key and
3
+ * read back. It gives you the `BlobStore` port bound to S3, with the bucket
4
+ * read from one prefixed variable and a loud stub in its place when nobody set
5
+ * it.
6
+ */
7
+ export { blobStoreUnavailableWithoutConfiguration, type ClosableS3Transport, DEFAULT_CONTENT_TYPE, makeS3BlobStore, openS3Transport, type S3FetchedObject, type S3KeyRequest, type S3ObjectTransport, type S3PutRequest, scopedS3Transport, signalsAbsentKey, } from './blob-store.js';
8
+ export { blobStoreFromEnvironmentLive, s3BlobStoreLive } from './layer.js';
9
+ export { type BlobStoreSettings, blobStoreSettings, bucketVariable, ENVIRONMENT, } from './settings.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public entry to the storage mechanism: whole objects stored under a key and
3
+ * read back. It gives you the `BlobStore` port bound to S3, with the bucket
4
+ * read from one prefixed variable and a loud stub in its place when nobody set
5
+ * it.
6
+ */
7
+ export { blobStoreUnavailableWithoutConfiguration, DEFAULT_CONTENT_TYPE, makeS3BlobStore, openS3Transport, scopedS3Transport, signalsAbsentKey, } from './blob-store.js';
8
+ export { blobStoreFromEnvironmentLive, s3BlobStoreLive } from './layer.js';
9
+ export { blobStoreSettings, bucketVariable, ENVIRONMENT, } from './settings.js';
@@ -0,0 +1,17 @@
1
+ import { BlobStore } from '@satorio/machinery/ports';
2
+ import { type ConfigError, Layer } from 'effect';
3
+ import { type ClosableS3Transport } from './blob-store.js';
4
+ import { type BlobStoreSettings } from './settings.js';
5
+ /**
6
+ * A blob store built from environment variables with the given prefix.
7
+ * With no bucket set it mounts the loud stub and logs the one refusal
8
+ * every later operation repeats, so startup carries on and nothing goes
9
+ * missing in silence. The transport is a parameter, so a test
10
+ * substitutes its own without touching the wiring.
11
+ */
12
+ export declare const blobStoreFromEnvironmentLive: (variablePrefix: string, openTransport: (settings: BlobStoreSettings) => ClosableS3Transport) => Layer.Layer<BlobStore, ConfigError.ConfigError>;
13
+ /**
14
+ * The blob store on the real AWS SDK transport, reading the variables
15
+ * this prefix names.
16
+ */
17
+ export declare const s3BlobStoreLive: (variablePrefix: string) => Layer.Layer<BlobStore, ConfigError.ConfigError>;
@@ -0,0 +1,30 @@
1
+ import { refusalText } from '@satorio/machinery/core';
2
+ import { BlobStore } from '@satorio/machinery/ports';
3
+ import { Effect, Layer, Option } from 'effect';
4
+ import { blobStoreUnavailableWithoutConfiguration, makeS3BlobStore, openS3Transport, scopedS3Transport, } from './blob-store.js';
5
+ import { blobStoreSettings, bucketRefusal, configuredBucket, } from './settings.js';
6
+ /**
7
+ * A blob store built from environment variables with the given prefix.
8
+ * With no bucket set it mounts the loud stub and logs the one refusal
9
+ * every later operation repeats, so startup carries on and nothing goes
10
+ * missing in silence. The transport is a parameter, so a test
11
+ * substitutes its own without touching the wiring.
12
+ */
13
+ export const blobStoreFromEnvironmentLive = (variablePrefix, openTransport) => Layer.unwrapEffect(Effect.gen(function* () {
14
+ const configured = yield* configuredBucket(variablePrefix);
15
+ if (Option.isNone(configured)) {
16
+ const refusal = bucketRefusal(variablePrefix);
17
+ yield* Effect.logError(refusalText(refusal));
18
+ return Layer.succeed(BlobStore, blobStoreUnavailableWithoutConfiguration(refusal));
19
+ }
20
+ return Layer.scoped(BlobStore, Effect.gen(function* () {
21
+ const settings = yield* blobStoreSettings(variablePrefix);
22
+ const transport = yield* scopedS3Transport(() => openTransport(settings));
23
+ return makeS3BlobStore(transport, settings);
24
+ }));
25
+ }));
26
+ /**
27
+ * The blob store on the real AWS SDK transport, reading the variables
28
+ * this prefix names.
29
+ */
30
+ export const s3BlobStoreLive = (variablePrefix) => blobStoreFromEnvironmentLive(variablePrefix, openS3Transport);
@@ -0,0 +1,48 @@
1
+ import { type EnvironmentEntry, type Refusal } from '@satorio/machinery/core';
2
+ import { Config, type Option } from 'effect';
3
+ /**
4
+ * Builds the environment variable name from the prefix the application
5
+ * gives at mount time. One machinery instance serves any number of buckets
6
+ * in one process this way, with readable names: `MEDIA_BUCKET`,
7
+ * `REPORTS_BUCKET`, and so on.
8
+ */
9
+ export declare const bucketVariable: (variablePrefix: string) => string;
10
+ /**
11
+ * What the store needs before it can talk to the object store.
12
+ *
13
+ * What the store needs before it can talk to S3: one bucket, and the two
14
+ * variables the AWS SDK reads for itself when nobody names them.
15
+ */
16
+ export interface BlobStoreSettings {
17
+ readonly bucket: string;
18
+ readonly region: Option.Option<string>;
19
+ readonly endpoint: Option.Option<string>;
20
+ }
21
+ /**
22
+ * The three parts a reader meets when the bucket is not set: what
23
+ * happened, why the store cannot go on, and what to do next. The mount
24
+ * desk logs this text and every operation of the loud stub fails with
25
+ * it, so an operator reads one sentence rather than two.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare const bucketRefusal: (variablePrefix: string) => Refusal;
30
+ /**
31
+ * Reads the bucket without refusing, so the mount desk can put up the
32
+ * loud stub instead of failing service startup.
33
+ *
34
+ * @internal
35
+ */
36
+ export declare const configuredBucket: (variablePrefix: string) => Config.Config<Option.Option<string>>;
37
+ /**
38
+ * Every variable this adapter reads, and the one state each of them
39
+ * stands in. `<PREFIX>` is the prefix the application passes at mount
40
+ * time.
41
+ */
42
+ export declare const ENVIRONMENT: readonly EnvironmentEntry[];
43
+ /**
44
+ * Reads every variable of `ENVIRONMENT` under one prefix. A missing or
45
+ * blank bucket refuses in three parts; region and endpoint stay
46
+ * `Option.none`, and the SDK decides.
47
+ */
48
+ export declare const blobStoreSettings: (variablePrefix: string) => Config.Config<BlobStoreSettings>;
@@ -0,0 +1,65 @@
1
+ import { handedOn, optionalText, refuses, requiredText, } from '@satorio/machinery/core';
2
+ import { Config } from 'effect';
3
+ const BUCKET_SUFFIX = '_BUCKET';
4
+ const REGION = 'AWS_REGION';
5
+ const ENDPOINT = 'AWS_ENDPOINT_URL';
6
+ /**
7
+ * Builds the environment variable name from the prefix the application
8
+ * gives at mount time. One machinery instance serves any number of buckets
9
+ * in one process this way, with readable names: `MEDIA_BUCKET`,
10
+ * `REPORTS_BUCKET`, and so on.
11
+ */
12
+ export const bucketVariable = (variablePrefix) => `${variablePrefix}${BUCKET_SUFFIX}`;
13
+ /**
14
+ * The three parts a reader meets when the bucket is not set: what
15
+ * happened, why the store cannot go on, and what to do next. The mount
16
+ * desk logs this text and every operation of the loud stub fails with
17
+ * it, so an operator reads one sentence rather than two.
18
+ *
19
+ * @internal
20
+ */
21
+ export const bucketRefusal = (variablePrefix) => ({
22
+ what: `${bucketVariable(variablePrefix)} is not set, so this object store has no bucket.`,
23
+ why: 'One process serves several buckets, each under its own prefix, so the store cannot guess which one you meant.',
24
+ whatToDo: `Set ${bucketVariable(variablePrefix)} to the bucket name from the AWS console or your .env, then start the service again.`,
25
+ });
26
+ /**
27
+ * Reads the bucket without refusing, so the mount desk can put up the
28
+ * loud stub instead of failing service startup.
29
+ *
30
+ * @internal
31
+ */
32
+ export const configuredBucket = (variablePrefix) => optionalText(bucketVariable(variablePrefix));
33
+ /**
34
+ * Every variable this adapter reads, and the one state each of them
35
+ * stands in. `<PREFIX>` is the prefix the application passes at mount
36
+ * time.
37
+ */
38
+ export const ENVIRONMENT = [
39
+ {
40
+ name: BUCKET_SUFFIX,
41
+ prefixed: true,
42
+ what: 'The bucket this store puts objects into and reads them back from.',
43
+ state: refuses(bucketRefusal('<PREFIX>')),
44
+ },
45
+ {
46
+ name: REGION,
47
+ what: 'The region the S3 client talks to.',
48
+ state: handedOn('the AWS SDK finds its own region from the machine it runs on.'),
49
+ },
50
+ {
51
+ name: ENDPOINT,
52
+ what: 'A local stand to talk to instead of real S3, such as localstack.',
53
+ state: handedOn('the client goes to the real S3 for the region it found.'),
54
+ },
55
+ ];
56
+ /**
57
+ * Reads every variable of `ENVIRONMENT` under one prefix. A missing or
58
+ * blank bucket refuses in three parts; region and endpoint stay
59
+ * `Option.none`, and the SDK decides.
60
+ */
61
+ export const blobStoreSettings = (variablePrefix) => Config.all({
62
+ bucket: requiredText(bucketVariable(variablePrefix), bucketRefusal(variablePrefix)),
63
+ region: optionalText(REGION),
64
+ endpoint: optionalText(ENDPOINT),
65
+ });
@@ -0,0 +1,27 @@
1
+ import { BlobStore, type BlobStoreShape, type StoredBlob } from '@satorio/machinery/ports';
2
+ import { Effect, Layer, Ref } from 'effect';
3
+ /**
4
+ * What an in-memory blob store holds: every blob it was given, by key. A
5
+ * scenario keeps the `Ref` and reads this map to see what a use case
6
+ * stored.
7
+ */
8
+ export type BlobsByKey = ReadonlyMap<string, StoredBlob>;
9
+ /**
10
+ * An in-memory blob store over a `Ref` you keep, so a test can seed it
11
+ * before the run and read it after.
12
+ */
13
+ export declare const makeInMemoryBlobStoreWith: (blobs: Ref.Ref<BlobsByKey>) => BlobStoreShape;
14
+ /**
15
+ * An in-memory blob store over a `Ref` of its own, empty at the start.
16
+ */
17
+ export declare const makeInMemoryBlobStore: Effect.Effect<BlobStoreShape, never, never>;
18
+ /**
19
+ * The in-memory blob store, mounted.
20
+ */
21
+ export declare const InMemoryBlobStoreLive: Layer.Layer<BlobStore, never, never>;
22
+ /**
23
+ * A blob store that answers nothing: every operation fails with
24
+ * `BlobStoreUnavailableError`. Mount it to walk the branch a working
25
+ * stand-in never reaches.
26
+ */
27
+ export declare const BlobStoreUnavailableLive: Layer.Layer<BlobStore>;