@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,45 @@
1
+ import { BlobStore, BlobStoreUnavailableError, } from '@satorio/machinery/ports';
2
+ import { Effect, Layer, Option, Ref } from 'effect';
3
+ const NOT_MOUNTED = 'object store unavailable (test)';
4
+ /**
5
+ * An in-memory blob store over a `Ref` you keep, so a test can seed it
6
+ * before the run and read it after.
7
+ */
8
+ export const makeInMemoryBlobStoreWith = (blobs) => ({
9
+ put: (key, blob) => Ref.update(blobs, (current) => new Map(current).set(key, blob)),
10
+ get: (key) => Ref.get(blobs).pipe(Effect.map((current) => Option.fromNullable(current.get(key)))),
11
+ exists: (key) => Ref.get(blobs).pipe(Effect.map((current) => current.has(key))),
12
+ delete: (key) => Ref.update(blobs, (current) => {
13
+ const next = new Map(current);
14
+ next.delete(key);
15
+ return next;
16
+ }),
17
+ });
18
+ /**
19
+ * An in-memory blob store over a `Ref` of its own, empty at the start.
20
+ */
21
+ export const makeInMemoryBlobStore = Effect.gen(function* () {
22
+ const blobs = yield* Ref.make(new Map());
23
+ return makeInMemoryBlobStoreWith(blobs);
24
+ });
25
+ /**
26
+ * The in-memory blob store, mounted.
27
+ */
28
+ export const InMemoryBlobStoreLive = Layer.effect(BlobStore, makeInMemoryBlobStore);
29
+ /**
30
+ * A blob store that answers nothing: every operation fails with
31
+ * `BlobStoreUnavailableError`. Mount it to walk the branch a working
32
+ * stand-in never reaches.
33
+ */
34
+ export const BlobStoreUnavailableLive = Layer.succeed(BlobStore, {
35
+ put: () => new BlobStoreUnavailableError({ operation: 'put', cause: NOT_MOUNTED }),
36
+ get: () => new BlobStoreUnavailableError({ operation: 'get', cause: NOT_MOUNTED }),
37
+ exists: () => new BlobStoreUnavailableError({
38
+ operation: 'exists',
39
+ cause: NOT_MOUNTED,
40
+ }),
41
+ delete: () => new BlobStoreUnavailableError({
42
+ operation: 'delete',
43
+ cause: NOT_MOUNTED,
44
+ }),
45
+ });
@@ -0,0 +1,29 @@
1
+ import { Clock, DateTime, type Duration, Effect } from 'effect';
2
+ /**
3
+ * Sets the controlled test clock to the given ISO moment. Tests read
4
+ * time through Effect, so this is the only clock code ever sees.
5
+ */
6
+ export declare const setNowTo: (isoTimestamp: string) => Effect.Effect<void>;
7
+ /**
8
+ * Moves the controlled test clock on by the span handed in, so a test reaches
9
+ * a later moment without waiting for it.
10
+ */
11
+ export declare const advanceBy: (duration: Duration.DurationInput) => Effect.Effect<void>;
12
+ /**
13
+ * The moment the clock reads now, as a moment on the world's clock.
14
+ */
15
+ export declare const nowAsDateTime: Effect.Effect<DateTime.Utc>;
16
+ /**
17
+ * The moment the clock reads now, written the way it travels the wire.
18
+ */
19
+ export declare const nowAsIsoString: Effect.Effect<string>;
20
+ /**
21
+ * Builds a Clock pinned to one moment: every time read inside returns
22
+ * that moment, while `sleep` still waits on the real clock.
23
+ */
24
+ export declare const clockPinnedAt: (at: DateTime.Utc) => Clock.Clock;
25
+ /**
26
+ * Runs the given work "at" the given moment: every clock read inside
27
+ * it sees `isoTimestamp`.
28
+ */
29
+ export declare const atTheMoment: (isoTimestamp: string) => <A, E, R>(work: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
@@ -0,0 +1,41 @@
1
+ import { Clock, DateTime, Effect, TestClock } from 'effect';
2
+ /**
3
+ * Sets the controlled test clock to the given ISO moment. Tests read
4
+ * time through Effect, so this is the only clock code ever sees.
5
+ */
6
+ export const setNowTo = (isoTimestamp) => TestClock.setTime(DateTime.toEpochMillis(DateTime.unsafeMake(isoTimestamp)));
7
+ /**
8
+ * Moves the controlled test clock on by the span handed in, so a test reaches
9
+ * a later moment without waiting for it.
10
+ */
11
+ export const advanceBy = (duration) => TestClock.adjust(duration);
12
+ /**
13
+ * The moment the clock reads now, as a moment on the world's clock.
14
+ */
15
+ export const nowAsDateTime = DateTime.now;
16
+ /**
17
+ * The moment the clock reads now, written the way it travels the wire.
18
+ */
19
+ export const nowAsIsoString = DateTime.now.pipe(Effect.map(DateTime.formatIso));
20
+ /**
21
+ * Builds a Clock pinned to one moment: every time read inside returns
22
+ * that moment, while `sleep` still waits on the real clock.
23
+ */
24
+ export const clockPinnedAt = (at) => {
25
+ const millis = DateTime.toEpochMillis(at);
26
+ const nanos = BigInt(millis) * 1000000n;
27
+ const live = Clock.make();
28
+ return {
29
+ [Clock.ClockTypeId]: Clock.ClockTypeId,
30
+ unsafeCurrentTimeMillis: () => millis,
31
+ currentTimeMillis: Effect.succeed(millis),
32
+ unsafeCurrentTimeNanos: () => nanos,
33
+ currentTimeNanos: Effect.succeed(nanos),
34
+ sleep: (duration) => live.sleep(duration),
35
+ };
36
+ };
37
+ /**
38
+ * Runs the given work "at" the given moment: every clock read inside
39
+ * it sees `isoTimestamp`.
40
+ */
41
+ export const atTheMoment = (isoTimestamp) => (work) => Effect.withClock(work, clockPinnedAt(DateTime.unsafeMake(isoTimestamp)));
@@ -0,0 +1,55 @@
1
+ import type { ChainRange } from './chains.js';
2
+ /**
3
+ * How much attention one chain receives, counted in mutants.
4
+ *
5
+ * @internal
6
+ */
7
+ export interface ChainAttention {
8
+ readonly chain: string;
9
+ readonly mutants: number;
10
+ readonly caughtByStaticAnalysis: number;
11
+ readonly killedByTests: number;
12
+ readonly killingTestRuns: number;
13
+ readonly survived: number;
14
+ }
15
+ /**
16
+ * @internal
17
+ */
18
+ export interface ReportMutant {
19
+ readonly status: string;
20
+ readonly location: {
21
+ readonly start: {
22
+ readonly line: number;
23
+ };
24
+ };
25
+ readonly killedBy?: readonly string[];
26
+ readonly testsCompleted?: number;
27
+ }
28
+ /**
29
+ * One mutation-testing report, in the shape every mutation runner
30
+ * writes: files by path, each with the mutants it produced.
31
+ *
32
+ * @internal
33
+ */
34
+ export interface MutationReport {
35
+ readonly files: Readonly<Record<string, {
36
+ readonly mutants: readonly ReportMutant[];
37
+ }>>;
38
+ }
39
+ /**
40
+ * The attention map: for every chain of one dna file, how many mutants
41
+ * the mutation run produced, how many static analysis refused before a
42
+ * single test ran, how many the tests killed and with how many test
43
+ * runs, and how many survived. A chain with survivors is a chain nobody
44
+ * is really watching, whatever the coverage percentage says.
45
+ *
46
+ * @internal
47
+ */
48
+ export declare const attentionMapOf: (report: MutationReport, dnaPath: string, ranges: readonly ChainRange[]) => readonly ChainAttention[];
49
+ /**
50
+ * The attention map as the lines a person reads, one chain per line,
51
+ * with the chains nobody watches named last.
52
+ *
53
+ * @internal
54
+ */
55
+ export declare const attentionLinesOf: (implant: string, map: readonly ChainAttention[]) => readonly string[];
@@ -0,0 +1,49 @@
1
+ const isKilled = (status) => status === 'Killed' || status === 'Timeout';
2
+ const isCompileError = (status) => status === 'CompileError';
3
+ /**
4
+ * The attention map: for every chain of one dna file, how many mutants
5
+ * the mutation run produced, how many static analysis refused before a
6
+ * single test ran, how many the tests killed and with how many test
7
+ * runs, and how many survived. A chain with survivors is a chain nobody
8
+ * is really watching, whatever the coverage percentage says.
9
+ *
10
+ * @internal
11
+ */
12
+ export const attentionMapOf = (report, dnaPath, ranges) => {
13
+ const mutants = report.files[dnaPath]?.mutants ?? [];
14
+ return ranges.map((range) => {
15
+ const own = mutants.filter((mutant) => mutant.location.start.line >= range.startLine &&
16
+ mutant.location.start.line <= range.endLine);
17
+ const killed = own.filter((mutant) => isKilled(mutant.status));
18
+ return {
19
+ chain: range.chain,
20
+ mutants: own.length,
21
+ caughtByStaticAnalysis: own.filter((mutant) => isCompileError(mutant.status)).length,
22
+ killedByTests: killed.length,
23
+ killingTestRuns: killed.reduce((total, mutant) => total + (mutant.killedBy?.length ?? mutant.testsCompleted ?? 0), 0),
24
+ survived: own.filter((mutant) => !isKilled(mutant.status) && !isCompileError(mutant.status)).length,
25
+ };
26
+ });
27
+ };
28
+ /**
29
+ * The attention map as the lines a person reads, one chain per line,
30
+ * with the chains nobody watches named last.
31
+ *
32
+ * @internal
33
+ */
34
+ export const attentionLinesOf = (implant, map) => {
35
+ const rows = map.map((entry) => `${implant}/${entry.chain} — ${entry.mutants} mutants: ` +
36
+ `${entry.caughtByStaticAnalysis} caught by static analysis before a ` +
37
+ `test ran, ${entry.killedByTests} killed by tests ` +
38
+ `(${entry.killingTestRuns} test runs did the killing), ` +
39
+ `${entry.survived} survived`);
40
+ const unwatched = map.filter((entry) => entry.survived > 0);
41
+ return [
42
+ ...rows,
43
+ unwatched.length === 0
44
+ ? 'every chain of this implant has a test that notices when it changes.'
45
+ : `chains with survivors, watch these first: ${unwatched
46
+ .map((entry) => entry.chain)
47
+ .join(', ')}`,
48
+ ];
49
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * One chain of a dna file and the lines it occupies, counted from one.
3
+ *
4
+ * @internal
5
+ */
6
+ export interface ChainRange {
7
+ readonly chain: string;
8
+ readonly startLine: number;
9
+ readonly endLine: number;
10
+ }
11
+ /**
12
+ * Every chain of a dna file with the lines it owns: a chain runs from
13
+ * its own `export` line to the line before the next one.
14
+ *
15
+ * @internal
16
+ */
17
+ export declare const chainRangesOf: (text: string) => readonly ChainRange[];
18
+ /**
19
+ * The dna file with one chain removed outright. A removal is the break
20
+ * the demonstration wants: nothing subtle, nothing that only a linter
21
+ * would notice — the business chain is simply gone.
22
+ *
23
+ * @internal
24
+ */
25
+ export declare const withChainRemoved: (text: string, chain: string) => string | null;
26
+ /**
27
+ * The dna file with one line commented out, counted from one.
28
+ *
29
+ * @internal
30
+ */
31
+ export declare const withLineCommented: (text: string, line: number) => string | null;
@@ -0,0 +1,53 @@
1
+ const EXPORTED_VALUE = /^export\s+(?:const|function)\s+([A-Za-z_$][\w$]*)/;
2
+ /**
3
+ * Every chain of a dna file with the lines it owns: a chain runs from
4
+ * its own `export` line to the line before the next one.
5
+ *
6
+ * @internal
7
+ */
8
+ export const chainRangesOf = (text) => {
9
+ const lines = text.split('\n');
10
+ const starts = [];
11
+ lines.forEach((line, index) => {
12
+ const match = EXPORTED_VALUE.exec(line);
13
+ if (match?.[1] !== undefined) {
14
+ starts.push({ chain: match[1], startLine: index + 1 });
15
+ }
16
+ });
17
+ return starts.map((start, index) => ({
18
+ chain: start.chain,
19
+ startLine: start.startLine,
20
+ endLine: (starts[index + 1]?.startLine ?? lines.length + 1) - 1,
21
+ }));
22
+ };
23
+ /**
24
+ * The dna file with one chain removed outright. A removal is the break
25
+ * the demonstration wants: nothing subtle, nothing that only a linter
26
+ * would notice — the business chain is simply gone.
27
+ *
28
+ * @internal
29
+ */
30
+ export const withChainRemoved = (text, chain) => {
31
+ const range = chainRangesOf(text).find((entry) => entry.chain === chain);
32
+ if (range === undefined)
33
+ return null;
34
+ const lines = text.split('\n');
35
+ return [
36
+ ...lines.slice(0, range.startLine - 1),
37
+ ...lines.slice(range.endLine),
38
+ ].join('\n');
39
+ };
40
+ /**
41
+ * The dna file with one line commented out, counted from one.
42
+ *
43
+ * @internal
44
+ */
45
+ export const withLineCommented = (text, line) => {
46
+ const lines = text.split('\n');
47
+ const target = lines[line - 1];
48
+ if (target === undefined)
49
+ return null;
50
+ return lines
51
+ .map((entry, index) => (index === line - 1 ? `// ${target}` : entry))
52
+ .join('\n');
53
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The path of the json report a mutation run has just written, read out
3
+ * of that run's own output. Stryker names the file itself, once per
4
+ * reporter, so the tool never has to guess a location or trust a report
5
+ * an earlier run left behind: no line, no attention map. The html
6
+ * reporter prints the same sentence about its own file, so the line is
7
+ * matched by the reporter that wrote the json one, and the colour codes
8
+ * a terminal run wraps the line in are cut off the path.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare const jsonReportPathIn: (output: string) => string | null;
@@ -0,0 +1,22 @@
1
+ const REPORTED_AT = /JsonReporter[^\n]*?Your report can be found at:\s*(\S+)/;
2
+ const ESCAPE = String.fromCharCode(27);
3
+ const FILE_URL = 'file://';
4
+ /**
5
+ * The path of the json report a mutation run has just written, read out
6
+ * of that run's own output. Stryker names the file itself, once per
7
+ * reporter, so the tool never has to guess a location or trust a report
8
+ * an earlier run left behind: no line, no attention map. The html
9
+ * reporter prints the same sentence about its own file, so the line is
10
+ * matched by the reporter that wrote the json one, and the colour codes
11
+ * a terminal run wraps the line in are cut off the path.
12
+ *
13
+ * @internal
14
+ */
15
+ export const jsonReportPathIn = (output) => {
16
+ const named = REPORTED_AT.exec(output)?.[1]?.split(ESCAPE)[0];
17
+ if (named === undefined || named === '')
18
+ return null;
19
+ return named.startsWith(FILE_URL)
20
+ ? decodeURIComponent(named.slice(FILE_URL.length))
21
+ : named;
22
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What one deliberate break did: how many tests turned red, and whether
3
+ * static analysis refused the file before a single test ran.
4
+ *
5
+ * @internal
6
+ */
7
+ export interface BreakOutcome {
8
+ readonly what: string;
9
+ readonly caughtByStaticAnalysis: boolean;
10
+ readonly testsRed: number;
11
+ }
12
+ /**
13
+ * The one honest line a deliberate break earns. A break that leaves the
14
+ * suite green is printed as a finding, not as a pass: it means the
15
+ * chain that was removed is a chain nothing was holding.
16
+ *
17
+ * @internal
18
+ */
19
+ export declare const verdictLineOf: (outcome: BreakOutcome) => string;
20
+ /**
21
+ * How many tests a test-runner output says went red. Zero when the
22
+ * output never says.
23
+ *
24
+ * @internal
25
+ */
26
+ export declare const redTestCountIn: (output: string) => number;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The one honest line a deliberate break earns. A break that leaves the
3
+ * suite green is printed as a finding, not as a pass: it means the
4
+ * chain that was removed is a chain nothing was holding.
5
+ *
6
+ * @internal
7
+ */
8
+ export const verdictLineOf = (outcome) => {
9
+ if (outcome.caughtByStaticAnalysis) {
10
+ return (`${outcome.what}: static analysis refused it before a single test ` +
11
+ `ran, and ${outcome.testsRed} test(s) went red after that.`);
12
+ }
13
+ if (outcome.testsRed === 0) {
14
+ return (`${outcome.what}: FINDING — nothing went red. Static analysis let it ` +
15
+ 'through and every test still passed, so nothing was holding this ' +
16
+ 'chain.');
17
+ }
18
+ return (`${outcome.what}: static analysis let it through, and ${outcome.testsRed} ` +
19
+ 'test(s) went red.');
20
+ };
21
+ const FAILED_TESTS = /Tests\s+(\d+)\s+failed/;
22
+ /**
23
+ * How many tests a test-runner output says went red. Zero when the
24
+ * output never says.
25
+ *
26
+ * @internal
27
+ */
28
+ export const redTestCountIn = (output) => Number(FAILED_TESTS.exec(output)?.[1] ?? 0);
@@ -0,0 +1,48 @@
1
+ import { type MutationReport } from './dna-perturbator/attention.js';
2
+ /**
3
+ * What one command did.
4
+ *
5
+ * @internal
6
+ */
7
+ export interface CommandRun {
8
+ readonly code: number;
9
+ readonly output: string;
10
+ }
11
+ /**
12
+ * Everything the perturbator touches outside itself, handed in so a test
13
+ * can drive the whole tool without starting a mutation run.
14
+ *
15
+ * @internal
16
+ */
17
+ export interface PerturbatorTools {
18
+ readonly run: (command: string, args: readonly string[]) => CommandRun;
19
+ readonly print: (line: string) => void;
20
+ readonly readFile: (path: string) => string;
21
+ readonly writeFile: (path: string, text: string) => void;
22
+ readonly readReport: (path: string) => MutationReport | null;
23
+ }
24
+ /**
25
+ * The dna file of one implant.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare const dnaPathOf: (implant: string) => string;
30
+ /**
31
+ * The mutation run this tool asks for: every mutant of one implant's dna
32
+ * file, reported as json. Stryker takes the file name of that report from
33
+ * its own configuration and never from the command line, so the tool asks
34
+ * for the reporter and then reads back the path the run itself names.
35
+ *
36
+ * @internal
37
+ */
38
+ export declare const mutationCommandOf: (implant: string) => readonly string[];
39
+ /**
40
+ * The whole tool. With one argument it runs mutation testing over that
41
+ * implant's dna file and prints the attention map. With a second one — a
42
+ * chain name or a line number — it removes that chain or comments that
43
+ * line, runs static analysis and then the implant's tests, prints one
44
+ * honest line, and puts the file back whatever happens.
45
+ *
46
+ * @internal
47
+ */
48
+ export declare const runDnaPerturbator: (argv: readonly string[], tools: PerturbatorTools) => number;
@@ -0,0 +1,145 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { attentionLinesOf, attentionMapOf, } from './dna-perturbator/attention.js';
4
+ import { chainRangesOf, withChainRemoved, withLineCommented, } from './dna-perturbator/chains.js';
5
+ import { jsonReportPathIn } from './dna-perturbator/report.js';
6
+ import { redTestCountIn, verdictLineOf } from './dna-perturbator/verdict.js';
7
+ /**
8
+ * The dna file of one implant.
9
+ *
10
+ * @internal
11
+ */
12
+ export const dnaPathOf = (implant) => `business/${implant}/domain/dna.ts`;
13
+ /**
14
+ * The mutation run this tool asks for: every mutant of one implant's dna
15
+ * file, reported as json. Stryker takes the file name of that report from
16
+ * its own configuration and never from the command line, so the tool asks
17
+ * for the reporter and then reads back the path the run itself names.
18
+ *
19
+ * @internal
20
+ */
21
+ export const mutationCommandOf = (implant) => [
22
+ 'stryker',
23
+ 'run',
24
+ '--mutate',
25
+ dnaPathOf(implant),
26
+ '--reporters',
27
+ 'json',
28
+ ];
29
+ const mutationRun = (implant, tools) => {
30
+ const dnaPath = dnaPathOf(implant);
31
+ const started = tools.run('bunx', [...mutationCommandOf(implant)]);
32
+ const reportPath = jsonReportPathIn(started.output);
33
+ if (reportPath === null) {
34
+ tools.print('dna:perturbator: the mutation run named no json report ' +
35
+ `(it exited ${started.code}), so there is nothing honest to say ` +
36
+ 'about attention here. Switch the json reporter on in this ' +
37
+ "repository's stryker configuration and run it again.");
38
+ return 1;
39
+ }
40
+ const report = tools.readReport(reportPath);
41
+ if (report === null) {
42
+ tools.print(`dna:perturbator: the mutation run named ${reportPath} and nothing ` +
43
+ `readable is there (it exited ${started.code}), so there is ` +
44
+ 'nothing honest to say about attention here.');
45
+ return 1;
46
+ }
47
+ const ranges = chainRangesOf(tools.readFile(dnaPath));
48
+ for (const line of attentionLinesOf(implant, attentionMapOf(report, dnaPath, ranges))) {
49
+ tools.print(line);
50
+ }
51
+ return 0;
52
+ };
53
+ const perturbedTextOf = (text, target) => {
54
+ const asLine = Number(target);
55
+ if (Number.isInteger(asLine) && asLine > 0) {
56
+ const commented = withLineCommented(text, asLine);
57
+ return commented === null
58
+ ? null
59
+ : { text: commented, what: `commenting out line ${asLine}` };
60
+ }
61
+ const removed = withChainRemoved(text, target);
62
+ return removed === null
63
+ ? null
64
+ : { text: removed, what: `removing the chain ${target}` };
65
+ };
66
+ const breakRun = (implant, target, tools) => {
67
+ const dnaPath = dnaPathOf(implant);
68
+ const before = tools.readFile(dnaPath);
69
+ const perturbed = perturbedTextOf(before, target);
70
+ if (perturbed === null) {
71
+ tools.print(`dna:perturbator: ${dnaPath} holds no chain or line named ${target}.`);
72
+ return 1;
73
+ }
74
+ try {
75
+ tools.writeFile(dnaPath, perturbed.text);
76
+ const typecheck = tools.run('bun', ['run', 'typecheck']);
77
+ const tests = tools.run('bunx', ['vitest', 'run', `business/${implant}`]);
78
+ const testsRed = redTestCountIn(tests.output);
79
+ if (testsRed === 0 && tests.code !== 0) {
80
+ tools.print(`${perturbed.what}: the test run itself never reported a count ` +
81
+ `(it exited ${tests.code}), so this run proves nothing. Read its ` +
82
+ 'output before trusting either colour.');
83
+ return 1;
84
+ }
85
+ const line = verdictLineOf({
86
+ what: perturbed.what,
87
+ caughtByStaticAnalysis: typecheck.code !== 0,
88
+ testsRed,
89
+ });
90
+ tools.print(line);
91
+ return line.includes('FINDING') ? 1 : 0;
92
+ }
93
+ finally {
94
+ tools.writeFile(dnaPath, before);
95
+ }
96
+ };
97
+ /**
98
+ * The whole tool. With one argument it runs mutation testing over that
99
+ * implant's dna file and prints the attention map. With a second one — a
100
+ * chain name or a line number — it removes that chain or comments that
101
+ * line, runs static analysis and then the implant's tests, prints one
102
+ * honest line, and puts the file back whatever happens.
103
+ *
104
+ * @internal
105
+ */
106
+ export const runDnaPerturbator = (argv, tools) => {
107
+ const implant = argv[0];
108
+ if (implant === undefined || implant === '') {
109
+ tools.print('dna:perturbator: name the implant. With one argument it measures ' +
110
+ 'attention across every chain; with a second one — a chain name or ' +
111
+ 'a line number — it breaks exactly that and tells you who noticed.');
112
+ return 1;
113
+ }
114
+ const target = argv[1];
115
+ return target === undefined || target === ''
116
+ ? mutationRun(implant, tools)
117
+ : breakRun(implant, target, tools);
118
+ };
119
+ const spawnTools = {
120
+ run: (command, args) => {
121
+ const finished = spawnSync(command, [...args], { encoding: 'utf8' });
122
+ return {
123
+ code: finished.status ?? 1,
124
+ output: `${finished.stdout ?? ''}${finished.stderr ?? ''}`,
125
+ };
126
+ },
127
+ print: (line) => {
128
+ console.log(line);
129
+ },
130
+ readFile: (path) => readFileSync(path, 'utf8'),
131
+ writeFile: (path, text) => {
132
+ writeFileSync(path, text);
133
+ },
134
+ readReport: (path) => {
135
+ try {
136
+ return JSON.parse(readFileSync(path, 'utf8'));
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ },
142
+ };
143
+ if (import.meta.main) {
144
+ process.exitCode = runDnaPerturbator(process.argv.slice(2), spawnTools);
145
+ }
@@ -0,0 +1,29 @@
1
+ import { DocumentNumberCounter, type DocumentNumberCounterShape } from '@satorio/machinery/ports';
2
+ import { Effect, Layer, Ref } from 'effect';
3
+ /**
4
+ * What an in-memory counter holds: how many documents each client has
5
+ * been sent in each reporting month, keyed by the two together. A
6
+ * scenario seeds this map to start a month part way through.
7
+ */
8
+ export type DocumentCountsByClientAndMonth = ReadonlyMap<string, number>;
9
+ /**
10
+ * An in-memory counter over a reference you keep, so a test can seed it and
11
+ * read it back.
12
+ *
13
+ * An in-memory counter over a `Ref` you keep, so a test can seed how far a
14
+ * month has already run and read back where it ended.
15
+ */
16
+ export declare const makeInMemoryDocumentNumberCounterWith: (counts: Ref.Ref<DocumentCountsByClientAndMonth>) => DocumentNumberCounterShape;
17
+ /**
18
+ * An in-memory counter over a `Ref` of its own, with every month at zero.
19
+ */
20
+ export declare const makeInMemoryDocumentNumberCounter: Effect.Effect<DocumentNumberCounterShape, never, never>;
21
+ /**
22
+ * The in-memory counter, mounted.
23
+ */
24
+ export declare const InMemoryDocumentNumberCounterLive: Layer.Layer<DocumentNumberCounter, never, never>;
25
+ /**
26
+ * A counter that counts nothing: every request comes back as a refusal.
27
+ * Mount it to walk the branch a working stand-in never reaches.
28
+ */
29
+ export declare const DocumentNumberCounterUnavailableLive: Layer.Layer<DocumentNumberCounter>;
@@ -0,0 +1,39 @@
1
+ import { DocumentNumberCounter, DocumentNumberCounterUnavailableError, } from '@satorio/machinery/ports';
2
+ import { Effect, Layer, Ref } from 'effect';
3
+ const NOT_MOUNTED = 'document number counter unavailable (test)';
4
+ const keyOf = (clientCode, reportingMonth) => `${clientCode}/${reportingMonth}`;
5
+ /**
6
+ * An in-memory counter over a reference you keep, so a test can seed it and
7
+ * read it back.
8
+ *
9
+ * An in-memory counter over a `Ref` you keep, so a test can seed how far a
10
+ * month has already run and read back where it ended.
11
+ */
12
+ export const makeInMemoryDocumentNumberCounterWith = (counts) => ({
13
+ nextCount: (input) => Ref.modify(counts, (current) => {
14
+ const key = keyOf(input.clientCode, input.reportingMonth);
15
+ const next = (current.get(key) ?? 0) + 1;
16
+ return [next, new Map(current).set(key, next)];
17
+ }),
18
+ });
19
+ /**
20
+ * An in-memory counter over a `Ref` of its own, with every month at zero.
21
+ */
22
+ export const makeInMemoryDocumentNumberCounter = Effect.gen(function* () {
23
+ const counts = yield* Ref.make(new Map());
24
+ return makeInMemoryDocumentNumberCounterWith(counts);
25
+ });
26
+ /**
27
+ * The in-memory counter, mounted.
28
+ */
29
+ export const InMemoryDocumentNumberCounterLive = Layer.effect(DocumentNumberCounter, makeInMemoryDocumentNumberCounter);
30
+ /**
31
+ * A counter that counts nothing: every request comes back as a refusal.
32
+ * Mount it to walk the branch a working stand-in never reaches.
33
+ */
34
+ export const DocumentNumberCounterUnavailableLive = Layer.succeed(DocumentNumberCounter, {
35
+ nextCount: () => new DocumentNumberCounterUnavailableError({
36
+ operation: 'nextCount',
37
+ cause: NOT_MOUNTED,
38
+ }),
39
+ });