@reventlessdev/reventless-spec 3.0.0-alpha.164 → 3.0.0-alpha.165

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 CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.165 (2026-10-16)
7
+
8
+ * feat!(automation): schedules set by events ([53bc436](https://github.com/ReventlessDev/reventless-core/commit/53bc4363dffce81ef36475728bf0dae704716021))
9
+ * feat!(automation): declared schedules tick an automation ([420acd2](https://github.com/ReventlessDev/reventless-core/commit/420acd2233aadf700141a31d9d3e0fdc8074bef7))
10
+
11
+ ### BREAKING CHANGES
12
+
13
+ * a hand-written AutomationSlice.Mapping.Make the PPX does
14
+ not see must define `let reschedule = _ => []`.
15
+ * a hand-written AutomationSlice spec the PPX does not see must
16
+ declare `let schedule = None`.
17
+
18
+
19
+
6
20
  # 3.0.0-alpha.164 (2026-10-15)
7
21
 
8
22
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.164",
3
+ "version": "3.0.0-alpha.165",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -47,6 +47,7 @@ type Platform_AutomationSliceDef {
47
47
  consumedEventTypes: [String!]!
48
48
  name: String!
49
49
  producedCommandTypes: [String!]!
50
+ schedule: String
50
51
  targetName: String
51
52
  }
52
53
 
@@ -72,6 +72,14 @@ module type Spec = {
72
72
 
73
73
  /** Name of the aggregate or StateChangeSlice that receives the produced command. */
74
74
  let targetName: string
75
+
76
+ /**
77
+ The time this automation runs on, or `None` for one started by events only. A
78
+ declared time is the slice's schedule under `Clock.defaultKey`; each time it comes,
79
+ the slice's `Clock` mapping receives a `Tick`. The `@@reventless.spec` PPX injects
80
+ `None` when the spec declares none.
81
+ */
82
+ let schedule: option<Schedule.time>
75
83
  }
76
84
 
77
85
  // ── Plan 04: Mixed-source mappings ───────────────────────────────────────────
@@ -117,6 +125,17 @@ type context = {
117
125
  recordedAt?: float,
118
126
  }
119
127
 
128
+ /**
129
+ What an event does to one of the slice's schedules. `key` names the schedule:
130
+ `Clock.defaultKey` for the slice-wide one (replacing a declared time), or an id for a
131
+ time per entity, which the tick carries back to `collect`. `Set` replaces the key's
132
+ time, the last one winning; `Clear` of a key with no schedule does nothing. An
133
+ `After` is counted from when the event was recorded.
134
+ */
135
+ type scheduleChange =
136
+ | Set({key: string, time: Schedule.time})
137
+ | Clear({key: string})
138
+
120
139
  /**
121
140
  A compiled per-source-to-AutomationSlice mapping.
122
141
 
@@ -148,6 +167,9 @@ module type Mapping = {
148
167
  */
149
168
  let collect: (sourceEvent, ~sourceId: string, context) => array<(string, todoItem)>
150
169
  let resolve: sourceEvent => option<string>
170
+ /** The schedules the event sets or clears. The `@@reventless.automation` PPX adds
171
+ `let reschedule = _ => []` to a mapping written without it. */
172
+ let reschedule: sourceEvent => array<scheduleChange>
151
173
  }
152
174
 
153
175
  /**
@@ -233,6 +255,9 @@ module type MappingImpl = {
233
255
  */
234
256
  let collect: (sourceEvent, ~sourceId: string, context) => array<(string, todoItem)>
235
257
  let resolve: sourceEvent => option<string>
258
+ /** The schedules the event sets or clears. The `@@reventless.automation` PPX adds
259
+ `let reschedule = _ => []` to a mapping written without it. */
260
+ let reschedule: sourceEvent => array<scheduleChange>
236
261
  }
237
262
 
238
263
  /**
@@ -314,6 +339,7 @@ module Mapping = {
314
339
  let sourceName = Source.name
315
340
  let collect = Impl.collect
316
341
  let resolve = Impl.resolve
342
+ let reschedule = Impl.reschedule
317
343
  }
318
344
  }
319
345
 
@@ -7,7 +7,8 @@ function Make(Source) {
7
7
  sourceEventSchema: Source.eventSchema,
8
8
  sourceName: Source.name,
9
9
  collect: Impl.collect,
10
- resolve: Impl.resolve
10
+ resolve: Impl.resolve,
11
+ reschedule: Impl.reschedule
11
12
  }));
12
13
  }
13
14
 
@@ -0,0 +1,38 @@
1
+ /**
2
+ The framework source a scheduled automation maps. When one of the slice's
3
+ schedules comes due, the platform's Scheduler fires and the slice receives
4
+ `Tick({key, at})`: which of its schedules fired (`default` for the declared one)
5
+ and the time it was scheduled for, in milliseconds since 1970 (UTC). What the tick
6
+ means is the slice's own: a mapping's `collect` makes the item, `process` sends the
7
+ command. Never written by an application.
8
+
9
+ @example
10
+ ```rescript
11
+ module FromClock = Mapping.Make(
12
+ Reventless.Clock,
13
+ DemandReport,
14
+ {
15
+ open Reventless.Clock
16
+ let collect = (Tick({key, at}), ~sourceId as _, _ctx) =>
17
+ [(itemId(key, at), ({day: at}: DemandReport.todoItem))]
18
+ let resolve = _ => None
19
+ },
20
+ )
21
+ ```
22
+ */
23
+ module Id = Id.String
24
+
25
+ let name = "Clock"
26
+
27
+ @schema
28
+ type event = Tick({key: string, at: float})
29
+
30
+ /** The key a declared `schedule` is held under. */
31
+ let defaultKey = "default"
32
+
33
+ /**
34
+ The to-do item id for a tick: `<key>@<scheduled time in UTC, to the minute>`, such
35
+ as `default@2026-10-16T06:00Z`. The same tick delivered twice names the same item.
36
+ */
37
+ let itemId = (key, at) =>
38
+ `${key}@${at->Date.fromTime->Date.toISOString->String.slice(~start=0, ~end=16)}Z`
@@ -0,0 +1,28 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Sury from "sury";
4
+
5
+ let eventSchema = Sury.$schema(s => ({
6
+ TAG: "Tick",
7
+ key: s.m(Sury.string),
8
+ at: s.m(Sury.number)
9
+ }));
10
+
11
+ function itemId(key, at) {
12
+ return key + `@` + new Date(at).toISOString().slice(0, 16) + `Z`;
13
+ }
14
+
15
+ let Id;
16
+
17
+ let name = "Clock";
18
+
19
+ let defaultKey = "default";
20
+
21
+ export {
22
+ Id,
23
+ name,
24
+ eventSchema,
25
+ defaultKey,
26
+ itemId,
27
+ }
28
+ /* eventSchema Not a pure module */
@@ -301,6 +301,9 @@ type automationSliceDef = {
301
301
  targetName: string,
302
302
  /** Chapter grouping band — see `queryableDef.chapter`. */
303
303
  chapter: option<string>,
304
+ /** The declared `schedule`, as the JSON of `Schedule.timeSchema`; `None` for an
305
+ automation started by events only, and in a structure from before the field. */
306
+ schedule?: string,
304
307
  }
305
308
 
306
309
  @schema
@@ -158,7 +158,8 @@ let automationSliceDefSchema = Sury.$schema(s => ({
158
158
  consumedEventTypes: s.m(Sury.array(Sury.string)),
159
159
  producedCommandTypes: s.m(Sury.array(Sury.string)),
160
160
  targetName: s.m(Sury.string),
161
- chapter: s.m(Sury.$option(Sury.string))
161
+ chapter: s.m(Sury.$option(Sury.string)),
162
+ schedule: s.m(Sury.$option(Sury.string))
162
163
  }));
163
164
 
164
165
  let outboundTranslationSliceDefSchema = Sury.$schema(s => ({