@smthrs/run-store 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 +112 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/cjs/AttemptStore.d.ts +359 -0
- package/dist/cjs/AttemptStore.d.ts.map +1 -0
- package/dist/cjs/AttemptStore.js +731 -0
- package/dist/cjs/AttemptStore.js.map +7 -0
- package/dist/cjs/Heartbeat.d.ts +46 -0
- package/dist/cjs/Heartbeat.d.ts.map +1 -0
- package/dist/cjs/Heartbeat.js +28 -0
- package/dist/cjs/Heartbeat.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 +55 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/Ownership.d.ts +275 -0
- package/dist/cjs/Ownership.d.ts.map +1 -0
- package/dist/cjs/Ownership.js +129 -0
- package/dist/cjs/Ownership.js.map +7 -0
- package/dist/cjs/RunStore.d.ts +571 -0
- package/dist/cjs/RunStore.d.ts.map +1 -0
- package/dist/cjs/RunStore.js +1079 -0
- package/dist/cjs/RunStore.js.map +7 -0
- package/dist/cjs/RunStoreMetrics.d.ts +151 -0
- package/dist/cjs/RunStoreMetrics.d.ts.map +1 -0
- package/dist/cjs/RunStoreMetrics.js +91 -0
- package/dist/cjs/RunStoreMetrics.js.map +7 -0
- package/dist/cjs/index.d.ts +47 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/Boundary.d.ts +80 -0
- package/dist/cjs/internal/Boundary.d.ts.map +1 -0
- package/dist/cjs/internal/Boundary.js +92 -0
- package/dist/cjs/internal/Boundary.js.map +7 -0
- package/dist/cjs/internal/Companion.d.ts +44 -0
- package/dist/cjs/internal/Companion.d.ts.map +1 -0
- package/dist/cjs/internal/Companion.js +50 -0
- package/dist/cjs/internal/Companion.js.map +7 -0
- package/dist/cjs/internal/SpanOutcome.d.ts +37 -0
- package/dist/cjs/internal/SpanOutcome.d.ts.map +1 -0
- package/dist/cjs/internal/SpanOutcome.js +62 -0
- package/dist/cjs/internal/SpanOutcome.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 +110 -0
- package/dist/cjs/migrations/0001_initial.js.map +7 -0
- package/dist/cjs/migrations/0002_lineage.d.ts +27 -0
- package/dist/cjs/migrations/0002_lineage.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_lineage.js +43 -0
- package/dist/cjs/migrations/0002_lineage.js.map +7 -0
- package/dist/cjs/migrations/0003_execution_revisions.d.ts +15 -0
- package/dist/cjs/migrations/0003_execution_revisions.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_execution_revisions.js +77 -0
- package/dist/cjs/migrations/0003_execution_revisions.js.map +7 -0
- package/dist/cjs/migrations/0004_waiting_request.d.ts +32 -0
- package/dist/cjs/migrations/0004_waiting_request.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_waiting_request.js +42 -0
- package/dist/cjs/migrations/0004_waiting_request.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestRunStore.d.ts +17 -0
- package/dist/cjs/test/TestRunStore.d.ts.map +1 -0
- package/dist/cjs/test/TestRunStore.js +42 -0
- package/dist/cjs/test/TestRunStore.js.map +7 -0
- package/dist/esm/AttemptStore.d.ts +359 -0
- package/dist/esm/AttemptStore.d.ts.map +1 -0
- package/dist/esm/AttemptStore.js +835 -0
- package/dist/esm/AttemptStore.js.map +1 -0
- package/dist/esm/Heartbeat.d.ts +46 -0
- package/dist/esm/Heartbeat.d.ts.map +1 -0
- package/dist/esm/Heartbeat.js +46 -0
- package/dist/esm/Heartbeat.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 +56 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/Ownership.d.ts +275 -0
- package/dist/esm/Ownership.d.ts.map +1 -0
- package/dist/esm/Ownership.js +299 -0
- package/dist/esm/Ownership.js.map +1 -0
- package/dist/esm/RunStore.d.ts +571 -0
- package/dist/esm/RunStore.d.ts.map +1 -0
- package/dist/esm/RunStore.js +1287 -0
- package/dist/esm/RunStore.js.map +1 -0
- package/dist/esm/RunStoreMetrics.d.ts +151 -0
- package/dist/esm/RunStoreMetrics.d.ts.map +1 -0
- package/dist/esm/RunStoreMetrics.js +120 -0
- package/dist/esm/RunStoreMetrics.js.map +1 -0
- package/dist/esm/index.d.ts +47 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/Boundary.d.ts +80 -0
- package/dist/esm/internal/Boundary.d.ts.map +1 -0
- package/dist/esm/internal/Boundary.js +109 -0
- package/dist/esm/internal/Boundary.js.map +1 -0
- package/dist/esm/internal/Companion.d.ts +44 -0
- package/dist/esm/internal/Companion.d.ts.map +1 -0
- package/dist/esm/internal/Companion.js +53 -0
- package/dist/esm/internal/Companion.js.map +1 -0
- package/dist/esm/internal/SpanOutcome.d.ts +37 -0
- package/dist/esm/internal/SpanOutcome.d.ts.map +1 -0
- package/dist/esm/internal/SpanOutcome.js +48 -0
- package/dist/esm/internal/SpanOutcome.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 +88 -0
- package/dist/esm/migrations/0001_initial.js.map +1 -0
- package/dist/esm/migrations/0002_lineage.d.ts +27 -0
- package/dist/esm/migrations/0002_lineage.d.ts.map +1 -0
- package/dist/esm/migrations/0002_lineage.js +33 -0
- package/dist/esm/migrations/0002_lineage.js.map +1 -0
- package/dist/esm/migrations/0003_execution_revisions.d.ts +15 -0
- package/dist/esm/migrations/0003_execution_revisions.d.ts.map +1 -0
- package/dist/esm/migrations/0003_execution_revisions.js +58 -0
- package/dist/esm/migrations/0003_execution_revisions.js.map +1 -0
- package/dist/esm/migrations/0004_waiting_request.d.ts +32 -0
- package/dist/esm/migrations/0004_waiting_request.d.ts.map +1 -0
- package/dist/esm/migrations/0004_waiting_request.js +37 -0
- package/dist/esm/migrations/0004_waiting_request.js.map +1 -0
- package/dist/esm/test/TestRunStore.d.ts +17 -0
- package/dist/esm/test/TestRunStore.d.ts.map +1 -0
- package/dist/esm/test/TestRunStore.js +19 -0
- package/dist/esm/test/TestRunStore.js.map +1 -0
- package/docs/README.md +142 -0
- package/docs/api.md +901 -0
- package/docs/concepts/durable-values.md +140 -0
- package/docs/concepts/execution-revisions.md +53 -0
- package/docs/concepts/fencing.md +146 -0
- package/docs/concepts/leases.md +154 -0
- package/docs/concepts/liveness-evidence.md +142 -0
- package/docs/guides/cancel-a-run.md +156 -0
- package/docs/guides/claim-and-finish-a-run.md +175 -0
- package/docs/guides/compose-the-stores.md +142 -0
- package/docs/guides/observe-outcomes.md +182 -0
- package/docs/guides/record-step-attempts.md +170 -0
- package/docs/guides/recover-a-stalled-run.md +173 -0
- package/docs/guides/testing.md +141 -0
- package/docs/installation.md +109 -0
- package/docs/quickstart.md +201 -0
- package/docs/troubleshooting.md +255 -0
- package/package.json +183 -3
- package/src/AttemptStore.ts +1195 -0
- package/src/Heartbeat.ts +47 -0
- package/src/Migrations.ts +59 -0
- package/src/Ownership.ts +407 -0
- package/src/RunStore.ts +2063 -0
- package/src/RunStoreMetrics.ts +165 -0
- package/src/index.ts +51 -0
- package/src/internal/Boundary.ts +130 -0
- package/src/internal/Companion.ts +64 -0
- package/src/internal/SpanOutcome.ts +71 -0
- package/src/migrations/0001_initial.ts +114 -0
- package/src/migrations/0002_lineage.ts +38 -0
- package/src/migrations/0003_execution_revisions.ts +69 -0
- package/src/migrations/0004_waiting_request.ts +39 -0
- package/src/test/TestRunStore.ts +22 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @smthrs/run-store
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Required `RunStore.Service` operations `lineage`, `latestRound`, and
|
|
8
|
+
`requestCancelLineage` resolve logical runs across trampoline rounds. Logical
|
|
9
|
+
cancellation is atomic and preserves terminal-round history and existing
|
|
10
|
+
request timestamps. Custom adapters must implement these operations; the
|
|
11
|
+
unavailable stub refuses them explicitly. No database migration is needed.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fixed the CommonJS build of the migration set. `migrations/0001_initial`
|
|
16
|
+
now exports `initial` and `migrations/0002_lineage` exports `lineage` as
|
|
17
|
+
named bindings, and every importer reads them by name, because esbuild's
|
|
18
|
+
Node interop for a default import of a sibling module resolved to the whole
|
|
19
|
+
exports object instead of the Effect, so `require("@smthrs/run-store")`
|
|
20
|
+
produced a migration set whose entries had no `pipe` and the migrator failed
|
|
21
|
+
at load.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `RunStore.claim`, `claimAndOwn`, `steal`, `heartbeat`, and `recoverClaim`
|
|
26
|
+
refuse a `nowMs` that runs ahead of the Effect `Clock` by more than
|
|
27
|
+
`heartbeatSkewAllowance` with `invalid_run`, so a runaway caller clock can
|
|
28
|
+
neither steal a run from a fresh owner nor pin a lease past the stale
|
|
29
|
+
cutoff. A reading behind the clock is still admitted, `requestCancel` keeps
|
|
30
|
+
its literal timestamp, and `claimedAtMs` fence tokens stay unbounded.
|
|
31
|
+
|
|
32
|
+
## [1.0.0-rc.0] - 2026-09-01
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Split out of `@smthrs/journal`: `RunStore`, `AttemptStore`, and `Ownership`
|
|
37
|
+
now live here, and the package owns the `flows_runs` and `flows_attempts`
|
|
38
|
+
migrations. No schema or behavioral change; see
|
|
39
|
+
`docs/pages/concepts/journal.md`.
|
|
40
|
+
- `Ownership` re-exports the `OwnerId` schema, which `@smthrs/journal` now
|
|
41
|
+
defines because it is the fence `emitDurable` accepts.
|
|
42
|
+
- Package-owned generated API documentation and an explicit Node test subpath.
|
|
43
|
+
- Added the `0002_lineage` migration and the `CreateOptions` trampoline pair
|
|
44
|
+
`lineageId` and `roundOrdinal`, read back on `RunRow`. Both absent means the
|
|
45
|
+
run is a lineage of one, read as round 0 of itself.
|
|
46
|
+
- Added `RequestCancelOutcome.Terminal`, so a request that lost to an ending
|
|
47
|
+
reports the status it lost to instead of claiming the row is gone.
|
|
48
|
+
- Added `ClaimAndOwnOutcome.EvidenceRequired` for a no-evidence claim against
|
|
49
|
+
another live owner, which previously asserted a `SnapshotChanged` comparison
|
|
50
|
+
that branch never performed.
|
|
51
|
+
- Added `StealOutcome.LivenessUnconfirmed` for evidence that does not match the
|
|
52
|
+
expected owner, host relation, or observation time before any comparison.
|
|
53
|
+
- Added the `RunStoreMetrics` module: the `flows_run_claims`,
|
|
54
|
+
`flows_run_transitions`, and `flows_run_heartbeats` counters plus one
|
|
55
|
+
attributed view per operation outcome, including `abandon_claim` and
|
|
56
|
+
`recover_claim`.
|
|
57
|
+
- Added `LivenessCheck`, `LivenessContext`, `leaseLiveness`,
|
|
58
|
+
`sameHostIncarnation`, and `sameHostPidProbe`, so a deployment supplies its
|
|
59
|
+
own liveness answer instead of the store guessing one.
|
|
60
|
+
- Added the `Heartbeat` leaf and its `@smthrs/run-store/Heartbeat` subpath, the
|
|
61
|
+
one place `heartbeatInterval`, `heartbeatStaleAfter`,
|
|
62
|
+
`heartbeatSkewAllowance`, and `heartbeatWriteTolerance` are related.
|
|
63
|
+
- Added `AttemptStore.Options` (`inProgressStates`, `maxCheckpointBytes`,
|
|
64
|
+
`putMode`) with `makeWith` and `layerWith`.
|
|
65
|
+
- Added `AttemptStoreError.method`, so a refusal names the operation that
|
|
66
|
+
produced it and its message reads `<code>: AttemptStore.<method>: <detail>`,
|
|
67
|
+
matching `RunStoreError`.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- Every caller timestamp is validated as a non-negative safe integer before it
|
|
72
|
+
reaches SQL, so `NaN`, a fraction, or a negative reading fails with a typed
|
|
73
|
+
`invalid_run` or `invalid_attempt` instead of a column-constraint error or a
|
|
74
|
+
success-shaped refusal. The two clock sources are unchanged and now written
|
|
75
|
+
down: evidence-bearing operations judge the caller's `nowMs` literally, while
|
|
76
|
+
`create`, `activate`, and `transitionOwned` stamp from the Effect `Clock`.
|
|
77
|
+
- Run and attempt state now crosses an inert-JSON boundary before every write
|
|
78
|
+
and after every read. It bounds shape, not size: accessors, `toJSON`, cycles,
|
|
79
|
+
malformed text, excess depth, and excess nodes are refused, while run state,
|
|
80
|
+
metadata, errors, and outcomes stay uncapped in bytes.
|
|
81
|
+
- Running snapshots require a complete owner and heartbeat pair; all other
|
|
82
|
+
statuses reject either field, and terminal attempts and runs cannot reopen.
|
|
83
|
+
- Same-host PID checks now treat only `ESRCH` as proof of death and fail closed
|
|
84
|
+
on invalid pids or unexpected host failures.
|
|
85
|
+
- `RunStore.heartbeat` writes `MAX(heartbeat_at_ms, now)`, so a pulse delayed
|
|
86
|
+
past a newer one cannot move the lease backwards; `AttemptStore.heartbeat`
|
|
87
|
+
does the same.
|
|
88
|
+
- `putMode: "upsert"` restates only a row that is still in progress. A finished
|
|
89
|
+
attempt is immutable, and a re-put of one resolves `ExistingSame` or
|
|
90
|
+
`Conflict` rather than reopening it.
|
|
91
|
+
- `put` compares an existing row structurally, so a re-put whose JSON carries
|
|
92
|
+
the same content in a different key order resolves `ExistingSame` instead of
|
|
93
|
+
`Conflict`.
|
|
94
|
+
- `transitionOwned` refuses `pending` as a target and validates its
|
|
95
|
+
`TransitionGuard` at runtime, so a malformed guard fails instead of silently
|
|
96
|
+
disabling the cancellation rule.
|
|
97
|
+
- `RunStoreErrorCode` no longer publishes `unknown`, a code no path in
|
|
98
|
+
`RunStore` produced.
|
|
99
|
+
- `./migrations/*` is closed in the export map: the migration modules are
|
|
100
|
+
implementation, reachable only through `Migrations.set`.
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- Fenced mutations now validate exact identifiers, monotonic timestamps, and
|
|
105
|
+
complete ownership tuples without retaining executable values in errors.
|
|
106
|
+
A failure cause carries the shape of `state_json`, never its text.
|
|
107
|
+
- Cancellation, claim recovery, and attempt update races now resolve to typed
|
|
108
|
+
contention or persistence outcomes instead of ambiguous missing rows.
|
|
109
|
+
- Identifiers carrying an unpaired UTF-16 surrogate are refused. SQLite maps
|
|
110
|
+
them to `U+FFFD`, so two distinct run ids collided on one durable key.
|
|
111
|
+
- `requestCancel` maps an undecodable persisted status to a typed
|
|
112
|
+
`decode_failed` failure instead of dying.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Cory and the Smithers Flows contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,196 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @smthrs/run-store
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Release candidate scope, host requirements and compatibility review are defined in the [library support policy](https://github.com/smithersai/smithers/blob/main/RELEASE_SUPPORT.md).
|
|
4
|
+
|
|
5
|
+
This package declares `effect` as an exact
|
|
6
|
+
`4.0.0-rc.115` peer dependency. Keep the application on that version so
|
|
7
|
+
all Smithers packages share one Effect runtime.
|
|
8
|
+
|
|
9
|
+
**Documentation:** https://run-store.smithers.sh
|
|
10
|
+
|
|
11
|
+
Durable run state and fenced ownership for long-running jobs, as
|
|
12
|
+
[Effect](https://effect.website) services. `@smthrs/run-store` keeps one SQL row
|
|
13
|
+
per run holding what the run is doing, which process owns it, and the executable
|
|
14
|
+
state a restart re-enters. It also arbitrates who is allowed to touch that row,
|
|
15
|
+
so a process that comes back from the dead cannot overwrite the one that
|
|
16
|
+
replaced it.
|
|
17
|
+
|
|
18
|
+
Two services, `RunStore` and `AttemptStore`, plus the `Ownership` liveness
|
|
19
|
+
checks and heartbeat supervision. Arbitration itself — who holds the claim,
|
|
20
|
+
who holds the lease, whether a fence still stands — is delegated to
|
|
21
|
+
[`@smthrs/journal`](https://journal.smithers.sh)'s injectable `Consensus`
|
|
22
|
+
strategy, the same one the journal fences its durable channel through; the
|
|
23
|
+
run row mirrors the strategy's answer in the same transaction. They carry no
|
|
24
|
+
database of their own: both are written against the driver-neutral
|
|
25
|
+
[`@smthrs/database`](https://database.smithers.sh) contract, so the same code
|
|
26
|
+
runs over a local SQLite file, over a server, or over an in-memory database in a
|
|
27
|
+
test.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
|
|
32
|
+
|
|
33
|
+
`effect` is a peer dependency at exactly `4.0.0-rc.115`. Two copies of `effect` in
|
|
34
|
+
one program are two sets of service tags, so a store layer built against one
|
|
35
|
+
copy cannot be provided to a program holding the other.
|
|
36
|
+
|
|
37
|
+
The package requires Node.js 26.4.0 or later and ships as both ESM and
|
|
38
|
+
CommonJS with TypeScript declarations. Only the in-memory test layer binds a
|
|
39
|
+
Node built-in; the root entry point names no driver and bundles for the
|
|
40
|
+
browser.
|
|
41
|
+
|
|
42
|
+
## Take a run, do the work, settle it
|
|
43
|
+
|
|
44
|
+
This program creates a run, claims ownership of it, and finishes it. The stores
|
|
45
|
+
are the production ones; `TestRunStore.layer` provides them over a migrated
|
|
46
|
+
in-memory database, so the file runs with no configuration.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { RunStore } from "@smthrs/run-store"
|
|
50
|
+
import type { OwnerId } from "@smthrs/run-store/Ownership"
|
|
51
|
+
import * as TestRunStore from "@smthrs/run-store/test/TestRunStore"
|
|
52
|
+
import * as Clock from "effect/Clock"
|
|
53
|
+
import * as Effect from "effect/Effect"
|
|
54
|
+
|
|
55
|
+
const owner: OwnerId = { hostId: "worker-1", pid: 4102, nonce: "9c31-af02" }
|
|
56
|
+
|
|
57
|
+
const program = Effect.gen(function*() {
|
|
58
|
+
const runs = yield* RunStore.RunStore
|
|
59
|
+
|
|
60
|
+
yield* runs.create("build-42", JSON.stringify({ step: "checkout" }))
|
|
61
|
+
|
|
62
|
+
const nowMs = yield* Clock.currentTimeMillis
|
|
63
|
+
const taken = yield* runs.claimAndOwn(
|
|
64
|
+
"build-42",
|
|
65
|
+
{ status: "pending", owner: null, heartbeatAtMs: null },
|
|
66
|
+
owner,
|
|
67
|
+
nowMs
|
|
68
|
+
)
|
|
69
|
+
if (taken._tag !== "Activated") return `another process has it: ${taken._tag}`
|
|
70
|
+
|
|
71
|
+
// Do the work here, heartbeating while it runs.
|
|
72
|
+
|
|
73
|
+
const settled = yield* runs.transitionOwned(
|
|
74
|
+
"build-42",
|
|
75
|
+
owner,
|
|
76
|
+
"completed",
|
|
77
|
+
JSON.stringify({ step: "done" })
|
|
78
|
+
)
|
|
79
|
+
return `run ended as ${settled._tag}`
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
console.log(
|
|
83
|
+
await Effect.runPromise(
|
|
84
|
+
program.pipe(Effect.provide(TestRunStore.layer), Effect.scoped)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
run ended as Transitioned
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The second argument to `claimAndOwn` is the row as you last read it, restated as
|
|
94
|
+
the three fields a claim guards. The third argument is the new owner. The claim
|
|
95
|
+
is admitted only while the row still matches. If a peer took the run between
|
|
96
|
+
your read and your write, this caller loses the race and leaves the peer's
|
|
97
|
+
ownership unchanged.
|
|
98
|
+
|
|
99
|
+
## What the stores guarantee
|
|
100
|
+
|
|
101
|
+
- **One row is the authority.** A restarted process reads the run row for its
|
|
102
|
+
status, owner, heartbeat, cancellation request, and the executable state to
|
|
103
|
+
re-enter, instead of replaying a log to work out where it was.
|
|
104
|
+
- **Every owned write carries a fence.** An owner identity is
|
|
105
|
+
`{ hostId, pid, nonce }`, and every owned mutation asks the `Consensus`
|
|
106
|
+
strategy whether that identity still holds the run inside the same
|
|
107
|
+
serialized write transaction as the mutation it guards. There is no window
|
|
108
|
+
between checking ownership and using it, so two processes cannot both win.
|
|
109
|
+
- **Competition is a value, not an error.** Losing a race returns
|
|
110
|
+
`AlreadyClaimed`, `HeartbeatFresh`, or `FenceLost` as an ordinary success
|
|
111
|
+
value you branch on. The error channel is reserved for real defects: invalid
|
|
112
|
+
input, a corrupt row, a constraint violation, a database failure.
|
|
113
|
+
- **A takeover needs evidence, not just a timeout.** An expired lease says the
|
|
114
|
+
owner stopped writing, not that it stopped working. `steal` and `recoverClaim`
|
|
115
|
+
additionally require `LivenessEvidence` about the recorded owner, bound to the
|
|
116
|
+
exact instant it is spent, and every unknown probe answer is read as life.
|
|
117
|
+
- **Step attempts inherit the run's fence.** `AttemptStore` records when each
|
|
118
|
+
execution of a step started, the checkpoints it wrote, and how it ended, and
|
|
119
|
+
refuses every write from a process that no longer owns the run.
|
|
120
|
+
- **Persisted values are copied inert.** Run state, checkpoints, errors,
|
|
121
|
+
outcomes, and metadata are walked into a frozen detached copy before
|
|
122
|
+
persistence can yield, so a getter, a cycle, or a `toJSON` method is refused
|
|
123
|
+
rather than persisted, and rows are validated again on the way out.
|
|
124
|
+
|
|
125
|
+
## Public API
|
|
126
|
+
|
|
127
|
+
The root exports each module as a namespace, and each is also importable from
|
|
128
|
+
the matching `@smthrs/run-store/*` subpath. The
|
|
129
|
+
[API reference](https://run-store.smithers.sh/reference/api/) lists every export.
|
|
130
|
+
|
|
131
|
+
| Namespace | What it holds |
|
|
132
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
+
| `RunStore` | The run lifecycle: create, read, cancel, the six claim operations, heartbeat, and owned transitions, with `layer` over `SqlConsensus` and `layerWith` over an injected strategy. |
|
|
134
|
+
| `AttemptStore` | Fenced step attempts: start, heartbeat with a checkpoint, finish, patch, and read, with its policy options. |
|
|
135
|
+
| `Ownership` | `OwnerId` and `LivenessEvidence` from the journal, fail-closed pid probing, the lease checks, and the heartbeat supervision loop. |
|
|
136
|
+
| `RunStoreMetrics` | `flows_run_claims`, `flows_run_heartbeats`, and `flows_run_transitions`, attributed by operation and outcome. |
|
|
137
|
+
| `Migrations` | The `flows_runs` and `flows_attempts` migration set, and the runner and layer that install the journal's set ahead of it. |
|
|
138
|
+
|
|
139
|
+
The root and those subpaths are driver-neutral and bundle for the browser.
|
|
140
|
+
Two subpaths are not namespaces of the root:
|
|
141
|
+
|
|
142
|
+
| Import | Platform | What it holds |
|
|
143
|
+
| ------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `@smthrs/run-store/Heartbeat` | any | The lease durations `heartbeatInterval`, `heartbeatStaleAfter`, `heartbeatSkewAllowance`, and `heartbeatWriteTolerance`, defined by `@smthrs/journal`'s `Consensus` and also re-exported from `Ownership`. A consumer that needs only the durations imports this leaf and pulls in no store. |
|
|
145
|
+
| `@smthrs/run-store/test/TestRunStore` | Node | `layer`, providing migrated in-memory `RunStore` and `AttemptStore` services. |
|
|
146
|
+
|
|
147
|
+
Migration implementations and package internals are blocked in the export map:
|
|
148
|
+
the migration set is the contract, not the numbered files inside it.
|
|
149
|
+
|
|
150
|
+
## Where the run store sits
|
|
151
|
+
|
|
152
|
+
`@smthrs/run-store` is one of the storage services inside
|
|
153
|
+
[`@smthrs/flows`](https://flows.smithers.sh), the package that carries the whole
|
|
154
|
+
Smithers durable flow engine in a single dependency. Flow authors never call it.
|
|
155
|
+
|
|
156
|
+
Reach for it directly when you are building the host rather than the flow: a
|
|
157
|
+
scheduler, a worker pool, or any service that hands durable work between
|
|
158
|
+
processes and needs ownership arbitration it can trust. Most hosts do not wire
|
|
159
|
+
it by hand even then.
|
|
160
|
+
[`@smthrs/engine-store`](https://engine-store.smithers.sh) already composes
|
|
161
|
+
these stores with the journal, the step cache, and the engine's own state into
|
|
162
|
+
one storage ladder.
|
|
163
|
+
|
|
164
|
+
The stores hold executable state. History lives in
|
|
165
|
+
[`@smthrs/journal`](https://journal.smithers.sh), and the two halves stay
|
|
166
|
+
consistent because both write through the same `@smthrs/database`
|
|
167
|
+
`DurableWriter`: `Journal.transact` commits a state projection and its durable
|
|
168
|
+
events in one transaction.
|
|
169
|
+
|
|
170
|
+
## Documentation
|
|
171
|
+
|
|
172
|
+
Full documentation is at
|
|
173
|
+
[run-store.smithers.sh](https://run-store.smithers.sh):
|
|
174
|
+
|
|
175
|
+
- [Installation](https://run-store.smithers.sh/installation/)
|
|
176
|
+
- [Quickstart](https://run-store.smithers.sh/quickstart/): one run through its
|
|
177
|
+
whole lifecycle, including a step attempt.
|
|
178
|
+
- [Fencing and ownership](https://run-store.smithers.sh/concepts/fencing/): the
|
|
179
|
+
compare-and-swap every write goes through.
|
|
180
|
+
- [The heartbeat lease](https://run-store.smithers.sh/concepts/leases/): the
|
|
181
|
+
four durations, the two clocks that stamp one row, and what a wall-clock lease
|
|
182
|
+
cannot promise.
|
|
183
|
+
- [Liveness evidence](https://run-store.smithers.sh/concepts/liveness-evidence/):
|
|
184
|
+
what admits a takeover.
|
|
185
|
+
- [Durable values](https://run-store.smithers.sh/concepts/durable-values/): the
|
|
186
|
+
persistence boundary and its shape limits.
|
|
187
|
+
- [Execution revisions](https://run-store.smithers.sh/concepts/execution-revisions/):
|
|
188
|
+
database identity, monotonic changes, retained tombstones and cancellation acknowledgement.
|
|
189
|
+
- [API reference](https://run-store.smithers.sh/reference/api/): every public
|
|
190
|
+
export.
|
|
191
|
+
- [Troubleshooting](https://run-store.smithers.sh/troubleshooting/): each
|
|
192
|
+
refused outcome and typed error, and what to change.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|