@smthrs/triggers 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.
Files changed (226) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/LICENSE +21 -0
  3. package/README.md +109 -2
  4. package/dist/cjs/CatchUp.d.ts +25 -0
  5. package/dist/cjs/CatchUp.d.ts.map +1 -0
  6. package/dist/cjs/CatchUp.js +72 -0
  7. package/dist/cjs/CatchUp.js.map +7 -0
  8. package/dist/cjs/Channel.d.ts +108 -0
  9. package/dist/cjs/Channel.d.ts.map +1 -0
  10. package/dist/cjs/Channel.js +25 -0
  11. package/dist/cjs/Channel.js.map +7 -0
  12. package/dist/cjs/ClaimDecision.d.ts +183 -0
  13. package/dist/cjs/ClaimDecision.d.ts.map +1 -0
  14. package/dist/cjs/ClaimDecision.js +144 -0
  15. package/dist/cjs/ClaimDecision.js.map +7 -0
  16. package/dist/cjs/Cron.d.ts +91 -0
  17. package/dist/cjs/Cron.d.ts.map +1 -0
  18. package/dist/cjs/Cron.js +108 -0
  19. package/dist/cjs/Cron.js.map +7 -0
  20. package/dist/cjs/DispatchReader.d.ts +77 -0
  21. package/dist/cjs/DispatchReader.d.ts.map +1 -0
  22. package/dist/cjs/DispatchReader.js +121 -0
  23. package/dist/cjs/DispatchReader.js.map +7 -0
  24. package/dist/cjs/Overlap.d.ts +40 -0
  25. package/dist/cjs/Overlap.d.ts.map +1 -0
  26. package/dist/cjs/Overlap.js +37 -0
  27. package/dist/cjs/Overlap.js.map +7 -0
  28. package/dist/cjs/Schedule.d.ts +96 -0
  29. package/dist/cjs/Schedule.d.ts.map +1 -0
  30. package/dist/cjs/Schedule.js +61 -0
  31. package/dist/cjs/Schedule.js.map +7 -0
  32. package/dist/cjs/Scheduler.d.ts +248 -0
  33. package/dist/cjs/Scheduler.d.ts.map +1 -0
  34. package/dist/cjs/Scheduler.js +647 -0
  35. package/dist/cjs/Scheduler.js.map +7 -0
  36. package/dist/cjs/SqlTriggerStore.d.ts +29 -0
  37. package/dist/cjs/SqlTriggerStore.d.ts.map +1 -0
  38. package/dist/cjs/SqlTriggerStore.js +483 -0
  39. package/dist/cjs/SqlTriggerStore.js.map +7 -0
  40. package/dist/cjs/Trigger.d.ts +81 -0
  41. package/dist/cjs/Trigger.d.ts.map +1 -0
  42. package/dist/cjs/Trigger.js +54 -0
  43. package/dist/cjs/Trigger.js.map +7 -0
  44. package/dist/cjs/TriggerError.d.ts +58 -0
  45. package/dist/cjs/TriggerError.d.ts.map +1 -0
  46. package/dist/cjs/TriggerError.js +83 -0
  47. package/dist/cjs/TriggerError.js.map +7 -0
  48. package/dist/cjs/TriggerStore.d.ts +411 -0
  49. package/dist/cjs/TriggerStore.d.ts.map +1 -0
  50. package/dist/cjs/TriggerStore.js +127 -0
  51. package/dist/cjs/TriggerStore.js.map +7 -0
  52. package/dist/cjs/Webhook.d.ts +118 -0
  53. package/dist/cjs/Webhook.d.ts.map +1 -0
  54. package/dist/cjs/Webhook.js +153 -0
  55. package/dist/cjs/Webhook.js.map +7 -0
  56. package/dist/cjs/index.d.ts +30 -0
  57. package/dist/cjs/index.d.ts.map +1 -0
  58. package/dist/cjs/index.js +57 -0
  59. package/dist/cjs/index.js.map +7 -0
  60. package/dist/cjs/internal/ActiveRuns.d.ts +51 -0
  61. package/dist/cjs/internal/ActiveRuns.d.ts.map +1 -0
  62. package/dist/cjs/internal/ActiveRuns.js +56 -0
  63. package/dist/cjs/internal/ActiveRuns.js.map +7 -0
  64. package/dist/cjs/internal/DueOccurrences.d.ts +32 -0
  65. package/dist/cjs/internal/DueOccurrences.d.ts.map +1 -0
  66. package/dist/cjs/internal/DueOccurrences.js +80 -0
  67. package/dist/cjs/internal/DueOccurrences.js.map +7 -0
  68. package/dist/cjs/internal/WallClock.d.ts +59 -0
  69. package/dist/cjs/internal/WallClock.d.ts.map +1 -0
  70. package/dist/cjs/internal/WallClock.js +132 -0
  71. package/dist/cjs/internal/WallClock.js.map +7 -0
  72. package/dist/cjs/migrations/0001_triggers.d.ts +15 -0
  73. package/dist/cjs/migrations/0001_triggers.d.ts.map +1 -0
  74. package/dist/cjs/migrations/0001_triggers.js +64 -0
  75. package/dist/cjs/migrations/0001_triggers.js.map +7 -0
  76. package/dist/cjs/migrations/0002_reservation_lease.d.ts +15 -0
  77. package/dist/cjs/migrations/0002_reservation_lease.d.ts.map +1 -0
  78. package/dist/cjs/migrations/0002_reservation_lease.js +41 -0
  79. package/dist/cjs/migrations/0002_reservation_lease.js.map +7 -0
  80. package/dist/cjs/migrations/0003_heartbeat.d.ts +16 -0
  81. package/dist/cjs/migrations/0003_heartbeat.d.ts.map +1 -0
  82. package/dist/cjs/migrations/0003_heartbeat.js +44 -0
  83. package/dist/cjs/migrations/0003_heartbeat.js.map +7 -0
  84. package/dist/cjs/migrations/0004_fire_run_index.d.ts +17 -0
  85. package/dist/cjs/migrations/0004_fire_run_index.d.ts.map +1 -0
  86. package/dist/cjs/migrations/0004_fire_run_index.js +40 -0
  87. package/dist/cjs/migrations/0004_fire_run_index.js.map +7 -0
  88. package/dist/cjs/migrations/index.d.ts +33 -0
  89. package/dist/cjs/migrations/index.d.ts.map +1 -0
  90. package/dist/cjs/migrations/index.js +50 -0
  91. package/dist/cjs/migrations/index.js.map +7 -0
  92. package/dist/cjs/package.json +1 -0
  93. package/dist/cjs/test/TestTriggers.d.ts +12 -0
  94. package/dist/cjs/test/TestTriggers.d.ts.map +1 -0
  95. package/dist/cjs/test/TestTriggers.js +421 -0
  96. package/dist/cjs/test/TestTriggers.js.map +7 -0
  97. package/dist/esm/CatchUp.d.ts +25 -0
  98. package/dist/esm/CatchUp.d.ts.map +1 -0
  99. package/dist/esm/CatchUp.js +52 -0
  100. package/dist/esm/CatchUp.js.map +1 -0
  101. package/dist/esm/Channel.d.ts +108 -0
  102. package/dist/esm/Channel.d.ts.map +1 -0
  103. package/dist/esm/Channel.js +19 -0
  104. package/dist/esm/Channel.js.map +1 -0
  105. package/dist/esm/ClaimDecision.d.ts +183 -0
  106. package/dist/esm/ClaimDecision.d.ts.map +1 -0
  107. package/dist/esm/ClaimDecision.js +172 -0
  108. package/dist/esm/ClaimDecision.js.map +1 -0
  109. package/dist/esm/Cron.d.ts +91 -0
  110. package/dist/esm/Cron.d.ts.map +1 -0
  111. package/dist/esm/Cron.js +132 -0
  112. package/dist/esm/Cron.js.map +1 -0
  113. package/dist/esm/DispatchReader.d.ts +77 -0
  114. package/dist/esm/DispatchReader.d.ts.map +1 -0
  115. package/dist/esm/DispatchReader.js +152 -0
  116. package/dist/esm/DispatchReader.js.map +1 -0
  117. package/dist/esm/Overlap.d.ts +40 -0
  118. package/dist/esm/Overlap.d.ts.map +1 -0
  119. package/dist/esm/Overlap.js +31 -0
  120. package/dist/esm/Overlap.js.map +1 -0
  121. package/dist/esm/Schedule.d.ts +96 -0
  122. package/dist/esm/Schedule.d.ts.map +1 -0
  123. package/dist/esm/Schedule.js +73 -0
  124. package/dist/esm/Schedule.js.map +1 -0
  125. package/dist/esm/Scheduler.d.ts +248 -0
  126. package/dist/esm/Scheduler.d.ts.map +1 -0
  127. package/dist/esm/Scheduler.js +734 -0
  128. package/dist/esm/Scheduler.js.map +1 -0
  129. package/dist/esm/SqlTriggerStore.d.ts +29 -0
  130. package/dist/esm/SqlTriggerStore.d.ts.map +1 -0
  131. package/dist/esm/SqlTriggerStore.js +483 -0
  132. package/dist/esm/SqlTriggerStore.js.map +1 -0
  133. package/dist/esm/Trigger.d.ts +81 -0
  134. package/dist/esm/Trigger.d.ts.map +1 -0
  135. package/dist/esm/Trigger.js +56 -0
  136. package/dist/esm/Trigger.js.map +1 -0
  137. package/dist/esm/TriggerError.d.ts +58 -0
  138. package/dist/esm/TriggerError.d.ts.map +1 -0
  139. package/dist/esm/TriggerError.js +110 -0
  140. package/dist/esm/TriggerError.js.map +1 -0
  141. package/dist/esm/TriggerStore.d.ts +411 -0
  142. package/dist/esm/TriggerStore.d.ts.map +1 -0
  143. package/dist/esm/TriggerStore.js +209 -0
  144. package/dist/esm/TriggerStore.js.map +1 -0
  145. package/dist/esm/Webhook.d.ts +118 -0
  146. package/dist/esm/Webhook.d.ts.map +1 -0
  147. package/dist/esm/Webhook.js +186 -0
  148. package/dist/esm/Webhook.js.map +1 -0
  149. package/dist/esm/index.d.ts +30 -0
  150. package/dist/esm/index.d.ts.map +1 -0
  151. package/dist/esm/index.js +30 -0
  152. package/dist/esm/index.js.map +1 -0
  153. package/dist/esm/internal/ActiveRuns.d.ts +51 -0
  154. package/dist/esm/internal/ActiveRuns.d.ts.map +1 -0
  155. package/dist/esm/internal/ActiveRuns.js +34 -0
  156. package/dist/esm/internal/ActiveRuns.js.map +1 -0
  157. package/dist/esm/internal/DueOccurrences.d.ts +32 -0
  158. package/dist/esm/internal/DueOccurrences.d.ts.map +1 -0
  159. package/dist/esm/internal/DueOccurrences.js +55 -0
  160. package/dist/esm/internal/DueOccurrences.js.map +1 -0
  161. package/dist/esm/internal/WallClock.d.ts +59 -0
  162. package/dist/esm/internal/WallClock.d.ts.map +1 -0
  163. package/dist/esm/internal/WallClock.js +155 -0
  164. package/dist/esm/internal/WallClock.js.map +1 -0
  165. package/dist/esm/migrations/0001_triggers.d.ts +15 -0
  166. package/dist/esm/migrations/0001_triggers.d.ts.map +1 -0
  167. package/dist/esm/migrations/0001_triggers.js +42 -0
  168. package/dist/esm/migrations/0001_triggers.js.map +1 -0
  169. package/dist/esm/migrations/0002_reservation_lease.d.ts +15 -0
  170. package/dist/esm/migrations/0002_reservation_lease.d.ts.map +1 -0
  171. package/dist/esm/migrations/0002_reservation_lease.js +19 -0
  172. package/dist/esm/migrations/0002_reservation_lease.js.map +1 -0
  173. package/dist/esm/migrations/0003_heartbeat.d.ts +16 -0
  174. package/dist/esm/migrations/0003_heartbeat.d.ts.map +1 -0
  175. package/dist/esm/migrations/0003_heartbeat.js +23 -0
  176. package/dist/esm/migrations/0003_heartbeat.js.map +1 -0
  177. package/dist/esm/migrations/0004_fire_run_index.d.ts +17 -0
  178. package/dist/esm/migrations/0004_fire_run_index.d.ts.map +1 -0
  179. package/dist/esm/migrations/0004_fire_run_index.js +20 -0
  180. package/dist/esm/migrations/0004_fire_run_index.js.map +1 -0
  181. package/dist/esm/migrations/index.d.ts +33 -0
  182. package/dist/esm/migrations/index.d.ts.map +1 -0
  183. package/dist/esm/migrations/index.js +37 -0
  184. package/dist/esm/migrations/index.js.map +1 -0
  185. package/dist/esm/test/TestTriggers.d.ts +12 -0
  186. package/dist/esm/test/TestTriggers.d.ts.map +1 -0
  187. package/dist/esm/test/TestTriggers.js +439 -0
  188. package/dist/esm/test/TestTriggers.js.map +1 -0
  189. package/docs/README.md +140 -0
  190. package/docs/api.md +1186 -0
  191. package/docs/concepts/channels.md +103 -0
  192. package/docs/concepts/claim-protocol.md +229 -0
  193. package/docs/concepts/policies.md +124 -0
  194. package/docs/concepts/schedules.md +134 -0
  195. package/docs/guides/choose-a-policy.md +113 -0
  196. package/docs/guides/declare-a-trigger.md +136 -0
  197. package/docs/guides/ingest-a-webhook.md +164 -0
  198. package/docs/guides/run-the-scheduler.md +173 -0
  199. package/docs/guides/testing.md +157 -0
  200. package/docs/installation.md +81 -0
  201. package/docs/quickstart.md +163 -0
  202. package/docs/troubleshooting.md +281 -0
  203. package/package.json +241 -3
  204. package/src/CatchUp.ts +73 -0
  205. package/src/Channel.ts +121 -0
  206. package/src/ClaimDecision.ts +303 -0
  207. package/src/Cron.ts +173 -0
  208. package/src/DispatchReader.ts +179 -0
  209. package/src/Overlap.ts +53 -0
  210. package/src/Schedule.ts +112 -0
  211. package/src/Scheduler.ts +1154 -0
  212. package/src/SqlTriggerStore.ts +638 -0
  213. package/src/Trigger.ts +84 -0
  214. package/src/TriggerError.ts +129 -0
  215. package/src/TriggerStore.ts +514 -0
  216. package/src/Webhook.ts +270 -0
  217. package/src/index.ts +30 -0
  218. package/src/internal/ActiveRuns.ts +83 -0
  219. package/src/internal/DueOccurrences.ts +101 -0
  220. package/src/internal/WallClock.ts +172 -0
  221. package/src/migrations/0001_triggers.ts +43 -0
  222. package/src/migrations/0002_reservation_lease.ts +20 -0
  223. package/src/migrations/0003_heartbeat.ts +24 -0
  224. package/src/migrations/0004_fire_run_index.ts +21 -0
  225. package/src/migrations/index.ts +40 -0
  226. package/src/test/TestTriggers.ts +563 -0
