@smthrs/triggers 0.0.0-stage → 1.0.0-rc.4
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 +219 -0
- package/LICENSE +21 -0
- package/README.md +109 -2
- package/dist/cjs/CatchUp.d.ts +25 -0
- package/dist/cjs/CatchUp.d.ts.map +1 -0
- package/dist/cjs/CatchUp.js +72 -0
- package/dist/cjs/CatchUp.js.map +7 -0
- package/dist/cjs/Channel.d.ts +108 -0
- package/dist/cjs/Channel.d.ts.map +1 -0
- package/dist/cjs/Channel.js +25 -0
- package/dist/cjs/Channel.js.map +7 -0
- package/dist/cjs/ClaimDecision.d.ts +183 -0
- package/dist/cjs/ClaimDecision.d.ts.map +1 -0
- package/dist/cjs/ClaimDecision.js +144 -0
- package/dist/cjs/ClaimDecision.js.map +7 -0
- package/dist/cjs/Cron.d.ts +91 -0
- package/dist/cjs/Cron.d.ts.map +1 -0
- package/dist/cjs/Cron.js +108 -0
- package/dist/cjs/Cron.js.map +7 -0
- package/dist/cjs/DispatchReader.d.ts +77 -0
- package/dist/cjs/DispatchReader.d.ts.map +1 -0
- package/dist/cjs/DispatchReader.js +121 -0
- package/dist/cjs/DispatchReader.js.map +7 -0
- package/dist/cjs/Overlap.d.ts +40 -0
- package/dist/cjs/Overlap.d.ts.map +1 -0
- package/dist/cjs/Overlap.js +37 -0
- package/dist/cjs/Overlap.js.map +7 -0
- package/dist/cjs/Schedule.d.ts +96 -0
- package/dist/cjs/Schedule.d.ts.map +1 -0
- package/dist/cjs/Schedule.js +61 -0
- package/dist/cjs/Schedule.js.map +7 -0
- package/dist/cjs/Scheduler.d.ts +248 -0
- package/dist/cjs/Scheduler.d.ts.map +1 -0
- package/dist/cjs/Scheduler.js +647 -0
- package/dist/cjs/Scheduler.js.map +7 -0
- package/dist/cjs/SqlTriggerStore.d.ts +29 -0
- package/dist/cjs/SqlTriggerStore.d.ts.map +1 -0
- package/dist/cjs/SqlTriggerStore.js +483 -0
- package/dist/cjs/SqlTriggerStore.js.map +7 -0
- package/dist/cjs/Trigger.d.ts +81 -0
- package/dist/cjs/Trigger.d.ts.map +1 -0
- package/dist/cjs/Trigger.js +54 -0
- package/dist/cjs/Trigger.js.map +7 -0
- package/dist/cjs/TriggerError.d.ts +58 -0
- package/dist/cjs/TriggerError.d.ts.map +1 -0
- package/dist/cjs/TriggerError.js +83 -0
- package/dist/cjs/TriggerError.js.map +7 -0
- package/dist/cjs/TriggerStore.d.ts +411 -0
- package/dist/cjs/TriggerStore.d.ts.map +1 -0
- package/dist/cjs/TriggerStore.js +127 -0
- package/dist/cjs/TriggerStore.js.map +7 -0
- package/dist/cjs/Webhook.d.ts +118 -0
- package/dist/cjs/Webhook.d.ts.map +1 -0
- package/dist/cjs/Webhook.js +153 -0
- package/dist/cjs/Webhook.js.map +7 -0
- package/dist/cjs/index.d.ts +30 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +57 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ActiveRuns.d.ts +51 -0
- package/dist/cjs/internal/ActiveRuns.d.ts.map +1 -0
- package/dist/cjs/internal/ActiveRuns.js +56 -0
- package/dist/cjs/internal/ActiveRuns.js.map +7 -0
- package/dist/cjs/internal/DueOccurrences.d.ts +32 -0
- package/dist/cjs/internal/DueOccurrences.d.ts.map +1 -0
- package/dist/cjs/internal/DueOccurrences.js +80 -0
- package/dist/cjs/internal/DueOccurrences.js.map +7 -0
- package/dist/cjs/internal/WallClock.d.ts +59 -0
- package/dist/cjs/internal/WallClock.d.ts.map +1 -0
- package/dist/cjs/internal/WallClock.js +132 -0
- package/dist/cjs/internal/WallClock.js.map +7 -0
- package/dist/cjs/migrations/0001_triggers.d.ts +15 -0
- package/dist/cjs/migrations/0001_triggers.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_triggers.js +64 -0
- package/dist/cjs/migrations/0001_triggers.js.map +7 -0
- package/dist/cjs/migrations/0002_reservation_lease.d.ts +15 -0
- package/dist/cjs/migrations/0002_reservation_lease.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_reservation_lease.js +41 -0
- package/dist/cjs/migrations/0002_reservation_lease.js.map +7 -0
- package/dist/cjs/migrations/0003_heartbeat.d.ts +16 -0
- package/dist/cjs/migrations/0003_heartbeat.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_heartbeat.js +44 -0
- package/dist/cjs/migrations/0003_heartbeat.js.map +7 -0
- package/dist/cjs/migrations/0004_fire_run_index.d.ts +17 -0
- package/dist/cjs/migrations/0004_fire_run_index.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_fire_run_index.js +40 -0
- package/dist/cjs/migrations/0004_fire_run_index.js.map +7 -0
- package/dist/cjs/migrations/index.d.ts +33 -0
- package/dist/cjs/migrations/index.d.ts.map +1 -0
- package/dist/cjs/migrations/index.js +50 -0
- package/dist/cjs/migrations/index.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestTriggers.d.ts +12 -0
- package/dist/cjs/test/TestTriggers.d.ts.map +1 -0
- package/dist/cjs/test/TestTriggers.js +421 -0
- package/dist/cjs/test/TestTriggers.js.map +7 -0
- package/dist/esm/CatchUp.d.ts +25 -0
- package/dist/esm/CatchUp.d.ts.map +1 -0
- package/dist/esm/CatchUp.js +52 -0
- package/dist/esm/CatchUp.js.map +1 -0
- package/dist/esm/Channel.d.ts +108 -0
- package/dist/esm/Channel.d.ts.map +1 -0
- package/dist/esm/Channel.js +19 -0
- package/dist/esm/Channel.js.map +1 -0
- package/dist/esm/ClaimDecision.d.ts +183 -0
- package/dist/esm/ClaimDecision.d.ts.map +1 -0
- package/dist/esm/ClaimDecision.js +172 -0
- package/dist/esm/ClaimDecision.js.map +1 -0
- package/dist/esm/Cron.d.ts +91 -0
- package/dist/esm/Cron.d.ts.map +1 -0
- package/dist/esm/Cron.js +132 -0
- package/dist/esm/Cron.js.map +1 -0
- package/dist/esm/DispatchReader.d.ts +77 -0
- package/dist/esm/DispatchReader.d.ts.map +1 -0
- package/dist/esm/DispatchReader.js +152 -0
- package/dist/esm/DispatchReader.js.map +1 -0
- package/dist/esm/Overlap.d.ts +40 -0
- package/dist/esm/Overlap.d.ts.map +1 -0
- package/dist/esm/Overlap.js +31 -0
- package/dist/esm/Overlap.js.map +1 -0
- package/dist/esm/Schedule.d.ts +96 -0
- package/dist/esm/Schedule.d.ts.map +1 -0
- package/dist/esm/Schedule.js +73 -0
- package/dist/esm/Schedule.js.map +1 -0
- package/dist/esm/Scheduler.d.ts +248 -0
- package/dist/esm/Scheduler.d.ts.map +1 -0
- package/dist/esm/Scheduler.js +734 -0
- package/dist/esm/Scheduler.js.map +1 -0
- package/dist/esm/SqlTriggerStore.d.ts +29 -0
- package/dist/esm/SqlTriggerStore.d.ts.map +1 -0
- package/dist/esm/SqlTriggerStore.js +483 -0
- package/dist/esm/SqlTriggerStore.js.map +1 -0
- package/dist/esm/Trigger.d.ts +81 -0
- package/dist/esm/Trigger.d.ts.map +1 -0
- package/dist/esm/Trigger.js +56 -0
- package/dist/esm/Trigger.js.map +1 -0
- package/dist/esm/TriggerError.d.ts +58 -0
- package/dist/esm/TriggerError.d.ts.map +1 -0
- package/dist/esm/TriggerError.js +110 -0
- package/dist/esm/TriggerError.js.map +1 -0
- package/dist/esm/TriggerStore.d.ts +411 -0
- package/dist/esm/TriggerStore.d.ts.map +1 -0
- package/dist/esm/TriggerStore.js +209 -0
- package/dist/esm/TriggerStore.js.map +1 -0
- package/dist/esm/Webhook.d.ts +118 -0
- package/dist/esm/Webhook.d.ts.map +1 -0
- package/dist/esm/Webhook.js +186 -0
- package/dist/esm/Webhook.js.map +1 -0
- package/dist/esm/index.d.ts +30 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +30 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ActiveRuns.d.ts +51 -0
- package/dist/esm/internal/ActiveRuns.d.ts.map +1 -0
- package/dist/esm/internal/ActiveRuns.js +34 -0
- package/dist/esm/internal/ActiveRuns.js.map +1 -0
- package/dist/esm/internal/DueOccurrences.d.ts +32 -0
- package/dist/esm/internal/DueOccurrences.d.ts.map +1 -0
- package/dist/esm/internal/DueOccurrences.js +55 -0
- package/dist/esm/internal/DueOccurrences.js.map +1 -0
- package/dist/esm/internal/WallClock.d.ts +59 -0
- package/dist/esm/internal/WallClock.d.ts.map +1 -0
- package/dist/esm/internal/WallClock.js +155 -0
- package/dist/esm/internal/WallClock.js.map +1 -0
- package/dist/esm/migrations/0001_triggers.d.ts +15 -0
- package/dist/esm/migrations/0001_triggers.d.ts.map +1 -0
- package/dist/esm/migrations/0001_triggers.js +42 -0
- package/dist/esm/migrations/0001_triggers.js.map +1 -0
- package/dist/esm/migrations/0002_reservation_lease.d.ts +15 -0
- package/dist/esm/migrations/0002_reservation_lease.d.ts.map +1 -0
- package/dist/esm/migrations/0002_reservation_lease.js +19 -0
- package/dist/esm/migrations/0002_reservation_lease.js.map +1 -0
- package/dist/esm/migrations/0003_heartbeat.d.ts +16 -0
- package/dist/esm/migrations/0003_heartbeat.d.ts.map +1 -0
- package/dist/esm/migrations/0003_heartbeat.js +23 -0
- package/dist/esm/migrations/0003_heartbeat.js.map +1 -0
- package/dist/esm/migrations/0004_fire_run_index.d.ts +17 -0
- package/dist/esm/migrations/0004_fire_run_index.d.ts.map +1 -0
- package/dist/esm/migrations/0004_fire_run_index.js +20 -0
- package/dist/esm/migrations/0004_fire_run_index.js.map +1 -0
- package/dist/esm/migrations/index.d.ts +33 -0
- package/dist/esm/migrations/index.d.ts.map +1 -0
- package/dist/esm/migrations/index.js +37 -0
- package/dist/esm/migrations/index.js.map +1 -0
- package/dist/esm/test/TestTriggers.d.ts +12 -0
- package/dist/esm/test/TestTriggers.d.ts.map +1 -0
- package/dist/esm/test/TestTriggers.js +439 -0
- package/dist/esm/test/TestTriggers.js.map +1 -0
- package/docs/README.md +140 -0
- package/docs/api.md +1186 -0
- package/docs/concepts/channels.md +103 -0
- package/docs/concepts/claim-protocol.md +229 -0
- package/docs/concepts/policies.md +124 -0
- package/docs/concepts/schedules.md +134 -0
- package/docs/guides/choose-a-policy.md +113 -0
- package/docs/guides/declare-a-trigger.md +136 -0
- package/docs/guides/ingest-a-webhook.md +164 -0
- package/docs/guides/run-the-scheduler.md +173 -0
- package/docs/guides/testing.md +157 -0
- package/docs/installation.md +81 -0
- package/docs/quickstart.md +163 -0
- package/docs/troubleshooting.md +281 -0
- package/package.json +241 -3
- package/src/CatchUp.ts +73 -0
- package/src/Channel.ts +121 -0
- package/src/ClaimDecision.ts +303 -0
- package/src/Cron.ts +173 -0
- package/src/DispatchReader.ts +179 -0
- package/src/Overlap.ts +53 -0
- package/src/Schedule.ts +112 -0
- package/src/Scheduler.ts +1154 -0
- package/src/SqlTriggerStore.ts +638 -0
- package/src/Trigger.ts +84 -0
- package/src/TriggerError.ts +129 -0
- package/src/TriggerStore.ts +514 -0
- package/src/Webhook.ts +270 -0
- package/src/index.ts +30 -0
- package/src/internal/ActiveRuns.ts +83 -0
- package/src/internal/DueOccurrences.ts +101 -0
- package/src/internal/WallClock.ts +172 -0
- package/src/migrations/0001_triggers.ts +43 -0
- package/src/migrations/0002_reservation_lease.ts +20 -0
- package/src/migrations/0003_heartbeat.ts +24 -0
- package/src/migrations/0004_fire_run_index.ts +21 -0
- package/src/migrations/index.ts +40 -0
- package/src/test/TestTriggers.ts +563 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable trigger declaration and fire store.
|
|
3
|
+
*
|
|
4
|
+
* @see packages/smithers/agent/triggers/docs/api.md
|
|
5
|
+
*
|
|
6
|
+
* @since 0.1.0
|
|
7
|
+
*/
|
|
8
|
+
import * as Context from "effect/Context";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import type * as Option from "effect/Option";
|
|
12
|
+
import * as Decode from "effect/Result";
|
|
13
|
+
import type { Action } from "./Overlap.ts";
|
|
14
|
+
import type { Trigger } from "./Trigger.ts";
|
|
15
|
+
import { TriggerError } from "./TriggerError.ts";
|
|
16
|
+
/**
|
|
17
|
+
* A stored trigger, with the revision that fences concurrent edits and
|
|
18
|
+
* when it last fired.
|
|
19
|
+
*
|
|
20
|
+
* @category models
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
*/
|
|
23
|
+
export interface Registered extends Trigger {
|
|
24
|
+
readonly revision: number;
|
|
25
|
+
readonly lastFiredAt?: number | undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One scheduled occurrence of a trigger, addressed by its occurrence
|
|
29
|
+
* number so a retry cannot fire it twice.
|
|
30
|
+
*
|
|
31
|
+
* @category models
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export interface Fire {
|
|
35
|
+
readonly triggerId: string;
|
|
36
|
+
readonly occurrence: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A {@link Fire} together with the revision it was computed from and whether
|
|
40
|
+
* it is resuming a buffered occurrence.
|
|
41
|
+
*
|
|
42
|
+
* The overlap policy is deliberately absent. A claim applies the policy stored
|
|
43
|
+
* on the trigger row, read inside the same transaction, so a caller holding a
|
|
44
|
+
* stale snapshot cannot fire a trigger that has since been disabled, cannot
|
|
45
|
+
* point it at a different flow, and cannot supersede a run that the stored
|
|
46
|
+
* declaration says to leave alone. `expectedRevision` is the fence: a claim
|
|
47
|
+
* whose revision no longer matches the row is refused with `revision_mismatch`
|
|
48
|
+
* so the caller re-reads before deciding again.
|
|
49
|
+
*
|
|
50
|
+
* @category models
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
*/
|
|
53
|
+
export interface ClaimFire extends Fire {
|
|
54
|
+
readonly expectedRevision: number;
|
|
55
|
+
readonly resumeBuffered?: boolean | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The outcome of claiming an occurrence: either another worker holds it, or
|
|
59
|
+
* this caller does and must take `action`.
|
|
60
|
+
*
|
|
61
|
+
* A claim that hands the caller work to launch always names the reservation it
|
|
62
|
+
* wrote against the trigger row, so the caller has an id to release. A claim
|
|
63
|
+
* that only records a decision has no reservation and names none: the two
|
|
64
|
+
* shapes are separate so a caller cannot read a reservation id that was never
|
|
65
|
+
* written.
|
|
66
|
+
*
|
|
67
|
+
* @category models
|
|
68
|
+
* @since 0.1.0
|
|
69
|
+
*/
|
|
70
|
+
export type Claim = {
|
|
71
|
+
readonly claimed: false;
|
|
72
|
+
} | {
|
|
73
|
+
readonly claimed: true;
|
|
74
|
+
readonly action: Extract<Action, "skip" | "buffer">;
|
|
75
|
+
} | {
|
|
76
|
+
readonly claimed: true;
|
|
77
|
+
readonly action: Extract<Action, "fire" | "supersede">;
|
|
78
|
+
readonly reservationId: string;
|
|
79
|
+
readonly activeRunId?: string | undefined;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Time after which an uncommitted launch reservation may be reclaimed.
|
|
83
|
+
*
|
|
84
|
+
* Both store implementations use this value so swapping the test store for
|
|
85
|
+
* the SQL store cannot change recovery timing.
|
|
86
|
+
*
|
|
87
|
+
* @category constants
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
*/
|
|
90
|
+
export declare const reservationLeaseMs: number;
|
|
91
|
+
/**
|
|
92
|
+
* The prefix marking an `active_run_id` that is a launch reservation rather
|
|
93
|
+
* than a run the runtime knows about.
|
|
94
|
+
*
|
|
95
|
+
* A reservation is written by the claim and replaced by the real run id once
|
|
96
|
+
* the launch reports one, so both stores and the scheduler read this prefix.
|
|
97
|
+
* It is a contract between them and lives here rather than being re-spelled at
|
|
98
|
+
* each site.
|
|
99
|
+
*
|
|
100
|
+
* @category constants
|
|
101
|
+
* @since 0.1.0
|
|
102
|
+
*/
|
|
103
|
+
export declare const reservationPrefix = "trigger-reservation:";
|
|
104
|
+
/**
|
|
105
|
+
* The reservation id one occurrence of one trigger claims.
|
|
106
|
+
*
|
|
107
|
+
* @category constructors
|
|
108
|
+
* @since 0.1.0
|
|
109
|
+
*/
|
|
110
|
+
export declare const reservationId: (triggerId: string, occurrence: number, attempt?: string) => string;
|
|
111
|
+
/**
|
|
112
|
+
* Whether a stored `active_run_id` is a launch reservation.
|
|
113
|
+
*
|
|
114
|
+
* @category predicates
|
|
115
|
+
* @since 0.1.0
|
|
116
|
+
*/
|
|
117
|
+
export declare const isReservation: (runId: string | undefined) => boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Reads the occurrence encoded in a launch reservation.
|
|
120
|
+
*
|
|
121
|
+
* @category getters
|
|
122
|
+
* @since 0.1.0
|
|
123
|
+
*/
|
|
124
|
+
export declare const reservationOccurrence: (runId: string) => number | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* How a claimed occurrence ended.
|
|
127
|
+
*
|
|
128
|
+
* @category models
|
|
129
|
+
* @since 0.1.0
|
|
130
|
+
*/
|
|
131
|
+
export type Outcome = "launched" | "completed" | "skipped" | "buffered" | "superseded" | "failed";
|
|
132
|
+
/**
|
|
133
|
+
* The reported end of one occurrence, with the run it started when it
|
|
134
|
+
* launched one.
|
|
135
|
+
*
|
|
136
|
+
* @category models
|
|
137
|
+
* @since 0.1.0
|
|
138
|
+
*/
|
|
139
|
+
export type Result = Fire & {
|
|
140
|
+
readonly error?: string | undefined;
|
|
141
|
+
} & ({
|
|
142
|
+
readonly outcome: "launched";
|
|
143
|
+
readonly runId: string;
|
|
144
|
+
readonly reservationId: string;
|
|
145
|
+
} | {
|
|
146
|
+
readonly outcome: Exclude<Outcome, "launched">;
|
|
147
|
+
readonly runId?: string | undefined;
|
|
148
|
+
readonly reservationId?: string | undefined;
|
|
149
|
+
});
|
|
150
|
+
/**
|
|
151
|
+
* Refuses results that no longer own a permissible fire transition.
|
|
152
|
+
* Launched run ids are validated at runtime for adapters outside TypeScript.
|
|
153
|
+
*
|
|
154
|
+
* @category predicates
|
|
155
|
+
* @since 1.0.0-rc.0
|
|
156
|
+
*/
|
|
157
|
+
export declare const resultRefusal: (result: Result, fire: FireRecord | undefined, activeRunId: string | undefined) => TriggerError | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* One row of the fire ledger: a claimed occurrence and what became of it.
|
|
160
|
+
*
|
|
161
|
+
* `outcome` is `null` between a claim and its `recordResult`, the window in
|
|
162
|
+
* which a launch is reserved but not yet reported.
|
|
163
|
+
*
|
|
164
|
+
* @category models
|
|
165
|
+
* @since 1.0.0-rc.0
|
|
166
|
+
*/
|
|
167
|
+
export interface FireRecord extends Fire {
|
|
168
|
+
readonly outcome: Outcome | null;
|
|
169
|
+
readonly runId?: string | undefined;
|
|
170
|
+
readonly error?: string | undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A ledger query. Every filter narrows; `cursor` is the last record of the
|
|
174
|
+
* previous page, so the page after it holds only older records; `limit` caps
|
|
175
|
+
* a page and must be a positive safe integer when stated. With no limit the
|
|
176
|
+
* whole ledger answers in one page.
|
|
177
|
+
*
|
|
178
|
+
* @category models
|
|
179
|
+
* @since 1.0.0-rc.0
|
|
180
|
+
*/
|
|
181
|
+
export interface HistoryQuery {
|
|
182
|
+
readonly triggerId?: string | undefined;
|
|
183
|
+
readonly runId?: string | undefined;
|
|
184
|
+
readonly outcome?: Outcome | undefined;
|
|
185
|
+
readonly cursor?: Fire | undefined;
|
|
186
|
+
readonly limit?: number | undefined;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* One page of the ledger, newest occurrence first, with the cursor that
|
|
190
|
+
* addresses the next page when the limit cut the page short.
|
|
191
|
+
*
|
|
192
|
+
* @category models
|
|
193
|
+
* @since 1.0.0-rc.0
|
|
194
|
+
*/
|
|
195
|
+
export interface HistoryPage {
|
|
196
|
+
readonly items: ReadonlyArray<FireRecord>;
|
|
197
|
+
readonly nextCursor?: Fire | undefined;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* What one trigger holds right now: the run or launch reservation on its row
|
|
201
|
+
* and the occurrence buffered behind it. A plain read; `activeRun` is the read
|
|
202
|
+
* that expires a stale reservation.
|
|
203
|
+
*
|
|
204
|
+
* @category models
|
|
205
|
+
* @since 1.0.0-rc.0
|
|
206
|
+
*/
|
|
207
|
+
export interface Held {
|
|
208
|
+
readonly activeRunId?: string | undefined;
|
|
209
|
+
readonly pendingAt?: number | undefined;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* One row of a listing: what the trigger holds right now, and either the
|
|
213
|
+
* declaration the row decoded to or the failure it could not be read through.
|
|
214
|
+
*
|
|
215
|
+
* `trigger` is an `effect/Result`: narrow it with `Result.isFailure` before
|
|
216
|
+
* reading the declaration. An undecodable row is isolated here rather than
|
|
217
|
+
* failing the whole listing,
|
|
218
|
+
* so one corrupt input cannot stop every other trigger from being scheduled.
|
|
219
|
+
* The held state travels with the row because a scheduler tick would otherwise
|
|
220
|
+
* ask for it again per trigger, once to expire a reservation that is not there
|
|
221
|
+
* and once for a buffered occurrence that is not there either.
|
|
222
|
+
*
|
|
223
|
+
* @category models
|
|
224
|
+
* @since 1.0.0-rc.0
|
|
225
|
+
*/
|
|
226
|
+
export interface Listed extends Held {
|
|
227
|
+
readonly triggerId: string;
|
|
228
|
+
readonly trigger: Decode.Result<Registered, TriggerError>;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* A listed row for a declaration that decoded, holding what the trigger holds
|
|
232
|
+
* right now. Both stores build their rows through this, so a caller composing
|
|
233
|
+
* a store or a scheduler fixture of its own does not restate the shape.
|
|
234
|
+
*
|
|
235
|
+
* @category constructors
|
|
236
|
+
* @since 1.0.0-rc.0
|
|
237
|
+
*/
|
|
238
|
+
export declare const listed: (trigger: Registered, held?: Held) => Listed;
|
|
239
|
+
/**
|
|
240
|
+
* The last poll one scheduler host recorded.
|
|
241
|
+
*
|
|
242
|
+
* @category models
|
|
243
|
+
* @since 1.0.0-rc.0
|
|
244
|
+
*/
|
|
245
|
+
export interface Heartbeat {
|
|
246
|
+
readonly host: string;
|
|
247
|
+
readonly tickedAt: number;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Validates a {@link HistoryQuery} limit: absent, or a positive safe integer.
|
|
251
|
+
* Both stores apply this before reading so swapping one for the other cannot
|
|
252
|
+
* change which queries are refused.
|
|
253
|
+
*
|
|
254
|
+
* @category constructors
|
|
255
|
+
* @since 1.0.0-rc.0
|
|
256
|
+
*/
|
|
257
|
+
export declare const historyLimit: (limit: number | undefined) => Effect.Effect<number | undefined, TriggerError>;
|
|
258
|
+
/**
|
|
259
|
+
* Validates a prune cutoff: any safe integer. Both stores apply this before
|
|
260
|
+
* deleting so swapping one for the other cannot change which cutoffs are
|
|
261
|
+
* refused.
|
|
262
|
+
*
|
|
263
|
+
* @category constructors
|
|
264
|
+
* @since 1.0.0-rc.0
|
|
265
|
+
*/
|
|
266
|
+
export declare const pruneCutoff: (olderThan: number) => Effect.Effect<number, TriggerError>;
|
|
267
|
+
/**
|
|
268
|
+
* Newest occurrence first; equal occurrences of different triggers order by
|
|
269
|
+
* descending trigger id so a cursor names one position.
|
|
270
|
+
*
|
|
271
|
+
* @category ordering
|
|
272
|
+
* @since 1.0.0-rc.0
|
|
273
|
+
*/
|
|
274
|
+
export declare const compareNewestFirst: (left: Fire, right: Fire) => number;
|
|
275
|
+
/**
|
|
276
|
+
* Whether a record lies after `cursor` in {@link compareNewestFirst} order.
|
|
277
|
+
*
|
|
278
|
+
* @category predicates
|
|
279
|
+
* @since 1.0.0-rc.0
|
|
280
|
+
*/
|
|
281
|
+
export declare const isAfterCursor: (record: Fire, cursor: Fire) => boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Cuts an ordered, filtered record list to one page. The caller fetches one
|
|
284
|
+
* record past the limit; that record's presence is what says a next page
|
|
285
|
+
* exists, and the page's last record is the cursor addressing it.
|
|
286
|
+
*
|
|
287
|
+
* @category constructors
|
|
288
|
+
* @since 1.0.0-rc.0
|
|
289
|
+
*/
|
|
290
|
+
export declare const historyPage: (records: ReadonlyArray<FireRecord>, limit: number | undefined) => HistoryPage;
|
|
291
|
+
/**
|
|
292
|
+
* Durable trigger state: registration, listing, and the claim protocol that
|
|
293
|
+
* keeps two schedulers from firing the same occurrence.
|
|
294
|
+
*
|
|
295
|
+
* Neither listing is a due-time query. Due-ness is a cron computation the
|
|
296
|
+
* scheduler performs against its own watermark, so the store is asked only for
|
|
297
|
+
* the rows eligible to be considered. A scheduler tick reads `list`, not
|
|
298
|
+
* `listEnabled`: a disabled trigger can still hold an active occurrence that
|
|
299
|
+
* has to recover, and the tick skips only its new claims. `listEnabled`
|
|
300
|
+
* answers the narrower question for a caller that wants the declarations.
|
|
301
|
+
*
|
|
302
|
+
* A claim, result, pending-state, or active-run method fails with
|
|
303
|
+
* `unknown_trigger` when the row it addresses does not exist. `get` answers
|
|
304
|
+
* `None` for an absent row and `register` creates one. `clearActive` is a
|
|
305
|
+
* compare-and-swap that cannot tell a missing trigger from a run id that no
|
|
306
|
+
* longer matches, so it stays a no-op for both. `history`, `pruneFires`,
|
|
307
|
+
* `heartbeat`, and `lastHeartbeat` address the whole store.
|
|
308
|
+
*
|
|
309
|
+
* @category models
|
|
310
|
+
* @since 0.1.0
|
|
311
|
+
*/
|
|
312
|
+
export interface Service {
|
|
313
|
+
readonly register: (trigger: Trigger) => Effect.Effect<Registered, TriggerError>;
|
|
314
|
+
readonly get: (triggerId: string) => Effect.Effect<Option.Option<Registered>, TriggerError>;
|
|
315
|
+
/**
|
|
316
|
+
* Every trigger, enabled or not, ordered by id. This is what a scheduler
|
|
317
|
+
* tick polls: see {@link Listed} for why a row carries its decode result and
|
|
318
|
+
* its held state rather than a bare declaration.
|
|
319
|
+
*/
|
|
320
|
+
readonly list: () => Effect.Effect<ReadonlyArray<Listed>, TriggerError>;
|
|
321
|
+
readonly listEnabled: () => Effect.Effect<ReadonlyArray<Registered>, TriggerError>;
|
|
322
|
+
readonly claimFire: (fire: ClaimFire) => Effect.Effect<Claim, TriggerError>;
|
|
323
|
+
/**
|
|
324
|
+
* Claims the buffered occurrence, when one exists.
|
|
325
|
+
*
|
|
326
|
+
* One transaction reads the occurrence, applies the same claim protocol as
|
|
327
|
+
* {@link Service.claimFire}, and clears the buffer only when the decision
|
|
328
|
+
* consumes it. A concurrent buffer decision keeps it pending. No failure can
|
|
329
|
+
* land between reading the buffer and claiming it.
|
|
330
|
+
*/
|
|
331
|
+
readonly claimPending: (fire: {
|
|
332
|
+
readonly triggerId: string;
|
|
333
|
+
readonly expectedRevision: number;
|
|
334
|
+
}) => Effect.Effect<Option.Option<{
|
|
335
|
+
readonly occurrence: number;
|
|
336
|
+
readonly claim: Claim;
|
|
337
|
+
}>, TriggerError>;
|
|
338
|
+
readonly recordResult: (result: Result) => Effect.Effect<void, TriggerError>;
|
|
339
|
+
/** Atomically re-arms unfinished work and releases only its matching reservation.
|
|
340
|
+
* A retained predecessor is restored with the pending pointer in the same write.
|
|
341
|
+
* A stale token fails with `stale_owner`; a failed write leaves the lease intact.
|
|
342
|
+
*/
|
|
343
|
+
readonly restorePending: (fire: Fire & {
|
|
344
|
+
readonly reservationId: string;
|
|
345
|
+
}) => Effect.Effect<void, TriggerError>;
|
|
346
|
+
readonly setPending: (fire: Fire) => Effect.Effect<void, TriggerError>;
|
|
347
|
+
readonly activeRun: (triggerId: string) => Effect.Effect<Option.Option<string>, TriggerError>;
|
|
348
|
+
/**
|
|
349
|
+
* Returns the occurrence owned by one active run or launch reservation.
|
|
350
|
+
*
|
|
351
|
+
* `lastFiredAt` cannot answer this: later skipped and buffered occurrences
|
|
352
|
+
* advance that cursor while an older run remains active.
|
|
353
|
+
*/
|
|
354
|
+
readonly activeOccurrence: (triggerId: string, runId: string) => Effect.Effect<Option.Option<number>, TriggerError>;
|
|
355
|
+
readonly clearActive: (triggerId: string, runId: string) => Effect.Effect<void, TriggerError>;
|
|
356
|
+
/**
|
|
357
|
+
* Reads the fire ledger newest first. See {@link HistoryQuery} for the
|
|
358
|
+
* filters and paging contract; a limit that is not a positive safe integer
|
|
359
|
+
* is refused with `invalid_options`.
|
|
360
|
+
*/
|
|
361
|
+
readonly history: (query?: HistoryQuery) => Effect.Effect<HistoryPage, TriggerError>;
|
|
362
|
+
/**
|
|
363
|
+
* Deletes settled fire ledger rows older than `olderThan`, answering how
|
|
364
|
+
* many it removed. Nothing else in the store deletes from the ledger, so a
|
|
365
|
+
* host that never calls this keeps one row per occurrence forever.
|
|
366
|
+
*
|
|
367
|
+
* A row is settled when its outcome is `completed`, `failed`, `skipped`, or
|
|
368
|
+
* `superseded`. The occurrence a trigger currently buffers and the row
|
|
369
|
+
* naming its active run are kept whatever their age, so pruning cannot take
|
|
370
|
+
* state the scheduler still reads. `olderThan` that is not a safe integer is
|
|
371
|
+
* refused with `invalid_options`.
|
|
372
|
+
*/
|
|
373
|
+
readonly pruneFires: (options: {
|
|
374
|
+
readonly olderThan: number;
|
|
375
|
+
}) => Effect.Effect<number, TriggerError>;
|
|
376
|
+
/**
|
|
377
|
+
* Reads what one trigger holds without expiring anything, so a listing can
|
|
378
|
+
* report a reservation or a buffered occurrence exactly as the row has it.
|
|
379
|
+
*/
|
|
380
|
+
readonly inspect: (triggerId: string) => Effect.Effect<Held, TriggerError>;
|
|
381
|
+
/** Records that `host` polled the store at the store clock's current time. */
|
|
382
|
+
readonly heartbeat: (host: string) => Effect.Effect<void, TriggerError>;
|
|
383
|
+
/** The most recent heartbeat across every host, or `None` when no scheduler has ever polled. */
|
|
384
|
+
readonly lastHeartbeat: () => Effect.Effect<Option.Option<Heartbeat>, TriggerError>;
|
|
385
|
+
}
|
|
386
|
+
declare const TriggerStore_base: Context.ServiceClass<TriggerStore, "flows/triggers/TriggerStore", Service>;
|
|
387
|
+
/**
|
|
388
|
+
* The {@link Service} tag.
|
|
389
|
+
*
|
|
390
|
+
* @category services
|
|
391
|
+
* @since 0.1.0
|
|
392
|
+
*/
|
|
393
|
+
export declare class TriggerStore extends TriggerStore_base {
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* A {@link Service} that fails every method as unavailable, for an
|
|
397
|
+
* environment with no trigger store. Overrides replace individual methods.
|
|
398
|
+
*
|
|
399
|
+
* @category constructors
|
|
400
|
+
* @since 0.1.0
|
|
401
|
+
*/
|
|
402
|
+
export declare const makeNoop: (overrides?: Partial<Service>) => Service;
|
|
403
|
+
/**
|
|
404
|
+
* Provides {@link makeNoop}.
|
|
405
|
+
*
|
|
406
|
+
* @category layers
|
|
407
|
+
* @since 0.1.0
|
|
408
|
+
*/
|
|
409
|
+
export declare const layerNoop: (overrides?: Partial<Service>) => Layer.Layer<TriggerStore>;
|
|
410
|
+
export {};
|
|
411
|
+
//# sourceMappingURL=TriggerStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TriggerStore.d.ts","sourceRoot":"","sources":["../../src/TriggerStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAG5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;GAMG;AACH,MAAM,WAAW,UAAW,SAAQ,OAAO;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1C;AACD;;;;;;GAMG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AACD;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,SAAU,SAAQ,IAAI;IACrC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC9C;AACD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,KAAK,GACb;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAA;CAAE,GAC/E;IACA,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAA;IACtD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1C,CAAA;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,QAAgB,CAAA;AAE/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,yBAAyB,CAAA;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,WAAW,MAAM,EAAE,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,MACO,CAAA;AAE/F;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,OACE,CAAA;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,SAI9D,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,CAAA;AACjG;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,GACd,IAAI,GACJ;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACvC,CACE;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACxF;IACA,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5C,CACF,CAAA;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,MAAM,UAAU,GAAG,SAAS,EAC5B,aAAa,MAAM,GAAG,SAAS,KAC9B,YAAY,GAAG,SAqBjB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAW,SAAQ,IAAI;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,MAAO,SAAQ,IAAI;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;CAC1D;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,GAAI,SAAS,UAAU,EAAE,OAAM,IAAS,KAAG,MAI5D,CAAA;AAEF;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,YAAY,CASlG,CAAA;AAEL;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,WAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAS7E,CAAA;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,IAAI,EAAE,OAAO,IAAI,KAAG,MAEuB,CAAA;AAEpF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,IAAI,EAAE,QAAQ,IAAI,KAAG,OAAiD,CAAA;AAE5G;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,aAAa,CAAC,UAAU,CAAC,EAAE,OAAO,MAAM,GAAG,SAAS,KAAG,WAK3F,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IAChF,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAA;IAC3F;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAA;IACvE,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAA;IAClF,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IAC3E;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE;QAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;QAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;KAClC,KAAK,MAAM,CAAC,MAAM,CACjB,MAAM,CAAC,MAAM,CAAC;QAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,EACrE,YAAY,CACb,CAAA;IACD,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAC5E;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG;QAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAC/G,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IACtE,QAAQ,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAA;IAC7F;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,EAAE,CACzB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAA;IACvD,QAAQ,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAC7F;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;IACpF;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrG;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAC1E,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IACvE,gGAAgG;IAChG,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAA;CACpF;;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,iBAAuE;CAAG;AAK5G;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,YAAW,OAAO,CAAC,OAAO,CAAM,KAAG,OAmB1D,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,YAAW,OAAO,CAAC,OAAO,CAAM,KAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CACnC,CAAA"}
|
|
@@ -0,0 +1,127 @@
|
|
|
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 TriggerStore_exports = {};
|
|
30
|
+
__export(TriggerStore_exports, {
|
|
31
|
+
TriggerStore: () => TriggerStore,
|
|
32
|
+
compareNewestFirst: () => compareNewestFirst,
|
|
33
|
+
historyLimit: () => historyLimit,
|
|
34
|
+
historyPage: () => historyPage,
|
|
35
|
+
isAfterCursor: () => isAfterCursor,
|
|
36
|
+
isReservation: () => isReservation,
|
|
37
|
+
layerNoop: () => layerNoop,
|
|
38
|
+
listed: () => listed,
|
|
39
|
+
makeNoop: () => makeNoop,
|
|
40
|
+
pruneCutoff: () => pruneCutoff,
|
|
41
|
+
reservationId: () => reservationId,
|
|
42
|
+
reservationLeaseMs: () => reservationLeaseMs,
|
|
43
|
+
reservationOccurrence: () => reservationOccurrence,
|
|
44
|
+
reservationPrefix: () => reservationPrefix,
|
|
45
|
+
resultRefusal: () => resultRefusal
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(TriggerStore_exports);
|
|
48
|
+
var Context = __toESM(require("effect/Context"), 1);
|
|
49
|
+
var Effect = __toESM(require("effect/Effect"), 1);
|
|
50
|
+
var Layer = __toESM(require("effect/Layer"), 1);
|
|
51
|
+
var Decode = __toESM(require("effect/Result"), 1);
|
|
52
|
+
var import_TriggerError = require("./TriggerError.js");
|
|
53
|
+
const reservationLeaseMs = 5 * 60 * 1e3;
|
|
54
|
+
const reservationPrefix = "trigger-reservation:";
|
|
55
|
+
const reservationId = (triggerId, occurrence, attempt) => `${reservationPrefix}${triggerId}:${attempt === void 0 ? "" : `${attempt}:`}${occurrence}`;
|
|
56
|
+
const isReservation = (runId) => runId !== void 0 && runId.startsWith(reservationPrefix);
|
|
57
|
+
const reservationOccurrence = (runId) => {
|
|
58
|
+
if (!isReservation(runId)) return void 0;
|
|
59
|
+
const occurrence = Number(runId.slice(runId.lastIndexOf(":") + 1));
|
|
60
|
+
return Number.isFinite(occurrence) ? occurrence : void 0;
|
|
61
|
+
};
|
|
62
|
+
const resultRefusal = (result, fire, activeRunId) => {
|
|
63
|
+
if (result.outcome === "launched" && (typeof result.runId !== "string" || result.runId.trim().length === 0)) {
|
|
64
|
+
return new import_TriggerError.TriggerError({ code: "invalid_options", message: "launched requires a non-empty runId", path: "runId" });
|
|
65
|
+
}
|
|
66
|
+
const unfinished = fire?.outcome === null || fire?.outcome === "buffered";
|
|
67
|
+
const reservation = activeRunId !== void 0 && isReservation(activeRunId) && reservationOccurrence(activeRunId) === result.occurrence;
|
|
68
|
+
const owner = result.reservationId ?? result.runId ?? (reservation ? activeRunId : void 0);
|
|
69
|
+
const permitted = result.outcome === "launched" ? unfinished && reservation && result.reservationId === activeRunId : fire !== void 0 && // A repeated decision or settlement is harmless; callers do not rewrite it.
|
|
70
|
+
(fire.outcome === result.outcome && (result.runId === void 0 || result.runId === fire.runId) || fire.outcome === "launched" && result.outcome !== "skipped" && result.outcome !== "buffered" && result.reservationId === void 0 && (result.runId === void 0 || result.runId === fire.runId) || unfinished && reservation && owner === activeRunId);
|
|
71
|
+
return permitted ? void 0 : new import_TriggerError.TriggerError({
|
|
72
|
+
code: "stale_owner",
|
|
73
|
+
message: `result no longer owns trigger ${result.triggerId} occurrence ${result.occurrence}`
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const listed = (trigger, held = {}) => ({
|
|
77
|
+
triggerId: trigger.id,
|
|
78
|
+
trigger: Decode.succeed(trigger),
|
|
79
|
+
...held
|
|
80
|
+
});
|
|
81
|
+
const historyLimit = (limit) => limit === void 0 || Number.isSafeInteger(limit) && limit > 0 ? Effect.succeed(limit) : Effect.fail(
|
|
82
|
+
new import_TriggerError.TriggerError({
|
|
83
|
+
code: "invalid_options",
|
|
84
|
+
message: `history limit must be a positive safe integer, received ${limit}`,
|
|
85
|
+
path: "limit"
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
const pruneCutoff = (olderThan) => Number.isSafeInteger(olderThan) ? Effect.succeed(olderThan) : Effect.fail(
|
|
89
|
+
new import_TriggerError.TriggerError({
|
|
90
|
+
code: "invalid_options",
|
|
91
|
+
message: `prune cutoff must be a safe integer, received ${olderThan}`,
|
|
92
|
+
path: "olderThan"
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
const compareNewestFirst = (left, right) => right.occurrence - left.occurrence || (right.triggerId < left.triggerId ? -1 : right.triggerId > left.triggerId ? 1 : 0);
|
|
96
|
+
const isAfterCursor = (record, cursor) => compareNewestFirst(cursor, record) < 0;
|
|
97
|
+
const historyPage = (records, limit) => {
|
|
98
|
+
if (limit === void 0 || records.length <= limit) return { items: records };
|
|
99
|
+
const items = records.slice(0, limit);
|
|
100
|
+
const last = items[items.length - 1];
|
|
101
|
+
return { items, nextCursor: { triggerId: last.triggerId, occurrence: last.occurrence } };
|
|
102
|
+
};
|
|
103
|
+
class TriggerStore extends Context.Service()("flows/triggers/TriggerStore") {
|
|
104
|
+
}
|
|
105
|
+
const unavailable = (method) => Effect.fail(new import_TriggerError.TriggerError({ code: "store", message: `${method} is unavailable` }));
|
|
106
|
+
const makeNoop = (overrides = {}) => ({
|
|
107
|
+
register: () => unavailable("register"),
|
|
108
|
+
get: () => unavailable("get"),
|
|
109
|
+
list: () => unavailable("list"),
|
|
110
|
+
listEnabled: () => unavailable("listEnabled"),
|
|
111
|
+
claimFire: () => unavailable("claimFire"),
|
|
112
|
+
claimPending: () => unavailable("claimPending"),
|
|
113
|
+
recordResult: () => unavailable("recordResult"),
|
|
114
|
+
restorePending: () => unavailable("restorePending"),
|
|
115
|
+
setPending: () => unavailable("setPending"),
|
|
116
|
+
activeRun: () => unavailable("activeRun"),
|
|
117
|
+
activeOccurrence: () => unavailable("activeOccurrence"),
|
|
118
|
+
clearActive: () => unavailable("clearActive"),
|
|
119
|
+
history: () => unavailable("history"),
|
|
120
|
+
pruneFires: () => unavailable("pruneFires"),
|
|
121
|
+
inspect: () => unavailable("inspect"),
|
|
122
|
+
heartbeat: () => unavailable("heartbeat"),
|
|
123
|
+
lastHeartbeat: () => unavailable("lastHeartbeat"),
|
|
124
|
+
...overrides
|
|
125
|
+
});
|
|
126
|
+
const layerNoop = (overrides = {}) => Layer.succeed(TriggerStore)(makeNoop(overrides));
|
|
127
|
+
//# sourceMappingURL=TriggerStore.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/TriggerStore.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Durable trigger declaration and fire store.\n *\n * @see packages/smithers/agent/triggers/docs/api.md\n *\n * @since 0.1.0\n */\n\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport type * as Option from \"effect/Option\"\n// `Result` already names the reported end of an occurrence in this module, so\n// the `effect/Result` a listed row decodes to is imported as `Decode`.\nimport * as Decode from \"effect/Result\"\nimport type { Action } from \"./Overlap.ts\"\nimport type { Trigger } from \"./Trigger.ts\"\nimport { TriggerError } from \"./TriggerError.ts\"\n\n/**\n * A stored trigger, with the revision that fences concurrent edits and\n * when it last fired.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Registered extends Trigger {\n readonly revision: number\n readonly lastFiredAt?: number | undefined\n}\n/**\n * One scheduled occurrence of a trigger, addressed by its occurrence\n * number so a retry cannot fire it twice.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Fire {\n readonly triggerId: string\n readonly occurrence: number\n}\n/**\n * A {@link Fire} together with the revision it was computed from and whether\n * it is resuming a buffered occurrence.\n *\n * The overlap policy is deliberately absent. A claim applies the policy stored\n * on the trigger row, read inside the same transaction, so a caller holding a\n * stale snapshot cannot fire a trigger that has since been disabled, cannot\n * point it at a different flow, and cannot supersede a run that the stored\n * declaration says to leave alone. `expectedRevision` is the fence: a claim\n * whose revision no longer matches the row is refused with `revision_mismatch`\n * so the caller re-reads before deciding again.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface ClaimFire extends Fire {\n readonly expectedRevision: number\n readonly resumeBuffered?: boolean | undefined\n}\n/**\n * The outcome of claiming an occurrence: either another worker holds it, or\n * this caller does and must take `action`.\n *\n * A claim that hands the caller work to launch always names the reservation it\n * wrote against the trigger row, so the caller has an id to release. A claim\n * that only records a decision has no reservation and names none: the two\n * shapes are separate so a caller cannot read a reservation id that was never\n * written.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Claim =\n | { readonly claimed: false }\n | { readonly claimed: true; readonly action: Extract<Action, \"skip\" | \"buffer\"> }\n | {\n readonly claimed: true\n readonly action: Extract<Action, \"fire\" | \"supersede\">\n readonly reservationId: string\n readonly activeRunId?: string | undefined\n }\n\n/**\n * Time after which an uncommitted launch reservation may be reclaimed.\n *\n * Both store implementations use this value so swapping the test store for\n * the SQL store cannot change recovery timing.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const reservationLeaseMs = 5 * 60 * 1000\n\n/**\n * The prefix marking an `active_run_id` that is a launch reservation rather\n * than a run the runtime knows about.\n *\n * A reservation is written by the claim and replaced by the real run id once\n * the launch reports one, so both stores and the scheduler read this prefix.\n * It is a contract between them and lives here rather than being re-spelled at\n * each site.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const reservationPrefix = \"trigger-reservation:\"\n\n/**\n * The reservation id one occurrence of one trigger claims.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const reservationId = (triggerId: string, occurrence: number, attempt?: string): string =>\n `${reservationPrefix}${triggerId}:${attempt === undefined ? \"\" : `${attempt}:`}${occurrence}`\n\n/**\n * Whether a stored `active_run_id` is a launch reservation.\n *\n * @category predicates\n * @since 0.1.0\n */\nexport const isReservation = (runId: string | undefined): boolean =>\n runId !== undefined && runId.startsWith(reservationPrefix)\n\n/**\n * Reads the occurrence encoded in a launch reservation.\n *\n * @category getters\n * @since 0.1.0\n */\nexport const reservationOccurrence = (runId: string): number | undefined => {\n if (!isReservation(runId)) return undefined\n const occurrence = Number(runId.slice(runId.lastIndexOf(\":\") + 1))\n return Number.isFinite(occurrence) ? occurrence : undefined\n}\n\n/**\n * How a claimed occurrence ended.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Outcome = \"launched\" | \"completed\" | \"skipped\" | \"buffered\" | \"superseded\" | \"failed\"\n/**\n * The reported end of one occurrence, with the run it started when it\n * launched one.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Result =\n & Fire\n & { readonly error?: string | undefined }\n & (\n | { readonly outcome: \"launched\"; readonly runId: string; readonly reservationId: string }\n | {\n readonly outcome: Exclude<Outcome, \"launched\">\n readonly runId?: string | undefined\n readonly reservationId?: string | undefined\n }\n )\n\n/**\n * Refuses results that no longer own a permissible fire transition.\n * Launched run ids are validated at runtime for adapters outside TypeScript.\n *\n * @category predicates\n * @since 1.0.0-rc.0\n */\nexport const resultRefusal = (\n result: Result,\n fire: FireRecord | undefined,\n activeRunId: string | undefined\n): TriggerError | undefined => {\n if (result.outcome === \"launched\" && (typeof result.runId !== \"string\" || result.runId.trim().length === 0)) {\n return new TriggerError({ code: \"invalid_options\", message: \"launched requires a non-empty runId\", path: \"runId\" })\n }\n const unfinished = fire?.outcome === null || fire?.outcome === \"buffered\"\n const reservation = activeRunId !== undefined && isReservation(activeRunId) &&\n reservationOccurrence(activeRunId) === result.occurrence\n const owner = result.reservationId ?? result.runId ?? (reservation ? activeRunId : undefined)\n const permitted = result.outcome === \"launched\"\n ? unfinished && reservation && result.reservationId === activeRunId\n : fire !== undefined && (\n // A repeated decision or settlement is harmless; callers do not rewrite it.\n (fire.outcome === result.outcome && (result.runId === undefined || result.runId === fire.runId)) ||\n (fire.outcome === \"launched\" && result.outcome !== \"skipped\" && result.outcome !== \"buffered\" &&\n result.reservationId === undefined && (result.runId === undefined || result.runId === fire.runId)) ||\n (unfinished && reservation && owner === activeRunId)\n )\n return permitted ? undefined : new TriggerError({\n code: \"stale_owner\",\n message: `result no longer owns trigger ${result.triggerId} occurrence ${result.occurrence}`\n })\n}\n\n/**\n * One row of the fire ledger: a claimed occurrence and what became of it.\n *\n * `outcome` is `null` between a claim and its `recordResult`, the window in\n * which a launch is reserved but not yet reported.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface FireRecord extends Fire {\n readonly outcome: Outcome | null\n readonly runId?: string | undefined\n readonly error?: string | undefined\n}\n\n/**\n * A ledger query. Every filter narrows; `cursor` is the last record of the\n * previous page, so the page after it holds only older records; `limit` caps\n * a page and must be a positive safe integer when stated. With no limit the\n * whole ledger answers in one page.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface HistoryQuery {\n readonly triggerId?: string | undefined\n readonly runId?: string | undefined\n readonly outcome?: Outcome | undefined\n readonly cursor?: Fire | undefined\n readonly limit?: number | undefined\n}\n\n/**\n * One page of the ledger, newest occurrence first, with the cursor that\n * addresses the next page when the limit cut the page short.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface HistoryPage {\n readonly items: ReadonlyArray<FireRecord>\n readonly nextCursor?: Fire | undefined\n}\n\n/**\n * What one trigger holds right now: the run or launch reservation on its row\n * and the occurrence buffered behind it. A plain read; `activeRun` is the read\n * that expires a stale reservation.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Held {\n readonly activeRunId?: string | undefined\n readonly pendingAt?: number | undefined\n}\n\n/**\n * One row of a listing: what the trigger holds right now, and either the\n * declaration the row decoded to or the failure it could not be read through.\n *\n * `trigger` is an `effect/Result`: narrow it with `Result.isFailure` before\n * reading the declaration. An undecodable row is isolated here rather than\n * failing the whole listing,\n * so one corrupt input cannot stop every other trigger from being scheduled.\n * The held state travels with the row because a scheduler tick would otherwise\n * ask for it again per trigger, once to expire a reservation that is not there\n * and once for a buffered occurrence that is not there either.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Listed extends Held {\n readonly triggerId: string\n readonly trigger: Decode.Result<Registered, TriggerError>\n}\n\n/**\n * A listed row for a declaration that decoded, holding what the trigger holds\n * right now. Both stores build their rows through this, so a caller composing\n * a store or a scheduler fixture of its own does not restate the shape.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const listed = (trigger: Registered, held: Held = {}): Listed => ({\n triggerId: trigger.id,\n trigger: Decode.succeed(trigger),\n ...held\n})\n\n/**\n * The last poll one scheduler host recorded.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Heartbeat {\n readonly host: string\n readonly tickedAt: number\n}\n\n/**\n * Validates a {@link HistoryQuery} limit: absent, or a positive safe integer.\n * Both stores apply this before reading so swapping one for the other cannot\n * change which queries are refused.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const historyLimit = (limit: number | undefined): Effect.Effect<number | undefined, TriggerError> =>\n limit === undefined || (Number.isSafeInteger(limit) && limit > 0)\n ? Effect.succeed(limit)\n : Effect.fail(\n new TriggerError({\n code: \"invalid_options\",\n message: `history limit must be a positive safe integer, received ${limit}`,\n path: \"limit\"\n })\n )\n\n/**\n * Validates a prune cutoff: any safe integer. Both stores apply this before\n * deleting so swapping one for the other cannot change which cutoffs are\n * refused.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const pruneCutoff = (olderThan: number): Effect.Effect<number, TriggerError> =>\n Number.isSafeInteger(olderThan)\n ? Effect.succeed(olderThan)\n : Effect.fail(\n new TriggerError({\n code: \"invalid_options\",\n message: `prune cutoff must be a safe integer, received ${olderThan}`,\n path: \"olderThan\"\n })\n )\n\n/**\n * Newest occurrence first; equal occurrences of different triggers order by\n * descending trigger id so a cursor names one position.\n *\n * @category ordering\n * @since 1.0.0-rc.0\n */\nexport const compareNewestFirst = (left: Fire, right: Fire): number =>\n right.occurrence - left.occurrence ||\n (right.triggerId < left.triggerId ? -1 : right.triggerId > left.triggerId ? 1 : 0)\n\n/**\n * Whether a record lies after `cursor` in {@link compareNewestFirst} order.\n *\n * @category predicates\n * @since 1.0.0-rc.0\n */\nexport const isAfterCursor = (record: Fire, cursor: Fire): boolean => compareNewestFirst(cursor, record) < 0\n\n/**\n * Cuts an ordered, filtered record list to one page. The caller fetches one\n * record past the limit; that record's presence is what says a next page\n * exists, and the page's last record is the cursor addressing it.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const historyPage = (records: ReadonlyArray<FireRecord>, limit: number | undefined): HistoryPage => {\n if (limit === undefined || records.length <= limit) return { items: records }\n const items = records.slice(0, limit)\n const last = items[items.length - 1] as FireRecord\n return { items, nextCursor: { triggerId: last.triggerId, occurrence: last.occurrence } }\n}\n\n/**\n * Durable trigger state: registration, listing, and the claim protocol that\n * keeps two schedulers from firing the same occurrence.\n *\n * Neither listing is a due-time query. Due-ness is a cron computation the\n * scheduler performs against its own watermark, so the store is asked only for\n * the rows eligible to be considered. A scheduler tick reads `list`, not\n * `listEnabled`: a disabled trigger can still hold an active occurrence that\n * has to recover, and the tick skips only its new claims. `listEnabled`\n * answers the narrower question for a caller that wants the declarations.\n *\n * A claim, result, pending-state, or active-run method fails with\n * `unknown_trigger` when the row it addresses does not exist. `get` answers\n * `None` for an absent row and `register` creates one. `clearActive` is a\n * compare-and-swap that cannot tell a missing trigger from a run id that no\n * longer matches, so it stays a no-op for both. `history`, `pruneFires`,\n * `heartbeat`, and `lastHeartbeat` address the whole store.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n readonly register: (trigger: Trigger) => Effect.Effect<Registered, TriggerError>\n readonly get: (triggerId: string) => Effect.Effect<Option.Option<Registered>, TriggerError>\n /**\n * Every trigger, enabled or not, ordered by id. This is what a scheduler\n * tick polls: see {@link Listed} for why a row carries its decode result and\n * its held state rather than a bare declaration.\n */\n readonly list: () => Effect.Effect<ReadonlyArray<Listed>, TriggerError>\n readonly listEnabled: () => Effect.Effect<ReadonlyArray<Registered>, TriggerError>\n readonly claimFire: (fire: ClaimFire) => Effect.Effect<Claim, TriggerError>\n /**\n * Claims the buffered occurrence, when one exists.\n *\n * One transaction reads the occurrence, applies the same claim protocol as\n * {@link Service.claimFire}, and clears the buffer only when the decision\n * consumes it. A concurrent buffer decision keeps it pending. No failure can\n * land between reading the buffer and claiming it.\n */\n readonly claimPending: (fire: {\n readonly triggerId: string\n readonly expectedRevision: number\n }) => Effect.Effect<\n Option.Option<{ readonly occurrence: number; readonly claim: Claim }>,\n TriggerError\n >\n readonly recordResult: (result: Result) => Effect.Effect<void, TriggerError>\n /** Atomically re-arms unfinished work and releases only its matching reservation.\n * A retained predecessor is restored with the pending pointer in the same write.\n * A stale token fails with `stale_owner`; a failed write leaves the lease intact.\n */\n readonly restorePending: (fire: Fire & { readonly reservationId: string }) => Effect.Effect<void, TriggerError>\n readonly setPending: (fire: Fire) => Effect.Effect<void, TriggerError>\n readonly activeRun: (triggerId: string) => Effect.Effect<Option.Option<string>, TriggerError>\n /**\n * Returns the occurrence owned by one active run or launch reservation.\n *\n * `lastFiredAt` cannot answer this: later skipped and buffered occurrences\n * advance that cursor while an older run remains active.\n */\n readonly activeOccurrence: (\n triggerId: string,\n runId: string\n ) => Effect.Effect<Option.Option<number>, TriggerError>\n readonly clearActive: (triggerId: string, runId: string) => Effect.Effect<void, TriggerError>\n /**\n * Reads the fire ledger newest first. See {@link HistoryQuery} for the\n * filters and paging contract; a limit that is not a positive safe integer\n * is refused with `invalid_options`.\n */\n readonly history: (query?: HistoryQuery) => Effect.Effect<HistoryPage, TriggerError>\n /**\n * Deletes settled fire ledger rows older than `olderThan`, answering how\n * many it removed. Nothing else in the store deletes from the ledger, so a\n * host that never calls this keeps one row per occurrence forever.\n *\n * A row is settled when its outcome is `completed`, `failed`, `skipped`, or\n * `superseded`. The occurrence a trigger currently buffers and the row\n * naming its active run are kept whatever their age, so pruning cannot take\n * state the scheduler still reads. `olderThan` that is not a safe integer is\n * refused with `invalid_options`.\n */\n readonly pruneFires: (options: { readonly olderThan: number }) => Effect.Effect<number, TriggerError>\n /**\n * Reads what one trigger holds without expiring anything, so a listing can\n * report a reservation or a buffered occurrence exactly as the row has it.\n */\n readonly inspect: (triggerId: string) => Effect.Effect<Held, TriggerError>\n /** Records that `host` polled the store at the store clock's current time. */\n readonly heartbeat: (host: string) => Effect.Effect<void, TriggerError>\n /** The most recent heartbeat across every host, or `None` when no scheduler has ever polled. */\n readonly lastHeartbeat: () => Effect.Effect<Option.Option<Heartbeat>, TriggerError>\n}\n\n/**\n * The {@link Service} tag.\n *\n * @category services\n * @since 0.1.0\n */\nexport class TriggerStore extends Context.Service<TriggerStore, Service>()(\"flows/triggers/TriggerStore\") {}\n\nconst unavailable = (method: string): Effect.Effect<never, TriggerError> =>\n Effect.fail(new TriggerError({ code: \"store\", message: `${method} is unavailable` }))\n\n/**\n * A {@link Service} that fails every method as unavailable, for an\n * environment with no trigger store. Overrides replace individual methods.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeNoop = (overrides: Partial<Service> = {}): Service => ({\n register: () => unavailable(\"register\"),\n get: () => unavailable(\"get\"),\n list: () => unavailable(\"list\"),\n listEnabled: () => unavailable(\"listEnabled\"),\n claimFire: () => unavailable(\"claimFire\"),\n claimPending: () => unavailable(\"claimPending\"),\n recordResult: () => unavailable(\"recordResult\"),\n restorePending: () => unavailable(\"restorePending\"),\n setPending: () => unavailable(\"setPending\"),\n activeRun: () => unavailable(\"activeRun\"),\n activeOccurrence: () => unavailable(\"activeOccurrence\"),\n clearActive: () => unavailable(\"clearActive\"),\n history: () => unavailable(\"history\"),\n pruneFires: () => unavailable(\"pruneFires\"),\n inspect: () => unavailable(\"inspect\"),\n heartbeat: () => unavailable(\"heartbeat\"),\n lastHeartbeat: () => unavailable(\"lastHeartbeat\"),\n ...overrides\n})\n\n/**\n * Provides {@link makeNoop}.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<TriggerStore> =>\n Layer.succeed(TriggerStore)(makeNoop(overrides))\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|