@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,69 @@
1
+ /** Durable observations owned by an agent checkpoint action.
2
+ * @since 1.0.0
3
+ */
4
+
5
+ import * as Context from "effect/Context"
6
+ import * as Schema from "effect/Schema"
7
+
8
+ const nonnegative = Schema.Int.check(
9
+ Schema.isGreaterThanOrEqualTo(0),
10
+ Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)
11
+ )
12
+ const positive = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER))
13
+
14
+ /** Invocation and retry coordinates remain distinct across replay.
15
+ * @category schemas
16
+ * @since 1.0.0
17
+ */
18
+ export const Step = Schema.Struct({
19
+ stepId: Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/)),
20
+ executionId: Schema.String,
21
+ action: Schema.String,
22
+ attempt: positive,
23
+ ask: Schema.Union([nonnegative, Schema.Literal("repair")]),
24
+ retry: positive,
25
+ scope: Schema.String
26
+ })
27
+
28
+ /** Decoded invocation coordinates.
29
+ * @category models
30
+ * @since 1.0.0
31
+ */
32
+ export type Step = typeof Step.Type
33
+
34
+ /** The first recorded observation, including its original generation and time.
35
+ * @category schemas
36
+ * @since 1.0.0
37
+ */
38
+ export const Fact = Schema.Struct({
39
+ version: Schema.Literal(1),
40
+ step: Step,
41
+ generation: nonnegative,
42
+ frame: Schema.Int.check(Schema.isGreaterThanOrEqualTo(-1)),
43
+ ordinal: nonnegative,
44
+ cell: Schema.String,
45
+ at: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)),
46
+ eventType: Schema.String.check(Schema.isPattern(/^control\.agent\.[a-z-]+$/)),
47
+ sourceSequence: nonnegative,
48
+ payload: Schema.Json
49
+ })
50
+
51
+ /** Decoded checkpoint observation.
52
+ * @category models
53
+ * @since 1.0.0
54
+ */
55
+ export type Fact = typeof Fact.Type
56
+
57
+ /** Marks a checkpoint whose saved success is the fact to publish.
58
+ * @category services
59
+ * @since 1.0.0
60
+ */
61
+ // The annotation is a marker with no fields; its presence is the entire contract.
62
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
63
+ export class Annotation extends Context.Service<Annotation, {}>()("@smthrs/journal/StepFact/Annotation") {}
64
+
65
+ /** Native outbox event, committed with its owning checkpoint.
66
+ * @category events
67
+ * @since 1.0.0
68
+ */
69
+ export const eventType = "flows.harness.step-fact.v1"
package/src/index.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Durable journal services: the immutable event history and nothing else.
3
+ *
4
+ * Run and attempt state live in `@smthrs/run-store`, sealed step results in
5
+ * `@smthrs/step-cache`, and the durable deferred/clock tables in
6
+ * `@smthrs/engine-store`.
7
+ *
8
+ * This entry point is browser-bundleable: every service here is written
9
+ * against the driver-neutral `@smthrs/database` contract. The test doubles are
10
+ * kept out of it and live under explicit subpaths; of the two, only
11
+ * `TestJournal` binds a Node SQLite database:
12
+ *
13
+ * ```ts
14
+ * import { Journal, SqlJournal } from "@smthrs/journal"
15
+ * import * as TestJournal from "@smthrs/journal/test/TestJournal"
16
+ * import * as Notifying from "@smthrs/journal/test/Notifying"
17
+ * ```
18
+ *
19
+ * @since 0.1.0
20
+ */
21
+
22
+ /**
23
+ * @category events
24
+ * @since 0.1.0
25
+ */
26
+ export * as JournalEvent from "./JournalEvent.ts"
27
+
28
+ /**
29
+ * @category events
30
+ * @since 1.0.0
31
+ */
32
+ export * as EngineEvent from "./EngineEvent.ts"
33
+
34
+ /**
35
+ * @category events
36
+ * @since 1.0.0
37
+ */
38
+ export * as ExecutionFact from "./ExecutionFact.ts"
39
+
40
+ /**
41
+ * @category events
42
+ * @since 1.0.0
43
+ */
44
+ export * as CallFact from "./CallFact.ts"
45
+
46
+ /**
47
+ * @since 1.0.0
48
+ */
49
+ export * as StepFact from "./StepFact.ts"
50
+
51
+ /**
52
+ * @category services
53
+ * @since 0.1.0
54
+ */
55
+ export * as Journal from "./Journal.ts"
56
+
57
+ /**
58
+ * @category layers
59
+ * @since 0.1.0
60
+ */
61
+ export * as SqlJournal from "./SqlJournal.ts"
62
+
63
+ /**
64
+ * @category services
65
+ * @since 1.0.0
66
+ */
67
+ export * as Consensus from "./Consensus.ts"
68
+
69
+ /**
70
+ * @category layers
71
+ * @since 1.0.0
72
+ */
73
+ export * as SqlConsensus from "./SqlConsensus.ts"
74
+
75
+ /**
76
+ * @category metrics
77
+ * @since 0.1.0
78
+ */
79
+ export * as JournalMetrics from "./JournalMetrics.ts"
80
+
81
+ /**
82
+ * @category redaction
83
+ * @since 0.1.0
84
+ */
85
+ export * as Redaction from "./Redaction.ts"
86
+
87
+ /**
88
+ * @category redaction
89
+ * @since 0.1.0
90
+ */
91
+ export * as RedactedLogger from "./RedactedLogger.ts"
92
+
93
+ /**
94
+ * @category projections
95
+ * @since 0.1.0
96
+ */
97
+ export * as Projection from "./Projection.ts"
98
+
99
+ /**
100
+ * @category models
101
+ * @since 0.1.0
102
+ */
103
+ export * as OwnerId from "./OwnerId.ts"
104
+
105
+ /**
106
+ * @category migrations
107
+ * @since 0.1.0
108
+ */
109
+ export * as Migrations from "./Migrations.ts"
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Faults of the journal's own background maintenance. They are logged and
3
+ * damped, never surfaced to a caller's write, so a log reader classifies one by
4
+ * `_tag` and `code` instead of a message.
5
+ *
6
+ * @since 1.0.0
7
+ */
8
+
9
+ import * as Schema from "effect/Schema"
10
+
11
+ /**
12
+ * Which background step refused.
13
+ *
14
+ * @category models
15
+ * @since 1.0.0
16
+ */
17
+ export const JournalInternalFaultCode = Schema.Literals(["compaction_capture_timeout"])
18
+
19
+ /**
20
+ * The stable code of a {@link JournalInternalFault}.
21
+ *
22
+ * @category models
23
+ * @since 1.0.0
24
+ */
25
+ export type JournalInternalFaultCode = typeof JournalInternalFaultCode.Type
26
+
27
+ /**
28
+ * A post-commit maintenance step that refused.
29
+ *
30
+ * @category errors
31
+ * @since 1.0.0
32
+ */
33
+ export class JournalInternalFault
34
+ extends Schema.TaggedError<JournalInternalFault>()("@smthrs/journal/JournalInternalFault", {
35
+ code: JournalInternalFaultCode,
36
+ message: Schema.String
37
+ })
38
+ {
39
+ override readonly name = "JournalInternalFault"
40
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Initial durable journal event schema.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ import * as Dialect from "@smthrs/database/Dialect"
8
+ import * as Effect from "effect/Effect"
9
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
10
+
11
+ /**
12
+ * Creates the `flows_journal_events` table and its event-type index.
13
+ *
14
+ * @category migrations
15
+ * @since 0.1.0
16
+ */
17
+ export const initial: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
18
+ const sql = yield* SqlClient.SqlClient
19
+
20
+ yield* sql`CREATE TABLE flows_journal_events (
21
+ run_id TEXT NOT NULL CHECK (length(run_id) > 0),
22
+ seq ${Dialect.integer(sql)} NOT NULL CHECK (${
23
+ Dialect.isInteger(sql, sql`seq`)
24
+ } AND seq >= 0 AND seq <= 9007199254740991),
25
+ event_id TEXT NOT NULL UNIQUE CHECK (length(event_id) > 0),
26
+ source_id TEXT NOT NULL CHECK (length(source_id) > 0),
27
+ source_seq ${Dialect.integer(sql)} NOT NULL CHECK (${
28
+ Dialect.isInteger(sql, sql`source_seq`)
29
+ } AND source_seq >= 0 AND source_seq <= 9007199254740991),
30
+ emitted_at_ms ${Dialect.integer(sql)} NOT NULL CHECK (${
31
+ Dialect.isInteger(sql, sql`emitted_at_ms`)
32
+ } AND emitted_at_ms >= 0 AND emitted_at_ms <= 9007199254740991),
33
+ event_type TEXT NOT NULL CHECK (length(event_type) > 0),
34
+ payload_json TEXT NOT NULL CHECK (${Dialect.jsonValid(sql, sql`payload_json`)}),
35
+ meta_json TEXT NOT NULL CHECK (${Dialect.jsonValid(sql, sql`meta_json`)}),
36
+ PRIMARY KEY (run_id, seq),
37
+ UNIQUE (run_id, source_id, source_seq)
38
+ )`
39
+
40
+ yield* sql`CREATE INDEX flows_journal_events_event_type_idx ON flows_journal_events (event_type)`
41
+ })
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Durable journal checkpoints and the compaction floor.
3
+ *
4
+ * One row per `(run_id, seq)`: the caller-captured state that replays the run
5
+ * from `seq` without the entries before it. `compacted_at_ms` is NULL until a
6
+ * compaction has truncated the entries strictly below `seq`; the largest
7
+ * compacted `seq` per run is the run's compaction floor, and reads whose
8
+ * cursor starts below it fail with `compacted` instead of returning a
9
+ * silently shortened history.
10
+ *
11
+ * Prior art: Temporal's mutable state is exactly this shape, a durable
12
+ * snapshot pinned to a history offset (`last_first_event_id` in
13
+ * `reference/temporal/proto/internal/temporal/server/api/persistence/v1/executions.proto`),
14
+ * with history below it never replayed.
15
+ *
16
+ * @since 0.1.0
17
+ */
18
+
19
+ import * as Dialect from "@smthrs/database/Dialect"
20
+ import * as Effect from "effect/Effect"
21
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
22
+
23
+ /**
24
+ * Creates the `flows_journal_checkpoints` table.
25
+ *
26
+ * @category migrations
27
+ * @since 0.1.0
28
+ */
29
+ export const checkpoints: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
30
+ const sql = yield* SqlClient.SqlClient
31
+
32
+ yield* sql`CREATE TABLE flows_journal_checkpoints (
33
+ run_id TEXT NOT NULL CHECK (length(run_id) > 0),
34
+ seq ${Dialect.integer(sql)} NOT NULL CHECK (${
35
+ Dialect.isInteger(sql, sql`seq`)
36
+ } AND seq >= 0 AND seq <= 9007199254740991),
37
+ state_json TEXT NOT NULL CHECK (${Dialect.jsonValid(sql, sql`state_json`)}),
38
+ created_at_ms ${Dialect.integer(sql)} NOT NULL CHECK (${
39
+ Dialect.isInteger(sql, sql`created_at_ms`)
40
+ } AND created_at_ms >= 0 AND created_at_ms <= 9007199254740991),
41
+ compacted_at_ms ${Dialect.integer(sql)} CHECK (compacted_at_ms IS NULL OR (${
42
+ Dialect.isInteger(sql, sql`compacted_at_ms`)
43
+ } AND compacted_at_ms >= 0 AND compacted_at_ms <= 9007199254740991)),
44
+ PRIMARY KEY (run_id, seq)
45
+ )`
46
+ })
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Index for the bounded journal source-event startup window.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ import * as Effect from "effect/Effect"
8
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
9
+
10
+ /**
11
+ * Preserves deterministic timestamp/run/sequence ordering without sorting the retained history.
12
+ *
13
+ * @category migrations
14
+ * @since 0.1.0
15
+ */
16
+ export const startupIndex = Effect.gen(function*() {
17
+ const sql = yield* SqlClient.SqlClient
18
+ yield* sql`CREATE INDEX flows_journal_events_startup_idx ON flows_journal_events (emitted_at_ms DESC, run_id DESC, seq DESC)`
19
+ })
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Producer identities retained when compaction deletes event payloads.
3
+ *
4
+ * @since 1.0.0-rc.0
5
+ */
6
+
7
+ import * as Dialect from "@smthrs/database/Dialect"
8
+ import * as Effect from "effect/Effect"
9
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
10
+
11
+ /**
12
+ * Creates the durable compacted-identity index.
13
+ *
14
+ * @category migrations
15
+ * @since 1.0.0-rc.0
16
+ */
17
+ export const dedup: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
18
+ const sql = yield* SqlClient.SqlClient
19
+ yield* sql`CREATE TABLE flows_journal_dedup (
20
+ run_id TEXT NOT NULL CHECK (length(run_id) > 0),
21
+ source_id TEXT NOT NULL CHECK (length(source_id) > 0),
22
+ source_seq ${Dialect.integer(sql)} NOT NULL CHECK (${
23
+ Dialect.isInteger(sql, sql`source_seq`)
24
+ } AND source_seq >= 0 AND source_seq <= 9007199254740991),
25
+ event_id TEXT NOT NULL UNIQUE CHECK (length(event_id) > 0),
26
+ seq ${Dialect.integer(sql)} NOT NULL CHECK (${
27
+ Dialect.isInteger(sql, sql`seq`)
28
+ } AND seq >= 0 AND seq <= 9007199254740991),
29
+ content_hash TEXT NOT NULL CHECK (length(content_hash) = 64),
30
+ PRIMARY KEY (run_id, source_id, source_seq)
31
+ )`
32
+ // Enforce the same identity constraints after their event rows are removed.
33
+ // RAISE(IGNORE) leaves INSERT ... RETURNING empty, so the existing duplicate
34
+ // path checks the owner fence and then resolves the original receipt.
35
+ yield* Dialect.trigger(sql, {
36
+ name: "flows_journal_dedup_insert_guard",
37
+ table: "flows_journal_events",
38
+ event: "BEFORE INSERT",
39
+ when: `EXISTS (SELECT 1 FROM flows_journal_dedup WHERE event_id = NEW.event_id
40
+ OR (run_id = NEW.run_id AND source_id = NEW.source_id AND source_seq = NEW.source_seq))`,
41
+ ignore: true
42
+ })
43
+ })
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Indexed exact event-type reads within a run.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+
7
+ import * as Effect from "effect/Effect"
8
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
9
+
10
+ /**
11
+ * Keeps filtered cursor reads proportional to the matching entries.
12
+ *
13
+ * @category migrations
14
+ * @since 1.0.0
15
+ */
16
+ export const runEventType: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
17
+ const sql = yield* SqlClient.SqlClient
18
+ yield* sql`CREATE INDEX flows_journal_events_run_event_type_idx ON flows_journal_events (run_id, event_type, seq)`
19
+ })
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The `SqlConsensus` lease table.
3
+ *
4
+ * This is the private schema of the default consensus strategy: the owner
5
+ * tuple, the two-phase claim columns, and the grant timestamp relocated from
6
+ * `flows_runs`. Lease rows are strategy evidence, never history: heartbeats
7
+ * and lease state never enter the journal.
8
+ *
9
+ * @since 1.0.0
10
+ */
11
+
12
+ import * as Dialect from "@smthrs/database/Dialect"
13
+ import * as Effect from "effect/Effect"
14
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
15
+
16
+ /**
17
+ * Creates the `flows_consensus_leases` table and backfills a lease for every
18
+ * running or claimed run when `flows_runs` is already present, so an owner
19
+ * that was driving a run before this migration keeps its fence afterwards.
20
+ *
21
+ * @category migrations
22
+ * @since 1.0.0
23
+ */
24
+ export const consensus: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
25
+ const sql = yield* SqlClient.SqlClient
26
+
27
+ yield* sql`CREATE TABLE flows_consensus_leases (
28
+ run_id TEXT PRIMARY KEY CHECK (length(run_id) > 0),
29
+ owner_host_id TEXT,
30
+ owner_pid ${Dialect.integer(sql)} CHECK (owner_pid IS NULL OR (${
31
+ Dialect.isInteger(sql, sql`owner_pid`)
32
+ } AND owner_pid >= 0)),
33
+ owner_nonce TEXT,
34
+ granted_at_ms ${Dialect.integer(sql)} CHECK (granted_at_ms IS NULL OR (${
35
+ Dialect.isInteger(sql, sql`granted_at_ms`)
36
+ } AND granted_at_ms >= 0)),
37
+ heartbeat_at_ms ${Dialect.integer(sql)} CHECK (heartbeat_at_ms IS NULL OR (${
38
+ Dialect.isInteger(sql, sql`heartbeat_at_ms`)
39
+ } AND heartbeat_at_ms >= 0)),
40
+ claim_host_id TEXT,
41
+ claim_pid ${Dialect.integer(sql)} CHECK (claim_pid IS NULL OR (${
42
+ Dialect.isInteger(sql, sql`claim_pid`)
43
+ } AND claim_pid >= 0)),
44
+ claim_nonce TEXT,
45
+ claimed_at_ms ${Dialect.integer(sql)} CHECK (claimed_at_ms IS NULL OR (${
46
+ Dialect.isInteger(sql, sql`claimed_at_ms`)
47
+ } AND claimed_at_ms >= 0))
48
+ )`
49
+
50
+ const tables = yield* Dialect.tables(sql)
51
+ if (!tables.some((table) => table.name === "flows_runs")) {
52
+ return
53
+ }
54
+
55
+ yield* sql`
56
+ INSERT INTO flows_consensus_leases (
57
+ run_id,
58
+ owner_host_id,
59
+ owner_pid,
60
+ owner_nonce,
61
+ granted_at_ms,
62
+ heartbeat_at_ms,
63
+ claim_host_id,
64
+ claim_pid,
65
+ claim_nonce,
66
+ claimed_at_ms
67
+ )
68
+ SELECT
69
+ run_id,
70
+ owner_host_id,
71
+ owner_pid,
72
+ owner_nonce,
73
+ CASE WHEN status = 'running' THEN heartbeat_at_ms ELSE NULL END,
74
+ CASE WHEN status = 'running' THEN heartbeat_at_ms ELSE NULL END,
75
+ claim_host_id,
76
+ claim_pid,
77
+ claim_nonce,
78
+ claimed_at_ms
79
+ FROM flows_runs
80
+ WHERE status = 'running' OR claim_host_id IS NOT NULL
81
+ ON CONFLICT (run_id) DO NOTHING
82
+ `
83
+ })
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Effect-native analogue of Bazel Skyframe's `NotifyingHelper` and
3
+ * `NotifyingInMemoryGraph`: wrap a record-of-Effect-methods service so a listener hook fires around
4
+ * every operation. The hook runs in the calling fiber, so a hook that dies,
5
+ * interrupts, or awaits a `Latch` injects crashes, fence loss, and exact
6
+ * sequencing at any durable transition point, the mechanism that makes
7
+ * interstitial interleaving bugs testable at all.
8
+ *
9
+ * Deliberately NOT ported from Skyframe: `DeterministicHelper`'s
10
+ * deterministic-scheduler machinery, `ChainedFunction`, and `TrackingAwaiter`.
11
+ * Effect already gives us deterministic fibers, `TestClock`, `Latch`, and
12
+ * failure propagation across fiber joins, so those helpers have no residue
13
+ * here.
14
+ *
15
+ * @since 0.1.0
16
+ */
17
+
18
+ import type * as Context from "effect/Context"
19
+ import * as Effect from "effect/Effect"
20
+ import * as Layer from "effect/Layer"
21
+
22
+ /**
23
+ * Whether the hook is firing before or after the delegated operation.
24
+ * Skyframe's `NotifyingHelper.Order`, minus its `BEFORE_AND_AFTER` (pass the
25
+ * same hook body for both firings instead).
26
+ *
27
+ * @category models
28
+ * @since 0.1.0
29
+ */
30
+ export type Order = "before" | "after"
31
+
32
+ /**
33
+ * Listener invoked around every delegated operation. `op` is the service
34
+ * method name (Skyframe's `EventType`, but open-world), `args` the call
35
+ * arguments. Express crash injection as `Effect.die` or `Effect.interrupt`
36
+ * inside the hook, the hook's type stays `Effect<void>` so the wrapped
37
+ * service is structurally identical to the real one.
38
+ *
39
+ * The `after` firing only happens when the operation succeeds; a failed
40
+ * operation already is the interesting event.
41
+ *
42
+ * @category models
43
+ * @since 0.1.0
44
+ */
45
+ export type Hook = (
46
+ op: string,
47
+ order: Order,
48
+ args: ReadonlyArray<unknown>
49
+ ) => Effect.Effect<void>
50
+
51
+ /**
52
+ * Wraps a service so `hook(op, "before" | "after", args)` fires around every
53
+ * Effect-valued operation, delegating to the real implementation in between.
54
+ *
55
+ * Generic over any record-of-Effect-methods service (`Journal`, `CacheStore`,
56
+ * `AttemptStore`, `RunStore`, ...): Effect-returning methods and plain Effect
57
+ * properties are hooked; non-Effect members (e.g. `Stream`-returning methods)
58
+ * delegate untouched.
59
+ *
60
+ * @category constructors
61
+ * @since 0.1.0
62
+ */
63
+ export const wrap = <S extends object>(service: S, hook: Hook): S => {
64
+ const wrapped: Record<string, unknown> = {}
65
+ for (const key of Object.keys(service)) {
66
+ const value = (service as Record<string, unknown>)[key]
67
+ if (Effect.isEffect(value)) {
68
+ wrapped[key] = hook(key, "before", []).pipe(
69
+ Effect.andThen(value as Effect.Effect<unknown, unknown, unknown>),
70
+ Effect.tap(() => hook(key, "after", []))
71
+ )
72
+ } else if (typeof value === "function") {
73
+ const method = value as (...args: Array<unknown>) => unknown
74
+ wrapped[key] = (...args: Array<unknown>) => {
75
+ const result = method.apply(service, args)
76
+ return Effect.isEffect(result)
77
+ ? hook(key, "before", args).pipe(
78
+ Effect.andThen(result as Effect.Effect<unknown, unknown, unknown>),
79
+ Effect.tap(() => hook(key, "after", args))
80
+ )
81
+ : result
82
+ }
83
+ } else {
84
+ wrapped[key] = value
85
+ }
86
+ }
87
+ return wrapped as S
88
+ }
89
+
90
+ /**
91
+ * Layer form of {@link wrap}: reads the real service from the environment and
92
+ * re-provides the notifying wrapper under the same key, so an existing stack
93
+ * gains a listener without touching its construction.
94
+ *
95
+ * @category layers
96
+ * @since 0.1.0
97
+ */
98
+ export const layer = <Id, S extends object>(
99
+ tag: Context.Key<Id, S>,
100
+ hook: Hook
101
+ ): Layer.Layer<Id, never, Id> => Layer.effect(tag)(Effect.map(tag, (service) => wrap(service, hook)))
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Deterministic bundle of the production journal service.
3
+ *
4
+ * This provides the journal and nothing else. A consumer that also needs run
5
+ * state or the step cache composes `@smthrs/run-store/test/TestRunStore` and
6
+ * `@smthrs/step-cache/test/TestCacheStore` over the same database, or takes
7
+ * the whole engine bundle from `@smthrs/engine-store/test/TestStores`.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+
12
+ import * as TestDatabase from "@smthrs/database/test/TestDatabase"
13
+ import * as Layer from "effect/Layer"
14
+ import * as Migrations from "../Migrations.ts"
15
+ import * as SqlJournal from "../SqlJournal.ts"
16
+
17
+ /**
18
+ * Options for the deterministic journal bundle.
19
+ *
20
+ * The production options with `capacity` and `overflow` made optional. Every
21
+ * field forwards to {@link SqlJournal.layer} unchanged, so a suite can exercise
22
+ * index-bound eviction, the entry byte bound, a custom or noop redactor, and
23
+ * the compaction policy through the bundle instead of hand-assembling the layer
24
+ * stack the bundle exists to hide.
25
+ *
26
+ * The type is derived rather than mirrored because a mirror silently falls
27
+ * behind: the hand-copied list had already lost `maxEntryBytes`, so the one
28
+ * production bound a hostile-payload suite needs was unreachable here.
29
+ *
30
+ * @category models
31
+ * @since 0.1.0
32
+ */
33
+ export interface TestJournalOptions extends Omit<SqlJournal.SqlJournalOptions, "capacity" | "overflow"> {
34
+ /** Admission-queue bound and `changes` buffer size. Defaults to 1024. */
35
+ readonly capacity?: number | undefined
36
+ /** Policy applied when the admission queue is full. Defaults to `reject`. */
37
+ readonly overflow?: SqlJournal.SqlJournalOptions["overflow"] | undefined
38
+ }
39
+
40
+ /**
41
+ * Provides the production SQLite journal over an in-memory database.
42
+ * Migrations run before the journal is exposed.
43
+ *
44
+ * @category layers
45
+ * @since 0.1.0
46
+ */
47
+ export const layer = (options?: TestJournalOptions) =>
48
+ SqlJournal.layer({
49
+ ...options,
50
+ capacity: options?.capacity ?? 1024,
51
+ overflow: options?.overflow ?? "reject"
52
+ }).pipe(Layer.provide(Layer.provideMerge(Migrations.layer, TestDatabase.layer)))