@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,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var Consensus_exports = {};
|
|
30
|
+
__export(Consensus_exports, {
|
|
31
|
+
ActivateOutcome: () => ActivateOutcome,
|
|
32
|
+
Activated: () => Activated,
|
|
33
|
+
ClaimOutcome: () => ClaimOutcome,
|
|
34
|
+
Claimed: () => Claimed,
|
|
35
|
+
Consensus: () => Consensus,
|
|
36
|
+
ConsensusError: () => ConsensusError,
|
|
37
|
+
HeartbeatOutcome: () => HeartbeatOutcome,
|
|
38
|
+
LivenessEvidence: () => LivenessEvidence,
|
|
39
|
+
Lost: () => Lost,
|
|
40
|
+
RecoverOutcome: () => RecoverOutcome,
|
|
41
|
+
Recovered: () => Recovered,
|
|
42
|
+
Rejected: () => Rejected,
|
|
43
|
+
RejectionReason: () => RejectionReason,
|
|
44
|
+
Renewed: () => Renewed,
|
|
45
|
+
fenceLost: () => fenceLost,
|
|
46
|
+
heartbeatInterval: () => heartbeatInterval,
|
|
47
|
+
heartbeatSkewAllowance: () => heartbeatSkewAllowance,
|
|
48
|
+
heartbeatStaleAfter: () => heartbeatStaleAfter,
|
|
49
|
+
heartbeatWriteTolerance: () => heartbeatWriteTolerance,
|
|
50
|
+
layerLocal: () => layerLocal,
|
|
51
|
+
layerNoop: () => layerNoop,
|
|
52
|
+
make: () => make,
|
|
53
|
+
makeLocal: () => makeLocal,
|
|
54
|
+
makeNoop: () => makeNoop,
|
|
55
|
+
matchesEvidence: () => matchesEvidence,
|
|
56
|
+
sameOwner: () => sameOwner
|
|
57
|
+
});
|
|
58
|
+
module.exports = __toCommonJS(Consensus_exports);
|
|
59
|
+
var Context = __toESM(require("effect/Context"), 1);
|
|
60
|
+
var Duration = __toESM(require("effect/Duration"), 1);
|
|
61
|
+
var Effect = __toESM(require("effect/Effect"), 1);
|
|
62
|
+
var Layer = __toESM(require("effect/Layer"), 1);
|
|
63
|
+
var Schema = __toESM(require("effect/Schema"), 1);
|
|
64
|
+
var import_OwnerId = require("./OwnerId.js");
|
|
65
|
+
const heartbeatInterval = Duration.seconds(1);
|
|
66
|
+
const heartbeatStaleAfter = Duration.seconds(30);
|
|
67
|
+
const heartbeatSkewAllowance = Duration.seconds(10);
|
|
68
|
+
const heartbeatWriteTolerance = Duration.millis(
|
|
69
|
+
Duration.toMillis(heartbeatStaleAfter) - Duration.toMillis(heartbeatSkewAllowance) - Duration.toMillis(heartbeatInterval)
|
|
70
|
+
);
|
|
71
|
+
const staleAfterMs = Duration.toMillis(heartbeatStaleAfter);
|
|
72
|
+
const LivenessEvidence = Schema.Struct({
|
|
73
|
+
expectedOwner: import_OwnerId.OwnerId,
|
|
74
|
+
checkedAtMs: Schema.Number,
|
|
75
|
+
kind: Schema.Literals(["same-host-pid-dead", "cross-host-unreachable-stale", "lease-expired"])
|
|
76
|
+
});
|
|
77
|
+
const sameOwner = (left, right) => left.hostId === right.hostId && left.pid === right.pid && left.nonce === right.nonce;
|
|
78
|
+
const matchesEvidence = (expectedOwner, observer, nowMs, evidence) => {
|
|
79
|
+
if (!sameOwner(expectedOwner, evidence.expectedOwner) || evidence.checkedAtMs !== nowMs) return false;
|
|
80
|
+
switch (evidence.kind) {
|
|
81
|
+
// A pid means nothing outside the host that owns the process namespace.
|
|
82
|
+
case "same-host-pid-dead":
|
|
83
|
+
return expectedOwner.hostId === observer.hostId;
|
|
84
|
+
// Unreachability is what a peer host observes; on the owner's own host it
|
|
85
|
+
// would be an unprobed guess dressed as evidence.
|
|
86
|
+
case "cross-host-unreachable-stale":
|
|
87
|
+
return expectedOwner.hostId !== observer.hostId;
|
|
88
|
+
// The lease is host-neutral, and the strategy verifies the same
|
|
89
|
+
// caller-clock cutoff named by this evidence.
|
|
90
|
+
case "lease-expired":
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const RejectionReason = Schema.Literals([
|
|
95
|
+
"already_claimed",
|
|
96
|
+
"owner_held",
|
|
97
|
+
"owner_live",
|
|
98
|
+
"evidence_invalid",
|
|
99
|
+
"claim_fresh",
|
|
100
|
+
"claim_changed",
|
|
101
|
+
"unavailable"
|
|
102
|
+
]);
|
|
103
|
+
const Claimed = Schema.TaggedStruct("Claimed", {
|
|
104
|
+
grantedAtMs: Schema.Number
|
|
105
|
+
});
|
|
106
|
+
const Rejected = Schema.TaggedStruct("Rejected", {
|
|
107
|
+
reason: RejectionReason
|
|
108
|
+
});
|
|
109
|
+
const ClaimOutcome = Schema.Union([Claimed, Rejected]);
|
|
110
|
+
const Activated = Schema.TaggedStruct("Activated", {});
|
|
111
|
+
const Lost = Schema.TaggedStruct("Lost", {});
|
|
112
|
+
const ActivateOutcome = Schema.Union([Activated, Lost]);
|
|
113
|
+
const Renewed = Schema.TaggedStruct("Renewed", {
|
|
114
|
+
heartbeatAtMs: Schema.Number
|
|
115
|
+
});
|
|
116
|
+
const HeartbeatOutcome = Schema.Union([Renewed, Lost]);
|
|
117
|
+
const Recovered = Schema.TaggedStruct("Recovered", {});
|
|
118
|
+
const RecoverOutcome = Schema.Union([Recovered, Rejected]);
|
|
119
|
+
class ConsensusError extends Schema.TaggedError()("@smthrs/journal/ConsensusError", {
|
|
120
|
+
code: Schema.Literals(["fence_lost", "persistence_failed"]),
|
|
121
|
+
message: Schema.String,
|
|
122
|
+
cause: Schema.optional(Schema.Unknown)
|
|
123
|
+
}) {
|
|
124
|
+
}
|
|
125
|
+
class Consensus extends Context.Service()("@smthrs/journal/Consensus") {
|
|
126
|
+
}
|
|
127
|
+
const make = (implementation) => Consensus.of(implementation);
|
|
128
|
+
const rejected = (reason) => ({ _tag: "Rejected", reason });
|
|
129
|
+
const claimed = (grantedAtMs) => ({ _tag: "Claimed", grantedAtMs });
|
|
130
|
+
const lost = { _tag: "Lost" };
|
|
131
|
+
const activated = { _tag: "Activated" };
|
|
132
|
+
const recovered = { _tag: "Recovered" };
|
|
133
|
+
const fenceLost = (runId, owner) => new ConsensusError({
|
|
134
|
+
code: "fence_lost",
|
|
135
|
+
message: `run ${runId} is no longer owned by ${owner.hostId}:${owner.pid}:${owner.nonce}`
|
|
136
|
+
});
|
|
137
|
+
const makeNoop = (overrides = {}) => Consensus.of({
|
|
138
|
+
claim: Effect.fn("Consensus.claim")(() => Effect.succeed(rejected("unavailable"))),
|
|
139
|
+
activate: Effect.fn("Consensus.activate")(() => Effect.succeed(lost)),
|
|
140
|
+
heartbeat: Effect.fn("Consensus.heartbeat")(() => Effect.succeed(lost)),
|
|
141
|
+
reconfirm: Effect.fn("Consensus.reconfirm")(() => Effect.succeed(lost)),
|
|
142
|
+
release: Effect.fn("Consensus.release")(() => Effect.void),
|
|
143
|
+
steal: Effect.fn("Consensus.steal")(() => Effect.succeed(rejected("unavailable"))),
|
|
144
|
+
recover: Effect.fn("Consensus.recover")(() => Effect.succeed(rejected("unavailable"))),
|
|
145
|
+
guard: Effect.fn("Consensus.guard")((runId, owner) => Effect.fail(fenceLost(runId, owner))),
|
|
146
|
+
...overrides
|
|
147
|
+
});
|
|
148
|
+
const layerNoop = (overrides = {}) => Layer.succeed(Consensus)(makeNoop(overrides));
|
|
149
|
+
const makeLocal = Effect.sync(() => {
|
|
150
|
+
const leases = /* @__PURE__ */ new Map();
|
|
151
|
+
const leaseOf = (runId) => {
|
|
152
|
+
const existing = leases.get(runId);
|
|
153
|
+
if (existing !== void 0) return existing;
|
|
154
|
+
const fresh = { owner: void 0, claim: void 0 };
|
|
155
|
+
leases.set(runId, fresh);
|
|
156
|
+
return fresh;
|
|
157
|
+
};
|
|
158
|
+
const prune = (runId, lease) => {
|
|
159
|
+
if (lease.owner === void 0 && lease.claim === void 0) {
|
|
160
|
+
leases.delete(runId);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return Consensus.of({
|
|
164
|
+
claim: Effect.fn("Consensus.claim")(
|
|
165
|
+
(runId, claimant, nowMs) => Effect.sync(() => {
|
|
166
|
+
const lease = leaseOf(runId);
|
|
167
|
+
if (lease.claim !== void 0) return rejected("already_claimed");
|
|
168
|
+
if (lease.owner !== void 0) return rejected("owner_held");
|
|
169
|
+
lease.claim = { id: claimant, claimedAtMs: nowMs };
|
|
170
|
+
return claimed(nowMs);
|
|
171
|
+
})
|
|
172
|
+
),
|
|
173
|
+
activate: Effect.fn("Consensus.activate")(
|
|
174
|
+
(runId, owner, grantedAtMs, nowMs) => Effect.sync(() => {
|
|
175
|
+
const lease = leaseOf(runId);
|
|
176
|
+
if (lease.claim === void 0 || !sameOwner(lease.claim.id, owner) || lease.claim.claimedAtMs !== grantedAtMs) {
|
|
177
|
+
prune(runId, lease);
|
|
178
|
+
return lost;
|
|
179
|
+
}
|
|
180
|
+
lease.claim = void 0;
|
|
181
|
+
lease.owner = { id: owner, grantedAtMs, heartbeatAtMs: nowMs };
|
|
182
|
+
return activated;
|
|
183
|
+
})
|
|
184
|
+
),
|
|
185
|
+
heartbeat: Effect.fn("Consensus.heartbeat")(
|
|
186
|
+
(runId, owner, nowMs) => Effect.sync(() => {
|
|
187
|
+
const lease = leases.get(runId);
|
|
188
|
+
if (lease?.owner === void 0 || !sameOwner(lease.owner.id, owner)) return lost;
|
|
189
|
+
lease.owner.heartbeatAtMs = Math.max(lease.owner.heartbeatAtMs, nowMs);
|
|
190
|
+
return { _tag: "Renewed", heartbeatAtMs: lease.owner.heartbeatAtMs };
|
|
191
|
+
})
|
|
192
|
+
),
|
|
193
|
+
reconfirm: Effect.fn("Consensus.reconfirm")(
|
|
194
|
+
(runId, owner, nowMs) => Effect.sync(() => {
|
|
195
|
+
const lease = leases.get(runId);
|
|
196
|
+
if (lease?.owner === void 0 || lease.claim !== void 0 || !sameOwner(lease.owner.id, owner)) return lost;
|
|
197
|
+
lease.owner.heartbeatAtMs = Math.max(lease.owner.heartbeatAtMs, nowMs);
|
|
198
|
+
return { _tag: "Renewed", heartbeatAtMs: lease.owner.heartbeatAtMs };
|
|
199
|
+
})
|
|
200
|
+
),
|
|
201
|
+
release: Effect.fn("Consensus.release")(
|
|
202
|
+
(runId, owner) => Effect.sync(() => {
|
|
203
|
+
const lease = leases.get(runId);
|
|
204
|
+
if (lease === void 0) return;
|
|
205
|
+
if (lease.claim !== void 0 && sameOwner(lease.claim.id, owner)) {
|
|
206
|
+
lease.claim = void 0;
|
|
207
|
+
}
|
|
208
|
+
if (lease.owner !== void 0 && sameOwner(lease.owner.id, owner)) {
|
|
209
|
+
lease.owner = void 0;
|
|
210
|
+
}
|
|
211
|
+
prune(runId, lease);
|
|
212
|
+
})
|
|
213
|
+
),
|
|
214
|
+
steal: Effect.fn("Consensus.steal")(
|
|
215
|
+
(runId, claimant, nowMs, evidence) => Effect.sync(() => {
|
|
216
|
+
if (!matchesEvidence(evidence.expectedOwner, claimant, nowMs, evidence)) {
|
|
217
|
+
return rejected("evidence_invalid");
|
|
218
|
+
}
|
|
219
|
+
const lease = leases.get(runId);
|
|
220
|
+
if (lease?.claim !== void 0) return rejected("already_claimed");
|
|
221
|
+
if (lease?.owner === void 0 || !sameOwner(lease.owner.id, evidence.expectedOwner)) {
|
|
222
|
+
return rejected("evidence_invalid");
|
|
223
|
+
}
|
|
224
|
+
if (lease.owner.heartbeatAtMs >= nowMs - staleAfterMs) return rejected("owner_live");
|
|
225
|
+
lease.claim = { id: claimant, claimedAtMs: nowMs };
|
|
226
|
+
return claimed(nowMs);
|
|
227
|
+
})
|
|
228
|
+
),
|
|
229
|
+
recover: Effect.fn("Consensus.recover")(
|
|
230
|
+
(runId, staleClaimant, grantedAtMs, observer, nowMs, evidence) => Effect.sync(() => {
|
|
231
|
+
if (!matchesEvidence(staleClaimant, observer, nowMs, evidence)) return rejected("evidence_invalid");
|
|
232
|
+
const lease = leases.get(runId);
|
|
233
|
+
if (lease?.claim === void 0 || !sameOwner(lease.claim.id, staleClaimant) || lease.claim.claimedAtMs !== grantedAtMs) {
|
|
234
|
+
return rejected("claim_changed");
|
|
235
|
+
}
|
|
236
|
+
if (lease.claim.claimedAtMs >= nowMs - staleAfterMs) return rejected("claim_fresh");
|
|
237
|
+
lease.claim = void 0;
|
|
238
|
+
prune(runId, lease);
|
|
239
|
+
return recovered;
|
|
240
|
+
})
|
|
241
|
+
),
|
|
242
|
+
guard: Effect.fn("Consensus.guard")(
|
|
243
|
+
(runId, owner) => Effect.suspend(() => {
|
|
244
|
+
const lease = leases.get(runId);
|
|
245
|
+
return lease?.owner !== void 0 && sameOwner(lease.owner.id, owner) ? Effect.void : Effect.fail(fenceLost(runId, owner));
|
|
246
|
+
})
|
|
247
|
+
)
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
const layerLocal = Layer.effect(Consensus, makeLocal);
|
|
251
|
+
//# sourceMappingURL=Consensus.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/Consensus.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The injectable consensus strategy arbitrating the journal's ownership rules.\n *\n * The journal owns the consensus rules \u2014 single writer per run, fenced and\n * unfenced appends, commit-time admission, the generation fence, and steal\n * only on staleness plus liveness evidence \u2014 and this service is how a deployment chooses *how* those rules are\n * arbitrated: where the lease lives, how liveness is probed, and how the\n * fence is checked at commit time. `Consensus` lives here beside `OwnerId`\n * for the same reason `OwnerId` does: the journal is what it fences.\n * `@smthrs/run-store`'s `Ownership` keeps its public API and rules but\n * delegates arbitration to this strategy.\n *\n * Every operation that reasons about staleness takes the caller's clock\n * reading (`nowMs`) rather than reading a clock itself: staleness (R5) and\n * the monotonic lease stamp are defined against the arbitrating caller's\n * reading, exactly as `RunStore`'s compare-and-swap operations always were,\n * so the recorded lease never differs from the outcome the caller observed.\n *\n * Governing design: `packages/smithers/flows/journal/docs/concepts/owner-fence.md`.\n * Rule constants and their provenance:\n * `packages/smithers/flows/run-store/docs/concepts/leases.md`.\n *\n * Prior art: Effect `EventJournal` (`reference/effect` `unstable/eventlog`)\n * resolves multi-writer by merge with conflict surfacing; driving a run is not\n * mergeable, so this surface deviates to exclusive-writer admission. The\n * fencing and generation semantics follow Temporal's shard `rangeID`\n * (`reference/temporal/service/history/shard/context_impl.go`).\n *\n * @since 1.0.0\n */\n\nimport * as Context from \"effect/Context\"\nimport * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport { OwnerId } from \"./OwnerId.ts\"\n\n/**\n * How often the supervision loop pulses to renew the owner's lease.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const heartbeatInterval: Duration.Duration = Duration.seconds(1)\n\n/**\n * How old a persisted heartbeat must be before a peer may steal the run.\n * Every strategy judges R5 staleness against this one definition.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const heartbeatStaleAfter: Duration.Duration = Duration.seconds(30)\n\n/**\n * How far the owner's wall clock may run behind a peer's before the lease\n * reasoning stops holding.\n *\n * The owner stamps its heartbeat from *its* clock and a would-be stealer\n * compares that stamp against *its own*, so the two hosts' clock offset is\n * subtracted directly from the owner's real safety margin. This constant names\n * that allowance instead of leaving it implicit in a \"two ticks\" arithmetic\n * accident, and it is a budgeted allowance, not a guarantee: see\n * {@link heartbeatWriteTolerance} for what happens once it is exceeded.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const heartbeatSkewAllowance: Duration.Duration = Duration.seconds(10)\n\n/**\n * How long heartbeat writes may go unconfirmed before ownership must be reconfirmed.\n *\n * A peer judges staleness by the persisted heartbeat and may steal the run the\n * instant it is {@link heartbeatStaleAfter} old, so an owner that tolerated\n * write failures for exactly that long would still be executing side effects\n * when the steal is admitted. The budget is therefore\n * {@link heartbeatStaleAfter} minus {@link heartbeatSkewAllowance} (the peer's\n * clock may already read that much later than the owner's) minus one\n * {@link heartbeatInterval} (reserved for one bounded compare-and-swap\n * reconfirmation before the owner interrupts).\n *\n * This bounds, but does not eliminate, overlap. Beyond\n * {@link heartbeatSkewAllowance} of clock offset a peer can be admitted while\n * the old owner is still running. *Durable* writes stay safe regardless \u2014 they\n * are fenced by the strategy's commit-time admission, so the displaced owner's\n * writes fail rather than corrupt. Non-durable external side effects (an HTTP\n * call, a spawned process) can genuinely overlap. That is inherent to any\n * wall-clock lease and is stated here rather than asserted away; a caller that\n * cannot tolerate any overlap needs an external fencing token at the side\n * effect itself, not a larger timeout.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const heartbeatWriteTolerance: Duration.Duration = Duration.millis(\n Duration.toMillis(heartbeatStaleAfter) -\n Duration.toMillis(heartbeatSkewAllowance) -\n Duration.toMillis(heartbeatInterval)\n)\n\nconst staleAfterMs = Duration.toMillis(heartbeatStaleAfter)\n\n/**\n * Evidence that the owner in an exact run snapshot is no longer live.\n *\n * Defined here because R5 \u2014 steal requires staleness plus liveness evidence \u2014\n * is a consensus rule every strategy validates. `@smthrs/run-store`'s\n * `Ownership` re-exports it alongside the probes that mint it.\n *\n * Two of the three kinds are collected outside the strategy:\n * `same-host-pid-dead` is a local process probe, and\n * `cross-host-unreachable-stale` is a reachability judgement the deployment\n * makes. `lease-expired` is different \u2014 it asserts only that the persisted\n * heartbeat is older than the staleness cutoff, which is the one claim a\n * strategy can check for itself, and `steal` checks it: the takeover refuses\n * any lease whose heartbeat is still inside the window. It is therefore\n * accepted from a claimant on any host, while the other two stay bound to the\n * host relation that makes them meaningful. The observation must carry the\n * exact instant supplied to the consuming operation: `checkedAtMs` must equal\n * that operation's `nowMs`. A caller that probes at T and calls at T+1 is\n * refused and must build fresh evidence.\n *\n * @since 1.0.0\n * @category models\n */\nexport const LivenessEvidence = Schema.Struct({\n expectedOwner: OwnerId,\n checkedAtMs: Schema.Number,\n kind: Schema.Literals([\"same-host-pid-dead\", \"cross-host-unreachable-stale\", \"lease-expired\"])\n})\n\n/**\n * Evidence that the owner in an exact run snapshot is no longer live.\n *\n * @since 1.0.0\n * @category models\n */\nexport type LivenessEvidence = typeof LivenessEvidence.Type\n\n/**\n * Structural equality on the process identity tuple.\n *\n * @since 1.0.0\n * @category predicates\n */\nexport const sameOwner = (left: OwnerId, right: OwnerId): boolean =>\n left.hostId === right.hostId && left.pid === right.pid && left.nonce === right.nonce\n\n/**\n * Validates liveness evidence against the owner it claims to disprove.\n *\n * The evidence must name the expected owner, carry the observer's current\n * clock reading, and respect the host rule: a `same-host-pid-dead` probe is\n * only meaningful from the same host, a `cross-host-unreachable-stale`\n * observation only from a different one, and `lease-expired` is host-neutral\n * because the strategy verifies the cutoff it names for itself.\n *\n * @since 1.0.0\n * @category predicates\n */\nexport const matchesEvidence = (\n expectedOwner: OwnerId,\n observer: OwnerId,\n nowMs: number,\n evidence: LivenessEvidence\n): boolean => {\n if (!sameOwner(expectedOwner, evidence.expectedOwner) || evidence.checkedAtMs !== nowMs) return false\n switch (evidence.kind) {\n // A pid means nothing outside the host that owns the process namespace.\n case \"same-host-pid-dead\":\n return expectedOwner.hostId === observer.hostId\n // Unreachability is what a peer host observes; on the owner's own host it\n // would be an unprobed guess dressed as evidence.\n case \"cross-host-unreachable-stale\":\n return expectedOwner.hostId !== observer.hostId\n // The lease is host-neutral, and the strategy verifies the same\n // caller-clock cutoff named by this evidence.\n case \"lease-expired\":\n return true\n }\n}\n\n/**\n * Why a strategy refused a claim, steal, or recovery.\n *\n * @since 1.0.0\n * @category models\n */\nexport const RejectionReason = Schema.Literals([\n \"already_claimed\",\n \"owner_held\",\n \"owner_live\",\n \"evidence_invalid\",\n \"claim_fresh\",\n \"claim_changed\",\n \"unavailable\"\n])\n\n/**\n * Why a strategy refused a claim, steal, or recovery.\n *\n * @since 1.0.0\n * @category models\n */\nexport type RejectionReason = typeof RejectionReason.Type\n\n/**\n * A granted claim, carrying the grant timestamp the later activation must\n * present (R4 \u2014 the generation fence).\n *\n * @since 1.0.0\n * @category models\n */\nexport const Claimed = Schema.TaggedStruct(\"Claimed\", {\n grantedAtMs: Schema.Number\n})\n\n/**\n * A granted claim.\n *\n * @since 1.0.0\n * @category models\n */\nexport type Claimed = typeof Claimed.Type\n\n/**\n * A refused claim, steal, or recovery, with the strategy's typed reason.\n *\n * @since 1.0.0\n * @category models\n */\nexport const Rejected = Schema.TaggedStruct(\"Rejected\", {\n reason: RejectionReason\n})\n\n/**\n * A refused claim, steal, or recovery.\n *\n * @since 1.0.0\n * @category models\n */\nexport type Rejected = typeof Rejected.Type\n\n/**\n * Outcome of `claim` and `steal`.\n *\n * @since 1.0.0\n * @category models\n */\nexport const ClaimOutcome = Schema.Union([Claimed, Rejected])\n\n/**\n * Outcome of `claim` and `steal`.\n *\n * @since 1.0.0\n * @category models\n */\nexport type ClaimOutcome = typeof ClaimOutcome.Type\n\n/**\n * A claim promoted to ownership.\n *\n * @since 1.0.0\n * @category models\n */\nexport const Activated = Schema.TaggedStruct(\"Activated\", {})\n\n/**\n * A claim promoted to ownership.\n *\n * @since 1.0.0\n * @category models\n */\nexport type Activated = typeof Activated.Type\n\n/**\n * The claim or lease the caller presented no longer exists.\n *\n * @since 1.0.0\n * @category models\n */\nexport const Lost = Schema.TaggedStruct(\"Lost\", {})\n\n/**\n * The claim or lease the caller presented no longer exists.\n *\n * @since 1.0.0\n * @category models\n */\nexport type Lost = typeof Lost.Type\n\n/**\n * Outcome of `activate`.\n *\n * @since 1.0.0\n * @category models\n */\nexport const ActivateOutcome = Schema.Union([Activated, Lost])\n\n/**\n * Outcome of `activate`.\n *\n * @since 1.0.0\n * @category models\n */\nexport type ActivateOutcome = typeof ActivateOutcome.Type\n\n/**\n * A renewed lease, carrying the monotonic heartbeat stamp the strategy\n * recorded \u2014 the maximum of the stored stamp and the caller's reading, so a\n * late pulse never moves the lease backwards.\n *\n * @since 1.0.0\n * @category models\n */\nexport const Renewed = Schema.TaggedStruct(\"Renewed\", {\n heartbeatAtMs: Schema.Number\n})\n\n/**\n * A renewed lease.\n *\n * @since 1.0.0\n * @category models\n */\nexport type Renewed = typeof Renewed.Type\n\n/**\n * Outcome of `heartbeat` and `reconfirm`.\n *\n * @since 1.0.0\n * @category models\n */\nexport const HeartbeatOutcome = Schema.Union([Renewed, Lost])\n\n/**\n * Outcome of `heartbeat` and `reconfirm`.\n *\n * @since 1.0.0\n * @category models\n */\nexport type HeartbeatOutcome = typeof HeartbeatOutcome.Type\n\n/**\n * A stale claim cleared after its claimant was proven dead.\n *\n * @since 1.0.0\n * @category models\n */\nexport const Recovered = Schema.TaggedStruct(\"Recovered\", {})\n\n/**\n * A stale claim cleared after its claimant was proven dead.\n *\n * @since 1.0.0\n * @category models\n */\nexport type Recovered = typeof Recovered.Type\n\n/**\n * Outcome of `recover`.\n *\n * @since 1.0.0\n * @category models\n */\nexport const RecoverOutcome = Schema.Union([Recovered, Rejected])\n\n/**\n * Outcome of `recover`.\n *\n * @since 1.0.0\n * @category models\n */\nexport type RecoverOutcome = typeof RecoverOutcome.Type\n\n/**\n * Failure raised by a consensus strategy.\n *\n * `fence_lost` is `guard`'s refusal: the supplied owner no longer holds the\n * run, so the fenced write it joins must not commit. `persistence_failed`\n * wraps a strategy's storage failure; the original cause is preserved so the\n * durable writer's retry classification still sees a transient conflict\n * through the wrapper.\n *\n * @since 1.0.0\n * @category errors\n */\nexport class ConsensusError extends Schema.TaggedError<ConsensusError>()(\"@smthrs/journal/ConsensusError\", {\n code: Schema.Literals([\"fence_lost\", \"persistence_failed\"]),\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown)\n}) {}\n\n/**\n * Consensus operations.\n *\n * The rules being arbitrated are strategy-independent; a strategy\n * chooses where the lease lives and how the fence is checked at commit time.\n * The claim lifecycle is two-phase, exactly as `RunStore`'s always was:\n * `claim` (or `steal`, its evidence-gated form) reserves the run and returns\n * the grant timestamp, `activate` presents that grant to take ownership, and\n * `release` clears whichever of the caller's claim or ownership still stands.\n * `recover` clears a *third party's* stale claim after its claimant was proven\n * dead \u2014 the relocated `recoverClaim` mechanic. `guard` is the commit-time\n * admission check (R3) `Journal.emitDurable`, `checkpoint`, and `compact`\n * join: it succeeds while the owner holds the run and fails with `fence_lost`\n * otherwise.\n *\n * Heartbeats and lease state are the strategy's private evidence and never\n * enter the journal.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface Service {\n readonly claim: (\n runId: string,\n claimant: OwnerId,\n nowMs: number\n ) => Effect.Effect<ClaimOutcome, ConsensusError>\n readonly activate: (\n runId: string,\n owner: OwnerId,\n grantedAtMs: number,\n nowMs: number\n ) => Effect.Effect<ActivateOutcome, ConsensusError>\n readonly heartbeat: (\n runId: string,\n owner: OwnerId,\n nowMs: number\n ) => Effect.Effect<HeartbeatOutcome, ConsensusError>\n /** Renews only while this owner holds the lease and no takeover claim is pending. */\n readonly reconfirm: (\n runId: string,\n owner: OwnerId,\n nowMs: number\n ) => Effect.Effect<HeartbeatOutcome, ConsensusError>\n readonly release: (runId: string, owner: OwnerId) => Effect.Effect<void, ConsensusError>\n readonly steal: (\n runId: string,\n claimant: OwnerId,\n nowMs: number,\n evidence: LivenessEvidence\n ) => Effect.Effect<ClaimOutcome, ConsensusError>\n readonly recover: (\n runId: string,\n staleClaimant: OwnerId,\n grantedAtMs: number,\n observer: OwnerId,\n nowMs: number,\n evidence: LivenessEvidence\n ) => Effect.Effect<RecoverOutcome, ConsensusError>\n readonly guard: (runId: string, owner: OwnerId) => Effect.Effect<void, ConsensusError>\n}\n\n/**\n * Context service for the injected consensus strategy.\n *\n * @since 1.0.0\n * @category services\n */\nexport class Consensus extends Context.Service<Consensus, Service>()(\"@smthrs/journal/Consensus\") {}\n\n/**\n * Constructs a consensus service from an implementation.\n *\n * @since 1.0.0\n * @category constructors\n */\nexport const make = (implementation: Service): Service => Consensus.of(implementation)\n\nconst rejected = (reason: RejectionReason): Rejected => ({ _tag: \"Rejected\", reason })\nconst claimed = (grantedAtMs: number): Claimed => ({ _tag: \"Claimed\", grantedAtMs })\nconst lost: Lost = { _tag: \"Lost\" }\nconst activated: Activated = { _tag: \"Activated\" }\nconst recovered: Recovered = { _tag: \"Recovered\" }\n\n/**\n * The refusal `guard` raises for an owner that no longer holds the run.\n *\n * @since 1.0.0\n * @category errors\n */\nexport const fenceLost = (runId: string, owner: OwnerId): ConsensusError =>\n new ConsensusError({\n code: \"fence_lost\",\n message: `run ${runId} is no longer owned by ${owner.hostId}:${owner.pid}:${owner.nonce}`\n })\n\n/**\n * Constructs a strategy that arbitrates nothing: claims are refused, leases\n * are never held, and every fenced write is rejected.\n *\n * @since 1.0.0\n * @category constructors\n */\nexport const makeNoop = (overrides: Partial<Service> = {}): Service =>\n Consensus.of({\n claim: Effect.fn(\"Consensus.claim\")(() => Effect.succeed(rejected(\"unavailable\"))),\n activate: Effect.fn(\"Consensus.activate\")(() => Effect.succeed(lost)),\n heartbeat: Effect.fn(\"Consensus.heartbeat\")(() => Effect.succeed(lost)),\n reconfirm: Effect.fn(\"Consensus.reconfirm\")(() => Effect.succeed(lost)),\n release: Effect.fn(\"Consensus.release\")(() => Effect.void),\n steal: Effect.fn(\"Consensus.steal\")(() => Effect.succeed(rejected(\"unavailable\"))),\n recover: Effect.fn(\"Consensus.recover\")(() => Effect.succeed(rejected(\"unavailable\"))),\n guard: Effect.fn(\"Consensus.guard\")((runId: string, owner: OwnerId) => Effect.fail(fenceLost(runId, owner))),\n ...overrides\n })\n\n/**\n * Provides the refusing strategy stub.\n *\n * @since 1.0.0\n * @category layers\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<Consensus> =>\n Layer.succeed(Consensus)(makeNoop(overrides))\n\ninterface LocalLease {\n owner: { readonly id: OwnerId; readonly grantedAtMs: number; heartbeatAtMs: number } | undefined\n claim: { readonly id: OwnerId; readonly claimedAtMs: number } | undefined\n}\n\n/**\n * Constructs the in-memory single-process strategy.\n *\n * @since 1.0.0\n * @category constructors\n */\nexport const makeLocal: Effect.Effect<Service> = Effect.sync(() => {\n const leases = new Map<string, LocalLease>()\n\n const leaseOf = (runId: string): LocalLease => {\n const existing = leases.get(runId)\n if (existing !== undefined) return existing\n const fresh: LocalLease = { owner: undefined, claim: undefined }\n leases.set(runId, fresh)\n return fresh\n }\n\n const prune = (runId: string, lease: LocalLease): void => {\n if (lease.owner === undefined && lease.claim === undefined) {\n leases.delete(runId)\n }\n }\n\n return Consensus.of({\n claim: Effect.fn(\"Consensus.claim\")((runId: string, claimant: OwnerId, nowMs: number) =>\n Effect.sync(() => {\n const lease = leaseOf(runId)\n if (lease.claim !== undefined) return rejected(\"already_claimed\")\n if (lease.owner !== undefined) return rejected(\"owner_held\")\n lease.claim = { id: claimant, claimedAtMs: nowMs }\n return claimed(nowMs)\n })\n ),\n activate: Effect.fn(\"Consensus.activate\")((\n runId: string,\n owner: OwnerId,\n grantedAtMs: number,\n nowMs: number\n ) =>\n Effect.sync(() => {\n const lease = leaseOf(runId)\n if (\n lease.claim === undefined ||\n !sameOwner(lease.claim.id, owner) ||\n lease.claim.claimedAtMs !== grantedAtMs\n ) {\n prune(runId, lease)\n return lost\n }\n lease.claim = undefined\n lease.owner = { id: owner, grantedAtMs, heartbeatAtMs: nowMs }\n return activated\n })\n ),\n heartbeat: Effect.fn(\"Consensus.heartbeat\")((runId: string, owner: OwnerId, nowMs: number) =>\n Effect.sync(() => {\n const lease = leases.get(runId)\n if (lease?.owner === undefined || !sameOwner(lease.owner.id, owner)) return lost\n lease.owner.heartbeatAtMs = Math.max(lease.owner.heartbeatAtMs, nowMs)\n return ({ _tag: \"Renewed\", heartbeatAtMs: lease.owner.heartbeatAtMs } satisfies Renewed)\n })\n ),\n reconfirm: Effect.fn(\"Consensus.reconfirm\")((runId: string, owner: OwnerId, nowMs: number) =>\n Effect.sync(() => {\n const lease = leases.get(runId)\n if (lease?.owner === undefined || lease.claim !== undefined || !sameOwner(lease.owner.id, owner)) return lost\n lease.owner.heartbeatAtMs = Math.max(lease.owner.heartbeatAtMs, nowMs)\n return ({ _tag: \"Renewed\", heartbeatAtMs: lease.owner.heartbeatAtMs } satisfies Renewed)\n })\n ),\n release: Effect.fn(\"Consensus.release\")((runId: string, owner: OwnerId) =>\n Effect.sync(() => {\n const lease = leases.get(runId)\n if (lease === undefined) return\n if (lease.claim !== undefined && sameOwner(lease.claim.id, owner)) {\n lease.claim = undefined\n }\n if (lease.owner !== undefined && sameOwner(lease.owner.id, owner)) {\n lease.owner = undefined\n }\n prune(runId, lease)\n })\n ),\n steal: Effect.fn(\"Consensus.steal\")((\n runId: string,\n claimant: OwnerId,\n nowMs: number,\n evidence: LivenessEvidence\n ) =>\n Effect.sync(() => {\n if (!matchesEvidence(evidence.expectedOwner, claimant, nowMs, evidence)) {\n return rejected(\"evidence_invalid\")\n }\n const lease = leases.get(runId)\n if (lease?.claim !== undefined) return rejected(\"already_claimed\")\n if (lease?.owner === undefined || !sameOwner(lease.owner.id, evidence.expectedOwner)) {\n return rejected(\"evidence_invalid\")\n }\n if (lease.owner.heartbeatAtMs >= nowMs - staleAfterMs) return rejected(\"owner_live\")\n lease.claim = { id: claimant, claimedAtMs: nowMs }\n return claimed(nowMs)\n })\n ),\n recover: Effect.fn(\"Consensus.recover\")((\n runId: string,\n staleClaimant: OwnerId,\n grantedAtMs: number,\n observer: OwnerId,\n nowMs: number,\n evidence: LivenessEvidence\n ) =>\n Effect.sync(() => {\n if (!matchesEvidence(staleClaimant, observer, nowMs, evidence)) return rejected(\"evidence_invalid\")\n const lease = leases.get(runId)\n if (\n lease?.claim === undefined ||\n !sameOwner(lease.claim.id, staleClaimant) ||\n lease.claim.claimedAtMs !== grantedAtMs\n ) {\n return rejected(\"claim_changed\")\n }\n if (lease.claim.claimedAtMs >= nowMs - staleAfterMs) return rejected(\"claim_fresh\")\n lease.claim = undefined\n prune(runId, lease)\n return recovered\n })\n ),\n guard: Effect.fn(\"Consensus.guard\")((runId: string, owner: OwnerId) =>\n Effect.suspend(() => {\n const lease = leases.get(runId)\n return lease?.owner !== undefined && sameOwner(lease.owner.id, owner)\n ? Effect.void\n : Effect.fail(fenceLost(runId, owner))\n })\n )\n })\n})\n\n/**\n * Provides the in-memory single-process strategy.\n *\n * The lease lives in process memory, so R3 is exact within the process \u2014 the\n * strategy's transitions and the serialized append transactions interleave\n * atomically on one event loop \u2014 and browser support needs nothing beyond\n * this layer plus the browser-safe journal. State is per layer build: provide\n * one instance to every service that must agree on it, which is what\n * `SqlJournal.layerWith` and `RunStore.layerWith` exist for.\n *\n * The lease is not part of any database transaction: a transition made inside\n * a `Journal.transact` that later rolls back stays applied in memory, so the\n * lease and a rolled-back run row can disagree until the next transition.\n * `SqlConsensus` keeps the two in one transaction; prefer it wherever a\n * database is present.\n *\n * @since 1.0.0\n * @category layers\n */\nexport const layerLocal: Layer.Layer<Consensus> = Layer.effect(Consensus, makeLocal)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|