@smthrs/journal 0.0.0-stage → 1.0.0-rc.3

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 (257) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/LICENSE +21 -0
  3. package/README.md +206 -2
  4. package/dist/cjs/CallFact.d.ts +129 -0
  5. package/dist/cjs/CallFact.d.ts.map +1 -0
  6. package/dist/cjs/CallFact.js +106 -0
  7. package/dist/cjs/CallFact.js.map +7 -0
  8. package/dist/cjs/Consensus.d.ts +440 -0
  9. package/dist/cjs/Consensus.d.ts.map +1 -0
  10. package/dist/cjs/Consensus.js +251 -0
  11. package/dist/cjs/Consensus.js.map +7 -0
  12. package/dist/cjs/EngineEvent.d.ts +890 -0
  13. package/dist/cjs/EngineEvent.d.ts.map +1 -0
  14. package/dist/cjs/EngineEvent.js +383 -0
  15. package/dist/cjs/EngineEvent.js.map +7 -0
  16. package/dist/cjs/ExecutionFact.d.ts +216 -0
  17. package/dist/cjs/ExecutionFact.d.ts.map +1 -0
  18. package/dist/cjs/ExecutionFact.js +303 -0
  19. package/dist/cjs/ExecutionFact.js.map +7 -0
  20. package/dist/cjs/Journal.d.ts +617 -0
  21. package/dist/cjs/Journal.d.ts.map +1 -0
  22. package/dist/cjs/Journal.js +190 -0
  23. package/dist/cjs/Journal.js.map +7 -0
  24. package/dist/cjs/JournalEvent.d.ts +421 -0
  25. package/dist/cjs/JournalEvent.d.ts.map +1 -0
  26. package/dist/cjs/JournalEvent.js +281 -0
  27. package/dist/cjs/JournalEvent.js.map +7 -0
  28. package/dist/cjs/JournalGeneration.d.ts +34 -0
  29. package/dist/cjs/JournalGeneration.d.ts.map +1 -0
  30. package/dist/cjs/JournalGeneration.js +72 -0
  31. package/dist/cjs/JournalGeneration.js.map +7 -0
  32. package/dist/cjs/JournalMetrics.d.ts +72 -0
  33. package/dist/cjs/JournalMetrics.d.ts.map +1 -0
  34. package/dist/cjs/JournalMetrics.js +55 -0
  35. package/dist/cjs/JournalMetrics.js.map +7 -0
  36. package/dist/cjs/Migrations.d.ts +42 -0
  37. package/dist/cjs/Migrations.d.ts.map +1 -0
  38. package/dist/cjs/Migrations.js +58 -0
  39. package/dist/cjs/Migrations.js.map +7 -0
  40. package/dist/cjs/OwnerId.d.ts +40 -0
  41. package/dist/cjs/OwnerId.d.ts.map +1 -0
  42. package/dist/cjs/OwnerId.js +40 -0
  43. package/dist/cjs/OwnerId.js.map +7 -0
  44. package/dist/cjs/Projection.d.ts +29 -0
  45. package/dist/cjs/Projection.d.ts.map +1 -0
  46. package/dist/cjs/Projection.js +25 -0
  47. package/dist/cjs/Projection.js.map +7 -0
  48. package/dist/cjs/RedactedLogger.d.ts +121 -0
  49. package/dist/cjs/RedactedLogger.d.ts.map +1 -0
  50. package/dist/cjs/RedactedLogger.js +235 -0
  51. package/dist/cjs/RedactedLogger.js.map +7 -0
  52. package/dist/cjs/Redaction.d.ts +350 -0
  53. package/dist/cjs/Redaction.d.ts.map +1 -0
  54. package/dist/cjs/Redaction.js +928 -0
  55. package/dist/cjs/Redaction.js.map +7 -0
  56. package/dist/cjs/SqlConsensus.d.ts +44 -0
  57. package/dist/cjs/SqlConsensus.d.ts.map +1 -0
  58. package/dist/cjs/SqlConsensus.js +243 -0
  59. package/dist/cjs/SqlConsensus.js.map +7 -0
  60. package/dist/cjs/SqlJournal.d.ts +173 -0
  61. package/dist/cjs/SqlJournal.d.ts.map +1 -0
  62. package/dist/cjs/SqlJournal.js +1568 -0
  63. package/dist/cjs/SqlJournal.js.map +7 -0
  64. package/dist/cjs/StepFact.d.ts +66 -0
  65. package/dist/cjs/StepFact.d.ts.map +1 -0
  66. package/dist/cjs/StepFact.js +68 -0
  67. package/dist/cjs/StepFact.js.map +7 -0
  68. package/dist/cjs/index.d.ts +95 -0
  69. package/dist/cjs/index.d.ts.map +1 -0
  70. package/dist/cjs/index.js +63 -0
  71. package/dist/cjs/index.js.map +7 -0
  72. package/dist/cjs/internal/JournalInternalFault.d.ts +37 -0
  73. package/dist/cjs/internal/JournalInternalFault.d.ts.map +1 -0
  74. package/dist/cjs/internal/JournalInternalFault.js +43 -0
  75. package/dist/cjs/internal/JournalInternalFault.js.map +7 -0
  76. package/dist/cjs/migrations/0001_initial.d.ts +15 -0
  77. package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
  78. package/dist/cjs/migrations/0001_initial.js +54 -0
  79. package/dist/cjs/migrations/0001_initial.js.map +7 -0
  80. package/dist/cjs/migrations/0002_checkpoints.d.ts +27 -0
  81. package/dist/cjs/migrations/0002_checkpoints.d.ts.map +1 -0
  82. package/dist/cjs/migrations/0002_checkpoints.js +48 -0
  83. package/dist/cjs/migrations/0002_checkpoints.js.map +7 -0
  84. package/dist/cjs/migrations/0003_startup_index.d.ts +15 -0
  85. package/dist/cjs/migrations/0003_startup_index.d.ts.map +1 -0
  86. package/dist/cjs/migrations/0003_startup_index.js +40 -0
  87. package/dist/cjs/migrations/0003_startup_index.js.map +7 -0
  88. package/dist/cjs/migrations/0004_dedup.d.ts +15 -0
  89. package/dist/cjs/migrations/0004_dedup.d.ts.map +1 -0
  90. package/dist/cjs/migrations/0004_dedup.js +57 -0
  91. package/dist/cjs/migrations/0004_dedup.js.map +7 -0
  92. package/dist/cjs/migrations/0005_run_event_type.d.ts +15 -0
  93. package/dist/cjs/migrations/0005_run_event_type.d.ts.map +1 -0
  94. package/dist/cjs/migrations/0005_run_event_type.js +40 -0
  95. package/dist/cjs/migrations/0005_run_event_type.js.map +7 -0
  96. package/dist/cjs/migrations/0006_consensus.d.ts +22 -0
  97. package/dist/cjs/migrations/0006_consensus.d.ts.map +1 -0
  98. package/dist/cjs/migrations/0006_consensus.js +84 -0
  99. package/dist/cjs/migrations/0006_consensus.js.map +7 -0
  100. package/dist/cjs/package.json +1 -0
  101. package/dist/cjs/test/Notifying.d.ts +65 -0
  102. package/dist/cjs/test/Notifying.d.ts.map +1 -0
  103. package/dist/cjs/test/Notifying.js +62 -0
  104. package/dist/cjs/test/Notifying.js.map +7 -0
  105. package/dist/cjs/test/TestJournal.d.ts +43 -0
  106. package/dist/cjs/test/TestJournal.d.ts.map +1 -0
  107. package/dist/cjs/test/TestJournal.js +43 -0
  108. package/dist/cjs/test/TestJournal.js.map +7 -0
  109. package/dist/esm/CallFact.d.ts +129 -0
  110. package/dist/esm/CallFact.d.ts.map +1 -0
  111. package/dist/esm/CallFact.js +96 -0
  112. package/dist/esm/CallFact.js.map +1 -0
  113. package/dist/esm/Consensus.d.ts +440 -0
  114. package/dist/esm/Consensus.d.ts.map +1 -0
  115. package/dist/esm/Consensus.js +454 -0
  116. package/dist/esm/Consensus.js.map +1 -0
  117. package/dist/esm/EngineEvent.d.ts +890 -0
  118. package/dist/esm/EngineEvent.d.ts.map +1 -0
  119. package/dist/esm/EngineEvent.js +614 -0
  120. package/dist/esm/EngineEvent.js.map +1 -0
  121. package/dist/esm/ExecutionFact.d.ts +216 -0
  122. package/dist/esm/ExecutionFact.d.ts.map +1 -0
  123. package/dist/esm/ExecutionFact.js +374 -0
  124. package/dist/esm/ExecutionFact.js.map +1 -0
  125. package/dist/esm/Journal.d.ts +617 -0
  126. package/dist/esm/Journal.d.ts.map +1 -0
  127. package/dist/esm/Journal.js +323 -0
  128. package/dist/esm/Journal.js.map +1 -0
  129. package/dist/esm/JournalEvent.d.ts +421 -0
  130. package/dist/esm/JournalEvent.d.ts.map +1 -0
  131. package/dist/esm/JournalEvent.js +517 -0
  132. package/dist/esm/JournalEvent.js.map +1 -0
  133. package/dist/esm/JournalGeneration.d.ts +34 -0
  134. package/dist/esm/JournalGeneration.d.ts.map +1 -0
  135. package/dist/esm/JournalGeneration.js +65 -0
  136. package/dist/esm/JournalGeneration.js.map +1 -0
  137. package/dist/esm/JournalMetrics.d.ts +72 -0
  138. package/dist/esm/JournalMetrics.d.ts.map +1 -0
  139. package/dist/esm/JournalMetrics.js +79 -0
  140. package/dist/esm/JournalMetrics.js.map +1 -0
  141. package/dist/esm/Migrations.d.ts +42 -0
  142. package/dist/esm/Migrations.d.ts.map +1 -0
  143. package/dist/esm/Migrations.js +59 -0
  144. package/dist/esm/Migrations.js.map +1 -0
  145. package/dist/esm/OwnerId.d.ts +40 -0
  146. package/dist/esm/OwnerId.d.ts.map +1 -0
  147. package/dist/esm/OwnerId.js +33 -0
  148. package/dist/esm/OwnerId.js.map +1 -0
  149. package/dist/esm/Projection.d.ts +29 -0
  150. package/dist/esm/Projection.d.ts.map +1 -0
  151. package/dist/esm/Projection.js +13 -0
  152. package/dist/esm/Projection.js.map +1 -0
  153. package/dist/esm/RedactedLogger.d.ts +121 -0
  154. package/dist/esm/RedactedLogger.d.ts.map +1 -0
  155. package/dist/esm/RedactedLogger.js +445 -0
  156. package/dist/esm/RedactedLogger.js.map +1 -0
  157. package/dist/esm/Redaction.d.ts +350 -0
  158. package/dist/esm/Redaction.d.ts.map +1 -0
  159. package/dist/esm/Redaction.js +1584 -0
  160. package/dist/esm/Redaction.js.map +1 -0
  161. package/dist/esm/SqlConsensus.d.ts +44 -0
  162. package/dist/esm/SqlConsensus.d.ts.map +1 -0
  163. package/dist/esm/SqlConsensus.js +252 -0
  164. package/dist/esm/SqlConsensus.js.map +1 -0
  165. package/dist/esm/SqlJournal.d.ts +173 -0
  166. package/dist/esm/SqlJournal.d.ts.map +1 -0
  167. package/dist/esm/SqlJournal.js +1858 -0
  168. package/dist/esm/SqlJournal.js.map +1 -0
  169. package/dist/esm/StepFact.d.ts +66 -0
  170. package/dist/esm/StepFact.d.ts.map +1 -0
  171. package/dist/esm/StepFact.js +50 -0
  172. package/dist/esm/StepFact.js.map +1 -0
  173. package/dist/esm/index.d.ts +95 -0
  174. package/dist/esm/index.d.ts.map +1 -0
  175. package/dist/esm/index.js +95 -0
  176. package/dist/esm/index.js.map +1 -0
  177. package/dist/esm/internal/JournalInternalFault.d.ts +37 -0
  178. package/dist/esm/internal/JournalInternalFault.d.ts.map +1 -0
  179. package/dist/esm/internal/JournalInternalFault.js +28 -0
  180. package/dist/esm/internal/JournalInternalFault.js.map +1 -0
  181. package/dist/esm/migrations/0001_initial.d.ts +15 -0
  182. package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
  183. package/dist/esm/migrations/0001_initial.js +32 -0
  184. package/dist/esm/migrations/0001_initial.js.map +1 -0
  185. package/dist/esm/migrations/0002_checkpoints.d.ts +27 -0
  186. package/dist/esm/migrations/0002_checkpoints.d.ts.map +1 -0
  187. package/dist/esm/migrations/0002_checkpoints.js +38 -0
  188. package/dist/esm/migrations/0002_checkpoints.js.map +1 -0
  189. package/dist/esm/migrations/0003_startup_index.d.ts +15 -0
  190. package/dist/esm/migrations/0003_startup_index.d.ts.map +1 -0
  191. package/dist/esm/migrations/0003_startup_index.js +18 -0
  192. package/dist/esm/migrations/0003_startup_index.js.map +1 -0
  193. package/dist/esm/migrations/0004_dedup.d.ts +15 -0
  194. package/dist/esm/migrations/0004_dedup.d.ts.map +1 -0
  195. package/dist/esm/migrations/0004_dedup.js +38 -0
  196. package/dist/esm/migrations/0004_dedup.js.map +1 -0
  197. package/dist/esm/migrations/0005_run_event_type.d.ts +15 -0
  198. package/dist/esm/migrations/0005_run_event_type.d.ts.map +1 -0
  199. package/dist/esm/migrations/0005_run_event_type.js +18 -0
  200. package/dist/esm/migrations/0005_run_event_type.js.map +1 -0
  201. package/dist/esm/migrations/0006_consensus.d.ts +22 -0
  202. package/dist/esm/migrations/0006_consensus.d.ts.map +1 -0
  203. package/dist/esm/migrations/0006_consensus.js +69 -0
  204. package/dist/esm/migrations/0006_consensus.js.map +1 -0
  205. package/dist/esm/test/Notifying.d.ts +65 -0
  206. package/dist/esm/test/Notifying.d.ts.map +1 -0
  207. package/dist/esm/test/Notifying.js +62 -0
  208. package/dist/esm/test/Notifying.js.map +1 -0
  209. package/dist/esm/test/TestJournal.d.ts +43 -0
  210. package/dist/esm/test/TestJournal.d.ts.map +1 -0
  211. package/dist/esm/test/TestJournal.js +27 -0
  212. package/dist/esm/test/TestJournal.js.map +1 -0
  213. package/docs/README.md +164 -0
  214. package/docs/api.md +639 -0
  215. package/docs/concepts/compaction.md +157 -0
  216. package/docs/concepts/idempotency.md +127 -0
  217. package/docs/concepts/owner-fence.md +124 -0
  218. package/docs/concepts/redaction.md +218 -0
  219. package/docs/concepts/state-event-authority.md +159 -0
  220. package/docs/concepts/two-channels.md +167 -0
  221. package/docs/guides/commit-state-and-entry.md +114 -0
  222. package/docs/guides/compact-a-run.md +134 -0
  223. package/docs/guides/fold-a-projection.md +106 -0
  224. package/docs/guides/read-a-run.md +139 -0
  225. package/docs/guides/redact-log-output.md +94 -0
  226. package/docs/guides/testing.md +124 -0
  227. package/docs/guides/write-lifecycle-events.md +110 -0
  228. package/docs/installation.md +133 -0
  229. package/docs/quickstart.md +143 -0
  230. package/docs/troubleshooting.md +214 -0
  231. package/package.json +263 -3
  232. package/src/CallFact.ts +117 -0
  233. package/src/Consensus.ts +683 -0
  234. package/src/EngineEvent.ts +823 -0
  235. package/src/ExecutionFact.ts +433 -0
  236. package/src/Journal.ts +688 -0
  237. package/src/JournalEvent.ts +654 -0
  238. package/src/JournalGeneration.ts +77 -0
  239. package/src/JournalMetrics.ts +89 -0
  240. package/src/Migrations.ts +62 -0
  241. package/src/OwnerId.ts +42 -0
  242. package/src/Projection.ts +31 -0
  243. package/src/RedactedLogger.ts +483 -0
  244. package/src/Redaction.ts +1784 -0
  245. package/src/SqlConsensus.ts +344 -0
  246. package/src/SqlJournal.ts +2732 -0
  247. package/src/StepFact.ts +69 -0
  248. package/src/index.ts +109 -0
  249. package/src/internal/JournalInternalFault.ts +40 -0
  250. package/src/migrations/0001_initial.ts +41 -0
  251. package/src/migrations/0002_checkpoints.ts +46 -0
  252. package/src/migrations/0003_startup_index.ts +19 -0
  253. package/src/migrations/0004_dedup.ts +43 -0
  254. package/src/migrations/0005_run_event_type.ts +19 -0
  255. package/src/migrations/0006_consensus.ts +83 -0
  256. package/src/test/Notifying.ts +101 -0
  257. package/src/test/TestJournal.ts +52 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Logical journal contract with durable lifecycle and lossy telemetry channels.
