@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,134 @@
1
+ import { type EnvironmentEntry, type Refusal } from '@satorio/machinery/core';
2
+ import { Config, type ConfigError, ConfigProvider, Either, Option, type Redacted } from 'effect';
3
+ /**
4
+ * Every way a connection can be encrypted and verified, from no encryption at
5
+ * all to a fully verified chain.
6
+ */
7
+ export declare const POSTGRES_SSL_MODES: readonly ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"];
8
+ /**
9
+ * One of the ways a connection can be encrypted and verified.
10
+ */
11
+ export type PostgresSslMode = (typeof POSTGRES_SSL_MODES)[number];
12
+ /**
13
+ * The port a Postgres server listens on when nobody says otherwise.
14
+ */
15
+ export declare const DEFAULT_POSTGRES_PORT = 5432;
16
+ /**
17
+ * The schema every statement runs against when nobody names one.
18
+ */
19
+ export declare const DEFAULT_POSTGRES_SCHEMA = "app";
20
+ /**
21
+ * How a connection is encrypted when nobody says otherwise: the chain is
22
+ * verified, so the safe choice is the one you get by saying nothing.
23
+ */
24
+ export declare const DEFAULT_POSTGRES_SSL_MODE: PostgresSslMode;
25
+ /**
26
+ * The name this service shows under in the database's own list of connections
27
+ * when nobody names it.
28
+ */
29
+ export declare const DEFAULT_APPLICATION_NAME = "machinery";
30
+ /**
31
+ * How a service proves who it is when nothing says otherwise. A password
32
+ * is what a newcomer already has on a local database; production names
33
+ * the stronger choice out loud with `AUTH_MODE=iam`.
34
+ */
35
+ export declare const DEFAULT_AUTH_MODE: 'iam' | 'password';
36
+ /**
37
+ * The two ways to prove to the database who you are: a short-lived IAM
38
+ * token or a password. The list is closed, so a third way never appears
39
+ * silently. The IAM region is optional, the way it is everywhere else in
40
+ * this package — the AWS SDK finds its own when nobody names one.
41
+ */
42
+ export type PostgresAuthentication = {
43
+ readonly _tag: 'AwsIamAuthentication';
44
+ readonly region: Option.Option<string>;
45
+ } | {
46
+ readonly _tag: 'PasswordAuthentication';
47
+ readonly password: Redacted.Redacted;
48
+ };
49
+ /**
50
+ * Everything one connection needs, read in one go: where the server is, who
51
+ * connects, and how the connection is proven and encrypted.
52
+ */
53
+ export interface PostgresConnectionSettings {
54
+ readonly host: string;
55
+ readonly port: number;
56
+ readonly user: string;
57
+ readonly database: string;
58
+ readonly schema: string;
59
+ readonly applicationName: string;
60
+ readonly sslMode: PostgresSslMode;
61
+ readonly sslRootCertificatePath: Option.Option<string>;
62
+ readonly authentication: PostgresAuthentication;
63
+ }
64
+ /**
65
+ * The three parts a reader meets when the database address is missing.
66
+ *
67
+ * @internal
68
+ */
69
+ export declare const hostRefusal: Refusal;
70
+ /**
71
+ * The three parts a reader meets when nobody says who connects.
72
+ *
73
+ * @internal
74
+ */
75
+ export declare const userRefusal: Refusal;
76
+ /**
77
+ * The three parts a reader meets when nobody says which database.
78
+ *
79
+ * @internal
80
+ */
81
+ export declare const databaseRefusal: Refusal;
82
+ /**
83
+ * The three parts a reader meets when the password is missing. It names
84
+ * the default that put them on this path and both ways off it.
85
+ *
86
+ * @internal
87
+ */
88
+ export declare const passwordRefusal: Refusal;
89
+ /**
90
+ * Every variable this adapter reads, and the one state each of them
91
+ * stands in.
92
+ */
93
+ export declare const ENVIRONMENT: readonly EnvironmentEntry[];
94
+ interface AuthenticationCandidates {
95
+ readonly mode: 'iam' | 'password';
96
+ readonly password: Option.Option<Redacted.Redacted>;
97
+ readonly region: Option.Option<string>;
98
+ }
99
+ /**
100
+ * Reads both ways of proving who this service is, then keeps the one
101
+ * `AUTH_MODE` names. Both are read before either is chosen, so every
102
+ * refusal of this settings block reaches the reader in one answer
103
+ * instead of one at a time.
104
+ *
105
+ * @internal
106
+ */
107
+ export declare const authenticationFrom: ({ mode, password, region, }: AuthenticationCandidates) => Either.Either<PostgresAuthentication, ConfigError.ConfigError>;
108
+ /**
109
+ * Every variable of `ENVIRONMENT`, read in one go. A missing required
110
+ * variable refuses in three parts, and an environment missing several of
111
+ * them refuses about all of them at once, so an operator fixes one .env
112
+ * rather than restarting after every line.
113
+ */
114
+ export declare const postgresConnectionSettings: Config.Config<PostgresConnectionSettings>;
115
+ /**
116
+ * Builds a config provider that reads from `primary`, falling back to one
117
+ * fixed value when `primary` has no such key. Kept separate from
118
+ * `schemaDefaultingTo` so a test can check "environment wins, default is
119
+ * only a fallback" against an arbitrary `primary`, without touching the
120
+ * real `process.env`.
121
+ */
122
+ export declare const configProviderPreferring: (primary: ConfigProvider.ConfigProvider, fallbackKey: string, fallbackValue: string) => ConfigProvider.ConfigProvider;
123
+ /**
124
+ * Default schema for one service when `PGSCHEMA` isn't set. The
125
+ * environment always wins; this default only guards a local Postgres that
126
+ * several services share in turn. The migrations runner tracks the last
127
+ * applied migration per schema, not per service, so a shared schema
128
+ * without separate numbers would silently treat one service's
129
+ * migrations as already applied. A deployed service always has its own
130
+ * database or its own schema in the environment, so the mismatch never
131
+ * arises there.
132
+ */
133
+ export declare const schemaDefaultingTo: (schema: string) => ConfigProvider.ConfigProvider;
134
+ export {};
@@ -0,0 +1,210 @@
1
+ import { fallsBackTo, handedOn, optionalSecret, optionalText, refusalFor, refuses, requiredText, } from '@satorio/machinery/core';
2
+ import { Config, ConfigProvider, Either, Option, } from 'effect';
3
+ const HOST = 'PGHOST';
4
+ const PORT = 'PGPORT';
5
+ const USER = 'PGUSER';
6
+ const DATABASE = 'PGDATABASE';
7
+ const SCHEMA = 'PGSCHEMA';
8
+ const APPLICATION_NAME = 'PGAPPNAME';
9
+ const SSL_MODE = 'PGSSLMODE';
10
+ const SSL_ROOT_CERTIFICATE = 'PGSSLROOTCERT';
11
+ const AUTHENTICATION_MODE = 'AUTH_MODE';
12
+ const PASSWORD = 'PGPASSWORD';
13
+ const REGION = 'AWS_REGION';
14
+ /**
15
+ * Every way a connection can be encrypted and verified, from no encryption at
16
+ * all to a fully verified chain.
17
+ */
18
+ export const POSTGRES_SSL_MODES = [
19
+ 'disable',
20
+ 'allow',
21
+ 'prefer',
22
+ 'require',
23
+ 'verify-ca',
24
+ 'verify-full',
25
+ ];
26
+ /**
27
+ * The port a Postgres server listens on when nobody says otherwise.
28
+ */
29
+ export const DEFAULT_POSTGRES_PORT = 5432;
30
+ /**
31
+ * The schema every statement runs against when nobody names one.
32
+ */
33
+ export const DEFAULT_POSTGRES_SCHEMA = 'app';
34
+ /**
35
+ * How a connection is encrypted when nobody says otherwise: the chain is
36
+ * verified, so the safe choice is the one you get by saying nothing.
37
+ */
38
+ export const DEFAULT_POSTGRES_SSL_MODE = 'verify-full';
39
+ /**
40
+ * The name this service shows under in the database's own list of connections
41
+ * when nobody names it.
42
+ */
43
+ export const DEFAULT_APPLICATION_NAME = 'machinery';
44
+ /**
45
+ * How a service proves who it is when nothing says otherwise. A password
46
+ * is what a newcomer already has on a local database; production names
47
+ * the stronger choice out loud with `AUTH_MODE=iam`.
48
+ */
49
+ export const DEFAULT_AUTH_MODE = 'password';
50
+ /**
51
+ * The three parts a reader meets when the database address is missing.
52
+ *
53
+ * @internal
54
+ */
55
+ export const hostRefusal = {
56
+ what: `${HOST} is not set, so this service does not know which database to open.`,
57
+ why: 'A connection pool needs an address before it can open anything, and there is no address a service could sensibly guess.',
58
+ whatToDo: `Set ${HOST} to the database host from your .env or your deployment, then start the service again.`,
59
+ };
60
+ /**
61
+ * The three parts a reader meets when nobody says who connects.
62
+ *
63
+ * @internal
64
+ */
65
+ export const userRefusal = {
66
+ what: `${USER} is not set, so this service has no database user to connect as.`,
67
+ why: 'Postgres grants rights per role, so the connection cannot be opened under a role nobody named.',
68
+ whatToDo: `Set ${USER} to the role this service owns, then start the service again.`,
69
+ };
70
+ /**
71
+ * The three parts a reader meets when nobody says which database.
72
+ *
73
+ * @internal
74
+ */
75
+ export const databaseRefusal = {
76
+ what: `${DATABASE} is not set, so this service does not know which database to connect to.`,
77
+ why: 'One Postgres server holds many databases, and the wrong one would answer every query with tables this service never wrote.',
78
+ whatToDo: `Set ${DATABASE} to the database name this service owns, then start the service again.`,
79
+ };
80
+ /**
81
+ * The three parts a reader meets when the password is missing. It names
82
+ * the default that put them on this path and both ways off it.
83
+ *
84
+ * @internal
85
+ */
86
+ export const passwordRefusal = {
87
+ what: `${PASSWORD} is not set, and ${AUTHENTICATION_MODE} defaulted to password.`,
88
+ why: 'The connection has no way left to prove who it is to the database.',
89
+ whatToDo: `Set ${PASSWORD} to the database password, or set ${AUTHENTICATION_MODE}=iam to sign a short-lived AWS IAM token instead.`,
90
+ };
91
+ /**
92
+ * Every variable this adapter reads, and the one state each of them
93
+ * stands in.
94
+ */
95
+ export const ENVIRONMENT = [
96
+ {
97
+ name: HOST,
98
+ what: 'The host this service opens its connection pool against.',
99
+ state: refuses(hostRefusal),
100
+ },
101
+ {
102
+ name: PORT,
103
+ what: 'The TCP port the database listens on.',
104
+ state: fallsBackTo(String(DEFAULT_POSTGRES_PORT)),
105
+ },
106
+ {
107
+ name: USER,
108
+ what: 'The Postgres role this service connects as.',
109
+ state: refuses(userRefusal),
110
+ },
111
+ {
112
+ name: DATABASE,
113
+ what: 'The database this service reads and writes.',
114
+ state: refuses(databaseRefusal),
115
+ },
116
+ {
117
+ name: SCHEMA,
118
+ what: "The schema every statement runs against, set as the connection's search_path.",
119
+ state: fallsBackTo(DEFAULT_POSTGRES_SCHEMA),
120
+ },
121
+ {
122
+ name: APPLICATION_NAME,
123
+ what: 'The name this service shows under in pg_stat_activity.',
124
+ state: fallsBackTo(DEFAULT_APPLICATION_NAME),
125
+ },
126
+ {
127
+ name: SSL_MODE,
128
+ what: `How the connection is encrypted and verified, one of ${POSTGRES_SSL_MODES.join(', ')}.`,
129
+ state: fallsBackTo(DEFAULT_POSTGRES_SSL_MODE),
130
+ },
131
+ {
132
+ name: SSL_ROOT_CERTIFICATE,
133
+ what: 'The path to a certificate authority file the database chain is verified against.',
134
+ state: handedOn("the driver verifies the chain against the machine's own trust store."),
135
+ },
136
+ {
137
+ name: AUTHENTICATION_MODE,
138
+ what: 'Which of the two ways this service proves who it is, iam or password.',
139
+ state: fallsBackTo(DEFAULT_AUTH_MODE),
140
+ },
141
+ {
142
+ name: PASSWORD,
143
+ what: 'The database password, read under the default password mode.',
144
+ state: refuses(passwordRefusal),
145
+ },
146
+ {
147
+ name: REGION,
148
+ what: 'The region the IAM token signer signs against, read under AUTH_MODE=iam.',
149
+ state: handedOn('the AWS SDK finds its own region from the machine it runs on.'),
150
+ },
151
+ ];
152
+ const authenticationMode = Config.literal('iam', 'password')(AUTHENTICATION_MODE).pipe(Config.withDefault(DEFAULT_AUTH_MODE));
153
+ /**
154
+ * Reads both ways of proving who this service is, then keeps the one
155
+ * `AUTH_MODE` names. Both are read before either is chosen, so every
156
+ * refusal of this settings block reaches the reader in one answer
157
+ * instead of one at a time.
158
+ *
159
+ * @internal
160
+ */
161
+ export const authenticationFrom = ({ mode, password, region, }) => {
162
+ if (mode === 'iam') {
163
+ return Either.right({ _tag: 'AwsIamAuthentication', region });
164
+ }
165
+ return Option.match(password, {
166
+ onNone: () => Either.left(refusalFor(PASSWORD, passwordRefusal)),
167
+ onSome: (secret) => Either.right({ _tag: 'PasswordAuthentication', password: secret }),
168
+ });
169
+ };
170
+ const authentication = Config.all({
171
+ mode: authenticationMode,
172
+ password: optionalSecret(PASSWORD),
173
+ region: optionalText(REGION),
174
+ }).pipe(Config.mapOrFail(authenticationFrom));
175
+ /**
176
+ * Every variable of `ENVIRONMENT`, read in one go. A missing required
177
+ * variable refuses in three parts, and an environment missing several of
178
+ * them refuses about all of them at once, so an operator fixes one .env
179
+ * rather than restarting after every line.
180
+ */
181
+ export const postgresConnectionSettings = Config.all({
182
+ host: requiredText(HOST, hostRefusal),
183
+ port: Config.integer(PORT).pipe(Config.withDefault(DEFAULT_POSTGRES_PORT)),
184
+ user: requiredText(USER, userRefusal),
185
+ database: requiredText(DATABASE, databaseRefusal),
186
+ schema: Config.string(SCHEMA).pipe(Config.withDefault(DEFAULT_POSTGRES_SCHEMA)),
187
+ applicationName: Config.string(APPLICATION_NAME).pipe(Config.withDefault(DEFAULT_APPLICATION_NAME)),
188
+ sslMode: Config.literal(...POSTGRES_SSL_MODES)(SSL_MODE).pipe(Config.withDefault(DEFAULT_POSTGRES_SSL_MODE)),
189
+ sslRootCertificatePath: optionalText(SSL_ROOT_CERTIFICATE),
190
+ authentication,
191
+ });
192
+ /**
193
+ * Builds a config provider that reads from `primary`, falling back to one
194
+ * fixed value when `primary` has no such key. Kept separate from
195
+ * `schemaDefaultingTo` so a test can check "environment wins, default is
196
+ * only a fallback" against an arbitrary `primary`, without touching the
197
+ * real `process.env`.
198
+ */
199
+ export const configProviderPreferring = (primary, fallbackKey, fallbackValue) => primary.pipe(ConfigProvider.orElse(() => ConfigProvider.fromMap(new Map([[fallbackKey, fallbackValue]]))));
200
+ /**
201
+ * Default schema for one service when `PGSCHEMA` isn't set. The
202
+ * environment always wins; this default only guards a local Postgres that
203
+ * several services share in turn. The migrations runner tracks the last
204
+ * applied migration per schema, not per service, so a shared schema
205
+ * without separate numbers would silently treat one service's
206
+ * migrations as already applied. A deployed service always has its own
207
+ * database or its own schema in the environment, so the mismatch never
208
+ * arises there.
209
+ */
210
+ export const schemaDefaultingTo = (schema) => configProviderPreferring(ConfigProvider.fromEnv(), SCHEMA, schema);
@@ -0,0 +1,4 @@
1
+ CREATE TABLE IF NOT EXISTS processed_event (
2
+ event_id text PRIMARY KEY,
3
+ processed_at timestamptz NOT NULL
4
+ );
@@ -0,0 +1,13 @@
1
+ CREATE TABLE IF NOT EXISTS integration_event_outbox (
2
+ sequence_number bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
3
+ message_id text NOT NULL UNIQUE,
4
+ event_type text NOT NULL,
5
+ schema_version integer NOT NULL,
6
+ occurred_at timestamptz NOT NULL,
7
+ payload jsonb NOT NULL,
8
+ dispatched_at timestamptz
9
+ );
10
+
11
+ CREATE INDEX IF NOT EXISTS integration_event_outbox_pending
12
+ ON integration_event_outbox (sequence_number)
13
+ WHERE dispatched_at IS NULL;
@@ -0,0 +1,6 @@
1
+ CREATE TABLE IF NOT EXISTS document_number_counter (
2
+ client_code text NOT NULL,
3
+ reporting_month text NOT NULL,
4
+ issued integer NOT NULL,
5
+ PRIMARY KEY (client_code, reporting_month)
6
+ );
@@ -0,0 +1,19 @@
1
+ import type { SqlError } from '@effect/sql/SqlError';
2
+ import { Option } from 'effect';
3
+ /**
4
+ * The SQLSTATE Postgres answers a duplicate key with.
5
+ *
6
+ * @internal
7
+ */
8
+ export declare const UNIQUE_VIOLATION_SQL_STATE = "23505";
9
+ /**
10
+ * The SQLSTATE a driver failure carries, when it carries one.
11
+ *
12
+ * @internal
13
+ */
14
+ export declare const sqlStateOf: (error: SqlError) => Option.Option<string>;
15
+ /**
16
+ * Whether this driver failure is a duplicate key rather than anything
17
+ * else the database could refuse.
18
+ */
19
+ export declare const isUniqueViolation: (error: SqlError) => boolean;
@@ -0,0 +1,22 @@
1
+ import { Option } from 'effect';
2
+ /**
3
+ * The SQLSTATE Postgres answers a duplicate key with.
4
+ *
5
+ * @internal
6
+ */
7
+ export const UNIQUE_VIOLATION_SQL_STATE = '23505';
8
+ const carriesStringCode = (cause) => typeof cause === 'object' &&
9
+ cause !== null &&
10
+ 'code' in cause &&
11
+ typeof cause.code === 'string';
12
+ /**
13
+ * The SQLSTATE a driver failure carries, when it carries one.
14
+ *
15
+ * @internal
16
+ */
17
+ export const sqlStateOf = (error) => carriesStringCode(error.cause) ? Option.some(error.cause.code) : Option.none();
18
+ /**
19
+ * Whether this driver failure is a duplicate key rather than anything
20
+ * else the database could refuse.
21
+ */
22
+ export const isUniqueViolation = (error) => Option.contains(sqlStateOf(error), UNIQUE_VIOLATION_SQL_STATE);
@@ -0,0 +1,17 @@
1
+ import type { SqlError } from '@effect/sql/SqlError';
2
+ import { Effect, Option } from 'effect';
3
+ /**
4
+ * Splits a write path at the database boundary: a unique violation
5
+ * becomes a domain conflict, any other SqlError becomes "store
6
+ * unavailable". Pass a `unique` branch that builds the conflict failure
7
+ * and an `unavailable` branch, `portUnavailable` bound to `operation`.
8
+ */
9
+ export declare const onUniqueViolation: <Conflict, Unavailable>(operation: string, branches: {
10
+ readonly unique: () => Conflict;
11
+ readonly unavailable: (operation: string) => (cause: unknown) => Unavailable;
12
+ }) => <A, R>(effect: Effect.Effect<A, SqlError, R>) => Effect.Effect<A, Conflict | Unavailable, R>;
13
+ /**
14
+ * Returns the first row, or `Option.none()` for an empty result. Use it
15
+ * for a lookup whose shape is `Option`.
16
+ */
17
+ export declare const firstRow: <A>(rows: readonly A[]) => Option.Option<A>;
@@ -0,0 +1,16 @@
1
+ import { Effect, Option } from 'effect';
2
+ import { isUniqueViolation } from './sql-failure.js';
3
+ /**
4
+ * Splits a write path at the database boundary: a unique violation
5
+ * becomes a domain conflict, any other SqlError becomes "store
6
+ * unavailable". Pass a `unique` branch that builds the conflict failure
7
+ * and an `unavailable` branch, `portUnavailable` bound to `operation`.
8
+ */
9
+ export const onUniqueViolation = (operation, branches) => (effect) => effect.pipe(Effect.catchTag('SqlError', (error) => isUniqueViolation(error)
10
+ ? Effect.fail(branches.unique())
11
+ : Effect.fail(branches.unavailable(operation)(error))));
12
+ /**
13
+ * Returns the first row, or `Option.none()` for an empty result. Use it
14
+ * for a lookup whose shape is `Option`.
15
+ */
16
+ export const firstRow = (rows) => Option.fromNullable(rows[0]);
@@ -0,0 +1,13 @@
1
+ import { SqlClient } from '@effect/sql';
2
+ import { TransactionBoundaryUnavailableError } from '@satorio/machinery/ports';
3
+ import { Effect } from 'effect';
4
+ /**
5
+ * Builds the boundary on a real Postgres transaction: a `SqlError` on
6
+ * the way in or out becomes the port's own unavailability failure, and
7
+ * the work's own failures pass through untouched.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare const makePgTransactionBoundary: Effect.Effect<{
12
+ inTransaction: <A, E, R>(work: Effect.Effect<A, E, R>) => Effect.Effect<A, E | TransactionBoundaryUnavailableError, R>;
13
+ }, never, SqlClient.SqlClient>;
@@ -0,0 +1,21 @@
1
+ import { SqlClient } from '@effect/sql';
2
+ import { SqlError } from '@effect/sql/SqlError';
3
+ import { TransactionBoundaryUnavailableError, } from '@satorio/machinery/ports';
4
+ import { Effect } from 'effect';
5
+ /**
6
+ * Builds the boundary on a real Postgres transaction: a `SqlError` on
7
+ * the way in or out becomes the port's own unavailability failure, and
8
+ * the work's own failures pass through untouched.
9
+ *
10
+ * @internal
11
+ */
12
+ export const makePgTransactionBoundary = Effect.gen(function* () {
13
+ const sql = yield* SqlClient.SqlClient;
14
+ const inTransaction = (work) => sql.withTransaction(work).pipe(Effect.mapError((failure) => failure instanceof SqlError
15
+ ? new TransactionBoundaryUnavailableError({
16
+ operation: 'inTransaction',
17
+ cause: failure,
18
+ })
19
+ : failure));
20
+ return { inTransaction };
21
+ });
@@ -0,0 +1,66 @@
1
+ import { type Refusal } from '@satorio/machinery/core';
2
+ import { type EventPublisherShape, type OutboxMessage } from '@satorio/machinery/ports';
3
+ import { Effect, type Scope } from 'effect';
4
+ import type { SnsPublisherSettings } from './settings.js';
5
+ /**
6
+ * What one publish call asks for.
7
+ *
8
+ * @internal
9
+ */
10
+ export interface SnsPublishRequest {
11
+ readonly TopicArn: string;
12
+ readonly Message: string;
13
+ }
14
+ /**
15
+ * The narrowest slice of the SDK the publisher uses: put one message on
16
+ * the topic. A test can substitute its own object of this shape.
17
+ *
18
+ * @internal
19
+ */
20
+ export interface SnsPublishTransport {
21
+ readonly publish: (request: SnsPublishRequest, signal?: AbortSignal) => Promise<unknown>;
22
+ }
23
+ /**
24
+ * The transport plus the one call that closes the client behind it.
25
+ *
26
+ * @internal
27
+ */
28
+ export interface ClosableSnsTransport extends SnsPublishTransport {
29
+ readonly close: () => void;
30
+ }
31
+ /**
32
+ * The message body a subscriber reads: the same envelope as the stdout
33
+ * line, minus the channel — here the topic itself plays that role.
34
+ */
35
+ export declare const snsMessageBodyOf: (message: OutboxMessage) => string;
36
+ /**
37
+ * The publisher over one SNS transport: every message goes to the topic
38
+ * this prefix named, one publish call each.
39
+ *
40
+ * @internal
41
+ */
42
+ export declare const makeSnsEventPublisher: (transport: SnsPublishTransport, settings: SnsPublisherSettings) => EventPublisherShape;
43
+ /**
44
+ * Holds one transport open for the length of a scope and closes it when
45
+ * the scope ends.
46
+ *
47
+ * @internal
48
+ */
49
+ export declare const scopedSnsTransport: (openTransport: () => ClosableSnsTransport) => Effect.Effect<SnsPublishTransport, never, Scope.Scope>;
50
+ /**
51
+ * Opens the real SNS client. This is the only place the SDK is
52
+ * constructed.
53
+ *
54
+ * @internal
55
+ */
56
+ export declare const openSnsTransport: (settings: SnsPublisherSettings) => ClosableSnsTransport;
57
+ /**
58
+ * Without a topic set, the publisher does not fail service startup: it
59
+ * mounts a stand that fails `publish` loudly with the same three-part
60
+ * refusal the mount desk logged. The rest of the service keeps running,
61
+ * and the outbox relay reports the missing topic honestly instead of
62
+ * silently losing events.
63
+ *
64
+ * @internal
65
+ */
66
+ export declare const eventPublisherUnavailableWithoutConfiguration: (refusal: Refusal) => EventPublisherShape;
@@ -0,0 +1,75 @@
1
+ import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';
2
+ import { bigIntsAsDecimalStrings, refusalText, } from '@satorio/machinery/core';
3
+ import { EventPublisherUnavailableError, } from '@satorio/machinery/ports';
4
+ import { DateTime, Effect, Option } from 'effect';
5
+ /**
6
+ * The message body a subscriber reads: the same envelope as the stdout
7
+ * line, minus the channel — here the topic itself plays that role.
8
+ */
9
+ export const snsMessageBodyOf = (message) => JSON.stringify({
10
+ messageId: message.messageId,
11
+ eventType: message.eventType,
12
+ schemaVersion: message.schemaVersion,
13
+ occurredAt: DateTime.formatIso(message.occurredAt),
14
+ payload: message.payload,
15
+ }, bigIntsAsDecimalStrings);
16
+ /**
17
+ * The publisher over one SNS transport: every message goes to the topic
18
+ * this prefix named, one publish call each.
19
+ *
20
+ * @internal
21
+ */
22
+ export const makeSnsEventPublisher = (transport, settings) => ({
23
+ publish: (messages) => Effect.forEach(messages, (message) => Effect.tryPromise({
24
+ try: (signal) => transport.publish({
25
+ TopicArn: settings.topicArn,
26
+ Message: snsMessageBodyOf(message),
27
+ }, signal),
28
+ catch: (cause) => new EventPublisherUnavailableError({ operation: 'publish', cause }),
29
+ }), { discard: true }),
30
+ });
31
+ /**
32
+ * Holds one transport open for the length of a scope and closes it when
33
+ * the scope ends.
34
+ *
35
+ * @internal
36
+ */
37
+ export const scopedSnsTransport = (openTransport) => Effect.acquireRelease(Effect.sync(openTransport), (transport) => Effect.sync(() => transport.close()));
38
+ const clientConfigurationOf = (settings) => ({
39
+ ...Option.match(settings.region, {
40
+ onNone: () => ({}),
41
+ onSome: (clientRegion) => ({ region: clientRegion }),
42
+ }),
43
+ ...Option.match(settings.endpoint, {
44
+ onNone: () => ({}),
45
+ onSome: (clientEndpoint) => ({ endpoint: clientEndpoint }),
46
+ }),
47
+ });
48
+ /**
49
+ * Opens the real SNS client. This is the only place the SDK is
50
+ * constructed.
51
+ *
52
+ * @internal
53
+ */
54
+ export const openSnsTransport = (settings) => {
55
+ const client = new SNSClient(clientConfigurationOf(settings));
56
+ return {
57
+ publish: (request, signal) => client.send(new PublishCommand(request), signal === undefined ? {} : { abortSignal: signal }),
58
+ close: () => client.destroy(),
59
+ };
60
+ };
61
+ /**
62
+ * Without a topic set, the publisher does not fail service startup: it
63
+ * mounts a stand that fails `publish` loudly with the same three-part
64
+ * refusal the mount desk logged. The rest of the service keeps running,
65
+ * and the outbox relay reports the missing topic honestly instead of
66
+ * silently losing events.
67
+ *
68
+ * @internal
69
+ */
70
+ export const eventPublisherUnavailableWithoutConfiguration = (refusal) => ({
71
+ publish: () => Effect.fail(new EventPublisherUnavailableError({
72
+ operation: 'publish',
73
+ cause: refusalText(refusal),
74
+ })),
75
+ });
@@ -0,0 +1,15 @@
1
+ import type { EventPublisherShape, OutboxMessage } from '@satorio/machinery/ports';
2
+ import { Effect } from 'effect';
3
+ /**
4
+ * The simplest publisher: an event travels as one JSON line on stdout,
5
+ * where a log collector picks it up. It proves the outbox relay works
6
+ * before a real message bus exists.
7
+ */
8
+ export declare const stdoutLineOf: (channel: string, message: OutboxMessage) => string;
9
+ /**
10
+ * The publisher over one line writer: one line per message, in the order
11
+ * the batch holds them.
12
+ *
13
+ * @internal
14
+ */
15
+ export declare const makeStdoutEventPublisher: (channel: string, writeLine: (line: string) => Effect.Effect<void>) => EventPublisherShape;
@@ -0,0 +1,24 @@
1
+ import { bigIntsAsDecimalStrings } from '@satorio/machinery/core';
2
+ import { DateTime, Effect } from 'effect';
3
+ /**
4
+ * The simplest publisher: an event travels as one JSON line on stdout,
5
+ * where a log collector picks it up. It proves the outbox relay works
6
+ * before a real message bus exists.
7
+ */
8
+ export const stdoutLineOf = (channel, message) => JSON.stringify({
9
+ channel,
10
+ messageId: message.messageId,
11
+ eventType: message.eventType,
12
+ schemaVersion: message.schemaVersion,
13
+ occurredAt: DateTime.formatIso(message.occurredAt),
14
+ payload: message.payload,
15
+ }, bigIntsAsDecimalStrings);
16
+ /**
17
+ * The publisher over one line writer: one line per message, in the order
18
+ * the batch holds them.
19
+ *
20
+ * @internal
21
+ */
22
+ export const makeStdoutEventPublisher = (channel, writeLine) => ({
23
+ publish: (messages) => Effect.forEach(messages, (message) => writeLine(stdoutLineOf(channel, message)), { discard: true }),
24
+ });