@@ -0,0 +1,439 @@
1
+ /** @since 0.1.0 */
2
+ import * as Clock from "effect/Clock";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Option from "effect/Option";
6
+ import * as Ref from "effect/Ref";
7
+ import * as Result from "effect/Result";
8
+ import * as Schema from "effect/Schema";
9
+ import * as ClaimDecision from "../ClaimDecision.js";
10
+ import * as Overlap from "../Overlap.js";
11
+ import * as Schedule from "../Schedule.js";
12
+ import * as Trigger from "../Trigger.js";
13
+ import { fromSchemaError, TriggerError } from "../TriggerError.js";
14
+ import { compareNewestFirst, historyLimit, historyPage, isAfterCursor, isReservation, listed, pruneCutoff, reservationId, reservationOccurrence, resultRefusal, TriggerStore } from "../TriggerStore.js";
15
+ const registered = (stored) => ({ ...stored, input: JSON.parse(stored.input) });
16
+ const byId = (left, right) => left.id < right.id ? -1 : 1;
17
+ const key = (triggerId, occurrence) => `${triggerId}:${occurrence}`;
18
+ // The occurrence is the numeric tail, so a trigger id holding a colon still
19
+ // splits at the right place.
20
+ const fireRecord = (current, fireKey, outcome) => {
21
+ const at = fireKey.lastIndexOf(":");
22
+ const runId = current.fireRunIds.get(fireKey);
23
+ const error = current.fireErrors.get(fireKey);
24
+ return {
25
+ triggerId: fireKey.slice(0, at),
26
+ occurrence: Number(fireKey.slice(at + 1)),
27
+ outcome,
28
+ ...(runId === undefined ? {} : { runId }),
29
+ ...(error === undefined ? {} : { error })
30
+ };
31
+ };
32
+ const unknown = (triggerId) => new TriggerError({ code: "unknown_trigger", message: `unknown trigger ${triggerId}` });
33
+ const advanced = (trigger, occurrence) => ({
34
+ ...trigger,
35
+ lastFiredAt: Math.max(trigger.lastFiredAt ?? occurrence, occurrence)
36
+ });
37
+ const fireSnapshot = (current, fireKey) => {
38
+ if (!current.fires.has(fireKey))
39
+ return undefined;
40
+ const runId = current.fireRunIds.get(fireKey);
41
+ return { outcome: current.fires.get(fireKey) ?? null, ...(runId === undefined ? {} : { runId }) };
42
+ };
43
+ /**
44
+ * Gathers the rows {@link ClaimDecision.decide} reads, then applies the writes
45
+ * it answers. The protocol itself lives in that module, so this layer refuses
46
+ * and reclaims exactly what the SQL store does rather than restating the rules
47
+ * over maps.
48
+ */
49
+ const applyClaim = (trigger, fire, current, claimedAt) => {
50
+ const fireKey = key(fire.triggerId, fire.occurrence);
51
+ const existingFire = fireSnapshot(current, fireKey);
52
+ const held = current.active.get(fire.triggerId);
53
+ const activeRunId = held?.runId;
54
+ const activeOccurrence = activeRunId !== undefined && isReservation(activeRunId)
55
+ ? reservationOccurrence(activeRunId)
56
+ : undefined;
57
+ // The SQL store reads the reservation's row after inserting the claimed
58
+ // occurrence, so a row this claim creates reads back as unsettled.
59
+ const activeFire = activeOccurrence === undefined
60
+ ? undefined
61
+ : activeOccurrence === fire.occurrence
62
+ ? existingFire ?? { outcome: null }
63
+ : fireSnapshot(current, key(fire.triggerId, activeOccurrence));
64
+ const claimedAtMs = held?.claimedAt;
65
+ const pendingAt = current.pending.get(fire.triggerId);
66
+ const decision = ClaimDecision.decide({
67
+ fire,
68
+ claimedAt,
69
+ reservationId: reservationId(fire.triggerId, fire.occurrence, globalThis.crypto.randomUUID()),
70
+ snapshot: {
71
+ revision: trigger.revision,
72
+ enabled: trigger.enabled,
73
+ overlap: trigger.overlap,
74
+ ...(activeRunId === undefined ? {} : { activeRunId }),
75
+ ...(claimedAtMs === undefined ? {} : { activeClaimedAt: claimedAtMs }),
76
+ ...(pendingAt === undefined ? {} : { pending: pendingAt }),
77
+ ...(existingFire === undefined ? {} : { existingFire }),
78
+ ...(activeFire === undefined ? {} : { activeFire })
79
+ }
80
+ });
81
+ if (decision._tag === "Refused")
82
+ return { _tag: "Failure", error: decision.error };
83
+ if (!decision.claim.claimed)
84
+ return { _tag: "Success", claim: decision.claim, state: current };
85
+ const triggers = new Map(current.triggers);
86
+ const fires = new Map(current.fires);
87
+ const fireRunIds = new Map(current.fireRunIds);
88
+ const pending = new Map(current.pending);
89
+ const active = new Map(current.active);
90
+ if (!fires.has(fireKey))
91
+ fires.set(fireKey, null);
92
+ for (const write of decision.writes) {
93
+ switch (write._tag) {
94
+ case "SetOutcome": {
95
+ const target = key(fire.triggerId, write.occurrence);
96
+ // The decision and application are one Ref.modify operation. An
97
+ // active reservation's fire cannot settle between these two steps.
98
+ fires.set(target, write.outcome);
99
+ break;
100
+ }
101
+ case "SetRunId": {
102
+ fireRunIds.set(key(fire.triggerId, write.occurrence), write.runId);
103
+ break;
104
+ }
105
+ case "ReleaseReservation": {
106
+ // The decision and its writes share this Ref.modify snapshot; the
107
+ // expected reservation cannot change between them.
108
+ if (write.activeRunId === undefined)
109
+ active.delete(fire.triggerId);
110
+ else
111
+ active.set(fire.triggerId, { runId: write.activeRunId });
112
+ if (write.pending === undefined)
113
+ pending.delete(fire.triggerId);
114
+ else
115
+ pending.set(fire.triggerId, write.pending);
116
+ break;
117
+ }
118
+ case "AdvanceCursor": {
119
+ triggers.set(fire.triggerId, advanced(trigger, write.occurrence));
120
+ break;
121
+ }
122
+ case "SetPending": {
123
+ pending.set(fire.triggerId, write.occurrence);
124
+ break;
125
+ }
126
+ case "Reserve": {
127
+ active.set(fire.triggerId, { runId: write.reservationId, claimedAt: write.claimedAt });
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ return {
133
+ _tag: "Success",
134
+ claim: decision.claim,
135
+ state: { ...current, triggers, fires, fireRunIds, pending, active }
136
+ };
137
+ };
138
+ /**
139
+ * Provides an in-memory {@link TriggerStore} for tests: real claim and
140
+ * overlap semantics, no database.
141
+ *
142
+ * @category layers
143
+ * @since 0.1.0
144
+ */
145
+ export const layer = Layer.effect(TriggerStore)(Effect.gen(function* () {
146
+ const state = yield* Ref.make({
147
+ triggers: new Map(),
148
+ fires: new Map(),
149
+ fireRunIds: new Map(),
150
+ fireErrors: new Map(),
151
+ heartbeats: new Map(),
152
+ runOccurrences: new Map(),
153
+ pending: new Map(),
154
+ active: new Map()
155
+ });
156
+ const get = (triggerId) => Ref.get(state).pipe(Effect.map((current) => {
157
+ const trigger = current.triggers.get(triggerId);
158
+ return trigger === undefined ? Option.none() : Option.some(registered(trigger));
159
+ }));
160
+ const requireTrigger = (triggerId, modify) => Ref.modify(state, (current) => {
161
+ const trigger = current.triggers.get(triggerId);
162
+ if (trigger === undefined)
163
+ return [Effect.fail(unknown(triggerId)), current];
164
+ const [value, next] = modify(trigger, current);
165
+ return [Effect.succeed(value), next];
166
+ }).pipe(Effect.flatten);
167
+ const claimFire = (fire) => Effect.flatMap(Clock.currentTimeMillis, (claimedAt) => Ref.modify(state, (current) => {
168
+ const trigger = current.triggers.get(fire.triggerId);
169
+ if (trigger === undefined)
170
+ return [Effect.fail(unknown(fire.triggerId)), current];
171
+ const decision = applyClaim(trigger, fire, current, claimedAt);
172
+ return decision._tag === "Failure"
173
+ ? [Effect.fail(decision.error), current]
174
+ : [Effect.succeed(decision.claim), decision.state];
175
+ }).pipe(Effect.flatten));
176
+ return TriggerStore.of({
177
+ // Registration decodes and serializes at the call boundary, before the
178
+ // returned Effect runs, so the declaration this layer keeps is the one the
179
+ // caller passed rather than whatever its object became later. The SQL
180
+ // store does the same, and its lazy schedule validation runs here too.
181
+ register: (declaration) => {
182
+ const decoded = Schema.decodeUnknownResult(Trigger.Trigger)(declaration);
183
+ if (Result.isFailure(decoded)) {
184
+ return Effect.fail(fromSchemaError("invalid_trigger", "Trigger declaration is invalid", decoded.failure));
185
+ }
186
+ const snapshot = decoded.success;
187
+ let input;
188
+ try {
189
+ input = JSON.stringify(snapshot.input);
190
+ }
191
+ catch (cause) {
192
+ return Effect.fail(new TriggerError({ code: "store", message: "trigger input is not JSON-serializable", cause }));
193
+ }
194
+ if (input === undefined) {
195
+ return Effect.fail(new TriggerError({
196
+ code: "invalid_trigger",
197
+ message: "trigger input has no JSON representation",
198
+ path: "input"
199
+ }));
200
+ }
201
+ const serialized = input;
202
+ return Effect.suspend(() => Schedule.validate(snapshot).pipe(Effect.andThen(Ref.modify(state, (current) => {
203
+ const prior = current.triggers.get(snapshot.id);
204
+ const stored = {
205
+ ...snapshot,
206
+ input: serialized,
207
+ revision: (prior?.revision ?? 0) + 1,
208
+ ...(prior?.lastFiredAt === undefined ? {} : { lastFiredAt: prior.lastFiredAt })
209
+ };
210
+ return [registered(stored), { ...current, triggers: new Map(current.triggers).set(snapshot.id, stored) }];
211
+ }))));
212
+ },
213
+ get,
214
+ list: () => Ref.get(state).pipe(Effect.map((current) =>
215
+ // Nothing writes a declaration this layer cannot read back, so every
216
+ // listed row decodes. The SQL store is where a corrupt `input_json`
217
+ // can arrive.
218
+ [...current.triggers.values()].sort(byId).map((stored) => {
219
+ const activeRunId = current.active.get(stored.id)?.runId;
220
+ const pendingAt = current.pending.get(stored.id);
221
+ return listed(registered(stored), {
222
+ ...(activeRunId === undefined ? {} : { activeRunId }),
223
+ ...(pendingAt === undefined ? {} : { pendingAt })
224
+ });
225
+ }))),
226
+ listEnabled: () => Ref.get(state).pipe(Effect.map((current) => [...current.triggers.values()].filter((trigger) => trigger.enabled).sort(byId).map(registered))),
227
+ claimFire,
228
+ claimPending: (fire) => Effect.flatMap(Clock.currentTimeMillis, (claimedAt) => Ref.modify(state, (current) => {
229
+ const trigger = current.triggers.get(fire.triggerId);
230
+ if (trigger === undefined)
231
+ return [Effect.fail(unknown(fire.triggerId)), current];
232
+ const occurrence = current.pending.get(fire.triggerId);
233
+ if (occurrence === undefined)
234
+ return [Effect.succeed(Option.none()), current];
235
+ const decision = applyClaim(trigger, {
236
+ triggerId: fire.triggerId,
237
+ occurrence,
238
+ expectedRevision: fire.expectedRevision,
239
+ resumeBuffered: true
240
+ }, current, claimedAt);
241
+ if (decision._tag === "Failure")
242
+ return [Effect.fail(decision.error), current];
243
+ let next = decision.state;
244
+ if (decision.claim.claimed && decision.claim.action !== "buffer") {
245
+ const pending = new Map(decision.state.pending);
246
+ pending.delete(fire.triggerId);
247
+ next = { ...decision.state, pending };
248
+ }
249
+ return [Effect.succeed(Option.some({ occurrence, claim: decision.claim })), next];
250
+ }).pipe(Effect.flatten)),
251
+ recordResult: (result) => requireTrigger(result.triggerId, (trigger, current) => {
252
+ const existingOutcome = current.fires.get(key(result.triggerId, result.occurrence));
253
+ const existing = existingOutcome === undefined
254
+ ? undefined
255
+ : fireRecord(current, key(result.triggerId, result.occurrence), existingOutcome);
256
+ const refusal = resultRefusal(result, existing, current.active.get(result.triggerId)?.runId);
257
+ if (refusal !== undefined)
258
+ return [Effect.fail(refusal), current];
259
+ if (result.outcome !== "launched" && existing?.outcome === result.outcome)
260
+ return [Effect.void, current];
261
+ const triggers = new Map(current.triggers).set(result.triggerId, advanced(trigger, result.occurrence));
262
+ const active = new Map(current.active);
263
+ const fires = new Map(current.fires);
264
+ const fireRunIds = new Map(current.fireRunIds);
265
+ const fireErrors = new Map(current.fireErrors);
266
+ const runOccurrences = new Map(current.runOccurrences);
267
+ const fireKey = key(result.triggerId, result.occurrence);
268
+ const recordedRunId = current.fireRunIds.get(fireKey);
269
+ const terminal = result.outcome === "completed" ||
270
+ result.outcome === "failed" ||
271
+ result.outcome === "superseded";
272
+ // resultRefusal proved this ledger entry exists and can transition.
273
+ fires.set(fireKey, result.outcome);
274
+ if (result.runId !== undefined)
275
+ fireRunIds.set(fireKey, result.runId);
276
+ else if (!terminal)
277
+ fireRunIds.delete(fireKey);
278
+ // The SQL store overwrites the error column on every result.
279
+ if (result.error === undefined)
280
+ fireErrors.delete(fireKey);
281
+ else
282
+ fireErrors.set(fireKey, result.error);
283
+ if (result.outcome === "launched") {
284
+ active.set(result.triggerId, { runId: result.runId });
285
+ runOccurrences.set(result.runId, result.occurrence);
286
+ }
287
+ else if (terminal) {
288
+ const currentRunId = active.get(result.triggerId)?.runId;
289
+ const resultOwner = existing?.outcome === "launched"
290
+ ? recordedRunId
291
+ : result.reservationId ?? result.runId ?? currentRunId;
292
+ if (currentRunId === resultOwner)
293
+ active.delete(result.triggerId);
294
+ }
295
+ else {
296
+ // A changed skip/buffer decision passed the reservation fence above.
297
+ active.delete(result.triggerId);
298
+ }
299
+ return [Effect.void, { ...current, triggers, fires, fireRunIds, fireErrors, runOccurrences, active }];
300
+ }).pipe(Effect.flatten),
301
+ restorePending: (fire) => requireTrigger(fire.triggerId, (_trigger, current) => {
302
+ const outcome = current.fires.get(key(fire.triggerId, fire.occurrence));
303
+ if (current.active.get(fire.triggerId)?.runId !== fire.reservationId ||
304
+ reservationOccurrence(fire.reservationId) !== fire.occurrence || (outcome !== null && outcome !== "buffered")) {
305
+ return [
306
+ Effect.fail(new TriggerError({ code: "stale_owner", message: "pending restoration no longer owns reservation" })),
307
+ current
308
+ ];
309
+ }
310
+ const pending = new Map(current.pending).set(fire.triggerId, Overlap.pendingAfter({ running: false, pending: current.pending.get(fire.triggerId), due: fire.occurrence }));
311
+ const active = new Map(current.active);
312
+ const predecessor = current.fireRunIds.get(key(fire.triggerId, fire.occurrence));
313
+ const predecessorOccurrence = predecessor === undefined ? undefined : current.runOccurrences.get(predecessor);
314
+ if (predecessor !== undefined && predecessorOccurrence !== undefined &&
315
+ current.fires.get(key(fire.triggerId, predecessorOccurrence)) === "launched") {
316
+ active.set(fire.triggerId, { runId: predecessor });
317
+ }
318
+ else {
319
+ active.delete(fire.triggerId);
320
+ }
321
+ return [Effect.void, { ...current, pending, active }];
322
+ }).pipe(Effect.flatten),
323
+ setPending: (fire) => requireTrigger(fire.triggerId, (_trigger, current) => {
324
+ const pending = new Map(current.pending);
325
+ pending.set(fire.triggerId, Overlap.pendingAfter({ running: true, pending: pending.get(fire.triggerId), due: fire.occurrence }));
326
+ return [undefined, { ...current, pending }];
327
+ }),
328
+ activeRun: (triggerId) => Effect.flatMap(Clock.currentTimeMillis, (now) => requireTrigger(triggerId, (_trigger, current) => {
329
+ const held = current.active.get(triggerId);
330
+ const runId = held?.runId;
331
+ const occurrence = runId !== undefined && isReservation(runId) ? reservationOccurrence(runId) : undefined;
332
+ const unfinished = occurrence === undefined
333
+ ? undefined
334
+ : fireSnapshot(current, key(triggerId, occurrence));
335
+ const claimedAt = held?.claimedAt;
336
+ const pendingAt = current.pending.get(triggerId);
337
+ const lease = ClaimDecision.lease({
338
+ now,
339
+ ...(runId === undefined ? {} : { activeRunId: runId }),
340
+ ...(claimedAt === undefined ? {} : { activeClaimedAt: claimedAt }),
341
+ ...(pendingAt === undefined ? {} : { pending: pendingAt }),
342
+ ...(unfinished === undefined || (unfinished.outcome !== null && unfinished.outcome !== "buffered")
343
+ ? {}
344
+ : { unfinished })
345
+ });
346
+ if (lease._tag === "Idle")
347
+ return [Option.none(), current];
348
+ if (lease._tag === "Held")
349
+ return [Option.some(lease.runId), current];
350
+ const active = new Map(current.active);
351
+ const pending = new Map(current.pending);
352
+ if (lease.pending !== undefined)
353
+ pending.set(triggerId, lease.pending);
354
+ if (lease.recovered === undefined)
355
+ active.delete(triggerId);
356
+ else
357
+ active.set(triggerId, { runId: lease.recovered });
358
+ return [
359
+ lease.recovered === undefined ? Option.none() : Option.some(lease.recovered),
360
+ { ...current, active, pending }
361
+ ];
362
+ })),
363
+ activeOccurrence: (triggerId, runId) => requireTrigger(triggerId, (_trigger, current) => {
364
+ const reserved = reservationOccurrence(runId);
365
+ const launched = current.runOccurrences.get(runId);
366
+ const occurrence = reserved ??
367
+ (launched !== undefined && current.fires.get(key(triggerId, launched)) === "launched" ? launched : undefined);
368
+ return [occurrence === undefined ? Option.none() : Option.some(occurrence), current];
369
+ }),
370
+ clearActive: (triggerId, runId) => Ref.update(state, (current) => {
371
+ if (current.active.get(triggerId)?.runId !== runId)
372
+ return current;
373
+ const active = new Map(current.active);
374
+ active.delete(triggerId);
375
+ return { ...current, active };
376
+ }),
377
+ history: (query = {}) => Effect.flatMap(historyLimit(query.limit), (limit) => Ref.get(state).pipe(Effect.map((current) => {
378
+ const records = [...current.fires.entries()]
379
+ .map(([fireKey, outcome]) => fireRecord(current, fireKey, outcome))
380
+ .filter((record) => (query.triggerId === undefined || record.triggerId === query.triggerId) &&
381
+ (query.runId === undefined || record.runId === query.runId) &&
382
+ (query.outcome === undefined || record.outcome === query.outcome) &&
383
+ (query.cursor === undefined || isAfterCursor(record, query.cursor)))
384
+ .sort(compareNewestFirst);
385
+ return historyPage(records, limit);
386
+ }))),
387
+ pruneFires: ({ olderThan }) => Effect.flatMap(pruneCutoff(olderThan), (cutoff) => Ref.modify(state, (current) => {
388
+ const fires = new Map(current.fires);
389
+ const fireRunIds = new Map(current.fireRunIds);
390
+ const fireErrors = new Map(current.fireErrors);
391
+ const runOccurrences = new Map(current.runOccurrences);
392
+ let removed = 0;
393
+ for (const [fireKey, outcome] of current.fires) {
394
+ if (outcome !== "completed" && outcome !== "failed" && outcome !== "skipped" && outcome !== "superseded")
395
+ continue;
396
+ const record = fireRecord(current, fireKey, outcome);
397
+ if (record.occurrence >= cutoff)
398
+ continue;
399
+ if (current.pending.get(record.triggerId) === record.occurrence)
400
+ continue;
401
+ if (record.runId !== undefined && current.active.get(record.triggerId)?.runId === record.runId)
402
+ continue;
403
+ fires.delete(fireKey);
404
+ fireErrors.delete(fireKey);
405
+ if (record.runId !== undefined) {
406
+ fireRunIds.delete(fireKey);
407
+ if (runOccurrences.get(record.runId) === record.occurrence)
408
+ runOccurrences.delete(record.runId);
409
+ }
410
+ removed += 1;
411
+ }
412
+ return [removed, { ...current, fires, fireRunIds, fireErrors, runOccurrences }];
413
+ })),
414
+ inspect: (triggerId) => requireTrigger(triggerId, (_trigger, current) => {
415
+ const activeRunId = current.active.get(triggerId)?.runId;
416
+ const pendingAt = current.pending.get(triggerId);
417
+ return [
418
+ {
419
+ ...(activeRunId === undefined ? {} : { activeRunId }),
420
+ ...(pendingAt === undefined ? {} : { pendingAt })
421
+ },
422
+ current
423
+ ];
424
+ }),
425
+ heartbeat: (host) => Effect.flatMap(Clock.currentTimeMillis, (tickedAt) => Ref.update(state, (current) => ({ ...current, heartbeats: new Map(current.heartbeats).set(host, tickedAt) }))),
426
+ lastHeartbeat: () => Ref.get(state).pipe(Effect.map((current) => {
427
+ let latest;
428
+ for (const [host, tickedAt] of current.heartbeats) {
429
+ // Newest wins; equal times fall to the lower host name, as the SQL
430
+ // store's ORDER BY does.
431
+ if (latest === undefined || tickedAt > latest.tickedAt || (tickedAt === latest.tickedAt && host < latest.host)) {
432
+ latest = { host, tickedAt };
433
+ }
434
+ }
435
+ return latest === undefined ? Option.none() : Option.some(latest);
436
+ }))
437
+ });
438
+ }));
439
+ //# sourceMappingURL=TestTriggers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTriggers.js","sourceRoot":"","sources":["../../../src/test/TestTriggers.ts"],"names":[],"mappings":"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","sourcesContent":["/** @since 0.1.0 */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * as Result from \"effect/Result\"\nimport * as Schema from \"effect/Schema\"\nimport * as ClaimDecision from \"../ClaimDecision.ts\"\nimport * as Overlap from \"../Overlap.ts\"\nimport * as Schedule from \"../Schedule.ts\"\nimport * as Trigger from \"../Trigger.ts\"\nimport { fromSchemaError, TriggerError } from \"../TriggerError.ts\"\nimport {\n type Claim,\n type ClaimFire,\n compareNewestFirst,\n type FireRecord,\n type Heartbeat,\n historyLimit,\n historyPage,\n isAfterCursor,\n isReservation,\n type Listed,\n listed,\n type Outcome,\n pruneCutoff,\n type Registered,\n reservationId,\n reservationOccurrence,\n resultRefusal,\n type Service,\n TriggerStore\n} from \"../TriggerStore.ts\"\n\n/**\n * The columns the SQL store keeps on `flows_triggers` and `flows_trigger_fires`,\n * spread across maps so one trigger's state can be replaced without copying the\n * rest.\n *\n * `active` holds one record per trigger with a run or reservation, so a claim,\n * an expiry, a launch and a clear each replace or delete one entry rather than\n * keeping parallel maps in step. A launch keeps the run but drops `claimedAt`,\n * which is how the SQL store spells a reservation that predates the lease\n * column.\n *\n * One invariant lets the readers below index without a fallback, and this\n * module alone writes it: `fireRunIds` names a run only for a fire that a\n * claim or a result attached one to, and every such run was recorded\n * `launched` first, so `runOccurrences` knows the occurrence it belongs to.\n *\n * `fireErrors` and `heartbeats` are the SQL store's `error` column and its\n * `flows_scheduler_heartbeat` table.\n */\n/**\n * A registered declaration as this layer keeps it: the SQL store persists\n * `input` as a JSON string, so holding the same string is what makes every\n * reading an independent value rather than an alias of the caller's object.\n */\ninterface Stored extends Omit<Registered, \"input\"> {\n readonly input: string\n}\n\nconst registered = (stored: Stored): Registered => ({ ...stored, input: JSON.parse(stored.input) })\n\nconst byId = (left: Stored, right: Stored): number => left.id < right.id ? -1 : 1\n\n/**\n * The run or reservation a trigger holds: the `active_run_id` and\n * `active_claimed_at_ms` columns of one `flows_triggers` row.\n */\ninterface Active {\n readonly runId: string\n readonly claimedAt?: number\n}\n\ninterface State {\n readonly triggers: ReadonlyMap<string, Stored>\n readonly fires: ReadonlyMap<string, Outcome | null>\n readonly fireRunIds: ReadonlyMap<string, string>\n readonly fireErrors: ReadonlyMap<string, string>\n readonly heartbeats: ReadonlyMap<string, number>\n readonly runOccurrences: ReadonlyMap<string, number>\n readonly pending: ReadonlyMap<string, number>\n readonly active: ReadonlyMap<string, Active>\n}\n\nconst key = (triggerId: string, occurrence: number) => `${triggerId}:${occurrence}`\n\n// The occurrence is the numeric tail, so a trigger id holding a colon still\n// splits at the right place.\nconst fireRecord = (current: State, fireKey: string, outcome: Outcome | null): FireRecord => {\n const at = fireKey.lastIndexOf(\":\")\n const runId = current.fireRunIds.get(fireKey)\n const error = current.fireErrors.get(fireKey)\n return {\n triggerId: fireKey.slice(0, at),\n occurrence: Number(fireKey.slice(at + 1)),\n outcome,\n ...(runId === undefined ? {} : { runId }),\n ...(error === undefined ? {} : { error })\n }\n}\nconst unknown = (triggerId: string) =>\n new TriggerError({ code: \"unknown_trigger\", message: `unknown trigger ${triggerId}` })\n\nconst advanced = (trigger: Stored, occurrence: number): Stored => ({\n ...trigger,\n lastFiredAt: Math.max(trigger.lastFiredAt ?? occurrence, occurrence)\n})\n\ntype Decision =\n | { readonly _tag: \"Failure\"; readonly error: TriggerError }\n | { readonly _tag: \"Success\"; readonly claim: Claim; readonly state: State }\n\nconst fireSnapshot = (current: State, fireKey: string): ClaimDecision.Fire | undefined => {\n if (!current.fires.has(fireKey)) return undefined\n const runId = current.fireRunIds.get(fireKey)\n return { outcome: current.fires.get(fireKey) ?? null, ...(runId === undefined ? {} : { runId }) }\n}\n\n/**\n * Gathers the rows {@link ClaimDecision.decide} reads, then applies the writes\n * it answers. The protocol itself lives in that module, so this layer refuses\n * and reclaims exactly what the SQL store does rather than restating the rules\n * over maps.\n */\nconst applyClaim = (\n trigger: Stored,\n fire: ClaimFire,\n current: State,\n claimedAt: number\n): Decision => {\n const fireKey = key(fire.triggerId, fire.occurrence)\n const existingFire = fireSnapshot(current, fireKey)\n const held = current.active.get(fire.triggerId)\n const activeRunId = held?.runId\n const activeOccurrence = activeRunId !== undefined && isReservation(activeRunId)\n ? reservationOccurrence(activeRunId)\n : undefined\n // The SQL store reads the reservation's row after inserting the claimed\n // occurrence, so a row this claim creates reads back as unsettled.\n const activeFire = activeOccurrence === undefined\n ? undefined\n : activeOccurrence === fire.occurrence\n ? existingFire ?? { outcome: null }\n : fireSnapshot(current, key(fire.triggerId, activeOccurrence))\n const claimedAtMs = held?.claimedAt\n const pendingAt = current.pending.get(fire.triggerId)\n const decision = ClaimDecision.decide({\n fire,\n claimedAt,\n reservationId: reservationId(fire.triggerId, fire.occurrence, globalThis.crypto.randomUUID()),\n snapshot: {\n revision: trigger.revision,\n enabled: trigger.enabled,\n overlap: trigger.overlap,\n ...(activeRunId === undefined ? {} : { activeRunId }),\n ...(claimedAtMs === undefined ? {} : { activeClaimedAt: claimedAtMs }),\n ...(pendingAt === undefined ? {} : { pending: pendingAt }),\n ...(existingFire === undefined ? {} : { existingFire }),\n ...(activeFire === undefined ? {} : { activeFire })\n }\n })\n if (decision._tag === \"Refused\") return { _tag: \"Failure\", error: decision.error }\n if (!decision.claim.claimed) return { _tag: \"Success\", claim: decision.claim, state: current }\n const triggers = new Map(current.triggers)\n const fires = new Map(current.fires)\n const fireRunIds = new Map(current.fireRunIds)\n const pending = new Map(current.pending)\n const active = new Map(current.active)\n if (!fires.has(fireKey)) fires.set(fireKey, null)\n for (const write of decision.writes) {\n switch (write._tag) {\n case \"SetOutcome\": {\n const target = key(fire.triggerId, write.occurrence)\n // The decision and application are one Ref.modify operation. An\n // active reservation's fire cannot settle between these two steps.\n fires.set(target, write.outcome)\n break\n }\n case \"SetRunId\": {\n fireRunIds.set(key(fire.triggerId, write.occurrence), write.runId)\n break\n }\n case \"ReleaseReservation\": {\n // The decision and its writes share this Ref.modify snapshot; the\n // expected reservation cannot change between them.\n if (write.activeRunId === undefined) active.delete(fire.triggerId)\n else active.set(fire.triggerId, { runId: write.activeRunId })\n if (write.pending === undefined) pending.delete(fire.triggerId)\n else pending.set(fire.triggerId, write.pending)\n break\n }\n case \"AdvanceCursor\": {\n triggers.set(fire.triggerId, advanced(trigger, write.occurrence))\n break\n }\n case \"SetPending\": {\n pending.set(fire.triggerId, write.occurrence)\n break\n }\n case \"Reserve\": {\n active.set(fire.triggerId, { runId: write.reservationId, claimedAt: write.claimedAt })\n break\n }\n }\n }\n return {\n _tag: \"Success\",\n claim: decision.claim,\n state: { ...current, triggers, fires, fireRunIds, pending, active }\n }\n}\n\n/**\n * Provides an in-memory {@link TriggerStore} for tests: real claim and\n * overlap semantics, no database.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer: Layer.Layer<TriggerStore> = Layer.effect(TriggerStore)(Effect.gen(function*() {\n const state = yield* Ref.make<State>({\n triggers: new Map(),\n fires: new Map(),\n fireRunIds: new Map(),\n fireErrors: new Map(),\n heartbeats: new Map(),\n runOccurrences: new Map(),\n pending: new Map(),\n active: new Map()\n })\n const get: Service[\"get\"] = (triggerId) =>\n Ref.get(state).pipe(Effect.map((current) => {\n const trigger = current.triggers.get(triggerId)\n return trigger === undefined ? Option.none() : Option.some(registered(trigger))\n }))\n const requireTrigger = <A>(\n triggerId: string,\n modify: (trigger: Stored, current: State) => readonly [A, State]\n ): Effect.Effect<A, TriggerError> =>\n Ref.modify(state, (current): readonly [Effect.Effect<A, TriggerError>, State] => {\n const trigger = current.triggers.get(triggerId)\n if (trigger === undefined) return [Effect.fail(unknown(triggerId)), current]\n const [value, next] = modify(trigger, current)\n return [Effect.succeed(value), next]\n }).pipe(Effect.flatten)\n const claimFire: Service[\"claimFire\"] = (fire) =>\n Effect.flatMap(\n Clock.currentTimeMillis,\n (claimedAt) =>\n Ref.modify(state, (current): readonly [Effect.Effect<Claim, TriggerError>, State] => {\n const trigger = current.triggers.get(fire.triggerId)\n if (trigger === undefined) return [Effect.fail(unknown(fire.triggerId)), current]\n const decision = applyClaim(trigger, fire, current, claimedAt)\n return decision._tag === \"Failure\"\n ? [Effect.fail(decision.error), current]\n : [Effect.succeed(decision.claim), decision.state]\n }).pipe(Effect.flatten)\n )\n return TriggerStore.of({\n // Registration decodes and serializes at the call boundary, before the\n // returned Effect runs, so the declaration this layer keeps is the one the\n // caller passed rather than whatever its object became later. The SQL\n // store does the same, and its lazy schedule validation runs here too.\n register: (declaration) => {\n const decoded = Schema.decodeUnknownResult(Trigger.Trigger)(declaration)\n if (Result.isFailure(decoded)) {\n return Effect.fail(fromSchemaError(\"invalid_trigger\", \"Trigger declaration is invalid\", decoded.failure))\n }\n const snapshot = decoded.success\n let input: string | undefined\n try {\n input = JSON.stringify(snapshot.input)\n } catch (cause) {\n return Effect.fail(\n new TriggerError({ code: \"store\", message: \"trigger input is not JSON-serializable\", cause })\n )\n }\n if (input === undefined) {\n return Effect.fail(\n new TriggerError({\n code: \"invalid_trigger\",\n message: \"trigger input has no JSON representation\",\n path: \"input\"\n })\n )\n }\n const serialized = input\n return Effect.suspend(() =>\n Schedule.validate(snapshot).pipe(\n Effect.andThen(Ref.modify(state, (current) => {\n const prior = current.triggers.get(snapshot.id)\n const stored: Stored = {\n ...snapshot,\n input: serialized,\n revision: (prior?.revision ?? 0) + 1,\n ...(prior?.lastFiredAt === undefined ? {} : { lastFiredAt: prior.lastFiredAt })\n }\n return [registered(stored), { ...current, triggers: new Map(current.triggers).set(snapshot.id, stored) }]\n }))\n )\n )\n },\n get,\n list: () =>\n Ref.get(state).pipe(\n Effect.map((current) =>\n // Nothing writes a declaration this layer cannot read back, so every\n // listed row decodes. The SQL store is where a corrupt `input_json`\n // can arrive.\n [...current.triggers.values()].sort(byId).map((stored): Listed => {\n const activeRunId = current.active.get(stored.id)?.runId\n const pendingAt = current.pending.get(stored.id)\n return listed(registered(stored), {\n ...(activeRunId === undefined ? {} : { activeRunId }),\n ...(pendingAt === undefined ? {} : { pendingAt })\n })\n })\n )\n ),\n listEnabled: () =>\n Ref.get(state).pipe(\n Effect.map((current) =>\n [...current.triggers.values()].filter((trigger) => trigger.enabled).sort(byId).map(registered)\n )\n ),\n claimFire,\n claimPending: (fire) =>\n Effect.flatMap(Clock.currentTimeMillis, (claimedAt) =>\n Ref.modify(state, (current): readonly [\n ReturnType<Service[\"claimPending\"]>,\n State\n ] => {\n const trigger = current.triggers.get(fire.triggerId)\n if (trigger === undefined) return [Effect.fail(unknown(fire.triggerId)), current]\n const occurrence = current.pending.get(fire.triggerId)\n if (occurrence === undefined) return [Effect.succeed(Option.none()), current]\n const decision = applyClaim(\n trigger,\n {\n triggerId: fire.triggerId,\n occurrence,\n expectedRevision: fire.expectedRevision,\n resumeBuffered: true\n },\n current,\n claimedAt\n )\n if (decision._tag === \"Failure\") return [Effect.fail(decision.error), current]\n let next = decision.state\n if (decision.claim.claimed && decision.claim.action !== \"buffer\") {\n const pending = new Map(decision.state.pending)\n pending.delete(fire.triggerId)\n next = { ...decision.state, pending }\n }\n return [Effect.succeed(Option.some({ occurrence, claim: decision.claim })), next]\n }).pipe(Effect.flatten)),\n recordResult: (result) =>\n requireTrigger(result.triggerId, (trigger, current): readonly [Effect.Effect<void, TriggerError>, State] => {\n const existingOutcome = current.fires.get(key(result.triggerId, result.occurrence))\n const existing = existingOutcome === undefined\n ? undefined\n : fireRecord(current, key(result.triggerId, result.occurrence), existingOutcome)\n const refusal = resultRefusal(result, existing, current.active.get(result.triggerId)?.runId)\n if (refusal !== undefined) return [Effect.fail(refusal), current]\n if (result.outcome !== \"launched\" && existing?.outcome === result.outcome) return [Effect.void, current]\n const triggers = new Map(current.triggers).set(result.triggerId, advanced(trigger, result.occurrence))\n const active = new Map(current.active)\n const fires = new Map(current.fires)\n const fireRunIds = new Map(current.fireRunIds)\n const fireErrors = new Map(current.fireErrors)\n const runOccurrences = new Map(current.runOccurrences)\n const fireKey = key(result.triggerId, result.occurrence)\n const recordedRunId = current.fireRunIds.get(fireKey)\n const terminal = result.outcome === \"completed\" ||\n result.outcome === \"failed\" ||\n result.outcome === \"superseded\"\n // resultRefusal proved this ledger entry exists and can transition.\n fires.set(fireKey, result.outcome)\n if (result.runId !== undefined) fireRunIds.set(fireKey, result.runId)\n else if (!terminal) fireRunIds.delete(fireKey)\n // The SQL store overwrites the error column on every result.\n if (result.error === undefined) fireErrors.delete(fireKey)\n else fireErrors.set(fireKey, result.error)\n if (result.outcome === \"launched\") {\n active.set(result.triggerId, { runId: result.runId })\n runOccurrences.set(result.runId, result.occurrence)\n } else if (terminal) {\n const currentRunId = active.get(result.triggerId)?.runId\n const resultOwner = existing?.outcome === \"launched\"\n ? recordedRunId\n : result.reservationId ?? result.runId ?? currentRunId\n if (currentRunId === resultOwner) active.delete(result.triggerId)\n } else {\n // A changed skip/buffer decision passed the reservation fence above.\n active.delete(result.triggerId)\n }\n return [Effect.void, { ...current, triggers, fires, fireRunIds, fireErrors, runOccurrences, active }]\n }).pipe(Effect.flatten),\n restorePending: (fire) =>\n requireTrigger(fire.triggerId, (_trigger, current): readonly [Effect.Effect<void, TriggerError>, State] => {\n const outcome = current.fires.get(key(fire.triggerId, fire.occurrence))\n if (\n current.active.get(fire.triggerId)?.runId !== fire.reservationId ||\n reservationOccurrence(fire.reservationId) !== fire.occurrence || (outcome !== null && outcome !== \"buffered\")\n ) {\n return [\n Effect.fail(\n new TriggerError({ code: \"stale_owner\", message: \"pending restoration no longer owns reservation\" })\n ),\n current\n ]\n }\n const pending = new Map(current.pending).set(\n fire.triggerId,\n Overlap.pendingAfter({ running: false, pending: current.pending.get(fire.triggerId), due: fire.occurrence })\n )\n const active = new Map(current.active)\n const predecessor = current.fireRunIds.get(key(fire.triggerId, fire.occurrence))\n const predecessorOccurrence = predecessor === undefined ? undefined : current.runOccurrences.get(predecessor)\n if (\n predecessor !== undefined && predecessorOccurrence !== undefined &&\n current.fires.get(key(fire.triggerId, predecessorOccurrence)) === \"launched\"\n ) {\n active.set(fire.triggerId, { runId: predecessor })\n } else {\n active.delete(fire.triggerId)\n }\n return [Effect.void, { ...current, pending, active }]\n }).pipe(Effect.flatten),\n setPending: (fire) =>\n requireTrigger(fire.triggerId, (_trigger, current) => {\n const pending = new Map(current.pending)\n pending.set(\n fire.triggerId,\n Overlap.pendingAfter({ running: true, pending: pending.get(fire.triggerId), due: fire.occurrence })\n )\n return [undefined, { ...current, pending }]\n }),\n activeRun: (triggerId) =>\n Effect.flatMap(Clock.currentTimeMillis, (now) =>\n requireTrigger(triggerId, (_trigger, current) => {\n const held = current.active.get(triggerId)\n const runId = held?.runId\n const occurrence = runId !== undefined && isReservation(runId) ? reservationOccurrence(runId) : undefined\n const unfinished = occurrence === undefined\n ? undefined\n : fireSnapshot(current, key(triggerId, occurrence))\n const claimedAt = held?.claimedAt\n const pendingAt = current.pending.get(triggerId)\n const lease = ClaimDecision.lease({\n now,\n ...(runId === undefined ? {} : { activeRunId: runId }),\n ...(claimedAt === undefined ? {} : { activeClaimedAt: claimedAt }),\n ...(pendingAt === undefined ? {} : { pending: pendingAt }),\n ...(unfinished === undefined || (unfinished.outcome !== null && unfinished.outcome !== \"buffered\")\n ? {}\n : { unfinished })\n })\n if (lease._tag === \"Idle\") return [Option.none(), current]\n if (lease._tag === \"Held\") return [Option.some(lease.runId), current]\n const active = new Map(current.active)\n const pending = new Map(current.pending)\n if (lease.pending !== undefined) pending.set(triggerId, lease.pending)\n if (lease.recovered === undefined) active.delete(triggerId)\n else active.set(triggerId, { runId: lease.recovered })\n return [\n lease.recovered === undefined ? Option.none() : Option.some(lease.recovered),\n { ...current, active, pending }\n ]\n })),\n activeOccurrence: (triggerId, runId) =>\n requireTrigger(triggerId, (_trigger, current) => {\n const reserved = reservationOccurrence(runId)\n const launched = current.runOccurrences.get(runId)\n const occurrence = reserved ??\n (launched !== undefined && current.fires.get(key(triggerId, launched)) === \"launched\" ? launched : undefined)\n return [occurrence === undefined ? Option.none() : Option.some(occurrence), current]\n }),\n clearActive: (triggerId, runId) =>\n Ref.update(state, (current) => {\n if (current.active.get(triggerId)?.runId !== runId) return current\n const active = new Map(current.active)\n active.delete(triggerId)\n return { ...current, active }\n }),\n history: (query = {}) =>\n Effect.flatMap(historyLimit(query.limit), (limit) =>\n Ref.get(state).pipe(Effect.map((current) => {\n const records = [...current.fires.entries()]\n .map(([fireKey, outcome]) => fireRecord(current, fireKey, outcome))\n .filter((record) =>\n (query.triggerId === undefined || record.triggerId === query.triggerId) &&\n (query.runId === undefined || record.runId === query.runId) &&\n (query.outcome === undefined || record.outcome === query.outcome) &&\n (query.cursor === undefined || isAfterCursor(record, query.cursor))\n )\n .sort(compareNewestFirst)\n return historyPage(records, limit)\n }))),\n pruneFires: ({ olderThan }) =>\n Effect.flatMap(pruneCutoff(olderThan), (cutoff) =>\n Ref.modify(state, (current) => {\n const fires = new Map(current.fires)\n const fireRunIds = new Map(current.fireRunIds)\n const fireErrors = new Map(current.fireErrors)\n const runOccurrences = new Map(current.runOccurrences)\n let removed = 0\n for (const [fireKey, outcome] of current.fires) {\n if (\n outcome !== \"completed\" && outcome !== \"failed\" && outcome !== \"skipped\" && outcome !== \"superseded\"\n ) continue\n const record = fireRecord(current, fireKey, outcome)\n if (record.occurrence >= cutoff) continue\n if (current.pending.get(record.triggerId) === record.occurrence) continue\n if (record.runId !== undefined && current.active.get(record.triggerId)?.runId === record.runId) continue\n fires.delete(fireKey)\n fireErrors.delete(fireKey)\n if (record.runId !== undefined) {\n fireRunIds.delete(fireKey)\n if (runOccurrences.get(record.runId) === record.occurrence) runOccurrences.delete(record.runId)\n }\n removed += 1\n }\n return [removed, { ...current, fires, fireRunIds, fireErrors, runOccurrences }]\n })),\n inspect: (triggerId) =>\n requireTrigger(triggerId, (_trigger, current) => {\n const activeRunId = current.active.get(triggerId)?.runId\n const pendingAt = current.pending.get(triggerId)\n return [\n {\n ...(activeRunId === undefined ? {} : { activeRunId }),\n ...(pendingAt === undefined ? {} : { pendingAt })\n },\n current\n ]\n }),\n heartbeat: (host) =>\n Effect.flatMap(\n Clock.currentTimeMillis,\n (tickedAt) =>\n Ref.update(state, (current) => ({ ...current, heartbeats: new Map(current.heartbeats).set(host, tickedAt) }))\n ),\n lastHeartbeat: () =>\n Ref.get(state).pipe(Effect.map((current) => {\n let latest: Heartbeat | undefined\n for (const [host, tickedAt] of current.heartbeats) {\n // Newest wins; equal times fall to the lower host name, as the SQL\n // store's ORDER BY does.\n if (\n latest === undefined || tickedAt > latest.tickedAt || (tickedAt === latest.tickedAt && host < latest.host)\n ) {\n latest = { host, tickedAt }\n }\n }\n return latest === undefined ? Option.none() : Option.some(latest)\n }))\n })\n}))\n"]}