@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,14 @@
1
+ /**
2
+ * Public entry to the identifiers mechanism: the id a machine mints, and the
3
+ * numbers a person reads off a document. It gives you the identifier a machine
4
+ * mints for a record, and the numbers a person reads off a document, quotes
5
+ * back over the phone, and types into a bank.
6
+ */
7
+ export { type ClientCode, clientCodeFrom, clientCodeInGroupedForm, digitsOfClientCode, newClientCode, } from './client-code.js';
8
+ export { type CreditorReference, creditorReferenceFrom, creditorReferenceInGroupedForm, creditorReferenceOf, digitsOfCreditorReference, } from './creditor-reference.js';
9
+ export { dammCheckDigit, dammHolds, withDammCheckDigit } from './damm.js';
10
+ export { clientCodeOfDocumentNumber, type DocumentNumber, type DocumentNumberParts, digitsOfDocumentNumber, reportingMonthOf, } from './document-number.js';
11
+ export { type DocumentNumbers, makeDocumentNumbers, } from './document-numbers.js';
12
+ export { type IdentifierFailure, NotAClientCodeError, NotACreditorReferenceError, NotADocumentNumberError, } from './failures.js';
13
+ export type { NextDocumentNumberInput } from './next-document-number.js';
14
+ export { newUlid, ulidOfSeed, ulidsInOrder } from './ulid.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public entry to the identifiers mechanism: the id a machine mints, and the
3
+ * numbers a person reads off a document. It gives you the identifier a machine
4
+ * mints for a record, and the numbers a person reads off a document, quotes
5
+ * back over the phone, and types into a bank.
6
+ */
7
+ export { clientCodeFrom, clientCodeInGroupedForm, digitsOfClientCode, newClientCode, } from './client-code.js';
8
+ export { creditorReferenceFrom, creditorReferenceInGroupedForm, creditorReferenceOf, digitsOfCreditorReference, } from './creditor-reference.js';
9
+ export { dammCheckDigit, dammHolds, withDammCheckDigit } from './damm.js';
10
+ export { clientCodeOfDocumentNumber, digitsOfDocumentNumber, reportingMonthOf, } from './document-number.js';
11
+ export { makeDocumentNumbers, } from './document-numbers.js';
12
+ export { NotAClientCodeError, NotACreditorReferenceError, NotADocumentNumberError, } from './failures.js';
13
+ export { newUlid, ulidOfSeed, ulidsInOrder } from './ulid.js';
@@ -0,0 +1,26 @@
1
+ import { DocumentNumberCounter, DocumentNumberCounterUnavailableError } from '@satorio/machinery/ports';
2
+ import type { CalendarDay } from '@satorio/machinery/time';
3
+ import { Effect } from 'effect';
4
+ import { type ClientCode } from './client-code.js';
5
+ import { type DocumentNumber } from './document-number.js';
6
+ /**
7
+ * Which document to number: its series, who it goes to, and the month it
8
+ * reports on.
9
+ */
10
+ export interface NextDocumentNumberInput<Series extends string = string> {
11
+ readonly series: Series;
12
+ readonly clientCode: ClientCode;
13
+ readonly reportingMonth: CalendarDay;
14
+ }
15
+ /**
16
+ * The number for the next document a client is sent this month. The
17
+ * count comes from the counter port, which is the only thing here that
18
+ * remembers anything: every series shares one count per client and
19
+ * month, so two documents never carry the same digits. A counter that
20
+ * answers with something no document can be numbered by is a broken
21
+ * counter, and it comes back as that port's own refusal rather than as
22
+ * a number nobody can pay against.
23
+ *
24
+ * @internal
25
+ */
26
+ export declare const nextDocumentNumber: (input: NextDocumentNumberInput) => Effect.Effect<DocumentNumber, DocumentNumberCounterUnavailableError, DocumentNumberCounter>;
@@ -0,0 +1,31 @@
1
+ import { DocumentNumberCounter, DocumentNumberCounterUnavailableError, } from '@satorio/machinery/ports';
2
+ import { Effect, Either } from 'effect';
3
+ import { digitsOfClientCode } from './client-code.js';
4
+ import { documentNumberOf, reportingMonthOf, } from './document-number.js';
5
+ /**
6
+ * The number for the next document a client is sent this month. The
7
+ * count comes from the counter port, which is the only thing here that
8
+ * remembers anything: every series shares one count per client and
9
+ * month, so two documents never carry the same digits. A counter that
10
+ * answers with something no document can be numbered by is a broken
11
+ * counter, and it comes back as that port's own refusal rather than as
12
+ * a number nobody can pay against.
13
+ *
14
+ * @internal
15
+ */
16
+ export const nextDocumentNumber = (input) => Effect.gen(function* () {
17
+ const counter = yield* DocumentNumberCounter;
18
+ const count = yield* counter.nextCount({
19
+ clientCode: digitsOfClientCode(input.clientCode),
20
+ reportingMonth: reportingMonthOf(input.reportingMonth),
21
+ });
22
+ const numbered = documentNumberOf({ ...input, count });
23
+ if (Either.isLeft(numbered)) {
24
+ return yield* new DocumentNumberCounterUnavailableError({
25
+ operation: 'nextCount',
26
+ cause: `The counter answered with ${count}, and no document can carry ` +
27
+ 'that as its count.',
28
+ });
29
+ }
30
+ return numbered.right;
31
+ });
@@ -0,0 +1,28 @@
1
+ import { type Ulid } from '@satorio/machinery/core';
2
+ import { type DateTime, Effect } from 'effect';
3
+ /**
4
+ * A fresh identifier, minted from the clock and the randomness the
5
+ * service already shares. Nothing here reads a global clock or a global
6
+ * random source, so a test that pins the moment and seeds the randomness
7
+ * gets the same identifier every run, and the effect diagnostics that
8
+ * refuse a global random source stay quiet in the service that calls it.
9
+ */
10
+ export declare const newUlid: Effect.Effect<Ulid>;
11
+ /**
12
+ * A maker of identifiers that keep their order even when two of them
13
+ * land inside one millisecond. Ask for the maker once, then call it for
14
+ * every identifier of one run: it remembers the last one it handed out
15
+ * and steps the next one past it, which is what a journal needs when
16
+ * several lines are written in the same breath. Two makers do not
17
+ * coordinate, so one run asks for one maker.
18
+ */
19
+ export declare const ulidsInOrder: Effect.Effect<Effect.Effect<Ulid>>;
20
+ /**
21
+ * The identifier a moment and a seed always mint the same way. Use it
22
+ * where nobody outside may name a record and no random source belongs
23
+ * either: a message off a queue carries the moment the fact happened and
24
+ * its own identifier, and those two are enough. The same envelope
25
+ * arriving twice mints one identifier, so a redelivery cannot enter
26
+ * under a second name, and the identifiers still sort by the moment.
27
+ */
28
+ export declare const ulidOfSeed: (moment: DateTime.Utc, seed: string) => Ulid;
@@ -0,0 +1,71 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { ulid } from '@satorio/machinery/core';
3
+ import { Clock, Effect, Random } from 'effect';
4
+ import { monotonicFactory, ulid as ulidText } from 'ulid';
5
+ const RANDOM_SYMBOLS = 16;
6
+ const BYTE_RANGE = 256;
7
+ const drawingFrom = (draws) => {
8
+ let at = 0;
9
+ return () => {
10
+ const draw = draws[at] ?? 0;
11
+ at += 1;
12
+ return draw;
13
+ };
14
+ };
15
+ const refillableDraws = () => {
16
+ let draws = [];
17
+ let at = 0;
18
+ return {
19
+ prng: () => {
20
+ const draw = draws[at] ?? 0;
21
+ at += 1;
22
+ return draw;
23
+ },
24
+ refill: (fresh) => {
25
+ draws = fresh;
26
+ at = 0;
27
+ },
28
+ };
29
+ };
30
+ /**
31
+ * A fresh identifier, minted from the clock and the randomness the
32
+ * service already shares. Nothing here reads a global clock or a global
33
+ * random source, so a test that pins the moment and seeds the randomness
34
+ * gets the same identifier every run, and the effect diagnostics that
35
+ * refuse a global random source stay quiet in the service that calls it.
36
+ */
37
+ export const newUlid = Effect.gen(function* () {
38
+ const millis = yield* Clock.currentTimeMillis;
39
+ const draws = yield* Effect.replicateEffect(Random.next, RANDOM_SYMBOLS);
40
+ return ulid(ulidText(millis, drawingFrom(draws)));
41
+ });
42
+ /**
43
+ * A maker of identifiers that keep their order even when two of them
44
+ * land inside one millisecond. Ask for the maker once, then call it for
45
+ * every identifier of one run: it remembers the last one it handed out
46
+ * and steps the next one past it, which is what a journal needs when
47
+ * several lines are written in the same breath. Two makers do not
48
+ * coordinate, so one run asks for one maker.
49
+ */
50
+ export const ulidsInOrder = Effect.sync(() => {
51
+ const draws = refillableDraws();
52
+ const nextText = monotonicFactory(draws.prng);
53
+ return Effect.gen(function* () {
54
+ const millis = yield* Clock.currentTimeMillis;
55
+ draws.refill(yield* Effect.replicateEffect(Random.next, RANDOM_SYMBOLS));
56
+ return ulid(nextText(millis));
57
+ });
58
+ });
59
+ /**
60
+ * The identifier a moment and a seed always mint the same way. Use it
61
+ * where nobody outside may name a record and no random source belongs
62
+ * either: a message off a queue carries the moment the fact happened and
63
+ * its own identifier, and those two are enough. The same envelope
64
+ * arriving twice mints one identifier, so a redelivery cannot enter
65
+ * under a second name, and the identifiers still sort by the moment.
66
+ */
67
+ export const ulidOfSeed = (moment, seed) => {
68
+ const digest = createHash('sha256').update(seed).digest();
69
+ const draws = [...digest].map((byte) => byte / BYTE_RANGE);
70
+ return ulid(ulidText(moment.epochMillis, drawingFrom(draws)));
71
+ };
@@ -0,0 +1,22 @@
1
+ import { HttpClient } from '@effect/platform';
2
+ import { type Refusal } from '@satorio/machinery/core';
3
+ import { type ExternalHttpClientShape } from '@satorio/machinery/ports';
4
+ import { Effect } from 'effect';
5
+ import type { ExternalServiceSettings } from './settings.js';
6
+ /**
7
+ * A ready-to-use client, with its transport already captured.
8
+ *
9
+ * A ready-to-use client: the transport (`HttpClient.HttpClient`) is captured
10
+ * into context once, so the piece handed to an adapter needs nothing more from
11
+ * the Effect context.
12
+ */
13
+ export declare const makeExternalHttpClient: (service: string, settings: ExternalServiceSettings) => Effect.Effect<ExternalHttpClientShape, never, HttpClient.HttpClient>;
14
+ /**
15
+ * The client that stands in when nobody set the address, and fails every call
16
+ * loudly.
17
+ *
18
+ * The client that stands in when the address is not set: every call fails with
19
+ * the same three-part refusal the mount desk logged, so nothing goes missing
20
+ * in silence and no second wording exists to keep in step.
21
+ */
22
+ export declare const externalHttpClientUnavailableWithoutConfiguration: (service: string, refusal: Refusal) => ExternalHttpClientShape;
@@ -0,0 +1,74 @@
1
+ import { HttpClient, HttpClientRequest, HttpClientResponse, } from '@effect/platform';
2
+ import { refusalText } from '@satorio/machinery/core';
3
+ import { ExternalServiceRefusedError, ExternalServiceResponseMalformedError, ExternalServiceUnavailableError, } from '@satorio/machinery/ports';
4
+ import { Clock, Duration, Effect, Either, Option } from 'effect';
5
+ import { callFailureOfStatus, retryWhileTransient, transientCallFailure, } from './retry.js';
6
+ const GET_JSON = 'getJson';
7
+ const outcomeOfResponse = (response) => Option.match(callFailureOfStatus(response.status), {
8
+ onNone: () => Effect.succeed(response),
9
+ onSome: (failure) => Effect.fail(failure),
10
+ });
11
+ const logCall = (service, operation, path, status, durationMillis) => Effect.logInfo(JSON.stringify({
12
+ event: 'external-service-call',
13
+ service,
14
+ operation,
15
+ method: 'GET',
16
+ path,
17
+ status,
18
+ durationMillis,
19
+ }));
20
+ const attemptOnce = (service, operation, settings, path) => Effect.gen(function* () {
21
+ const client = yield* HttpClient.HttpClient;
22
+ const request = HttpClientRequest.get(path).pipe(HttpClientRequest.prependUrl(settings.baseUrl));
23
+ const startedAt = yield* Clock.currentTimeMillis;
24
+ const outcome = yield* client.execute(request).pipe(Effect.timeoutFail({
25
+ duration: Duration.millis(settings.timeoutMillis),
26
+ onTimeout: () => 'timeout',
27
+ }), Effect.either);
28
+ const durationMillis = (yield* Clock.currentTimeMillis) - startedAt;
29
+ return yield* Either.match(outcome, {
30
+ onLeft: (error) => logCall(service, operation, path, error === 'timeout' ? 'timeout' : 'network-error', durationMillis).pipe(Effect.zipRight(Effect.fail(transientCallFailure(error)))),
31
+ onRight: (response) => logCall(service, operation, path, response.status, durationMillis).pipe(Effect.zipRight(outcomeOfResponse(response))),
32
+ });
33
+ });
34
+ const getJsonWith = (service, settings, path, schema) => attemptOnce(service, GET_JSON, settings, path).pipe(Effect.retry(retryWhileTransient), Effect.catchTag('TransientCallFailure', (failure) => new ExternalServiceUnavailableError({
35
+ service,
36
+ operation: GET_JSON,
37
+ cause: failure.cause,
38
+ })), Effect.catchTag('RefusedCallFailure', (failure) => new ExternalServiceRefusedError({
39
+ service,
40
+ operation: GET_JSON,
41
+ status: failure.status,
42
+ })), Effect.flatMap((response) => HttpClientResponse.schemaBodyJson(schema)(response).pipe(Effect.mapError((cause) => new ExternalServiceResponseMalformedError({
43
+ service,
44
+ operation: GET_JSON,
45
+ cause,
46
+ })))));
47
+ /**
48
+ * A ready-to-use client, with its transport already captured.
49
+ *
50
+ * A ready-to-use client: the transport (`HttpClient.HttpClient`) is captured
51
+ * into context once, so the piece handed to an adapter needs nothing more from
52
+ * the Effect context.
53
+ */
54
+ export const makeExternalHttpClient = (service, settings) => Effect.gen(function* () {
55
+ const context = yield* Effect.context();
56
+ return {
57
+ getJson: (path, schema) => getJsonWith(service, settings, path, schema).pipe(Effect.provide(context)),
58
+ };
59
+ });
60
+ /**
61
+ * The client that stands in when nobody set the address, and fails every call
62
+ * loudly.
63
+ *
64
+ * The client that stands in when the address is not set: every call fails with
65
+ * the same three-part refusal the mount desk logged, so nothing goes missing
66
+ * in silence and no second wording exists to keep in step.
67
+ */
68
+ export const externalHttpClientUnavailableWithoutConfiguration = (service, refusal) => ({
69
+ getJson: () => Effect.fail(new ExternalServiceUnavailableError({
70
+ service,
71
+ operation: GET_JSON,
72
+ cause: refusalText(refusal),
73
+ })),
74
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Public entry to the integration mechanism: one HTTP client for reference
3
+ * data outside this service. It gives you the `ExternalHttpClient` port bound
4
+ * to real HTTP — a base URL and a timeout read from two prefixed variables,
5
+ * bounded retry for network failures and 5xx, no retry for 4xx, response
6
+ * decoding through `Schema`, a log line for every call, and a loud stub in the
7
+ * client's place when nobody set the address.
8
+ */
9
+ export { externalHttpClientUnavailableWithoutConfiguration, makeExternalHttpClient, } from './external-http-client.js';
10
+ export { externalHttpClientFromEnvironmentLive, externalHttpClientLive, } from './layer.js';
11
+ export { defaultExternalRetrySchedule } from './retry.js';
12
+ export { baseUrlVariable, DEFAULT_TIMEOUT_MILLIS, ENVIRONMENT, type ExternalServiceSettings, externalServiceSettings, timeoutVariable, } from './settings.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Public entry to the integration mechanism: one HTTP client for reference
3
+ * data outside this service. It gives you the `ExternalHttpClient` port bound
4
+ * to real HTTP — a base URL and a timeout read from two prefixed variables,
5
+ * bounded retry for network failures and 5xx, no retry for 4xx, response
6
+ * decoding through `Schema`, a log line for every call, and a loud stub in the
7
+ * client's place when nobody set the address.
8
+ */
9
+ export { externalHttpClientUnavailableWithoutConfiguration, makeExternalHttpClient, } from './external-http-client.js';
10
+ export { externalHttpClientFromEnvironmentLive, externalHttpClientLive, } from './layer.js';
11
+ export { defaultExternalRetrySchedule } from './retry.js';
12
+ export { baseUrlVariable, DEFAULT_TIMEOUT_MILLIS, ENVIRONMENT, externalServiceSettings, timeoutVariable, } from './settings.js';
@@ -0,0 +1,19 @@
1
+ import { type HttpClient } from '@effect/platform';
2
+ import { ExternalHttpClient } from '@satorio/machinery/ports';
3
+ import { type ConfigError, Layer } from 'effect';
4
+ /**
5
+ * A client built from the environment variables under one prefix. With
6
+ * no address set it mounts the loud stub and logs the one refusal every
7
+ * later call repeats, so startup carries on and nothing goes missing in
8
+ * silence. The transport is a parameter, so a test substitutes its own
9
+ * without touching the wiring and without a live network.
10
+ */
11
+ export declare const externalHttpClientFromEnvironmentLive: (service: string, variablePrefix: string, transport?: Layer.Layer<HttpClient.HttpClient>) => Layer.Layer<ExternalHttpClient, ConfigError.ConfigError>;
12
+ /**
13
+ * One external service on the real fetch transport, reading the
14
+ * variables its prefix names. Mount one per service:
15
+ * `externalHttpClientLive('book-catalog', 'BOOK_CATALOG')` and
16
+ * `externalHttpClientLive('exchange-rates', 'EXCHANGE_RATES')` live side
17
+ * by side in one process.
18
+ */
19
+ export declare const externalHttpClientLive: (service: string, variablePrefix: string) => Layer.Layer<ExternalHttpClient, ConfigError.ConfigError>;
@@ -0,0 +1,30 @@
1
+ import { FetchHttpClient } from '@effect/platform';
2
+ import { refusalText } from '@satorio/machinery/core';
3
+ import { ExternalHttpClient } from '@satorio/machinery/ports';
4
+ import { Effect, Layer, Option } from 'effect';
5
+ import { externalHttpClientUnavailableWithoutConfiguration, makeExternalHttpClient, } from './external-http-client.js';
6
+ import { baseUrlRefusal, configuredBaseUrl, externalServiceSettings, } from './settings.js';
7
+ /**
8
+ * A client built from the environment variables under one prefix. With
9
+ * no address set it mounts the loud stub and logs the one refusal every
10
+ * later call repeats, so startup carries on and nothing goes missing in
11
+ * silence. The transport is a parameter, so a test substitutes its own
12
+ * without touching the wiring and without a live network.
13
+ */
14
+ export const externalHttpClientFromEnvironmentLive = (service, variablePrefix, transport = FetchHttpClient.layer) => Layer.unwrapEffect(Effect.gen(function* () {
15
+ const configured = yield* configuredBaseUrl(variablePrefix);
16
+ if (Option.isNone(configured)) {
17
+ const refusal = baseUrlRefusal(variablePrefix);
18
+ yield* Effect.logError(refusalText(refusal));
19
+ return Layer.succeed(ExternalHttpClient, externalHttpClientUnavailableWithoutConfiguration(service, refusal));
20
+ }
21
+ return Layer.effect(ExternalHttpClient, Effect.flatMap(externalServiceSettings(variablePrefix), (settings) => makeExternalHttpClient(service, settings))).pipe(Layer.provide(transport));
22
+ }));
23
+ /**
24
+ * One external service on the real fetch transport, reading the
25
+ * variables its prefix names. Mount one per service:
26
+ * `externalHttpClientLive('book-catalog', 'BOOK_CATALOG')` and
27
+ * `externalHttpClientLive('exchange-rates', 'EXCHANGE_RATES')` live side
28
+ * by side in one process.
29
+ */
30
+ export const externalHttpClientLive = (service, variablePrefix) => externalHttpClientFromEnvironmentLive(service, variablePrefix);
@@ -0,0 +1,65 @@
1
+ import { Option, Schedule } from 'effect';
2
+ /**
3
+ * How a call waits and tries again: growing pauses, jittered, on a short
4
+ * budget.
5
+ *
6
+ * Exponential growth with jitter and a hard cap on attempts — the same recipe
7
+ * as the queue consumer's `defaultQueueRetrySchedule`, on a shorter budget: an
8
+ * external reference call must not hold for minutes.
9
+ */
10
+ export declare const defaultExternalRetrySchedule: Schedule.Schedule<unknown, unknown>;
11
+ /**
12
+ * The service did not answer, or answered that it could not: a network
13
+ * failure, a timeout, or a 5xx. Trying again is worth it.
14
+ *
15
+ * @internal
16
+ */
17
+ export interface TransientCallFailure {
18
+ readonly _tag: 'TransientCallFailure';
19
+ readonly cause: unknown;
20
+ }
21
+ /**
22
+ * The service answered with a specific no — a 4xx. Trying again asks the
23
+ * same question and gets the same answer, so the client stops at once.
24
+ *
25
+ * @internal
26
+ */
27
+ export interface RefusedCallFailure {
28
+ readonly _tag: 'RefusedCallFailure';
29
+ readonly status: number;
30
+ }
31
+ /**
32
+ * The two ways one attempt can end badly, before the client turns either
33
+ * of them into the failure a caller reads.
34
+ *
35
+ * @internal
36
+ */
37
+ export type CallFailure = TransientCallFailure | RefusedCallFailure;
38
+ /**
39
+ * @internal
40
+ */
41
+ export declare const transientCallFailure: (cause: unknown) => TransientCallFailure;
42
+ /**
43
+ * @internal
44
+ */
45
+ export declare const refusedCallFailure: (status: number) => RefusedCallFailure;
46
+ /**
47
+ * Reads one response status as the kind of failure it is, or
48
+ * `Option.none` when the service answered. A 5xx is transient because
49
+ * the service is having a bad moment; a 4xx is a refusal because it
50
+ * answered the question and the answer was no.
51
+ *
52
+ * @internal
53
+ */
54
+ export declare const callFailureOfStatus: (status: number) => Option.Option<CallFailure>;
55
+ /**
56
+ * @internal
57
+ */
58
+ export declare const isTransientCallFailure: (failure: CallFailure) => failure is TransientCallFailure;
59
+ /**
60
+ * Retries while the attempt failed transiently, on the schedule above,
61
+ * and stops the moment the service gives a specific no.
62
+ *
63
+ * @internal
64
+ */
65
+ export declare const retryWhileTransient: Schedule.Schedule<readonly [CallFailure, unknown], CallFailure>;
@@ -0,0 +1,54 @@
1
+ import { Duration, Option, Schedule } from 'effect';
2
+ const LOWEST_SERVER_FAILURE_STATUS = 500;
3
+ const LOWEST_REFUSAL_STATUS = 400;
4
+ /**
5
+ * How a call waits and tries again: growing pauses, jittered, on a short
6
+ * budget.
7
+ *
8
+ * Exponential growth with jitter and a hard cap on attempts — the same recipe
9
+ * as the queue consumer's `defaultQueueRetrySchedule`, on a shorter budget: an
10
+ * external reference call must not hold for minutes.
11
+ */
12
+ export const defaultExternalRetrySchedule = Schedule.exponential(Duration.millis(100), 2).pipe(Schedule.union(Schedule.spaced(Duration.seconds(2))), Schedule.jittered, Schedule.intersect(Schedule.recurs(4)));
13
+ /**
14
+ * @internal
15
+ */
16
+ export const transientCallFailure = (cause) => ({
17
+ _tag: 'TransientCallFailure',
18
+ cause,
19
+ });
20
+ /**
21
+ * @internal
22
+ */
23
+ export const refusedCallFailure = (status) => ({
24
+ _tag: 'RefusedCallFailure',
25
+ status,
26
+ });
27
+ /**
28
+ * Reads one response status as the kind of failure it is, or
29
+ * `Option.none` when the service answered. A 5xx is transient because
30
+ * the service is having a bad moment; a 4xx is a refusal because it
31
+ * answered the question and the answer was no.
32
+ *
33
+ * @internal
34
+ */
35
+ export const callFailureOfStatus = (status) => {
36
+ if (status >= LOWEST_SERVER_FAILURE_STATUS) {
37
+ return Option.some(transientCallFailure(status));
38
+ }
39
+ if (status >= LOWEST_REFUSAL_STATUS) {
40
+ return Option.some(refusedCallFailure(status));
41
+ }
42
+ return Option.none();
43
+ };
44
+ /**
45
+ * @internal
46
+ */
47
+ export const isTransientCallFailure = (failure) => failure._tag === 'TransientCallFailure';
48
+ /**
49
+ * Retries while the attempt failed transiently, on the schedule above,
50
+ * and stops the moment the service gives a specific no.
51
+ *
52
+ * @internal
53
+ */
54
+ export const retryWhileTransient = Schedule.recurWhile(isTransientCallFailure).pipe(Schedule.intersect(defaultExternalRetrySchedule));
@@ -0,0 +1,73 @@
1
+ import { type EnvironmentEntry, type Refusal } from '@satorio/machinery/core';
2
+ import { Config, type Option } from 'effect';
3
+ /**
4
+ * The longest one external call may take: ten minutes. A number above it
5
+ * is a timeout nobody is waiting on any more, so the client refuses it
6
+ * rather than holding a request open past every deadline around it.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare const LONGEST_TIMEOUT_MILLIS = 600000;
11
+ /**
12
+ * How long one call may take when nobody names a number. Three seconds
13
+ * is long enough for a reference service on a good day and short enough
14
+ * that a bad one does not hold a use case open.
15
+ */
16
+ export declare const DEFAULT_TIMEOUT_MILLIS = 3000;
17
+ /**
18
+ * Builds the environment variable name from the prefix the application
19
+ * gives at mount time. One process calls any number of external services
20
+ * this way, with readable names: `BOOK_CATALOG_BASE_URL`,
21
+ * `EXCHANGE_RATES_BASE_URL`, and so on.
22
+ */
23
+ export declare const baseUrlVariable: (variablePrefix: string) => string;
24
+ /**
25
+ * Builds the timeout variable's name from the same prefix, so each
26
+ * external service in one process keeps its own patience.
27
+ */
28
+ export declare const timeoutVariable: (variablePrefix: string) => string;
29
+ /**
30
+ * What a client needs before it can call: the address every request goes
31
+ * under, and how long one call may take.
32
+ */
33
+ export interface ExternalServiceSettings {
34
+ readonly baseUrl: string;
35
+ readonly timeoutMillis: number;
36
+ }
37
+ /**
38
+ * The three parts a reader meets when the address is not set. The mount
39
+ * desk logs this text and every call to the loud stub fails with it, so
40
+ * an operator reads one sentence rather than two.
41
+ *
42
+ * @internal
43
+ */
44
+ export declare const baseUrlRefusal: (variablePrefix: string) => Refusal;
45
+ /**
46
+ * The three parts a reader meets when the timeout is set to something
47
+ * the client cannot wait on. A timeout left unset takes the default and
48
+ * refuses nothing.
49
+ *
50
+ * @internal
51
+ */
52
+ export declare const timeoutRefusal: (variablePrefix: string) => Refusal;
53
+ /**
54
+ * Reads the address without refusing, so the mount desk can put up the
55
+ * loud stub instead of failing service startup.
56
+ *
57
+ * @internal
58
+ */
59
+ export declare const configuredBaseUrl: (variablePrefix: string) => Config.Config<Option.Option<string>>;
60
+ /**
61
+ * Every variable this adapter reads, and the one state each of them
62
+ * stands in. `<PREFIX>` is the prefix the application passes at mount
63
+ * time, so a second external service in the same process reads a second
64
+ * pair of names.
65
+ */
66
+ export declare const ENVIRONMENT: readonly EnvironmentEntry[];
67
+ /**
68
+ * Reads every variable of `ENVIRONMENT` under one prefix. A missing or
69
+ * blank address refuses in three parts; a timeout left alone takes the
70
+ * default, and one set outside the range refuses in three parts of its
71
+ * own.
72
+ */
73
+ export declare const externalServiceSettings: (variablePrefix: string) => Config.Config<ExternalServiceSettings>;
@@ -0,0 +1,92 @@
1
+ import { fallsBackTo, optionalText, refuses, requiredText, wholeNumberFallingBackTo, } from '@satorio/machinery/core';
2
+ import { Config } from 'effect';
3
+ const BASE_URL_SUFFIX = '_BASE_URL';
4
+ const TIMEOUT_SUFFIX = '_TIMEOUT_MILLIS';
5
+ const SHORTEST_TIMEOUT_MILLIS = 1;
6
+ /**
7
+ * The longest one external call may take: ten minutes. A number above it
8
+ * is a timeout nobody is waiting on any more, so the client refuses it
9
+ * rather than holding a request open past every deadline around it.
10
+ *
11
+ * @internal
12
+ */
13
+ export const LONGEST_TIMEOUT_MILLIS = 600_000;
14
+ /**
15
+ * How long one call may take when nobody names a number. Three seconds
16
+ * is long enough for a reference service on a good day and short enough
17
+ * that a bad one does not hold a use case open.
18
+ */
19
+ export const DEFAULT_TIMEOUT_MILLIS = 3_000;
20
+ /**
21
+ * Builds the environment variable name from the prefix the application
22
+ * gives at mount time. One process calls any number of external services
23
+ * this way, with readable names: `BOOK_CATALOG_BASE_URL`,
24
+ * `EXCHANGE_RATES_BASE_URL`, and so on.
25
+ */
26
+ export const baseUrlVariable = (variablePrefix) => `${variablePrefix}${BASE_URL_SUFFIX}`;
27
+ /**
28
+ * Builds the timeout variable's name from the same prefix, so each
29
+ * external service in one process keeps its own patience.
30
+ */
31
+ export const timeoutVariable = (variablePrefix) => `${variablePrefix}${TIMEOUT_SUFFIX}`;
32
+ /**
33
+ * The three parts a reader meets when the address is not set. The mount
34
+ * desk logs this text and every call to the loud stub fails with it, so
35
+ * an operator reads one sentence rather than two.
36
+ *
37
+ * @internal
38
+ */
39
+ export const baseUrlRefusal = (variablePrefix) => ({
40
+ what: `${baseUrlVariable(variablePrefix)} is not set, so this external service has no address.`,
41
+ why: 'One process calls several external services, each under its own prefix, so the client cannot guess which address you meant.',
42
+ whatToDo: `Set ${baseUrlVariable(variablePrefix)} to the service's own base URL, such as https://openlibrary.org, then start the service again.`,
43
+ });
44
+ /**
45
+ * The three parts a reader meets when the timeout is set to something
46
+ * the client cannot wait on. A timeout left unset takes the default and
47
+ * refuses nothing.
48
+ *
49
+ * @internal
50
+ */
51
+ export const timeoutRefusal = (variablePrefix) => ({
52
+ what: `${timeoutVariable(variablePrefix)} is set to something this client cannot wait on.`,
53
+ why: `It says how many milliseconds one call may take, so it has to be a whole number from ${SHORTEST_TIMEOUT_MILLIS} to ${LONGEST_TIMEOUT_MILLIS}.`,
54
+ whatToDo: `Set ${timeoutVariable(variablePrefix)} to a whole number of milliseconds in that range, or leave it unset and take the ${DEFAULT_TIMEOUT_MILLIS} millisecond default.`,
55
+ });
56
+ /**
57
+ * Reads the address without refusing, so the mount desk can put up the
58
+ * loud stub instead of failing service startup.
59
+ *
60
+ * @internal
61
+ */
62
+ export const configuredBaseUrl = (variablePrefix) => optionalText(baseUrlVariable(variablePrefix));
63
+ /**
64
+ * Every variable this adapter reads, and the one state each of them
65
+ * stands in. `<PREFIX>` is the prefix the application passes at mount
66
+ * time, so a second external service in the same process reads a second
67
+ * pair of names.
68
+ */
69
+ export const ENVIRONMENT = [
70
+ {
71
+ name: BASE_URL_SUFFIX,
72
+ prefixed: true,
73
+ what: 'The address every call to this external service goes under.',
74
+ state: refuses(baseUrlRefusal('<PREFIX>')),
75
+ },
76
+ {
77
+ name: TIMEOUT_SUFFIX,
78
+ prefixed: true,
79
+ what: 'How many milliseconds one call may take before it counts as a network failure.',
80
+ state: fallsBackTo(String(DEFAULT_TIMEOUT_MILLIS)),
81
+ },
82
+ ];
83
+ /**
84
+ * Reads every variable of `ENVIRONMENT` under one prefix. A missing or
85
+ * blank address refuses in three parts; a timeout left alone takes the
86
+ * default, and one set outside the range refuses in three parts of its
87
+ * own.
88
+ */
89
+ export const externalServiceSettings = (variablePrefix) => Config.all({
90
+ baseUrl: requiredText(baseUrlVariable(variablePrefix), baseUrlRefusal(variablePrefix)),
91
+ timeoutMillis: wholeNumberFallingBackTo(timeoutVariable(variablePrefix), SHORTEST_TIMEOUT_MILLIS, LONGEST_TIMEOUT_MILLIS, DEFAULT_TIMEOUT_MILLIS, timeoutRefusal(variablePrefix)),
92
+ });