3
+ *
4
+ * The ownership fence and the lossless `emitDurable` / lossy `emitLossy`
5
+ * split are described at https://journal.smithers.sh/concepts/owner-fence/
6
+ * and https://journal.smithers.sh/concepts/two-channels/.
7
+ *
8
+ * @since 0.1.0
9
+ */
10
+ import * as Context from "effect/Context";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import * as PubSub from "effect/PubSub";
14
+ import * as Schema from "effect/Schema";
15
+ import * as Stream from "effect/Stream";
16
+ import { Entry, RunId, Seq, SourceSeq } from "./JournalEvent.js";
17
+ /**
18
+ * Defers a fold/cache update until the outermost durable write commits.
19
+ * Returns false without running it when no managed commit is available.
20
+ * Failed savepoints and retried transaction attempts never publish the update.
21
+ *
22
+ * @category transactions
23
+ * @since 1.0.0
24
+ */
25
+ export { afterCommit } from "@smthrs/database/DurableWriter";
26
+ /**
27
+ * Stable error codes returned by journal operations.
28
+ *
29
+ * `read_failed` identifies a database failure on a durable read path. Callers
30
+ * can distinguish that retryable storage condition from an unclassified
31
+ * `unknown` journal defect.
32
+ *
33
+ * @category models
34
+ * @since 0.1.0
35
+ */
36
+ export const JournalErrorCode = Schema.Literals([
37
+ "invalid_event",
38
+ "idempotency_conflict",
39
+ "sequence_conflict",
40
+ "fence_lost",
41
+ "queue_overflow",
42
+ "journal_closed",
43
+ "sink_failed",
44
+ "read_failed",
45
+ "decode_failed",
46
+ "projection_failed",
47
+ "checkpoint_invalid",
48
+ "reader_behind",
49
+ "compacted",
50
+ "unknown"
51
+ ]);
52
+ /**
53
+ * Error raised by journal admission, persistence, replay, or projection.
54
+ *
55
+ * `checkpointSeq` is set on the compaction-aware codes: on `compacted` it is
56
+ * the run's compaction floor, the checkpoint sequence a reader must resync
57
+ * from, and on `reader_behind` it is the checkpoint a compaction refused to
58
+ * truncate below.
59
+ *
60
+ * @category errors
61
+ * @since 0.1.0
62
+ */
63
+ export class JournalError extends Schema.TaggedError()("@smthrs/journal/JournalError", {
64
+ code: JournalErrorCode,
65
+ message: Schema.String,
66
+ cause: Schema.optional(Schema.Unknown),
67
+ checkpointSeq: Schema.optional(Seq)
68
+ }) {
69
+ }
70
+ /**
71
+ * Policy applied when the non-blocking admission queue is full.
72
+ *
73
+ * @category models
74
+ * @since 0.1.0
75
+ */
76
+ export const OverflowPolicy = Schema.Literals(["reject", "drop-newest", "drop-oldest"]);
77
+ /**
78
+ * Receipt for a newly admitted event.
79
+ *
80
+ * On the lossy channel, `seq` and `sourceSeq` are allocated synchronously and
81
+ * returned before SQL commits. This `seq` is provisional: the SQL writer
82
+ * moves it above the committed tail if another commit overtakes its reservation.
83
+ * Use committed entries for durable cursors. On the durable channel, the same
84
+ * receipt shape is returned only after the entry commits. `seq` orders the run;
85
+ * `sourceSeq` identifies producer retries and does not change at commit.
86
+ *
87
+ * @category models
88
+ * @since 0.1.0
89
+ */
90
+ export const Accepted = Schema.TaggedStruct("Accepted", {
91
+ seq: Seq,
92
+ sourceSeq: SourceSeq,
93
+ evicted: Schema.optionalKey(Schema.Struct({
94
+ policy: Schema.Literal("drop-oldest"),
95
+ count: Schema.Int.check(Schema.isGreaterThan(0))
96
+ }))
97
+ });
98
+ /**
99
+ * Receipt for an exact retry of an already pending or committed source event.
100
+ *
101
+ * `seq` is the original event's sequence, provisional while pending and
102
+ * canonical after commit. A duplicate does not allocate another sequence or
103
+ * enqueue another write. `status` distinguishes
104
+ * an optimistic retry from one whose original event is already durable.
105
+ *
106
+ * @category models
107
+ * @since 0.1.0
108
+ */
109
+ export const Duplicate = Schema.TaggedStruct("Duplicate", {
110
+ seq: Seq,
111
+ sourceSeq: SourceSeq,
112
+ status: Schema.Literals(["pending", "committed"])
113
+ });
114
+ /**
115
+ * Receipt for an event discarded by an explicit dropping policy.
116
+ *
117
+ * A dropped admission still consumes its synchronously allocated `seq` and
118
+ * `sourceSeq`, so sequence gaps are expected.
119
+ *
120
+ * @category models
121
+ * @since 0.1.0
122
+ */
123
+ export const Dropped = Schema.TaggedStruct("Dropped", {
124
+ seq: Seq,
125
+ sourceSeq: SourceSeq,
126
+ policy: Schema.Literal("drop-newest")
127
+ });
128
+ /**
129
+ * Receipt union for admission that may use the lossy queue.
130
+ *
131
+ * @category models
132
+ * @since 0.1.0
133
+ */
134
+ export const EmitReceipt = Schema.Union([Accepted, Duplicate, Dropped]);
135
+ /**
136
+ * Result of a synchronously durable journal admission.
137
+ *
138
+ * A durable admission is never dropped: it either commits, returns the
139
+ * committed sequence of an exact producer retry, or fails.
140
+ *
141
+ * @category models
142
+ * @since 0.1.0
143
+ */
144
+ export const DurableReceipt = Schema.Union([Accepted, Duplicate]);
145
+ /**
146
+ * Cursor used to replay a run and then follow its committed tail.
147
+ *
148
+ * @category models
149
+ * @since 0.1.0
150
+ */
151
+ export const StreamOptions = Schema.Struct({
152
+ runId: RunId,
153
+ afterSequence: Schema.optionalKey(Seq)
154
+ });
155
+ /**
156
+ * Largest page `entries` will read.
157
+ *
158
+ * A page is decoded into memory in full before the caller sees its first
159
+ * entry, so an unbounded limit lets one call materialize a whole run. The
160
+ * largest page any caller in this repository asks for is 512, so this
161
+ * ceiling refuses only a limit that was going to be a memory incident.
162
+ *
163
+ * @since 1.0.0
164
+ * @category constants
165
+ */
166
+ export const maxEntriesLimit = 10_000;
167
+ /**
168
+ * Cursor and page size for durable journal reads.
169
+ *
170
+ * `limit` is at most `maxEntriesLimit` (10,000 entries). Optional `eventTypes`
171
+ * selects a nonempty list of at most 64 exact types before pagination; cursors remain canonical run
172
+ * sequences and `hasMore` describes matching entries. Omit it for the full run.
173
+ * Filtering does not bypass the run's compaction floor.
174
+ *
175
+ * @category models
176
+ * @since 0.1.0
177
+ */
178
+ export const EntriesOptions = Schema.Struct({
179
+ runId: RunId,
180
+ after: Schema.optionalKey(Seq),
181
+ eventTypes: Schema.optionalKey(Schema.Array(Entry.fields.eventType).check(Schema.isMinLength(1), Schema.isMaxLength(64))),
182
+ limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(maxEntriesLimit))
183
+ });
184
+ /**
185
+ * One page of canonical durable journal entries.
186
+ *
187
+ * @category models
188
+ * @since 0.1.0
189
+ */
190
+ export const EntriesPage = Schema.Struct({
191
+ entries: Schema.Array(Entry),
192
+ hasMore: Schema.Boolean
193
+ });
194
+ /**
195
+ * A durable checkpoint: the caller-captured state that replays a run from
196
+ * `seq` without the entries before it.
197
+ *
198
+ * `state` is the caller's own replay snapshot and round-trips verbatim: the
199
+ * journal never interprets it, and redaction deliberately does not apply,
200
+ * exactly as it does not apply to executable state. A checkpoint at `seq`
201
+ * must subsume every entry with a sequence at or below `seq`: replay is
202
+ * `state` plus `stream({ runId, afterSequence: seq })`.
203
+ *
204
+ * `compactedAtMs` is `null` until a compaction has truncated the entries
205
+ * strictly below `seq`. The largest compacted `seq` for a run is its
206
+ * compaction floor.
207
+ *
208
+ * Prior art: Temporal's mutable state, a durable snapshot pinned to a
209
+ * history offset, with history below it never replayed
210
+ * (`reference/temporal/common/persistence/data_interfaces.go`,
211
+ * `WorkflowSnapshot`).
212
+ *
213
+ * @category models
214
+ * @since 0.1.0
215
+ */
216
+ export class Checkpoint extends Schema.Class("@smthrs/journal/Journal/Checkpoint")({
217
+ runId: RunId,
218
+ seq: Seq,
219
+ state: Schema.Unknown,
220
+ createdAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
221
+ compactedAtMs: Schema.NullOr(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
222
+ }) {
223
+ }
224
+ /**
225
+ * Arguments for writing a checkpoint.
226
+ *
227
+ * `seq` must name a committed entry of the run: the surviving row is what
228
+ * keeps the run's durable `MAX(seq)` allocation floor at or above the
229
+ * compaction boundary, so a process restarted after compaction can never
230
+ * re-allocate a truncated sequence.
231
+ *
232
+ * @category models
233
+ * @since 0.1.0
234
+ */
235
+ export const CheckpointOptions = Schema.Struct({
236
+ runId: RunId,
237
+ seq: Seq,
238
+ state: Schema.Unknown
239
+ });
240
+ /**
241
+ * Arguments for compacting a run.
242
+ *
243
+ * `upTo` selects the checkpoint to truncate below; omitted, the run's latest
244
+ * checkpoint is used.
245
+ *
246
+ * @category models
247
+ * @since 0.1.0
248
+ */
249
+ export const CompactOptions = Schema.Struct({
250
+ runId: RunId,
251
+ upTo: Schema.optionalKey(Seq)
252
+ });
253
+ /**
254
+ * Receipt for a completed compaction.
255
+ *
256
+ * `deleted` is `0` when the checkpoint was already the compaction floor, a
257
+ * retried compaction is idempotent.
258
+ *
259
+ * @category models
260
+ * @since 0.1.0
261
+ */
262
+ export const Compacted = Schema.Struct({
263
+ runId: RunId,
264
+ checkpointSeq: Seq,
265
+ deleted: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
266
+ });
267
+ /**
268
+ * Context service for durable lifecycle evidence and lossy telemetry.
269
+ *
270
+ * @category services
271
+ * @since 0.1.0
272
+ */
273
+ export class Journal extends Context.Service()("@smthrs/journal/Journal") {
274
+ }
275
+ /**
276
+ * Constructs a journal service from an implementation.
277
+ *
278
+ * @category constructors
279
+ * @since 0.1.0
280
+ */
281
+ export const make = (implementation) => Journal.of(implementation);
282
+ const unavailable = (method) => new JournalError({
283
+ code: "journal_closed",
284
+ message: `${method} is unavailable`
285
+ });
286
+ /**
287
+ * Constructs a closed journal stub, optionally overriding individual methods.
288
+ *
289
+ * @category constructors
290
+ * @since 0.1.0
291
+ */
292
+ export const makeNoop = (overrides = {}) => {
293
+ const service = {
294
+ emitLossy: Effect.fn("Journal.emitLossy")(() => Effect.fail(unavailable("emitLossy"))),
295
+ emitDurable: Effect.fn("Journal.emitDurable")(() => Effect.fail(unavailable("emitDurable"))),
296
+ emitDurableUnfenced: Effect.fn("Journal.emitDurableUnfenced")(() => Effect.fail(unavailable("emitDurableUnfenced"))),
297
+ /**
298
+ * The closed stub has no sink and therefore no transaction to open, so it
299
+ * runs the effect directly, the same posture as
300
+ * `DurableEngineState`'s in-memory twin, whose `transaction` has no crash
301
+ * window to close. A test double that models rollback overrides it.
302
+ */
303
+ transact: (effect) => effect,
304
+ whenCommitted: (update) => Effect.as(update, true),
305
+ stream: (options) => Stream.unwrap(Effect.fn("Journal.stream")((_options) => Effect.succeed(Stream.fail(unavailable("stream"))))(options)),
306
+ entries: Effect.fn("Journal.entries")(() => Effect.fail(unavailable("entries"))),
307
+ changes: Effect.acquireRelease(PubSub.sliding(1), PubSub.shutdown).pipe(Effect.flatMap(PubSub.subscribe)),
308
+ project: (projection, options) => Stream.unwrap(Effect.fn("Journal.project")((_projection, _options) => Effect.succeed(Stream.fail(unavailable("project"))))(projection, options)),
309
+ flush: Effect.fn("Journal.flush")(() => Effect.fail(unavailable("flush")))(),
310
+ checkpoint: Effect.fn("Journal.checkpoint")(() => Effect.fail(unavailable("checkpoint"))),
311
+ latestCheckpoint: Effect.fn("Journal.latestCheckpoint")(() => Effect.fail(unavailable("latestCheckpoint"))),
312
+ compact: Effect.fn("Journal.compact")(() => Effect.fail(unavailable("compact")))
313
+ };
314
+ return Journal.of({ ...service, ...overrides });
315
+ };
316
+ /**
317
+ * Provides a closed journal stub.
318
+ *
319
+ * @category layers
320
+ * @since 0.1.0
321
+ */
322
+ export const layerNoop = (overrides = {}) => Layer.succeed(Journal)(makeNoop(overrides));
323
+ //# sourceMappingURL=Journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Journal.js","sourceRoot":"","sources":["../../src/Journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Logical journal contract with durable lifecycle and lossy telemetry channels.\n *\n * The ownership fence and the lossless `emitDurable` / lossy `emitLossy`\n * split are described at https://journal.smithers.sh/concepts/owner-fence/\n * and https://journal.smithers.sh/concepts/two-channels/.\n *\n * @since 0.1.0\n */\n\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport type * as Option from \"effect/Option\"\nimport * as PubSub from \"effect/PubSub\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport { Entry, RunId, Seq, SourceSeq } from \"./JournalEvent.ts\"\nimport type { Input } from \"./JournalEvent.ts\"\nimport type { OwnerId } from \"./OwnerId.ts\"\nimport type { Projection } from \"./Projection.ts\"\n\n/**\n * Defers a fold/cache update until the outermost durable write commits.\n * Returns false without running it when no managed commit is available.\n * Failed savepoints and retried transaction attempts never publish the update.\n *\n * @category transactions\n * @since 1.0.0\n */\nexport { afterCommit } from \"@smthrs/database/DurableWriter\"\n\n/**\n * Stable error codes returned by journal operations.\n *\n * `read_failed` identifies a database failure on a durable read path. Callers\n * can distinguish that retryable storage condition from an unclassified\n * `unknown` journal defect.\n *\n * @category models\n * @since 0.1.0\n */\nexport const JournalErrorCode = Schema.Literals([\n \"invalid_event\",\n \"idempotency_conflict\",\n \"sequence_conflict\",\n \"fence_lost\",\n \"queue_overflow\",\n \"journal_closed\",\n \"sink_failed\",\n \"read_failed\",\n \"decode_failed\",\n \"projection_failed\",\n \"checkpoint_invalid\",\n \"reader_behind\",\n \"compacted\",\n \"unknown\"\n])\n\n/**\n * Stable error codes returned by journal operations.\n *\n * @category models\n * @since 0.1.0\n */\nexport type JournalErrorCode = typeof JournalErrorCode.Type\n\n/**\n * Error raised by journal admission, persistence, replay, or projection.\n *\n * `checkpointSeq` is set on the compaction-aware codes: on `compacted` it is\n * the run's compaction floor, the checkpoint sequence a reader must resync\n * from, and on `reader_behind` it is the checkpoint a compaction refused to\n * truncate below.\n *\n * @category errors\n * @since 0.1.0\n */\nexport class JournalError extends Schema.TaggedError<JournalError>()(\"@smthrs/journal/JournalError\", {\n code: JournalErrorCode,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n checkpointSeq: Schema.optional(Seq)\n}) {}\n\n/**\n * Policy applied when the non-blocking admission queue is full.\n *\n * @category models\n * @since 0.1.0\n */\nexport const OverflowPolicy = Schema.Literals([\"reject\", \"drop-newest\", \"drop-oldest\"])\n\n/**\n * Policy applied when the non-blocking admission queue is full.\n *\n * @category models\n * @since 0.1.0\n */\nexport type OverflowPolicy = typeof OverflowPolicy.Type\n\n/**\n * Receipt for a newly admitted event.\n *\n * On the lossy channel, `seq` and `sourceSeq` are allocated synchronously and\n * returned before SQL commits. This `seq` is provisional: the SQL writer\n * moves it above the committed tail if another commit overtakes its reservation.\n * Use committed entries for durable cursors. On the durable channel, the same\n * receipt shape is returned only after the entry commits. `seq` orders the run;\n * `sourceSeq` identifies producer retries and does not change at commit.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Accepted = Schema.TaggedStruct(\"Accepted\", {\n seq: Seq,\n sourceSeq: SourceSeq,\n evicted: Schema.optionalKey(Schema.Struct({\n policy: Schema.Literal(\"drop-oldest\"),\n count: Schema.Int.check(Schema.isGreaterThan(0))\n }))\n})\n\n/**\n * Receipt for a newly admitted event.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Accepted = typeof Accepted.Type\n\n/**\n * Receipt for an exact retry of an already pending or committed source event.\n *\n * `seq` is the original event's sequence, provisional while pending and\n * canonical after commit. A duplicate does not allocate another sequence or\n * enqueue another write. `status` distinguishes\n * an optimistic retry from one whose original event is already durable.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Duplicate = Schema.TaggedStruct(\"Duplicate\", {\n seq: Seq,\n sourceSeq: SourceSeq,\n status: Schema.Literals([\"pending\", \"committed\"])\n})\n\n/**\n * Receipt for an exact producer retry.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Duplicate = typeof Duplicate.Type\n\n/**\n * Receipt for an event discarded by an explicit dropping policy.\n *\n * A dropped admission still consumes its synchronously allocated `seq` and\n * `sourceSeq`, so sequence gaps are expected.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Dropped = Schema.TaggedStruct(\"Dropped\", {\n seq: Seq,\n sourceSeq: SourceSeq,\n policy: Schema.Literal(\"drop-newest\")\n})\n\n/**\n * Receipt for an event discarded by policy.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Dropped = typeof Dropped.Type\n\n/**\n * Receipt union for admission that may use the lossy queue.\n *\n * @category models\n * @since 0.1.0\n */\nexport const EmitReceipt = Schema.Union([Accepted, Duplicate, Dropped])\n\n/**\n * Receipt union for the lossy channel.\n *\n * @category models\n * @since 0.1.0\n */\nexport type EmitReceipt = typeof EmitReceipt.Type\n\n/**\n * Result of a synchronously durable journal admission.\n *\n * A durable admission is never dropped: it either commits, returns the\n * committed sequence of an exact producer retry, or fails.\n *\n * @category models\n * @since 0.1.0\n */\nexport const DurableReceipt = Schema.Union([Accepted, Duplicate])\n\n/**\n * Receipt union for the durable channel.\n *\n * @category models\n * @since 0.1.0\n */\nexport type DurableReceipt = typeof DurableReceipt.Type\n\n/**\n * Cursor used to replay a run and then follow its committed tail.\n *\n * @category models\n * @since 0.1.0\n */\nexport const StreamOptions = Schema.Struct({\n runId: RunId,\n afterSequence: Schema.optionalKey(Seq)\n})\n\n/**\n * Cursor used to replay a run and follow its committed tail.\n *\n * @category models\n * @since 0.1.0\n */\nexport type StreamOptions = typeof StreamOptions.Type\n\n/**\n * Largest page `entries` will read.\n *\n * A page is decoded into memory in full before the caller sees its first\n * entry, so an unbounded limit lets one call materialize a whole run. The\n * largest page any caller in this repository asks for is 512, so this\n * ceiling refuses only a limit that was going to be a memory incident.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const maxEntriesLimit = 10_000\n\n/**\n * Cursor and page size for durable journal reads.\n *\n * `limit` is at most `maxEntriesLimit` (10,000 entries). Optional `eventTypes`\n * selects a nonempty list of at most 64 exact types before pagination; cursors remain canonical run\n * sequences and `hasMore` describes matching entries. Omit it for the full run.\n * Filtering does not bypass the run's compaction floor.\n *\n * @category models\n * @since 0.1.0\n */\nexport const EntriesOptions = Schema.Struct({\n runId: RunId,\n after: Schema.optionalKey(Seq),\n eventTypes: Schema.optionalKey(\n Schema.Array(Entry.fields.eventType).check(Schema.isMinLength(1), Schema.isMaxLength(64))\n ),\n limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(maxEntriesLimit))\n})\n\n/**\n * Cursor and page size for durable journal reads.\n *\n * @category models\n * @since 0.1.0\n */\nexport type EntriesOptions = typeof EntriesOptions.Type\n\n/**\n * One page of canonical durable journal entries.\n *\n * @category models\n * @since 0.1.0\n */\nexport const EntriesPage = Schema.Struct({\n entries: Schema.Array(Entry),\n hasMore: Schema.Boolean\n})\n\n/**\n * One page of durable journal entries.\n *\n * @category models\n * @since 0.1.0\n */\nexport type EntriesPage = typeof EntriesPage.Type\n\n/**\n * A durable checkpoint: the caller-captured state that replays a run from\n * `seq` without the entries before it.\n *\n * `state` is the caller's own replay snapshot and round-trips verbatim: the\n * journal never interprets it, and redaction deliberately does not apply,\n * exactly as it does not apply to executable state. A checkpoint at `seq`\n * must subsume every entry with a sequence at or below `seq`: replay is\n * `state` plus `stream({ runId, afterSequence: seq })`.\n *\n * `compactedAtMs` is `null` until a compaction has truncated the entries\n * strictly below `seq`. The largest compacted `seq` for a run is its\n * compaction floor.\n *\n * Prior art: Temporal's mutable state, a durable snapshot pinned to a\n * history offset, with history below it never replayed\n * (`reference/temporal/common/persistence/data_interfaces.go`,\n * `WorkflowSnapshot`).\n *\n * @category models\n * @since 0.1.0\n */\nexport class Checkpoint extends Schema.Class<Checkpoint>(\"@smthrs/journal/Journal/Checkpoint\")({\n runId: RunId,\n seq: Seq,\n state: Schema.Unknown,\n createdAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n compactedAtMs: Schema.NullOr(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))\n}) {}\n\n/**\n * Arguments for writing a checkpoint.\n *\n * `seq` must name a committed entry of the run: the surviving row is what\n * keeps the run's durable `MAX(seq)` allocation floor at or above the\n * compaction boundary, so a process restarted after compaction can never\n * re-allocate a truncated sequence.\n *\n * @category models\n * @since 0.1.0\n */\nexport const CheckpointOptions = Schema.Struct({\n runId: RunId,\n seq: Seq,\n state: Schema.Unknown\n})\n\n/**\n * Arguments for writing a checkpoint.\n *\n * @category models\n * @since 0.1.0\n */\nexport type CheckpointOptions = typeof CheckpointOptions.Type\n\n/**\n * Arguments for compacting a run.\n *\n * `upTo` selects the checkpoint to truncate below; omitted, the run's latest\n * checkpoint is used.\n *\n * @category models\n * @since 0.1.0\n */\nexport const CompactOptions = Schema.Struct({\n runId: RunId,\n upTo: Schema.optionalKey(Seq)\n})\n\n/**\n * Arguments for compacting a run.\n *\n * @category models\n * @since 0.1.0\n */\nexport type CompactOptions = typeof CompactOptions.Type\n\n/**\n * Receipt for a completed compaction.\n *\n * `deleted` is `0` when the checkpoint was already the compaction floor, a\n * retried compaction is idempotent.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Compacted = Schema.Struct({\n runId: RunId,\n checkpointSeq: Seq,\n deleted: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n})\n\n/**\n * Receipt for a completed compaction.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Compacted = typeof Compacted.Type\n\n/**\n * Journal operations.\n *\n * There are two sequence domains:\n *\n * - `seq` is reserved synchronously per run. A lossy reservation is provisional;\n * the SQL writer moves it above the committed tail if overtaken. The committed\n * sequence is the canonical order for replay, paging, streams, and projections.\n * - `sourceSeq` is assigned synchronously per `(runId, sourceId)`, or supplied\n * by the producer, and is the idempotency key for producer retries.\n *\n * Rejected or dropped admissions consume both allocations, so gaps are valid.\n * A retry the in-process index still holds returns `Duplicate` with the\n * original `seq` (provisional while pending) and consumes neither allocation.\n *\n * A retry it no longer holds is admitted optimistically and settled by\n * `UNIQUE (run_id, source_id, source_seq)` at the insert: admission issues no\n * dedup read, so the receipt is `Accepted`, the entry collapses onto the\n * committed row instead of doubling it, and a reused identity carrying\n * different bytes surfaces its `idempotency_conflict` through `flush` rather\n * than through the emit. The read is deliberately absent. `emitLossy` exists\n * to be callable from inside somebody else's open write transaction, and a\n * SELECT there waits on the writer that is waiting on the caller: the agent\n * executor's exit flush deadlocked exactly so. What a producer keeps in\n * exchange is the choice of what a collision means, through `Input.dedupe`.\n *\n * This table is Smithers' logical (domain) write-ahead log and is intended to\n * become the authoritative state history. The storage engine's own WAL\n * underneath it is a durability substrate only and is never consumed as the\n * application event API. A durable boundary must not advance a run or expose\n * its result until its lifecycle entry is committed. No local commit makes a\n * remote effect atomic, so external effects still need idempotency keys,\n * fencing tokens, or compensation.\n *\n * `emitDurable` allocates `seq` inside the writer's SQL transaction, so the\n * returned sequence is already committed and independent writers never fork\n * the per-run clock.\n *\n * Caveat on scope: the stores above this log (`RunStore`, `AttemptStore`,\n * `CacheStore`, and engine-store's `DurableEngineState`) hold the executable\n * authoritative state, and no state is derived from these entries today.\n * `transact` is what keeps the two halves consistent anyway: a state\n * transition and the lifecycle entry describing it are appended in ONE write\n * transaction, so a crash can never leave durable state the journal does not\n * explain (or an entry for a transition that never landed).\n *\n * The surface is split into two channels:\n *\n * - The lifecycle channel, `emitDurable`, returns `DurableReceipt`, so a\n * dropped lifecycle event is unrepresentable: the write commits, dedupes, or\n * fails with a typed error.\n * - The lossy channel, `emitLossy`, keeps the optimistic queue and its\n * overflow policies for telemetry, where `Dropped` receipts and\n * `drop-oldest` evictions are acceptable.\n *\n * `emitDurable` is fenced on the run's ownership as arbitrated by the\n * injected `Consensus` strategy: the caller hands over its `OwnerId`, the\n * durable insert only commits while that strategy still records the owner as\n * holding the run, and a reclaimed run fails the write with a `fence_lost`\n * error. The fence is mandatory on the lifecycle channel: a lifecycle write is\n * what advances a run, and a zombie owner must not advance anything. The one\n * escape is {@link Service.emitDurableUnfenced}, for the rare admission that\n * is genuinely ownerless.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n /**\n * The durable rewind generation and its archive boundary. An append-only\n * adapter may omit this operation, which means generation zero forever.\n * Rewinding adapters must advance the generation atomically with truncation.\n */\n readonly generation?:\n | ((runId: RunId) => Effect.Effect<{\n readonly generation: number\n readonly afterSeq: number\n }, JournalError>)\n | undefined\n readonly emitLossy: (input: Input) => Effect.Effect<EmitReceipt, JournalError>\n readonly emitDurable: (input: Input, owner: OwnerId) => Effect.Effect<DurableReceipt, JournalError>\n /**\n * The unfenced durable write: same durability and receipt contract as\n * {@link Service.emitDurable}, with no ownership fence.\n *\n * This exists ONLY for admissions that are genuinely ownerless: writes\n * whose correctness does not depend on who currently owns the run because\n * they are first-writer-wins by design. The canonical case is the\n * external-trigger admission: a deferred completion or clock-schedule\n * record delivered by a sweeper that owns nothing, where the dedup index,\n * not the fence, is the idempotency mechanism. A caller that holds an\n * `OwnerId` must use `emitDurable`; reaching for this channel to dodge a\n * `fence_lost` is exactly the zombie write the fence exists to reject.\n */\n readonly emitDurableUnfenced: (input: Input) => Effect.Effect<DurableReceipt, JournalError>\n /**\n * Runs `effect`, a state projection plus the `emitDurable` calls describing\n * it, inside ONE write transaction.\n *\n * This is the seam that makes the logical WAL crash-consistent with\n * executable state. The stores above the journal (`RunStore`,\n * `AttemptStore`, `CacheStore`, `DurableEngineState`) write through the same\n * `DurableWriter`, so their writes join this transaction as savepoints: either\n * the transition and its lifecycle entry both commit, or neither does. It is\n * the local analogue of Temporal closing mutable state into a mutation plus\n * event batches and submitting them as one persistence request\n * (`reference/temporal/service/history/workflow/transaction_impl.go`).\n *\n * A `Duplicate` receipt is unaffected: an exact producer retry still\n * collapses onto the already-committed sequence.\n *\n * Two consequences a caller must plan for:\n *\n * - Publication (`changes`, `stream`, and the in-process source-event index)\n * is deferred until the transaction commits, so a subscriber never\n * observes an entry that later rolls back, and a rolled-back producer\n * identity stays re-emittable rather than deduplicating against a\n * sequence that does not exist.\n * - Everything inside runs while a write transaction is open. Keep it to\n * storage work: no flow bodies, no host calls, no waits.\n *\n * Nesting is safe: an inner `transact` becomes a savepoint of the outer\n * one and defers its publication to the outermost commit.\n */\n readonly transact: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | JournalError, R>\n /**\n * Publishes a short, non-failing process-local update after the current\n * transaction commits, or immediately when no transaction is open.\n *\n * Returns false without publishing inside a raw SQL transaction whose\n * commit this journal cannot observe. A true result can mean deferred, not\n * already run. Failed savepoints and retries discard their registrations.\n * Test doubles that implement transactions must preserve this contract.\n */\n readonly whenCommitted: (update: Effect.Effect<void>) => Effect.Effect<boolean>\n /**\n * Replays the run's durable history from `afterSequence`, then follows it.\n *\n * This is the lossless follower. It pages the history rather than\n * materializing it, so the first entry arrives after one page rather than\n * after the whole run; it reports a sink loss that happens while it is\n * following; and a cursor below the run's compaction floor fails with\n * `compacted` and the sequence to resync from, never a silently shortened\n * history.\n */\n readonly stream: (options: StreamOptions) => Stream.Stream<Entry, JournalError>\n /**\n * Reads one page of the run's durable history in sequence order.\n *\n * `hasMore` reports whether another page follows. A cursor below the run's\n * compaction floor fails with `compacted`, exactly as `stream` does, so a\n * poller and a follower cannot disagree about what history exists.\n */\n readonly entries: (options: EntriesOptions) => Effect.Effect<EntriesPage, JournalError>\n /**\n * Subscribes to entries this process commits, across every run.\n *\n * This is a bounded SLIDING buffer sized by the layer's `capacity`: a slow\n * consumer silently loses entries, with no error and no gap signal. Use it\n * for a local tail or a UI feed, and use `stream` wherever a missing entry\n * would be a correctness bug, because that is the lossless path and the one\n * that reports sink losses.\n *\n * Only this process's commits arrive here. Another writer on the same\n * database publishes into its own buffer, which is the second reason\n * `stream` exists: it rechecks the durable tail.\n *\n * Published entries are deeply frozen, so one subscriber cannot mutate the\n * value another subscriber is about to read.\n */\n readonly changes: Effect.Effect<PubSub.Subscription<Entry>, never, Scope.Scope>\n readonly project: <S, E, R>(\n projection: Projection<S, E, R>,\n options: StreamOptions\n ) => Stream.Stream<S, JournalError, R>\n readonly flush: Effect.Effect<void, JournalError>\n /**\n * Durably records the state that replays the run from `options.seq`.\n *\n * The write shares `transact`'s discipline: it runs through the same\n * `DurableWriter`, so inside an open `transact` it joins the caller's\n * transaction as a savepoint and rolls back with it. `options.seq` must\n * name a committed entry and must lie above the run's compaction floor;\n * otherwise the write fails with `checkpoint_invalid`. Re-checkpointing an\n * uncompacted `seq` replaces its state: last writer wins.\n *\n * The write is fenced on the run's ownership through the consensus\n * strategy, exactly as `emitDurable` is: a reclaimed run fails with\n * `fence_lost`. The fence is mandatory: a zombie owner must not replace\n * replay state behind a live successor.\n */\n readonly checkpoint: (options: CheckpointOptions, owner: OwnerId) => Effect.Effect<Checkpoint, JournalError>\n /**\n * Reads the run's most recent checkpoint, compacted or not.\n *\n * A reader that fails with `compacted` resyncs here: apply\n * `checkpoint.state`, then continue from\n * `stream({ runId, afterSequence: checkpoint.seq })`.\n */\n readonly latestCheckpoint: (runId: RunId) => Effect.Effect<Option.Option<Checkpoint>, JournalError>\n /**\n * Deletes the run's entries strictly below a checkpoint, atomically with\n * advancing the run's compaction floor.\n *\n * Refusals are typed: `checkpoint_invalid` when the run has no checkpoint\n * to truncate below, `reader_behind` when a live in-process stream still\n * needs a sequence the truncation would delete, and `fence_lost` when the\n * supplied `owner` no longer holds the run. The fence is mandatory, so a\n * zombie owner can never truncate history behind a live successor. Readers\n * this process cannot see (pollers of `entries` and followers in other\n * processes) are protected by the read-side guard instead: any read whose cursor starts\n * below the floor fails with `compacted` and the floor to resync from,\n * never a silently shortened history.\n */\n readonly compact: (options: CompactOptions, owner: OwnerId) => Effect.Effect<Compacted, JournalError>\n}\n\n/**\n * Context service for durable lifecycle evidence and lossy telemetry.\n *\n * @category services\n * @since 0.1.0\n */\nexport class Journal extends Context.Service<Journal, Service>()(\"@smthrs/journal/Journal\") {}\n\n/**\n * Constructs a journal service from an implementation.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = (implementation: Service): Service => Journal.of(implementation)\n\nconst unavailable = (method: string): JournalError =>\n new JournalError({\n code: \"journal_closed\",\n message: `${method} is unavailable`\n })\n\n/**\n * Constructs a closed journal stub, optionally overriding individual methods.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeNoop = (overrides: Partial<Service> = {}): Service => {\n const service: Service = {\n emitLossy: Effect.fn(\"Journal.emitLossy\")(() => Effect.fail(unavailable(\"emitLossy\"))),\n emitDurable: Effect.fn(\"Journal.emitDurable\")(() => Effect.fail(unavailable(\"emitDurable\"))),\n emitDurableUnfenced: Effect.fn(\"Journal.emitDurableUnfenced\")(() =>\n Effect.fail(unavailable(\"emitDurableUnfenced\"))\n ),\n /**\n * The closed stub has no sink and therefore no transaction to open, so it\n * runs the effect directly, the same posture as\n * `DurableEngineState`'s in-memory twin, whose `transaction` has no crash\n * window to close. A test double that models rollback overrides it.\n */\n transact: <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E | JournalError, R> => effect,\n whenCommitted: (update) => Effect.as(update, true),\n stream: (options) =>\n Stream.unwrap(\n Effect.fn(\"Journal.stream\")((_options: StreamOptions) => Effect.succeed(Stream.fail(unavailable(\"stream\"))))(\n options\n )\n ),\n entries: Effect.fn(\"Journal.entries\")(() => Effect.fail(unavailable(\"entries\"))),\n changes: Effect.acquireRelease(\n PubSub.sliding<Entry>(1),\n PubSub.shutdown\n ).pipe(Effect.flatMap(PubSub.subscribe)),\n project: (projection, options) =>\n Stream.unwrap(\n Effect.fn(\"Journal.project\")(\n <S, E, R>(_projection: Projection<S, E, R>, _options: StreamOptions) =>\n Effect.succeed(Stream.fail(unavailable(\"project\")))\n )(projection, options)\n ),\n flush: Effect.fn(\"Journal.flush\")(() => Effect.fail(unavailable(\"flush\")))(),\n checkpoint: Effect.fn(\"Journal.checkpoint\")(() => Effect.fail(unavailable(\"checkpoint\"))),\n latestCheckpoint: Effect.fn(\"Journal.latestCheckpoint\")(() => Effect.fail(unavailable(\"latestCheckpoint\"))),\n compact: Effect.fn(\"Journal.compact\")(() => Effect.fail(unavailable(\"compact\")))\n }\n return Journal.of({ ...service, ...overrides })\n}\n\n/**\n * Provides a closed journal stub.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<Journal> =>\n Layer.succeed(Journal)(makeNoop(overrides))\n"]}