@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.
- package/CHANGELOG.md +149 -0
- package/LICENSE +21 -0
- package/README.md +206 -2
- package/dist/cjs/CallFact.d.ts +129 -0
- package/dist/cjs/CallFact.d.ts.map +1 -0
- package/dist/cjs/CallFact.js +106 -0
- package/dist/cjs/CallFact.js.map +7 -0
- package/dist/cjs/Consensus.d.ts +440 -0
- package/dist/cjs/Consensus.d.ts.map +1 -0
- package/dist/cjs/Consensus.js +251 -0
- package/dist/cjs/Consensus.js.map +7 -0
- package/dist/cjs/EngineEvent.d.ts +890 -0
- package/dist/cjs/EngineEvent.d.ts.map +1 -0
- package/dist/cjs/EngineEvent.js +383 -0
- package/dist/cjs/EngineEvent.js.map +7 -0
- package/dist/cjs/ExecutionFact.d.ts +216 -0
- package/dist/cjs/ExecutionFact.d.ts.map +1 -0
- package/dist/cjs/ExecutionFact.js +303 -0
- package/dist/cjs/ExecutionFact.js.map +7 -0
- package/dist/cjs/Journal.d.ts +617 -0
- package/dist/cjs/Journal.d.ts.map +1 -0
- package/dist/cjs/Journal.js +190 -0
- package/dist/cjs/Journal.js.map +7 -0
- package/dist/cjs/JournalEvent.d.ts +421 -0
- package/dist/cjs/JournalEvent.d.ts.map +1 -0
- package/dist/cjs/JournalEvent.js +281 -0
- package/dist/cjs/JournalEvent.js.map +7 -0
- package/dist/cjs/JournalGeneration.d.ts +34 -0
- package/dist/cjs/JournalGeneration.d.ts.map +1 -0
- package/dist/cjs/JournalGeneration.js +72 -0
- package/dist/cjs/JournalGeneration.js.map +7 -0
- package/dist/cjs/JournalMetrics.d.ts +72 -0
- package/dist/cjs/JournalMetrics.d.ts.map +1 -0
- package/dist/cjs/JournalMetrics.js +55 -0
- package/dist/cjs/JournalMetrics.js.map +7 -0
- package/dist/cjs/Migrations.d.ts +42 -0
- package/dist/cjs/Migrations.d.ts.map +1 -0
- package/dist/cjs/Migrations.js +58 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/OwnerId.d.ts +40 -0
- package/dist/cjs/OwnerId.d.ts.map +1 -0
- package/dist/cjs/OwnerId.js +40 -0
- package/dist/cjs/OwnerId.js.map +7 -0
- package/dist/cjs/Projection.d.ts +29 -0
- package/dist/cjs/Projection.d.ts.map +1 -0
- package/dist/cjs/Projection.js +25 -0
- package/dist/cjs/Projection.js.map +7 -0
- package/dist/cjs/RedactedLogger.d.ts +121 -0
- package/dist/cjs/RedactedLogger.d.ts.map +1 -0
- package/dist/cjs/RedactedLogger.js +235 -0
- package/dist/cjs/RedactedLogger.js.map +7 -0
- package/dist/cjs/Redaction.d.ts +350 -0
- package/dist/cjs/Redaction.d.ts.map +1 -0
- package/dist/cjs/Redaction.js +928 -0
- package/dist/cjs/Redaction.js.map +7 -0
- package/dist/cjs/SqlConsensus.d.ts +44 -0
- package/dist/cjs/SqlConsensus.d.ts.map +1 -0
- package/dist/cjs/SqlConsensus.js +243 -0
- package/dist/cjs/SqlConsensus.js.map +7 -0
- package/dist/cjs/SqlJournal.d.ts +173 -0
- package/dist/cjs/SqlJournal.d.ts.map +1 -0
- package/dist/cjs/SqlJournal.js +1568 -0
- package/dist/cjs/SqlJournal.js.map +7 -0
- package/dist/cjs/StepFact.d.ts +66 -0
- package/dist/cjs/StepFact.d.ts.map +1 -0
- package/dist/cjs/StepFact.js +68 -0
- package/dist/cjs/StepFact.js.map +7 -0
- package/dist/cjs/index.d.ts +95 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +63 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/JournalInternalFault.d.ts +37 -0
- package/dist/cjs/internal/JournalInternalFault.d.ts.map +1 -0
- package/dist/cjs/internal/JournalInternalFault.js +43 -0
- package/dist/cjs/internal/JournalInternalFault.js.map +7 -0
- package/dist/cjs/migrations/0001_initial.d.ts +15 -0
- package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_initial.js +54 -0
- package/dist/cjs/migrations/0001_initial.js.map +7 -0
- package/dist/cjs/migrations/0002_checkpoints.d.ts +27 -0
- package/dist/cjs/migrations/0002_checkpoints.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_checkpoints.js +48 -0
- package/dist/cjs/migrations/0002_checkpoints.js.map +7 -0
- package/dist/cjs/migrations/0003_startup_index.d.ts +15 -0
- package/dist/cjs/migrations/0003_startup_index.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_startup_index.js +40 -0
- package/dist/cjs/migrations/0003_startup_index.js.map +7 -0
- package/dist/cjs/migrations/0004_dedup.d.ts +15 -0
- package/dist/cjs/migrations/0004_dedup.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_dedup.js +57 -0
- package/dist/cjs/migrations/0004_dedup.js.map +7 -0
- package/dist/cjs/migrations/0005_run_event_type.d.ts +15 -0
- package/dist/cjs/migrations/0005_run_event_type.d.ts.map +1 -0
- package/dist/cjs/migrations/0005_run_event_type.js +40 -0
- package/dist/cjs/migrations/0005_run_event_type.js.map +7 -0
- package/dist/cjs/migrations/0006_consensus.d.ts +22 -0
- package/dist/cjs/migrations/0006_consensus.d.ts.map +1 -0
- package/dist/cjs/migrations/0006_consensus.js +84 -0
- package/dist/cjs/migrations/0006_consensus.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/Notifying.d.ts +65 -0
- package/dist/cjs/test/Notifying.d.ts.map +1 -0
- package/dist/cjs/test/Notifying.js +62 -0
- package/dist/cjs/test/Notifying.js.map +7 -0
- package/dist/cjs/test/TestJournal.d.ts +43 -0
- package/dist/cjs/test/TestJournal.d.ts.map +1 -0
- package/dist/cjs/test/TestJournal.js +43 -0
- package/dist/cjs/test/TestJournal.js.map +7 -0
- package/dist/esm/CallFact.d.ts +129 -0
- package/dist/esm/CallFact.d.ts.map +1 -0
- package/dist/esm/CallFact.js +96 -0
- package/dist/esm/CallFact.js.map +1 -0
- package/dist/esm/Consensus.d.ts +440 -0
- package/dist/esm/Consensus.d.ts.map +1 -0
- package/dist/esm/Consensus.js +454 -0
- package/dist/esm/Consensus.js.map +1 -0
- package/dist/esm/EngineEvent.d.ts +890 -0
- package/dist/esm/EngineEvent.d.ts.map +1 -0
- package/dist/esm/EngineEvent.js +614 -0
- package/dist/esm/EngineEvent.js.map +1 -0
- package/dist/esm/ExecutionFact.d.ts +216 -0
- package/dist/esm/ExecutionFact.d.ts.map +1 -0
- package/dist/esm/ExecutionFact.js +374 -0
- package/dist/esm/ExecutionFact.js.map +1 -0
- package/dist/esm/Journal.d.ts +617 -0
- package/dist/esm/Journal.d.ts.map +1 -0
- package/dist/esm/Journal.js +323 -0
- package/dist/esm/Journal.js.map +1 -0
- package/dist/esm/JournalEvent.d.ts +421 -0
- package/dist/esm/JournalEvent.d.ts.map +1 -0
- package/dist/esm/JournalEvent.js +517 -0
- package/dist/esm/JournalEvent.js.map +1 -0
- package/dist/esm/JournalGeneration.d.ts +34 -0
- package/dist/esm/JournalGeneration.d.ts.map +1 -0
- package/dist/esm/JournalGeneration.js +65 -0
- package/dist/esm/JournalGeneration.js.map +1 -0
- package/dist/esm/JournalMetrics.d.ts +72 -0
- package/dist/esm/JournalMetrics.d.ts.map +1 -0
- package/dist/esm/JournalMetrics.js +79 -0
- package/dist/esm/JournalMetrics.js.map +1 -0
- package/dist/esm/Migrations.d.ts +42 -0
- package/dist/esm/Migrations.d.ts.map +1 -0
- package/dist/esm/Migrations.js +59 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/OwnerId.d.ts +40 -0
- package/dist/esm/OwnerId.d.ts.map +1 -0
- package/dist/esm/OwnerId.js +33 -0
- package/dist/esm/OwnerId.js.map +1 -0
- package/dist/esm/Projection.d.ts +29 -0
- package/dist/esm/Projection.d.ts.map +1 -0
- package/dist/esm/Projection.js +13 -0
- package/dist/esm/Projection.js.map +1 -0
- package/dist/esm/RedactedLogger.d.ts +121 -0
- package/dist/esm/RedactedLogger.d.ts.map +1 -0
- package/dist/esm/RedactedLogger.js +445 -0
- package/dist/esm/RedactedLogger.js.map +1 -0
- package/dist/esm/Redaction.d.ts +350 -0
- package/dist/esm/Redaction.d.ts.map +1 -0
- package/dist/esm/Redaction.js +1584 -0
- package/dist/esm/Redaction.js.map +1 -0
- package/dist/esm/SqlConsensus.d.ts +44 -0
- package/dist/esm/SqlConsensus.d.ts.map +1 -0
- package/dist/esm/SqlConsensus.js +252 -0
- package/dist/esm/SqlConsensus.js.map +1 -0
- package/dist/esm/SqlJournal.d.ts +173 -0
- package/dist/esm/SqlJournal.d.ts.map +1 -0
- package/dist/esm/SqlJournal.js +1858 -0
- package/dist/esm/SqlJournal.js.map +1 -0
- package/dist/esm/StepFact.d.ts +66 -0
- package/dist/esm/StepFact.d.ts.map +1 -0
- package/dist/esm/StepFact.js +50 -0
- package/dist/esm/StepFact.js.map +1 -0
- package/dist/esm/index.d.ts +95 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +95 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/JournalInternalFault.d.ts +37 -0
- package/dist/esm/internal/JournalInternalFault.d.ts.map +1 -0
- package/dist/esm/internal/JournalInternalFault.js +28 -0
- package/dist/esm/internal/JournalInternalFault.js.map +1 -0
- package/dist/esm/migrations/0001_initial.d.ts +15 -0
- package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
- package/dist/esm/migrations/0001_initial.js +32 -0
- package/dist/esm/migrations/0001_initial.js.map +1 -0
- package/dist/esm/migrations/0002_checkpoints.d.ts +27 -0
- package/dist/esm/migrations/0002_checkpoints.d.ts.map +1 -0
- package/dist/esm/migrations/0002_checkpoints.js +38 -0
- package/dist/esm/migrations/0002_checkpoints.js.map +1 -0
- package/dist/esm/migrations/0003_startup_index.d.ts +15 -0
- package/dist/esm/migrations/0003_startup_index.d.ts.map +1 -0
- package/dist/esm/migrations/0003_startup_index.js +18 -0
- package/dist/esm/migrations/0003_startup_index.js.map +1 -0
- package/dist/esm/migrations/0004_dedup.d.ts +15 -0
- package/dist/esm/migrations/0004_dedup.d.ts.map +1 -0
- package/dist/esm/migrations/0004_dedup.js +38 -0
- package/dist/esm/migrations/0004_dedup.js.map +1 -0
- package/dist/esm/migrations/0005_run_event_type.d.ts +15 -0
- package/dist/esm/migrations/0005_run_event_type.d.ts.map +1 -0
- package/dist/esm/migrations/0005_run_event_type.js +18 -0
- package/dist/esm/migrations/0005_run_event_type.js.map +1 -0
- package/dist/esm/migrations/0006_consensus.d.ts +22 -0
- package/dist/esm/migrations/0006_consensus.d.ts.map +1 -0
- package/dist/esm/migrations/0006_consensus.js +69 -0
- package/dist/esm/migrations/0006_consensus.js.map +1 -0
- package/dist/esm/test/Notifying.d.ts +65 -0
- package/dist/esm/test/Notifying.d.ts.map +1 -0
- package/dist/esm/test/Notifying.js +62 -0
- package/dist/esm/test/Notifying.js.map +1 -0
- package/dist/esm/test/TestJournal.d.ts +43 -0
- package/dist/esm/test/TestJournal.d.ts.map +1 -0
- package/dist/esm/test/TestJournal.js +27 -0
- package/dist/esm/test/TestJournal.js.map +1 -0
- package/docs/README.md +164 -0
- package/docs/api.md +639 -0
- package/docs/concepts/compaction.md +157 -0
- package/docs/concepts/idempotency.md +127 -0
- package/docs/concepts/owner-fence.md +124 -0
- package/docs/concepts/redaction.md +218 -0
- package/docs/concepts/state-event-authority.md +159 -0
- package/docs/concepts/two-channels.md +167 -0
- package/docs/guides/commit-state-and-entry.md +114 -0
- package/docs/guides/compact-a-run.md +134 -0
- package/docs/guides/fold-a-projection.md +106 -0
- package/docs/guides/read-a-run.md +139 -0
- package/docs/guides/redact-log-output.md +94 -0
- package/docs/guides/testing.md +124 -0
- package/docs/guides/write-lifecycle-events.md +110 -0
- package/docs/installation.md +133 -0
- package/docs/quickstart.md +143 -0
- package/docs/troubleshooting.md +214 -0
- package/package.json +263 -3
- package/src/CallFact.ts +117 -0
- package/src/Consensus.ts +683 -0
- package/src/EngineEvent.ts +823 -0
- package/src/ExecutionFact.ts +433 -0
- package/src/Journal.ts +688 -0
- package/src/JournalEvent.ts +654 -0
- package/src/JournalGeneration.ts +77 -0
- package/src/JournalMetrics.ts +89 -0
- package/src/Migrations.ts +62 -0
- package/src/OwnerId.ts +42 -0
- package/src/Projection.ts +31 -0
- package/src/RedactedLogger.ts +483 -0
- package/src/Redaction.ts +1784 -0
- package/src/SqlConsensus.ts +344 -0
- package/src/SqlJournal.ts +2732 -0
- package/src/StepFact.ts +69 -0
- package/src/index.ts +109 -0
- package/src/internal/JournalInternalFault.ts +40 -0
- package/src/migrations/0001_initial.ts +41 -0
- package/src/migrations/0002_checkpoints.ts +46 -0
- package/src/migrations/0003_startup_index.ts +19 -0
- package/src/migrations/0004_dedup.ts +43 -0
- package/src/migrations/0005_run_event_type.ts +19 -0
- package/src/migrations/0006_consensus.ts +83 -0
- package/src/test/Notifying.ts +101 -0
- package/src/test/TestJournal.ts +52 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable event-envelope schemas for the journal.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
/**
|
|
8
|
+
* A UTF-16 surrogate with no partner: a high surrogate not followed by a low
|
|
9
|
+
* one, or a low surrogate not preceded by a high one. `String.isWellFormed`
|
|
10
|
+
* answers the same question but needs the ES2024 lib, which this package does
|
|
11
|
+
* not target.
|
|
12
|
+
*/
|
|
13
|
+
const loneSurrogate = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
14
|
+
/**
|
|
15
|
+
* A NUL anywhere in an identifier.
|
|
16
|
+
*
|
|
17
|
+
* SQLite's `length()` counts the characters BEFORE the first NUL, and every
|
|
18
|
+
* identifier column carries `CHECK (length(...) > 0)`, so a leading-NUL run id
|
|
19
|
+
* measures zero and the write fails the constraint. Measured on this tree, an
|
|
20
|
+
* emit with a leading-NUL run id came back as `sink_failed` with a
|
|
21
|
+
* `DatabaseError` cause, which tells the caller the database is down when the
|
|
22
|
+
* real fault is the identifier it just supplied. A NUL after the first
|
|
23
|
+
* character is refused on the same terms: it makes the column's own length
|
|
24
|
+
* check disagree with the identifier's length.
|
|
25
|
+
*
|
|
26
|
+
* Built from a code point rather than written as a pattern literal: a control
|
|
27
|
+
* character in a regex is exactly the typo `no-control-regex` exists to catch,
|
|
28
|
+
* and no reader can see one in the source.
|
|
29
|
+
*/
|
|
30
|
+
const embeddedNul = new RegExp(String.fromCharCode(0));
|
|
31
|
+
/**
|
|
32
|
+
* Longest identifier the journal persists.
|
|
33
|
+
*
|
|
34
|
+
* A run id, a source id and an event type are index columns of a permanent
|
|
35
|
+
* table and are held in per-run maps for the layer's lifetime, so an
|
|
36
|
+
* unbounded one costs durable index space and heap that nothing ever
|
|
37
|
+
* reclaims. Every identifier this repository mints is a uuid or a short
|
|
38
|
+
* dotted name, two orders of magnitude below this bound, so the ceiling
|
|
39
|
+
* refuses only the shapes that were never identifiers.
|
|
40
|
+
*
|
|
41
|
+
* @since 1.0.0
|
|
42
|
+
* @category constants
|
|
43
|
+
*/
|
|
44
|
+
export const maxIdentifierLength = 1024;
|
|
45
|
+
/**
|
|
46
|
+
* A persistable identifier: non-empty, and representable in the store.
|
|
47
|
+
*
|
|
48
|
+
* SQLite binds a lone UTF-16 surrogate as U+FFFD, so two ill-formed
|
|
49
|
+
* identifiers that differ only in their surrogates land on ONE persisted key.
|
|
50
|
+
* The second run's first event then dedupes into the first run's row and reads
|
|
51
|
+
* by either id return the same history, which destroys run isolation at the
|
|
52
|
+
* persistence boundary. Rejecting ill-formed text at the schema keeps the
|
|
53
|
+
* identifier the caller decoded and the identifier the database stores the
|
|
54
|
+
* same value.
|
|
55
|
+
*
|
|
56
|
+
* A NUL is rejected for the reason {@link embeddedNul} gives: the column's own
|
|
57
|
+
* length check cannot see past one, so the store refuses the identifier as a
|
|
58
|
+
* constraint violation and the caller is told the sink failed.
|
|
59
|
+
*
|
|
60
|
+
* The empty string is rejected for a plainer reason: it names nothing, and the
|
|
61
|
+
* journal used to accept it at decode and then reject it at the service, so a
|
|
62
|
+
* caller could hold a "valid" identifier the next call refused.
|
|
63
|
+
* The length is bounded because every identifier occupies permanent index
|
|
64
|
+
* space and layer-lifetime heap.
|
|
65
|
+
*/
|
|
66
|
+
const identifier = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxIdentifierLength), Schema.makeFilter((value) => !loneSurrogate.test(value), { title: "wellFormedIdentifier" }), Schema.makeFilter((value) => !embeddedNul.test(value), { title: "nulFreeIdentifier" }));
|
|
67
|
+
/**
|
|
68
|
+
* Schema for an identifier of one durable run.
|
|
69
|
+
*
|
|
70
|
+
* Between 1 and 1,024 UTF-16 code units, free of unpaired UTF-16 surrogates
|
|
71
|
+
* because the store cannot tell two ill-formed identifiers apart, and free of
|
|
72
|
+
* NUL because the store's own length check cannot see past one.
|
|
73
|
+
*
|
|
74
|
+
* @category schemas
|
|
75
|
+
* @since 0.1.0
|
|
76
|
+
*/
|
|
77
|
+
export const RunId = identifier.pipe(Schema.brand("@smthrs/journal/JournalEvent/RunId"));
|
|
78
|
+
/**
|
|
79
|
+
* Schema for the canonical, durable sequence number within a run.
|
|
80
|
+
*
|
|
81
|
+
* `Number.MAX_SAFE_INTEGER` is excluded because the journal must always be
|
|
82
|
+
* able to allocate the next sequence. `Number.MAX_SAFE_INTEGER + 1` is not a
|
|
83
|
+
* distinct integer, so the maximum is not an allocatable sequence.
|
|
84
|
+
*
|
|
85
|
+
* @category schemas
|
|
86
|
+
* @since 0.1.0
|
|
87
|
+
*/
|
|
88
|
+
export const Seq = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThan(Number.MAX_SAFE_INTEGER)).pipe(Schema.brand("@smthrs/journal/JournalEvent/Seq"));
|
|
89
|
+
/**
|
|
90
|
+
* Schema for an event producer identifier.
|
|
91
|
+
*
|
|
92
|
+
* The same shape `RunId` accepts, on the same terms: the pair identifies a
|
|
93
|
+
* producer's retries in the database.
|
|
94
|
+
*
|
|
95
|
+
* @category schemas
|
|
96
|
+
* @since 0.1.0
|
|
97
|
+
*/
|
|
98
|
+
export const SourceId = identifier.pipe(Schema.brand("@smthrs/journal/JournalEvent/SourceId"));
|
|
99
|
+
/**
|
|
100
|
+
* Schema for a producer-local event sequence number.
|
|
101
|
+
*
|
|
102
|
+
* `Number.MAX_SAFE_INTEGER` is excluded because the journal must always be
|
|
103
|
+
* able to allocate the next sequence. `Number.MAX_SAFE_INTEGER + 1` is not a
|
|
104
|
+
* distinct integer, so the maximum is not an allocatable sequence.
|
|
105
|
+
*
|
|
106
|
+
* @category schemas
|
|
107
|
+
* @since 0.1.0
|
|
108
|
+
*/
|
|
109
|
+
export const SourceSeq = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThan(Number.MAX_SAFE_INTEGER)).pipe(Schema.brand("@smthrs/journal/JournalEvent/SourceSeq"));
|
|
110
|
+
/**
|
|
111
|
+
* Schema for what a re-emitted `(runId, sourceId, sourceSeq)` identity means.
|
|
112
|
+
*
|
|
113
|
+
* `content` is the default and the strict reading: the identity names one set
|
|
114
|
+
* of bytes, so a producer that re-emits it with different bytes has a bug and
|
|
115
|
+
* the journal says so with `idempotency_conflict`.
|
|
116
|
+
*
|
|
117
|
+
* `identity` is for a producer that derives the sequence from the event
|
|
118
|
+
* itself. There a collision IS the same event observed twice, and the bytes
|
|
119
|
+
* that differ between the two observations are metadata ABOUT the observation
|
|
120
|
+
* rather than the event: when a replayed frame was re-recorded, how long a
|
|
121
|
+
* step took the second time a durable engine served it from its record. The
|
|
122
|
+
* first admitted row stands and the re-emission settles as `Duplicate`. Only
|
|
123
|
+
* declare it with a sequence derived from the event's own content, because
|
|
124
|
+
* the journal then has nothing else with which to notice two different events
|
|
125
|
+
* wearing one identity.
|
|
126
|
+
*
|
|
127
|
+
* @category schemas
|
|
128
|
+
* @since 0.1.0
|
|
129
|
+
*/
|
|
130
|
+
export const Dedupe = Schema.Literals(["content", "identity"]);
|
|
131
|
+
/**
|
|
132
|
+
* Schema for an event submitted to the journal.
|
|
133
|
+
*
|
|
134
|
+
* Event types and values intentionally remain an open envelope. The durable
|
|
135
|
+
* core never closes this into an interpreter-specific union.
|
|
136
|
+
*
|
|
137
|
+
* @category schemas
|
|
138
|
+
* @since 0.1.0
|
|
139
|
+
*/
|
|
140
|
+
export class Input extends Schema.Class("@smthrs/journal/JournalEvent/Input")({
|
|
141
|
+
runId: RunId,
|
|
142
|
+
sourceId: SourceId,
|
|
143
|
+
sourceSeq: Schema.optional(SourceSeq),
|
|
144
|
+
/** How a collision on this event's identity is settled. Defaults to `content`. */
|
|
145
|
+
dedupe: Schema.optional(Dedupe),
|
|
146
|
+
/** Non-empty, NUL-free, well-formed UTF-16 of at most 1,024 code units. */
|
|
147
|
+
eventType: identifier,
|
|
148
|
+
payload: Schema.Unknown,
|
|
149
|
+
meta: Schema.optional(Schema.Unknown)
|
|
150
|
+
}) {
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Schema for a committed durable journal row.
|
|
154
|
+
*
|
|
155
|
+
* `seq` is allocated synchronously at journal admission and is the only
|
|
156
|
+
* sequence used for replay and durable provenance. `sourceSeq` identifies
|
|
157
|
+
* retries from one producer.
|
|
158
|
+
*
|
|
159
|
+
* @category schemas
|
|
160
|
+
* @since 0.1.0
|
|
161
|
+
*/
|
|
162
|
+
export class Entry extends Schema.Class("@smthrs/journal/JournalEvent/Entry")({
|
|
163
|
+
runId: RunId,
|
|
164
|
+
seq: Seq,
|
|
165
|
+
/**
|
|
166
|
+
* Derived by {@link makeEventId} from the other three identity members, never
|
|
167
|
+
* supplied by a caller, so it carries no check of its own.
|
|
168
|
+
*/
|
|
169
|
+
eventId: Schema.String,
|
|
170
|
+
sourceId: SourceId,
|
|
171
|
+
sourceSeq: SourceSeq,
|
|
172
|
+
emittedAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
173
|
+
/**
|
|
174
|
+
* The same bounded identifier `Input.eventType` accepts. It used to be a bare
|
|
175
|
+
* `Schema.String` here, so a consumer decoding an `Entry` accepted an empty,
|
|
176
|
+
* over-long, NUL-bearing, or ill-formed-UTF-16 event type that no emit could
|
|
177
|
+
* ever have produced.
|
|
178
|
+
*/
|
|
179
|
+
eventType: identifier,
|
|
180
|
+
payload: Schema.Unknown,
|
|
181
|
+
meta: Schema.Unknown
|
|
182
|
+
}) {
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Makes a collision-free deterministic event identifier from the idempotency
|
|
186
|
+
* key `(runId, sourceId, sourceSeq)`.
|
|
187
|
+
*
|
|
188
|
+
* Length prefixes preserve tuple boundaries even when identifiers contain the
|
|
189
|
+
* separator. The value is deliberately not random: retrying the same source
|
|
190
|
+
* event must produce the same durable id.
|
|
191
|
+
*
|
|
192
|
+
* @category constructors
|
|
193
|
+
* @since 0.1.0
|
|
194
|
+
*/
|
|
195
|
+
export const makeEventId = (runId, sourceId, sourceSeq) => `flows:event:${runId.length}:${runId}${sourceId.length}:${sourceId}${sourceSeq}`;
|
|
196
|
+
/**
|
|
197
|
+
* The reserved prefix of every companion stream id.
|
|
198
|
+
*
|
|
199
|
+
* A run id that begins with it names a companion stream, never a run: stores
|
|
200
|
+
* that admit run ids (`@smthrs/run-store`'s `RunStore.create`) refuse it.
|
|
201
|
+
*
|
|
202
|
+
* @category constants
|
|
203
|
+
* @since 1.0.0
|
|
204
|
+
*/
|
|
205
|
+
export const companionPrefix = "flows.companion/";
|
|
206
|
+
/** SHA-256 round constants: the first 32 bits of the cube roots of the first 64 primes. */
|
|
207
|
+
const sha256Rounds = Uint32Array.from([
|
|
208
|
+
0x428a2f98,
|
|
209
|
+
0x71374491,
|
|
210
|
+
0xb5c0fbcf,
|
|
211
|
+
0xe9b5dba5,
|
|
212
|
+
0x3956c25b,
|
|
213
|
+
0x59f111f1,
|
|
214
|
+
0x923f82a4,
|
|
215
|
+
0xab1c5ed5,
|
|
216
|
+
0xd807aa98,
|
|
217
|
+
0x12835b01,
|
|
218
|
+
0x243185be,
|
|
219
|
+
0x550c7dc3,
|
|
220
|
+
0x72be5d74,
|
|
221
|
+
0x80deb1fe,
|
|
222
|
+
0x9bdc06a7,
|
|
223
|
+
0xc19bf174,
|
|
224
|
+
0xe49b69c1,
|
|
225
|
+
0xefbe4786,
|
|
226
|
+
0x0fc19dc6,
|
|
227
|
+
0x240ca1cc,
|
|
228
|
+
0x2de92c6f,
|
|
229
|
+
0x4a7484aa,
|
|
230
|
+
0x5cb0a9dc,
|
|
231
|
+
0x76f988da,
|
|
232
|
+
0x983e5152,
|
|
233
|
+
0xa831c66d,
|
|
234
|
+
0xb00327c8,
|
|
235
|
+
0xbf597fc7,
|
|
236
|
+
0xc6e00bf3,
|
|
237
|
+
0xd5a79147,
|
|
238
|
+
0x06ca6351,
|
|
239
|
+
0x14292967,
|
|
240
|
+
0x27b70a85,
|
|
241
|
+
0x2e1b2138,
|
|
242
|
+
0x4d2c6dfc,
|
|
243
|
+
0x53380d13,
|
|
244
|
+
0x650a7354,
|
|
245
|
+
0x766a0abb,
|
|
246
|
+
0x81c2c92e,
|
|
247
|
+
0x92722c85,
|
|
248
|
+
0xa2bfe8a1,
|
|
249
|
+
0xa81a664b,
|
|
250
|
+
0xc24b8b70,
|
|
251
|
+
0xc76c51a3,
|
|
252
|
+
0xd192e819,
|
|
253
|
+
0xd6990624,
|
|
254
|
+
0xf40e3585,
|
|
255
|
+
0x106aa070,
|
|
256
|
+
0x19a4c116,
|
|
257
|
+
0x1e376c08,
|
|
258
|
+
0x2748774c,
|
|
259
|
+
0x34b0bcb5,
|
|
260
|
+
0x391c0cb3,
|
|
261
|
+
0x4ed8aa4a,
|
|
262
|
+
0x5b9cca4f,
|
|
263
|
+
0x682e6ff3,
|
|
264
|
+
0x748f82ee,
|
|
265
|
+
0x78a5636f,
|
|
266
|
+
0x84c87814,
|
|
267
|
+
0x8cc70208,
|
|
268
|
+
0x90befffa,
|
|
269
|
+
0xa4506ceb,
|
|
270
|
+
0xbef9a3f7,
|
|
271
|
+
0xc67178f2
|
|
272
|
+
]);
|
|
273
|
+
const rotate = (value, bits) => (value >>> bits) | (value << (32 - bits));
|
|
274
|
+
/**
|
|
275
|
+
* SHA-256 of the UTF-8 encoding of `text`, as lowercase hex.
|
|
276
|
+
*
|
|
277
|
+
* Synchronous on purpose: companion ids are derived inside write
|
|
278
|
+
* transactions, and an asynchronous digest would yield the event loop while
|
|
279
|
+
* the writer is held.
|
|
280
|
+
*/
|
|
281
|
+
const sha256Hex = (text) => {
|
|
282
|
+
const bytes = new TextEncoder().encode(text);
|
|
283
|
+
const length = ((bytes.length + 9 + 63) >> 6) << 6;
|
|
284
|
+
const padded = new Uint8Array(length);
|
|
285
|
+
padded.set(bytes);
|
|
286
|
+
padded[bytes.length] = 0x80;
|
|
287
|
+
const view = new DataView(padded.buffer);
|
|
288
|
+
view.setUint32(length - 8, Math.floor(bytes.length / 0x20000000));
|
|
289
|
+
view.setUint32(length - 4, (bytes.length << 3) >>> 0);
|
|
290
|
+
const hash = Uint32Array.from([
|
|
291
|
+
0x6a09e667,
|
|
292
|
+
0xbb67ae85,
|
|
293
|
+
0x3c6ef372,
|
|
294
|
+
0xa54ff53a,
|
|
295
|
+
0x510e527f,
|
|
296
|
+
0x9b05688c,
|
|
297
|
+
0x1f83d9ab,
|
|
298
|
+
0x5be0cd19
|
|
299
|
+
]);
|
|
300
|
+
const words = new Uint32Array(64);
|
|
301
|
+
for (let block = 0; block < length; block += 64) {
|
|
302
|
+
for (let index = 0; index < 16; index++)
|
|
303
|
+
words[index] = view.getUint32(block + index * 4);
|
|
304
|
+
for (let index = 16; index < 64; index++) {
|
|
305
|
+
const low = words[index - 15];
|
|
306
|
+
const high = words[index - 2];
|
|
307
|
+
const s0 = rotate(low, 7) ^ rotate(low, 18) ^ (low >>> 3);
|
|
308
|
+
const s1 = rotate(high, 17) ^ rotate(high, 19) ^ (high >>> 10);
|
|
309
|
+
words[index] = (words[index - 16] + s0 + words[index - 7] + s1) >>> 0;
|
|
310
|
+
}
|
|
311
|
+
let a = hash[0];
|
|
312
|
+
let b = hash[1];
|
|
313
|
+
let c = hash[2];
|
|
314
|
+
let d = hash[3];
|
|
315
|
+
let e = hash[4];
|
|
316
|
+
let f = hash[5];
|
|
317
|
+
let g = hash[6];
|
|
318
|
+
let h = hash[7];
|
|
319
|
+
for (let index = 0; index < 64; index++) {
|
|
320
|
+
const choose = (e & f) ^ (~e & g);
|
|
321
|
+
const t1 = (h + (rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25)) + choose + sha256Rounds[index] + words[index]) >>> 0;
|
|
322
|
+
const t2 = ((rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
|
|
323
|
+
h = g;
|
|
324
|
+
g = f;
|
|
325
|
+
f = e;
|
|
326
|
+
e = (d + t1) >>> 0;
|
|
327
|
+
d = c;
|
|
328
|
+
c = b;
|
|
329
|
+
b = a;
|
|
330
|
+
a = (t1 + t2) >>> 0;
|
|
331
|
+
}
|
|
332
|
+
hash[0] = (hash[0] + a) >>> 0;
|
|
333
|
+
hash[1] = (hash[1] + b) >>> 0;
|
|
334
|
+
hash[2] = (hash[2] + c) >>> 0;
|
|
335
|
+
hash[3] = (hash[3] + d) >>> 0;
|
|
336
|
+
hash[4] = (hash[4] + e) >>> 0;
|
|
337
|
+
hash[5] = (hash[5] + f) >>> 0;
|
|
338
|
+
hash[6] = (hash[6] + g) >>> 0;
|
|
339
|
+
hash[7] = (hash[7] + h) >>> 0;
|
|
340
|
+
}
|
|
341
|
+
return Array.from(hash, (word) => word.toString(16).padStart(8, "0")).join("");
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* The digest a journal fact uses to reference bytes kept outside the journal:
|
|
345
|
+
* the SHA-256 of the UTF-8 encoding of `text`, as 64 lowercase hex digits.
|
|
346
|
+
*
|
|
347
|
+
* The journal redacts every payload, so a fact never carries executable
|
|
348
|
+
* values. A store that folds state from its facts keeps the bytes in its own
|
|
349
|
+
* rows and records only their digest, which a rebuild checks the row against.
|
|
350
|
+
*
|
|
351
|
+
* @category constructors
|
|
352
|
+
* @since 1.0.0
|
|
353
|
+
*/
|
|
354
|
+
export const contentDigest = (text) => sha256Hex(text);
|
|
355
|
+
/**
|
|
356
|
+
* The id of the companion stream that carries one store's facts about a run.
|
|
357
|
+
*
|
|
358
|
+
* A companion stream is an ordinary journal stream with its own sequence
|
|
359
|
+
* clock, kept beside the run's own stream rather than inside it. A consumer
|
|
360
|
+
* that reads the run's stream by position never sees a companion fact, and a
|
|
361
|
+
* rewind or compaction of the run's stream never truncates one. The id is
|
|
362
|
+
* `flows.companion/<stream>/<sha-256 of the run id>`, so it stays within
|
|
363
|
+
* {@link maxIdentifierLength} for every run id; each fact carries its run id
|
|
364
|
+
* in its payload.
|
|
365
|
+
*
|
|
366
|
+
* @category constructors
|
|
367
|
+
* @since 1.0.0
|
|
368
|
+
*/
|
|
369
|
+
export const companionRunId = (stream, runId) => `${companionPrefix}${stream}/${contentDigest(runId)}`;
|
|
370
|
+
/**
|
|
371
|
+
* Whether a run id names a companion stream.
|
|
372
|
+
*
|
|
373
|
+
* @category predicates
|
|
374
|
+
* @since 1.0.0
|
|
375
|
+
*/
|
|
376
|
+
export const isCompanionRunId = (runId) => runId.startsWith(companionPrefix);
|
|
377
|
+
/**
|
|
378
|
+
* Bounded durable text shared by namespace contracts.
|
|
379
|
+
*
|
|
380
|
+
* @category schemas
|
|
381
|
+
* @since 1.0.0
|
|
382
|
+
*/
|
|
383
|
+
export const Identifier = identifier;
|
|
384
|
+
/**
|
|
385
|
+
* A lineage coordinate, distinct from the run that currently hosts it.
|
|
386
|
+
*
|
|
387
|
+
* @category schemas
|
|
388
|
+
* @since 1.0.0
|
|
389
|
+
*/
|
|
390
|
+
export const LineageId = identifier.pipe(Schema.brand("@smthrs/journal/LineageId"));
|
|
391
|
+
/**
|
|
392
|
+
* A concrete wait identity.
|
|
393
|
+
*
|
|
394
|
+
* @category schemas
|
|
395
|
+
* @since 1.0.0
|
|
396
|
+
*/
|
|
397
|
+
export const WaitId = identifier.pipe(Schema.brand("@smthrs/journal/WaitId"));
|
|
398
|
+
/**
|
|
399
|
+
* An admitted command identity.
|
|
400
|
+
*
|
|
401
|
+
* @category schemas
|
|
402
|
+
* @since 1.0.0
|
|
403
|
+
*/
|
|
404
|
+
export const CommandId = identifier.pipe(Schema.brand("@smthrs/journal/CommandId"));
|
|
405
|
+
/**
|
|
406
|
+
* A plan identity, never interchangeable with a dispatch or artifact.
|
|
407
|
+
*
|
|
408
|
+
* @category schemas
|
|
409
|
+
* @since 1.0.0
|
|
410
|
+
*/
|
|
411
|
+
export const PlanId = identifier.pipe(Schema.brand("@smthrs/journal/PlanId"));
|
|
412
|
+
/**
|
|
413
|
+
* A dispatch identity. Branding does not verify its digest.
|
|
414
|
+
*
|
|
415
|
+
* @category schemas
|
|
416
|
+
* @since 1.0.0
|
|
417
|
+
*/
|
|
418
|
+
export const DispatchId = identifier.pipe(Schema.brand("@smthrs/journal/DispatchId"));
|
|
419
|
+
/**
|
|
420
|
+
* An artifact identity. Branding does not verify content.
|
|
421
|
+
*
|
|
422
|
+
* @category schemas
|
|
423
|
+
* @since 1.0.0
|
|
424
|
+
*/
|
|
425
|
+
export const ArtifactId = identifier.pipe(Schema.brand("@smthrs/journal/ArtifactId"));
|
|
426
|
+
/**
|
|
427
|
+
* Integer quantities in the inclusive safe range, including zero.
|
|
428
|
+
*
|
|
429
|
+
* @category schemas
|
|
430
|
+
* @since 1.0.0
|
|
431
|
+
*/
|
|
432
|
+
export const NonNegativeQuantity = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
|
|
433
|
+
/**
|
|
434
|
+
* Integer quantities in the inclusive safe range, excluding zero.
|
|
435
|
+
*
|
|
436
|
+
* @category schemas
|
|
437
|
+
* @since 1.0.0
|
|
438
|
+
*/
|
|
439
|
+
export const PositiveQuantity = NonNegativeQuantity.check(Schema.isGreaterThan(0));
|
|
440
|
+
/**
|
|
441
|
+
* Non-negative integral milliseconds since the Unix epoch.
|
|
442
|
+
*
|
|
443
|
+
* @category schemas
|
|
444
|
+
* @since 1.0.0
|
|
445
|
+
*/
|
|
446
|
+
export const TimestampMs = NonNegativeQuantity.pipe(Schema.brand("@smthrs/journal/TimestampMs"));
|
|
447
|
+
/**
|
|
448
|
+
* Decode a run id without a cast; schema failures retain their issue.
|
|
449
|
+
*
|
|
450
|
+
* @category decoders
|
|
451
|
+
* @since 1.0.0
|
|
452
|
+
*/
|
|
453
|
+
export const decodeRunId = Schema.decodeUnknownEffect(RunId);
|
|
454
|
+
/**
|
|
455
|
+
* Decode a lineage id without a cast.
|
|
456
|
+
*
|
|
457
|
+
* @category decoders
|
|
458
|
+
* @since 1.0.0
|
|
459
|
+
*/
|
|
460
|
+
export const decodeLineageId = Schema.decodeUnknownEffect(LineageId);
|
|
461
|
+
/**
|
|
462
|
+
* Decode a wait id without a cast.
|
|
463
|
+
*
|
|
464
|
+
* @category decoders
|
|
465
|
+
* @since 1.0.0
|
|
466
|
+
*/
|
|
467
|
+
export const decodeWaitId = Schema.decodeUnknownEffect(WaitId);
|
|
468
|
+
/**
|
|
469
|
+
* Decode a command id without a cast.
|
|
470
|
+
*
|
|
471
|
+
* @category decoders
|
|
472
|
+
* @since 1.0.0
|
|
473
|
+
*/
|
|
474
|
+
export const decodeCommandId = Schema.decodeUnknownEffect(CommandId);
|
|
475
|
+
/**
|
|
476
|
+
* Decode a plan id without a cast.
|
|
477
|
+
*
|
|
478
|
+
* @category decoders
|
|
479
|
+
* @since 1.0.0
|
|
480
|
+
*/
|
|
481
|
+
export const decodePlanId = Schema.decodeUnknownEffect(PlanId);
|
|
482
|
+
/**
|
|
483
|
+
* Decode a dispatch id without a cast.
|
|
484
|
+
*
|
|
485
|
+
* @category decoders
|
|
486
|
+
* @since 1.0.0
|
|
487
|
+
*/
|
|
488
|
+
export const decodeDispatchId = Schema.decodeUnknownEffect(DispatchId);
|
|
489
|
+
/**
|
|
490
|
+
* Decode an artifact id without a cast.
|
|
491
|
+
*
|
|
492
|
+
* @category decoders
|
|
493
|
+
* @since 1.0.0
|
|
494
|
+
*/
|
|
495
|
+
export const decodeArtifactId = Schema.decodeUnknownEffect(ArtifactId);
|
|
496
|
+
/**
|
|
497
|
+
* Decode an absolute timestamp without coercion.
|
|
498
|
+
*
|
|
499
|
+
* @category decoders
|
|
500
|
+
* @since 1.0.0
|
|
501
|
+
*/
|
|
502
|
+
export const decodeTimestampMs = Schema.decodeUnknownEffect(TimestampMs);
|
|
503
|
+
/**
|
|
504
|
+
* Decode a budget that permits zero. Units belong to the field.
|
|
505
|
+
*
|
|
506
|
+
* @category decoders
|
|
507
|
+
* @since 1.0.0
|
|
508
|
+
*/
|
|
509
|
+
export const decodeNonNegativeQuantity = Schema.decodeUnknownEffect(NonNegativeQuantity);
|
|
510
|
+
/**
|
|
511
|
+
* Decode a budget that requires positive capacity.
|
|
512
|
+
*
|
|
513
|
+
* @category decoders
|
|
514
|
+
* @since 1.0.0
|
|
515
|
+
*/
|
|
516
|
+
export const decodePositiveQuantity = Schema.decodeUnknownEffect(PositiveQuantity);
|
|
517
|
+
//# sourceMappingURL=JournalEvent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JournalEvent.js","sourceRoot":"","sources":["../../src/JournalEvent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable event-envelope schemas for the journal.\n *\n * @since 0.1.0\n */\n\nimport * as Schema from \"effect/Schema\"\n\n/**\n * A UTF-16 surrogate with no partner: a high surrogate not followed by a low\n * one, or a low surrogate not preceded by a high one. `String.isWellFormed`\n * answers the same question but needs the ES2024 lib, which this package does\n * not target.\n */\nconst loneSurrogate = /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/\n\n/**\n * A NUL anywhere in an identifier.\n *\n * SQLite's `length()` counts the characters BEFORE the first NUL, and every\n * identifier column carries `CHECK (length(...) > 0)`, so a leading-NUL run id\n * measures zero and the write fails the constraint. Measured on this tree, an\n * emit with a leading-NUL run id came back as `sink_failed` with a\n * `DatabaseError` cause, which tells the caller the database is down when the\n * real fault is the identifier it just supplied. A NUL after the first\n * character is refused on the same terms: it makes the column's own length\n * check disagree with the identifier's length.\n *\n * Built from a code point rather than written as a pattern literal: a control\n * character in a regex is exactly the typo `no-control-regex` exists to catch,\n * and no reader can see one in the source.\n */\nconst embeddedNul = new RegExp(String.fromCharCode(0))\n\n/**\n * Longest identifier the journal persists.\n *\n * A run id, a source id and an event type are index columns of a permanent\n * table and are held in per-run maps for the layer's lifetime, so an\n * unbounded one costs durable index space and heap that nothing ever\n * reclaims. Every identifier this repository mints is a uuid or a short\n * dotted name, two orders of magnitude below this bound, so the ceiling\n * refuses only the shapes that were never identifiers.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const maxIdentifierLength = 1024\n\n/**\n * A persistable identifier: non-empty, and representable in the store.\n *\n * SQLite binds a lone UTF-16 surrogate as U+FFFD, so two ill-formed\n * identifiers that differ only in their surrogates land on ONE persisted key.\n * The second run's first event then dedupes into the first run's row and reads\n * by either id return the same history, which destroys run isolation at the\n * persistence boundary. Rejecting ill-formed text at the schema keeps the\n * identifier the caller decoded and the identifier the database stores the\n * same value.\n *\n * A NUL is rejected for the reason {@link embeddedNul} gives: the column's own\n * length check cannot see past one, so the store refuses the identifier as a\n * constraint violation and the caller is told the sink failed.\n *\n * The empty string is rejected for a plainer reason: it names nothing, and the\n * journal used to accept it at decode and then reject it at the service, so a\n * caller could hold a \"valid\" identifier the next call refused.\n * The length is bounded because every identifier occupies permanent index\n * space and layer-lifetime heap.\n */\nconst identifier = Schema.String.check(\n Schema.isMinLength(1),\n Schema.isMaxLength(maxIdentifierLength),\n Schema.makeFilter((value: string) => !loneSurrogate.test(value), { title: \"wellFormedIdentifier\" }),\n Schema.makeFilter((value: string) => !embeddedNul.test(value), { title: \"nulFreeIdentifier\" })\n)\n\n/**\n * Schema for an identifier of one durable run.\n *\n * Between 1 and 1,024 UTF-16 code units, free of unpaired UTF-16 surrogates\n * because the store cannot tell two ill-formed identifiers apart, and free of\n * NUL because the store's own length check cannot see past one.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const RunId = identifier.pipe(Schema.brand(\"@smthrs/journal/JournalEvent/RunId\"))\n\n/**\n * Branded identifier of one durable run.\n *\n * @category models\n * @since 0.1.0\n */\nexport type RunId = typeof RunId.Type\n\n/**\n * Schema for the canonical, durable sequence number within a run.\n *\n * `Number.MAX_SAFE_INTEGER` is excluded because the journal must always be\n * able to allocate the next sequence. `Number.MAX_SAFE_INTEGER + 1` is not a\n * distinct integer, so the maximum is not an allocatable sequence.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Seq = Schema.Int.check(\n Schema.isGreaterThanOrEqualTo(0),\n Schema.isLessThan(Number.MAX_SAFE_INTEGER)\n).pipe(\n Schema.brand(\"@smthrs/journal/JournalEvent/Seq\")\n)\n\n/**\n * Canonical durable per-run sequence number.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Seq = typeof Seq.Type\n\n/**\n * Schema for an event producer identifier.\n *\n * The same shape `RunId` accepts, on the same terms: the pair identifies a\n * producer's retries in the database.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const SourceId = identifier.pipe(Schema.brand(\"@smthrs/journal/JournalEvent/SourceId\"))\n\n/**\n * Identifier of an event producer.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SourceId = typeof SourceId.Type\n\n/**\n * Schema for a producer-local event sequence number.\n *\n * `Number.MAX_SAFE_INTEGER` is excluded because the journal must always be\n * able to allocate the next sequence. `Number.MAX_SAFE_INTEGER + 1` is not a\n * distinct integer, so the maximum is not an allocatable sequence.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const SourceSeq = Schema.Int.check(\n Schema.isGreaterThanOrEqualTo(0),\n Schema.isLessThan(Number.MAX_SAFE_INTEGER)\n).pipe(\n Schema.brand(\"@smthrs/journal/JournalEvent/SourceSeq\")\n)\n\n/**\n * Producer-local event sequence number.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SourceSeq = typeof SourceSeq.Type\n\n/**\n * Schema for what a re-emitted `(runId, sourceId, sourceSeq)` identity means.\n *\n * `content` is the default and the strict reading: the identity names one set\n * of bytes, so a producer that re-emits it with different bytes has a bug and\n * the journal says so with `idempotency_conflict`.\n *\n * `identity` is for a producer that derives the sequence from the event\n * itself. There a collision IS the same event observed twice, and the bytes\n * that differ between the two observations are metadata ABOUT the observation\n * rather than the event: when a replayed frame was re-recorded, how long a\n * step took the second time a durable engine served it from its record. The\n * first admitted row stands and the re-emission settles as `Duplicate`. Only\n * declare it with a sequence derived from the event's own content, because\n * the journal then has nothing else with which to notice two different events\n * wearing one identity.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Dedupe = Schema.Literals([\"content\", \"identity\"])\n\n/**\n * What a re-emitted producer identity means.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Dedupe = typeof Dedupe.Type\n\n/**\n * Schema for an event submitted to the journal.\n *\n * Event types and values intentionally remain an open envelope. The durable\n * core never closes this into an interpreter-specific union.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class Input extends Schema.Class<Input>(\"@smthrs/journal/JournalEvent/Input\")({\n runId: RunId,\n sourceId: SourceId,\n sourceSeq: Schema.optional(SourceSeq),\n /** How a collision on this event's identity is settled. Defaults to `content`. */\n dedupe: Schema.optional(Dedupe),\n /** Non-empty, NUL-free, well-formed UTF-16 of at most 1,024 code units. */\n eventType: identifier,\n payload: Schema.Unknown,\n meta: Schema.optional(Schema.Unknown)\n}) {}\n\n/**\n * Schema for a committed durable journal row.\n *\n * `seq` is allocated synchronously at journal admission and is the only\n * sequence used for replay and durable provenance. `sourceSeq` identifies\n * retries from one producer.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class Entry extends Schema.Class<Entry>(\"@smthrs/journal/JournalEvent/Entry\")({\n runId: RunId,\n seq: Seq,\n /**\n * Derived by {@link makeEventId} from the other three identity members, never\n * supplied by a caller, so it carries no check of its own.\n */\n eventId: Schema.String,\n sourceId: SourceId,\n sourceSeq: SourceSeq,\n emittedAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /**\n * The same bounded identifier `Input.eventType` accepts. It used to be a bare\n * `Schema.String` here, so a consumer decoding an `Entry` accepted an empty,\n * over-long, NUL-bearing, or ill-formed-UTF-16 event type that no emit could\n * ever have produced.\n */\n eventType: identifier,\n payload: Schema.Unknown,\n meta: Schema.Unknown\n}) {}\n\n/**\n * Makes a collision-free deterministic event identifier from the idempotency\n * key `(runId, sourceId, sourceSeq)`.\n *\n * Length prefixes preserve tuple boundaries even when identifiers contain the\n * separator. The value is deliberately not random: retrying the same source\n * event must produce the same durable id.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeEventId = (runId: RunId, sourceId: SourceId, sourceSeq: SourceSeq): string =>\n `flows:event:${runId.length}:${runId}${sourceId.length}:${sourceId}${sourceSeq}`\n\n/**\n * The reserved prefix of every companion stream id.\n *\n * A run id that begins with it names a companion stream, never a run: stores\n * that admit run ids (`@smthrs/run-store`'s `RunStore.create`) refuse it.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const companionPrefix = \"flows.companion/\"\n\n/** SHA-256 round constants: the first 32 bits of the cube roots of the first 64 primes. */\nconst sha256Rounds = Uint32Array.from([\n 0x428a2f98,\n 0x71374491,\n 0xb5c0fbcf,\n 0xe9b5dba5,\n 0x3956c25b,\n 0x59f111f1,\n 0x923f82a4,\n 0xab1c5ed5,\n 0xd807aa98,\n 0x12835b01,\n 0x243185be,\n 0x550c7dc3,\n 0x72be5d74,\n 0x80deb1fe,\n 0x9bdc06a7,\n 0xc19bf174,\n 0xe49b69c1,\n 0xefbe4786,\n 0x0fc19dc6,\n 0x240ca1cc,\n 0x2de92c6f,\n 0x4a7484aa,\n 0x5cb0a9dc,\n 0x76f988da,\n 0x983e5152,\n 0xa831c66d,\n 0xb00327c8,\n 0xbf597fc7,\n 0xc6e00bf3,\n 0xd5a79147,\n 0x06ca6351,\n 0x14292967,\n 0x27b70a85,\n 0x2e1b2138,\n 0x4d2c6dfc,\n 0x53380d13,\n 0x650a7354,\n 0x766a0abb,\n 0x81c2c92e,\n 0x92722c85,\n 0xa2bfe8a1,\n 0xa81a664b,\n 0xc24b8b70,\n 0xc76c51a3,\n 0xd192e819,\n 0xd6990624,\n 0xf40e3585,\n 0x106aa070,\n 0x19a4c116,\n 0x1e376c08,\n 0x2748774c,\n 0x34b0bcb5,\n 0x391c0cb3,\n 0x4ed8aa4a,\n 0x5b9cca4f,\n 0x682e6ff3,\n 0x748f82ee,\n 0x78a5636f,\n 0x84c87814,\n 0x8cc70208,\n 0x90befffa,\n 0xa4506ceb,\n 0xbef9a3f7,\n 0xc67178f2\n])\n\nconst rotate = (value: number, bits: number): number => (value >>> bits) | (value << (32 - bits))\n\n/**\n * SHA-256 of the UTF-8 encoding of `text`, as lowercase hex.\n *\n * Synchronous on purpose: companion ids are derived inside write\n * transactions, and an asynchronous digest would yield the event loop while\n * the writer is held.\n */\nconst sha256Hex = (text: string): string => {\n const bytes = new TextEncoder().encode(text)\n const length = ((bytes.length + 9 + 63) >> 6) << 6\n const padded = new Uint8Array(length)\n padded.set(bytes)\n padded[bytes.length] = 0x80\n const view = new DataView(padded.buffer)\n view.setUint32(length - 8, Math.floor(bytes.length / 0x20000000))\n view.setUint32(length - 4, (bytes.length << 3) >>> 0)\n const hash = Uint32Array.from([\n 0x6a09e667,\n 0xbb67ae85,\n 0x3c6ef372,\n 0xa54ff53a,\n 0x510e527f,\n 0x9b05688c,\n 0x1f83d9ab,\n 0x5be0cd19\n ])\n const words = new Uint32Array(64)\n for (let block = 0; block < length; block += 64) {\n for (let index = 0; index < 16; index++) words[index] = view.getUint32(block + index * 4)\n for (let index = 16; index < 64; index++) {\n const low = words[index - 15]!\n const high = words[index - 2]!\n const s0 = rotate(low, 7) ^ rotate(low, 18) ^ (low >>> 3)\n const s1 = rotate(high, 17) ^ rotate(high, 19) ^ (high >>> 10)\n words[index] = (words[index - 16]! + s0 + words[index - 7]! + s1) >>> 0\n }\n let a = hash[0]!\n let b = hash[1]!\n let c = hash[2]!\n let d = hash[3]!\n let e = hash[4]!\n let f = hash[5]!\n let g = hash[6]!\n let h = hash[7]!\n for (let index = 0; index < 64; index++) {\n const choose = (e & f) ^ (~e & g)\n const t1 =\n (h + (rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25)) + choose + sha256Rounds[index]! + words[index]!) >>> 0\n const t2 = ((rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0\n h = g\n g = f\n f = e\n e = (d + t1) >>> 0\n d = c\n c = b\n b = a\n a = (t1 + t2) >>> 0\n }\n hash[0] = (hash[0]! + a) >>> 0\n hash[1] = (hash[1]! + b) >>> 0\n hash[2] = (hash[2]! + c) >>> 0\n hash[3] = (hash[3]! + d) >>> 0\n hash[4] = (hash[4]! + e) >>> 0\n hash[5] = (hash[5]! + f) >>> 0\n hash[6] = (hash[6]! + g) >>> 0\n hash[7] = (hash[7]! + h) >>> 0\n }\n return Array.from(hash, (word) => word.toString(16).padStart(8, \"0\")).join(\"\")\n}\n\n/**\n * The digest a journal fact uses to reference bytes kept outside the journal:\n * the SHA-256 of the UTF-8 encoding of `text`, as 64 lowercase hex digits.\n *\n * The journal redacts every payload, so a fact never carries executable\n * values. A store that folds state from its facts keeps the bytes in its own\n * rows and records only their digest, which a rebuild checks the row against.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const contentDigest = (text: string): string => sha256Hex(text)\n\n/**\n * The id of the companion stream that carries one store's facts about a run.\n *\n * A companion stream is an ordinary journal stream with its own sequence\n * clock, kept beside the run's own stream rather than inside it. A consumer\n * that reads the run's stream by position never sees a companion fact, and a\n * rewind or compaction of the run's stream never truncates one. The id is\n * `flows.companion/<stream>/<sha-256 of the run id>`, so it stays within\n * {@link maxIdentifierLength} for every run id; each fact carries its run id\n * in its payload.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const companionRunId = (stream: string, runId: string): RunId =>\n `${companionPrefix}${stream}/${contentDigest(runId)}` as RunId\n\n/**\n * Whether a run id names a companion stream.\n *\n * @category predicates\n * @since 1.0.0\n */\nexport const isCompanionRunId = (runId: string): boolean => runId.startsWith(companionPrefix)\n\n/**\n * Bounded durable text shared by namespace contracts.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const Identifier = identifier\n\n/**\n * A lineage coordinate, distinct from the run that currently hosts it.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const LineageId = identifier.pipe(Schema.brand(\"@smthrs/journal/LineageId\"))\n/**\n * A lineage coordinate.\n *\n * @category models\n * @since 1.0.0\n */\nexport type LineageId = typeof LineageId.Type\n\n/**\n * A concrete wait identity.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const WaitId = identifier.pipe(Schema.brand(\"@smthrs/journal/WaitId\"))\n/**\n * A concrete wait identity.\n *\n * @category models\n * @since 1.0.0\n */\nexport type WaitId = typeof WaitId.Type\n\n/**\n * An admitted command identity.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const CommandId = identifier.pipe(Schema.brand(\"@smthrs/journal/CommandId\"))\n/**\n * An admitted command identity.\n *\n * @category models\n * @since 1.0.0\n */\nexport type CommandId = typeof CommandId.Type\n\n/**\n * A plan identity, never interchangeable with a dispatch or artifact.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const PlanId = identifier.pipe(Schema.brand(\"@smthrs/journal/PlanId\"))\n/**\n * A plan identity.\n *\n * @category models\n * @since 1.0.0\n */\nexport type PlanId = typeof PlanId.Type\n\n/**\n * A dispatch identity. Branding does not verify its digest.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const DispatchId = identifier.pipe(Schema.brand(\"@smthrs/journal/DispatchId\"))\n/**\n * A dispatch identity.\n *\n * @category models\n * @since 1.0.0\n */\nexport type DispatchId = typeof DispatchId.Type\n\n/**\n * An artifact identity. Branding does not verify content.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const ArtifactId = identifier.pipe(Schema.brand(\"@smthrs/journal/ArtifactId\"))\n/**\n * An artifact identity.\n *\n * @category models\n * @since 1.0.0\n */\nexport type ArtifactId = typeof ArtifactId.Type\n\n/**\n * Integer quantities in the inclusive safe range, including zero.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const NonNegativeQuantity = Schema.Int.check(\n Schema.isGreaterThanOrEqualTo(0),\n Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)\n)\n\n/**\n * Integer quantities in the inclusive safe range, excluding zero.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const PositiveQuantity = NonNegativeQuantity.check(Schema.isGreaterThan(0))\n\n/**\n * Non-negative integral milliseconds since the Unix epoch.\n *\n * @category schemas\n * @since 1.0.0\n */\nexport const TimestampMs = NonNegativeQuantity.pipe(Schema.brand(\"@smthrs/journal/TimestampMs\"))\n/**\n * Non-negative integral milliseconds since the Unix epoch.\n *\n * @category models\n * @since 1.0.0\n */\nexport type TimestampMs = typeof TimestampMs.Type\n\n/**\n * Decode a run id without a cast; schema failures retain their issue.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeRunId = Schema.decodeUnknownEffect(RunId)\n/**\n * Decode a lineage id without a cast.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeLineageId = Schema.decodeUnknownEffect(LineageId)\n/**\n * Decode a wait id without a cast.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeWaitId = Schema.decodeUnknownEffect(WaitId)\n/**\n * Decode a command id without a cast.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeCommandId = Schema.decodeUnknownEffect(CommandId)\n/**\n * Decode a plan id without a cast.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodePlanId = Schema.decodeUnknownEffect(PlanId)\n/**\n * Decode a dispatch id without a cast.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeDispatchId = Schema.decodeUnknownEffect(DispatchId)\n/**\n * Decode an artifact id without a cast.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeArtifactId = Schema.decodeUnknownEffect(ArtifactId)\n/**\n * Decode an absolute timestamp without coercion.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeTimestampMs = Schema.decodeUnknownEffect(TimestampMs)\n/**\n * Decode a budget that permits zero. Units belong to the field.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodeNonNegativeQuantity = Schema.decodeUnknownEffect(NonNegativeQuantity)\n/**\n * Decode a budget that requires positive capacity.\n *\n * @category decoders\n * @since 1.0.0\n */\nexport const decodePositiveQuantity = Schema.decodeUnknownEffect(PositiveQuantity)\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable generations distinguish histories that reuse journal sequences.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0-rc.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
8
|
+
/**
|
|
9
|
+
* Installs the generation table idempotently. Both the SQL journal and time
|
|
10
|
+
* travel initialize it, including databases whose migration ladder already
|
|
11
|
+
* passed the journal's reserved block. A read-only client installs nothing.
|
|
12
|
+
*
|
|
13
|
+
* @category migrations
|
|
14
|
+
* @since 1.0.0-rc.0
|
|
15
|
+
*/
|
|
16
|
+
export declare const initialize: Effect.Effect<void, import("effect/unstable/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
17
|
+
/**
|
|
18
|
+
* Registers a live journal's cache invalidation until its scope closes.
|
|
19
|
+
* Multiple journals over the same SQL client must observe the same rewind.
|
|
20
|
+
*
|
|
21
|
+
* @category lifecycle
|
|
22
|
+
* @since 1.0.0-rc.0
|
|
23
|
+
*/
|
|
24
|
+
export declare const onTruncate: (forget: (runIds: ReadonlyArray<string>) => void) => Effect.Effect<void, never, import("effect/Scope").Scope | SqlClient.SqlClient>;
|
|
25
|
+
/**
|
|
26
|
+
* Forgets cached identities and allocation floors after a committed truncation.
|
|
27
|
+
* The rewinder must quiesce producers and flush pending admissions first.
|
|
28
|
+
* Database rows remain authoritative; this operation changes no durable data.
|
|
29
|
+
*
|
|
30
|
+
* @category lifecycle
|
|
31
|
+
* @since 1.0.0-rc.0
|
|
32
|
+
*/
|
|
33
|
+
export declare const forget: (runIds: ReadonlyArray<string>) => Effect.Effect<void, never, SqlClient.SqlClient>;
|
|
34
|
+
//# sourceMappingURL=JournalGeneration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JournalGeneration.d.ts","sourceRoot":"","sources":["../../src/JournalGeneration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,2FAerB,CAAA;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,IAAI,mFAgBtE,CAAA;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,GAAI,QAAQ,aAAa,CAAC,MAAM,CAAC,oDAMhD,CAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable generations distinguish histories that reuse journal sequences.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0-rc.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Dialect from "@smthrs/database/Dialect";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
9
|
+
/**
|
|
10
|
+
* Installs the generation table idempotently. Both the SQL journal and time
|
|
11
|
+
* travel initialize it, including databases whose migration ladder already
|
|
12
|
+
* passed the journal's reserved block. A read-only client installs nothing.
|
|
13
|
+
*
|
|
14
|
+
* @category migrations
|
|
15
|
+
* @since 1.0.0-rc.0
|
|
16
|
+
*/
|
|
17
|
+
export const initialize = Effect.gen(function* () {
|
|
18
|
+
const sql = yield* SqlClient.SqlClient;
|
|
19
|
+
// A read-only observer reads the schema it finds and installs nothing.
|
|
20
|
+
if (Dialect.isReadOnly(sql))
|
|
21
|
+
return;
|
|
22
|
+
yield* sql `
|
|
23
|
+
CREATE TABLE IF NOT EXISTS flows_journal_generations (
|
|
24
|
+
run_id TEXT PRIMARY KEY NOT NULL CHECK (length(run_id) > 0),
|
|
25
|
+
generation ${Dialect.integer(sql)} NOT NULL CHECK (${Dialect.isInteger(sql, sql `generation`)} AND generation > 0 AND generation <= 9007199254740991),
|
|
26
|
+
after_seq ${Dialect.integer(sql)} NOT NULL CHECK (${Dialect.isInteger(sql, sql `after_seq`)} AND after_seq >= -1 AND after_seq <= 9007199254740991)
|
|
27
|
+
)
|
|
28
|
+
`;
|
|
29
|
+
});
|
|
30
|
+
const journals = new WeakMap();
|
|
31
|
+
/**
|
|
32
|
+
* Registers a live journal's cache invalidation until its scope closes.
|
|
33
|
+
* Multiple journals over the same SQL client must observe the same rewind.
|
|
34
|
+
*
|
|
35
|
+
* @category lifecycle
|
|
36
|
+
* @since 1.0.0-rc.0
|
|
37
|
+
*/
|
|
38
|
+
export const onTruncate = (forget) => Effect.gen(function* () {
|
|
39
|
+
const sql = yield* SqlClient.SqlClient;
|
|
40
|
+
let listeners = journals.get(sql);
|
|
41
|
+
if (listeners === undefined) {
|
|
42
|
+
listeners = new Set();
|
|
43
|
+
journals.set(sql, listeners);
|
|
44
|
+
}
|
|
45
|
+
const registered = listeners;
|
|
46
|
+
yield* Effect.acquireRelease(Effect.sync(() => registered.add(forget)), () => Effect.sync(() => {
|
|
47
|
+
registered.delete(forget);
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Forgets cached identities and allocation floors after a committed truncation.
|
|
52
|
+
* The rewinder must quiesce producers and flush pending admissions first.
|
|
53
|
+
* Database rows remain authoritative; this operation changes no durable data.
|
|
54
|
+
*
|
|
55
|
+
* @category lifecycle
|
|
56
|
+
* @since 1.0.0-rc.0
|
|
57
|
+
*/
|
|
58
|
+
export const forget = (runIds) => Effect.gen(function* () {
|
|
59
|
+
const sql = yield* SqlClient.SqlClient;
|
|
60
|
+
yield* Effect.sync(() => {
|
|
61
|
+
for (const invalidate of journals.get(sql) ?? [])
|
|
62
|
+
invalidate(runIds);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=JournalGeneration.js.map
|