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