@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
+ /**
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
+ import * as Dialect from "@smthrs/database/Dialect";
12
+ import * as Effect from "effect/Effect";
13
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
14
+ /**
15
+ * Creates the `flows_consensus_leases` table and backfills a lease for every
16
+ * running or claimed run when `flows_runs` is already present, so an owner
17
+ * that was driving a run before this migration keeps its fence afterwards.
18
+ *
19
+ * @category migrations
20
+ * @since 1.0.0
21
+ */
22
+ export const consensus = Effect.gen(function* () {
23
+ const sql = yield* SqlClient.SqlClient;
24
+ yield* sql `CREATE TABLE flows_consensus_leases (
25
+ run_id TEXT PRIMARY KEY CHECK (length(run_id) > 0),
26
+ owner_host_id TEXT,
27
+ owner_pid ${Dialect.integer(sql)} CHECK (owner_pid IS NULL OR (${Dialect.isInteger(sql, sql `owner_pid`)} AND owner_pid >= 0)),
28
+ owner_nonce TEXT,
29
+ granted_at_ms ${Dialect.integer(sql)} CHECK (granted_at_ms IS NULL OR (${Dialect.isInteger(sql, sql `granted_at_ms`)} AND granted_at_ms >= 0)),
30
+ heartbeat_at_ms ${Dialect.integer(sql)} CHECK (heartbeat_at_ms IS NULL OR (${Dialect.isInteger(sql, sql `heartbeat_at_ms`)} AND heartbeat_at_ms >= 0)),
31
+ claim_host_id TEXT,
32
+ claim_pid ${Dialect.integer(sql)} CHECK (claim_pid IS NULL OR (${Dialect.isInteger(sql, sql `claim_pid`)} AND claim_pid >= 0)),
33
+ claim_nonce TEXT,
34
+ claimed_at_ms ${Dialect.integer(sql)} CHECK (claimed_at_ms IS NULL OR (${Dialect.isInteger(sql, sql `claimed_at_ms`)} AND claimed_at_ms >= 0))
35
+ )`;
36
+ const tables = yield* Dialect.tables(sql);
37
+ if (!tables.some((table) => table.name === "flows_runs")) {
38
+ return;
39
+ }
40
+ yield* sql `
41
+ INSERT INTO flows_consensus_leases (
42
+ run_id,
43
+ owner_host_id,
44
+ owner_pid,
45
+ owner_nonce,
46
+ granted_at_ms,
47
+ heartbeat_at_ms,
48
+ claim_host_id,
49
+ claim_pid,
50
+ claim_nonce,
51
+ claimed_at_ms
52
+ )
53
+ SELECT
54
+ run_id,
55
+ owner_host_id,
56
+ owner_pid,
57
+ owner_nonce,
58
+ CASE WHEN status = 'running' THEN heartbeat_at_ms ELSE NULL END,
59
+ CASE WHEN status = 'running' THEN heartbeat_at_ms ELSE NULL END,
60
+ claim_host_id,
61
+ claim_pid,
62
+ claim_nonce,
63
+ claimed_at_ms
64
+ FROM flows_runs
65
+ WHERE status = 'running' OR claim_host_id IS NOT NULL
66
+ ON CONFLICT (run_id) DO NOTHING
67
+ `;
68
+ });
69
+ //# sourceMappingURL=0006_consensus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0006_consensus.js","sourceRoot":"","sources":["../../../src/migrations/0006_consensus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAA;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAsD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC9F,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAA;IAEtC,KAAK,CAAC,CAAC,GAAG,CAAA;;;gBAGI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iCAChC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAA,WAAW,CACvC;;oBAEkB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qCACpC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAA,eAAe,CAC3C;sBACoB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,uCACtC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAA,iBAAiB,CAC7C;;gBAEc,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iCAChC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAA,WAAW,CACvC;;oBAEkB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qCACpC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAA,eAAe,CAC3C;IACE,CAAA;IAEF,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,CAAC;QACzD,OAAM;IACR,CAAC;IAED,KAAK,CAAC,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BT,CAAA;AACH,CAAC,CAAC,CAAA","sourcesContent":["/**\n * The `SqlConsensus` lease table.\n *\n * This is the private schema of the default consensus strategy: the owner\n * tuple, the two-phase claim columns, and the grant timestamp relocated from\n * `flows_runs`. Lease rows are strategy evidence, never history: heartbeats\n * and lease state never enter the journal.\n *\n * @since 1.0.0\n */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport * as Effect from \"effect/Effect\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\n\n/**\n * Creates the `flows_consensus_leases` table and backfills a lease for every\n * running or claimed run when `flows_runs` is already present, so an owner\n * that was driving a run before this migration keeps its fence afterwards.\n *\n * @category migrations\n * @since 1.0.0\n */\nexport const consensus: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {\n const sql = yield* SqlClient.SqlClient\n\n yield* sql`CREATE TABLE flows_consensus_leases (\n run_id TEXT PRIMARY KEY CHECK (length(run_id) > 0),\n owner_host_id TEXT,\n owner_pid ${Dialect.integer(sql)} CHECK (owner_pid IS NULL OR (${\n Dialect.isInteger(sql, sql`owner_pid`)\n } AND owner_pid >= 0)),\n owner_nonce TEXT,\n granted_at_ms ${Dialect.integer(sql)} CHECK (granted_at_ms IS NULL OR (${\n Dialect.isInteger(sql, sql`granted_at_ms`)\n } AND granted_at_ms >= 0)),\n heartbeat_at_ms ${Dialect.integer(sql)} CHECK (heartbeat_at_ms IS NULL OR (${\n Dialect.isInteger(sql, sql`heartbeat_at_ms`)\n } AND heartbeat_at_ms >= 0)),\n claim_host_id TEXT,\n claim_pid ${Dialect.integer(sql)} CHECK (claim_pid IS NULL OR (${\n Dialect.isInteger(sql, sql`claim_pid`)\n } AND claim_pid >= 0)),\n claim_nonce TEXT,\n claimed_at_ms ${Dialect.integer(sql)} CHECK (claimed_at_ms IS NULL OR (${\n Dialect.isInteger(sql, sql`claimed_at_ms`)\n } AND claimed_at_ms >= 0))\n )`\n\n const tables = yield* Dialect.tables(sql)\n if (!tables.some((table) => table.name === \"flows_runs\")) {\n return\n }\n\n yield* sql`\n INSERT INTO flows_consensus_leases (\n run_id,\n owner_host_id,\n owner_pid,\n owner_nonce,\n granted_at_ms,\n heartbeat_at_ms,\n claim_host_id,\n claim_pid,\n claim_nonce,\n claimed_at_ms\n )\n SELECT\n run_id,\n owner_host_id,\n owner_pid,\n owner_nonce,\n CASE WHEN status = 'running' THEN heartbeat_at_ms ELSE NULL END,\n CASE WHEN status = 'running' THEN heartbeat_at_ms ELSE NULL END,\n claim_host_id,\n claim_pid,\n claim_nonce,\n claimed_at_ms\n FROM flows_runs\n WHERE status = 'running' OR claim_host_id IS NOT NULL\n ON CONFLICT (run_id) DO NOTHING\n `\n})\n"]}
@@ -0,0 +1,65 @@
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
+ import type * as Context from "effect/Context";
18
+ import * as Effect from "effect/Effect";
19
+ import * as Layer from "effect/Layer";
20
+ /**
21
+ * Whether the hook is firing before or after the delegated operation.
22
+ * Skyframe's `NotifyingHelper.Order`, minus its `BEFORE_AND_AFTER` (pass the
23
+ * same hook body for both firings instead).
24
+ *
25
+ * @category models
26
+ * @since 0.1.0
27
+ */
28
+ export type Order = "before" | "after";
29
+ /**
30
+ * Listener invoked around every delegated operation. `op` is the service
31
+ * method name (Skyframe's `EventType`, but open-world), `args` the call
32
+ * arguments. Express crash injection as `Effect.die` or `Effect.interrupt`
33
+ * inside the hook, the hook's type stays `Effect<void>` so the wrapped
34
+ * service is structurally identical to the real one.
35
+ *
36
+ * The `after` firing only happens when the operation succeeds; a failed
37
+ * operation already is the interesting event.
38
+ *
39
+ * @category models
40
+ * @since 0.1.0
41
+ */
42
+ export type Hook = (op: string, order: Order, args: ReadonlyArray<unknown>) => Effect.Effect<void>;
43
+ /**
44
+ * Wraps a service so `hook(op, "before" | "after", args)` fires around every
45
+ * Effect-valued operation, delegating to the real implementation in between.
46
+ *
47
+ * Generic over any record-of-Effect-methods service (`Journal`, `CacheStore`,
48
+ * `AttemptStore`, `RunStore`, ...): Effect-returning methods and plain Effect
49
+ * properties are hooked; non-Effect members (e.g. `Stream`-returning methods)
50
+ * delegate untouched.
51
+ *
52
+ * @category constructors
53
+ * @since 0.1.0
54
+ */
55
+ export declare const wrap: <S extends object>(service: S, hook: Hook) => S;
56
+ /**
57
+ * Layer form of {@link wrap}: reads the real service from the environment and
58
+ * re-provides the notifying wrapper under the same key, so an existing stack
59
+ * gains a listener without touching its construction.
60
+ *
61
+ * @category layers
62
+ * @since 0.1.0
63
+ */
64
+ export declare const layer: <Id, S extends object>(tag: Context.Key<Id, S>, hook: Hook) => Layer.Layer<Id, never, Id>;
65
+ //# sourceMappingURL=Notifying.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Notifying.d.ts","sourceRoot":"","sources":["../../../src/test/Notifying.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC;;;;;;;GAOG;AACH,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEtC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,IAAI,GAAG,CACjB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,KACzB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AAExB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,IAAI,KAAG,CAyB/D,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,GAAI,EAAE,EAAE,CAAC,SAAS,MAAM,EACxC,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EACvB,MAAM,IAAI,KACT,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAyE,CAAA"}
@@ -0,0 +1,62 @@
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
+ import * as Effect from "effect/Effect";
18
+ import * as Layer from "effect/Layer";
19
+ /**
20
+ * Wraps a service so `hook(op, "before" | "after", args)` fires around every
21
+ * Effect-valued operation, delegating to the real implementation in between.
22
+ *
23
+ * Generic over any record-of-Effect-methods service (`Journal`, `CacheStore`,
24
+ * `AttemptStore`, `RunStore`, ...): Effect-returning methods and plain Effect
25
+ * properties are hooked; non-Effect members (e.g. `Stream`-returning methods)
26
+ * delegate untouched.
27
+ *
28
+ * @category constructors
29
+ * @since 0.1.0
30
+ */
31
+ export const wrap = (service, hook) => {
32
+ const wrapped = {};
33
+ for (const key of Object.keys(service)) {
34
+ const value = service[key];
35
+ if (Effect.isEffect(value)) {
36
+ wrapped[key] = hook(key, "before", []).pipe(Effect.andThen(value), Effect.tap(() => hook(key, "after", [])));
37
+ }
38
+ else if (typeof value === "function") {
39
+ const method = value;
40
+ wrapped[key] = (...args) => {
41
+ const result = method.apply(service, args);
42
+ return Effect.isEffect(result)
43
+ ? hook(key, "before", args).pipe(Effect.andThen(result), Effect.tap(() => hook(key, "after", args)))
44
+ : result;
45
+ };
46
+ }
47
+ else {
48
+ wrapped[key] = value;
49
+ }
50
+ }
51
+ return wrapped;
52
+ };
53
+ /**
54
+ * Layer form of {@link wrap}: reads the real service from the environment and
55
+ * re-provides the notifying wrapper under the same key, so an existing stack
56
+ * gains a listener without touching its construction.
57
+ *
58
+ * @category layers
59
+ * @since 0.1.0
60
+ */
61
+ export const layer = (tag, hook) => Layer.effect(tag)(Effect.map(tag, (service) => wrap(service, hook)));
62
+ //# sourceMappingURL=Notifying.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Notifying.js","sourceRoot":"","sources":["../../../src/test/Notifying.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AA+BrC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAmB,OAAU,EAAE,IAAU,EAAK,EAAE;IAClE,MAAM,OAAO,GAA4B,EAAE,CAAA;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAA;QACvD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,CACzC,MAAM,CAAC,OAAO,CAAC,KAAiD,CAAC,EACjE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CACzC,CAAA;QACH,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,KAA6C,CAAA;YAC5D,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAoB,EAAE,EAAE;gBACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;gBAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAC5B,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAC9B,MAAM,CAAC,OAAO,CAAC,MAAkD,CAAC,EAClE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAC3C;oBACD,CAAC,CAAC,MAAM,CAAA;YACZ,CAAC,CAAA;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACtB,CAAC;IACH,CAAC;IACD,OAAO,OAAY,CAAA;AACrB,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,GAAuB,EACvB,IAAU,EACkB,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Effect-native analogue of Bazel Skyframe's `NotifyingHelper` and\n * `NotifyingInMemoryGraph`: wrap a record-of-Effect-methods service so a listener hook fires around\n * every operation. The hook runs in the calling fiber, so a hook that dies,\n * interrupts, or awaits a `Latch` injects crashes, fence loss, and exact\n * sequencing at any durable transition point, the mechanism that makes\n * interstitial interleaving bugs testable at all.\n *\n * Deliberately NOT ported from Skyframe: `DeterministicHelper`'s\n * deterministic-scheduler machinery, `ChainedFunction`, and `TrackingAwaiter`.\n * Effect already gives us deterministic fibers, `TestClock`, `Latch`, and\n * failure propagation across fiber joins, so those helpers have no residue\n * here.\n *\n * @since 0.1.0\n */\n\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\n\n/**\n * Whether the hook is firing before or after the delegated operation.\n * Skyframe's `NotifyingHelper.Order`, minus its `BEFORE_AND_AFTER` (pass the\n * same hook body for both firings instead).\n *\n * @category models\n * @since 0.1.0\n */\nexport type Order = \"before\" | \"after\"\n\n/**\n * Listener invoked around every delegated operation. `op` is the service\n * method name (Skyframe's `EventType`, but open-world), `args` the call\n * arguments. Express crash injection as `Effect.die` or `Effect.interrupt`\n * inside the hook, the hook's type stays `Effect<void>` so the wrapped\n * service is structurally identical to the real one.\n *\n * The `after` firing only happens when the operation succeeds; a failed\n * operation already is the interesting event.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Hook = (\n op: string,\n order: Order,\n args: ReadonlyArray<unknown>\n) => Effect.Effect<void>\n\n/**\n * Wraps a service so `hook(op, \"before\" | \"after\", args)` fires around every\n * Effect-valued operation, delegating to the real implementation in between.\n *\n * Generic over any record-of-Effect-methods service (`Journal`, `CacheStore`,\n * `AttemptStore`, `RunStore`, ...): Effect-returning methods and plain Effect\n * properties are hooked; non-Effect members (e.g. `Stream`-returning methods)\n * delegate untouched.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const wrap = <S extends object>(service: S, hook: Hook): S => {\n const wrapped: Record<string, unknown> = {}\n for (const key of Object.keys(service)) {\n const value = (service as Record<string, unknown>)[key]\n if (Effect.isEffect(value)) {\n wrapped[key] = hook(key, \"before\", []).pipe(\n Effect.andThen(value as Effect.Effect<unknown, unknown, unknown>),\n Effect.tap(() => hook(key, \"after\", []))\n )\n } else if (typeof value === \"function\") {\n const method = value as (...args: Array<unknown>) => unknown\n wrapped[key] = (...args: Array<unknown>) => {\n const result = method.apply(service, args)\n return Effect.isEffect(result)\n ? hook(key, \"before\", args).pipe(\n Effect.andThen(result as Effect.Effect<unknown, unknown, unknown>),\n Effect.tap(() => hook(key, \"after\", args))\n )\n : result\n }\n } else {\n wrapped[key] = value\n }\n }\n return wrapped as S\n}\n\n/**\n * Layer form of {@link wrap}: reads the real service from the environment and\n * re-provides the notifying wrapper under the same key, so an existing stack\n * gains a listener without touching its construction.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = <Id, S extends object>(\n tag: Context.Key<Id, S>,\n hook: Hook\n): Layer.Layer<Id, never, Id> => Layer.effect(tag)(Effect.map(tag, (service) => wrap(service, hook)))\n"]}
@@ -0,0 +1,43 @@
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
+ import * as Layer from "effect/Layer";
12
+ import * as SqlJournal from "../SqlJournal.ts";
13
+ /**
14
+ * Options for the deterministic journal bundle.
15
+ *
16
+ * The production options with `capacity` and `overflow` made optional. Every
17
+ * field forwards to {@link SqlJournal.layer} unchanged, so a suite can exercise
18
+ * index-bound eviction, the entry byte bound, a custom or noop redactor, and
19
+ * the compaction policy through the bundle instead of hand-assembling the layer
20
+ * stack the bundle exists to hide.
21
+ *
22
+ * The type is derived rather than mirrored because a mirror silently falls
23
+ * behind: the hand-copied list had already lost `maxEntryBytes`, so the one
24
+ * production bound a hostile-payload suite needs was unreachable here.
25
+ *
26
+ * @category models
27
+ * @since 0.1.0
28
+ */
29
+ export interface TestJournalOptions extends Omit<SqlJournal.SqlJournalOptions, "capacity" | "overflow"> {
30
+ /** Admission-queue bound and `changes` buffer size. Defaults to 1024. */
31
+ readonly capacity?: number | undefined;
32
+ /** Policy applied when the admission queue is full. Defaults to `reject`. */
33
+ readonly overflow?: SqlJournal.SqlJournalOptions["overflow"] | undefined;
34
+ }
35
+ /**
36
+ * Provides the production SQLite journal over an in-memory database.
37
+ * Migrations run before the journal is exposed.
38
+ *
39
+ * @category layers
40
+ * @since 0.1.0
41
+ */
42
+ export declare const layer: (options?: TestJournalOptions) => Layer.Layer<import("../Journal.ts").Journal, import("effect/unstable/sql/SqlError").SqlError | import("../Journal.ts").JournalError | import("effect/unstable/sql/Migrator").MigrationError, never>;
43
+ //# sourceMappingURL=TestJournal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestJournal.d.ts","sourceRoot":"","sources":["../../../src/test/TestJournal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,UAAU,GAAG,UAAU,CAAC;IACrG,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAAG,SAAS,CAAA;CACzE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,kBAAkB,wMAKgC,CAAA"}
@@ -0,0 +1,27 @@
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
+ import * as TestDatabase from "@smthrs/database/test/TestDatabase";
12
+ import * as Layer from "effect/Layer";
13
+ import * as Migrations from "../Migrations.js";
14
+ import * as SqlJournal from "../SqlJournal.js";
15
+ /**
16
+ * Provides the production SQLite journal over an in-memory database.
17
+ * Migrations run before the journal is exposed.
18
+ *
19
+ * @category layers
20
+ * @since 0.1.0
21
+ */
22
+ export const layer = (options) => SqlJournal.layer({
23
+ ...options,
24
+ capacity: options?.capacity ?? 1024,
25
+ overflow: options?.overflow ?? "reject"
26
+ }).pipe(Layer.provide(Layer.provideMerge(Migrations.layer, TestDatabase.layer)));
27
+ //# sourceMappingURL=TestJournal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestJournal.js","sourceRoot":"","sources":["../../../src/test/TestJournal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAClE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAyB9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,OAA4B,EAAE,EAAE,CACpD,UAAU,CAAC,KAAK,CAAC;IACf,GAAG,OAAO;IACV,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI;IACnC,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,QAAQ;CACxC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Deterministic bundle of the production journal service.\n *\n * This provides the journal and nothing else. A consumer that also needs run\n * state or the step cache composes `@smthrs/run-store/test/TestRunStore` and\n * `@smthrs/step-cache/test/TestCacheStore` over the same database, or takes\n * the whole engine bundle from `@smthrs/engine-store/test/TestStores`.\n *\n * @since 0.1.0\n */\n\nimport * as TestDatabase from \"@smthrs/database/test/TestDatabase\"\nimport * as Layer from \"effect/Layer\"\nimport * as Migrations from \"../Migrations.ts\"\nimport * as SqlJournal from \"../SqlJournal.ts\"\n\n/**\n * Options for the deterministic journal bundle.\n *\n * The production options with `capacity` and `overflow` made optional. Every\n * field forwards to {@link SqlJournal.layer} unchanged, so a suite can exercise\n * index-bound eviction, the entry byte bound, a custom or noop redactor, and\n * the compaction policy through the bundle instead of hand-assembling the layer\n * stack the bundle exists to hide.\n *\n * The type is derived rather than mirrored because a mirror silently falls\n * behind: the hand-copied list had already lost `maxEntryBytes`, so the one\n * production bound a hostile-payload suite needs was unreachable here.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface TestJournalOptions extends Omit<SqlJournal.SqlJournalOptions, \"capacity\" | \"overflow\"> {\n /** Admission-queue bound and `changes` buffer size. Defaults to 1024. */\n readonly capacity?: number | undefined\n /** Policy applied when the admission queue is full. Defaults to `reject`. */\n readonly overflow?: SqlJournal.SqlJournalOptions[\"overflow\"] | undefined\n}\n\n/**\n * Provides the production SQLite journal over an in-memory database.\n * Migrations run before the journal is exposed.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = (options?: TestJournalOptions) =>\n SqlJournal.layer({\n ...options,\n capacity: options?.capacity ?? 1024,\n overflow: options?.overflow ?? \"reject\"\n }).pipe(Layer.provide(Layer.provideMerge(Migrations.layer, TestDatabase.layer)))\n"]}
package/docs/README.md ADDED
@@ -0,0 +1,164 @@
1
+ ---
2
+ title: "@smthrs/journal"
3
+ description: "An append-only event history for Effect applications: a lossless lifecycle channel beside a lossy telemetry channel, credentials scrubbed before anything is persisted, and an owner fence that refuses a write from a process that lost the run."
4
+ ---
5
+
6
+ `@smthrs/journal` records what a long-running piece of work did, in order, in
7
+ SQLite. Rows are appended and never updated, so the history reads the same on
8
+ the tenth pass as on the first, and a process that dies halfway through leaves
9
+ behind everything it had already committed.
10
+
11
+ It is a set of [Effect](https://effect.website) services: every write returns a
12
+ typed receipt or a typed failure, and the database arrives as a layer you
13
+ compose. It belongs to the Smithers durable flow engine, but it runs on its
14
+ own: no other Smithers package is required.
15
+
16
+ Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
17
+
18
+ `effect` is a peer dependency at exactly `4.0.0-rc.115`. Two copies of `effect`
19
+ in one program are two sets of service tags, so a journal layer built against
20
+ one copy cannot be provided to a program holding the other.
21
+
22
+ ## What it solves
23
+
24
+ Application logs are the usual answer to "what happened here", and they stop
25
+ being an answer the moment another program has to read them. A journal entry is
26
+ a typed record with a sequence number, so a supervisor, a resumed run, or a UI
27
+ can replay the history instead of parsing it.
28
+
29
+ - **Two channels, one order.** A lifecycle event ("the run finished") is on
30
+ disk before its call returns. A telemetry event ("40 percent compiled") goes
31
+ through a bounded queue that drops under pressure rather than stalling the
32
+ writer. Both land in one per-run sequence, so the cheap channel cannot
33
+ reorder the expensive one.
34
+ - **Every write gets an answer.** An emission returns `Accepted`, `Duplicate`,
35
+ or `Dropped`, and every failure carries one of fourteen stable codes. A
36
+ caller never has to guess whether an entry survived.
37
+ - **A retry is not a second event.** Producer identity is
38
+ `(runId, sourceId, sourceSeq)`, unique in the database, so a producer that
39
+ replays after a crash gets `Duplicate` back rather than doubling its history.
40
+ - **Credentials never reach the file.** Payloads are scrubbed on the write
41
+ path, before encoding, because a row is permanent and gets replayed verbatim
42
+ to every later reader.
43
+ - **A stale process cannot append.** The durable channel takes an owner token
44
+ and commits only while the database still records that owner as the run's
45
+ owner. A process that was replaced fails with `fence_lost` instead of writing
46
+ into a history it no longer owns.
47
+ - **State and its entry commit together.** `transact` puts your own writes and
48
+ the entry that describes them in one transaction, so the two can never
49
+ disagree about what happened.
50
+ - **A long run stops growing.** A checkpoint pins replay state to a sequence,
51
+ and compaction deletes the entries below it.
52
+
53
+ ## One run, both channels
54
+
55
+ This program writes a lifecycle event and a telemetry event to the same run,
56
+ then reads the run back. It needs Node.js 26.4.0 or later, where the built-in
57
+ SQLite module lives, and it writes a real `history.db` in the working
58
+ directory:
59
+
60
+ ```ts
61
+ import * as DurableWriter from "@smthrs/database/DurableWriter"
62
+ import * as NodeDatabase from "@smthrs/database/node/NodeDatabase"
63
+ import { Journal, JournalEvent, Migrations, SqlJournal } from "@smthrs/journal"
64
+ import * as Effect from "effect/Effect"
65
+ import * as Layer from "effect/Layer"
66
+
67
+ const database = Layer.provideMerge(
68
+ DurableWriter.layer(),
69
+ NodeDatabase.layer({ filename: "history.db" })
70
+ )
71
+
72
+ const journalLayer = SqlJournal.layer({ capacity: 1024, overflow: "reject" }).pipe(
73
+ Layer.provide(Layer.provideMerge(Migrations.layer, database))
74
+ )
75
+
76
+ const runId = "run-1" as JournalEvent.RunId
77
+ const sourceId = "engine" as JournalEvent.SourceId
78
+
79
+ const program = Effect.gen(function*() {
80
+ const journal = yield* Journal.Journal
81
+
82
+ const created = yield* journal.emitDurableUnfenced({
83
+ runId,
84
+ sourceId,
85
+ eventType: "run.created",
86
+ payload: { flow: "build", token: "ghp_0123456789abcdefghijklmnopqrstuvwxyz" }
87
+ })
88
+ console.log("durable:", created._tag, "seq", created.seq)
89
+
90
+ yield* journal.emitLossy({
91
+ runId,
92
+ sourceId,
93
+ eventType: "step.progress",
94
+ payload: { step: "compile", percent: 40 }
95
+ })
96
+ yield* journal.flush
97
+
98
+ const page = yield* journal.entries({ runId, limit: 100 })
99
+ for (const entry of page.entries) {
100
+ console.log(entry.seq, entry.eventType, JSON.stringify(entry.payload))
101
+ }
102
+ })
103
+
104
+ await Effect.runPromise(program.pipe(Effect.provide(journalLayer), Effect.orDie))
105
+ ```
106
+
107
+ ```text
108
+ durable: Accepted seq 0
109
+ 0 run.created {"flow":"build","token":"[REDACTED]"}
110
+ 1 step.progress {"percent":40,"step":"compile"}
111
+ ```
112
+
113
+ The token came back as `[REDACTED]` because it was scrubbed before the row was
114
+ encoded, so the credential is not in `history.db` and never was. The two
115
+ entries share one `seq` line because both channels write one history. The
116
+ `flush` is the barrier for the lossy channel only: the durable entry was
117
+ already committed when its receipt arrived.
118
+
119
+ ## Where this sits
120
+
121
+ The journal owns exactly the history and nothing else.
122
+ [`@smthrs/flows`](/api/flows) is the engine's barrel: it re-exports this
123
+ package as a namespace next to the flow language, the stores, and the runtime
124
+ that wires them over one SQLite file, so
125
+ `import { Journal } from "@smthrs/flows"` reaches the same code as
126
+ `import * as Journal from "@smthrs/journal"`. Depend on the barrel when you
127
+ want a working engine, and on this package when the event history is the part
128
+ you need: an audit trail, a live feed for a UI, or the replay log of a system
129
+ you are building yourself.
130
+
131
+ The neighboring stores hold what the journal deliberately does not.
132
+ Run and attempt state live in [`@smthrs/run-store`](/api/run-store), sealed
133
+ step results in [`@smthrs/step-cache`](/api/step-cache), and the durable
134
+ deferred and clock tables in [`@smthrs/engine-store`](/api/engine-store), which
135
+ composes all of them. Underneath every one of them,
136
+ [`@smthrs/database`](/api/database) is the single write boundary they share,
137
+ which is what makes `transact` possible.
138
+
139
+ Above the whole engine sits the
140
+ [`smithers` command-line interface](/api/cli), which runs flows out of a
141
+ project directory without your composing any of this by hand.
142
+
143
+ One name collides. [`@smthrs/chain`](/api/chain) also has a journal: an
144
+ in-process event array that is a single chain's only state. It is a different
145
+ object with a different contract, described in
146
+ [the chain journal](/pkg/chain/concepts/journal).
147
+
148
+ ## Next steps
149
+
150
+ - [Installation](./installation.md): the database layer, the migration sets a
151
+ journal needs, and which extra table the fenced write path reads.
152
+ - [Quickstart](./quickstart.md): the program above built one step at a time,
153
+ with what each line of output proves.
154
+ - [The two channels](./concepts/two-channels.md): which write to reach for,
155
+ what each receipt means, and why sequence gaps are valid.
156
+ - [The owner fence](./concepts/owner-fence.md): why the durable channel takes
157
+ an owner, and when the unfenced one is correct.
158
+ - [Read and follow a run](./guides/read-a-run.md): paging, the lossless
159
+ follower, and the buffer that drops for a slow subscriber.
160
+ - [Test against a real journal](./guides/testing.md): the production layer over
161
+ an in-memory database, and injecting a crash at an exact transition.
162
+ - [API reference](./api.md): every public export, grouped by namespace.
163
+ - [Troubleshooting](./troubleshooting.md): every error code, what causes it,
164
+ and what to change.