@reventlessdev/reventless-core 3.0.0-alpha.295 → 3.0.0-alpha.296

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.
@@ -382,7 +382,7 @@ module Make = (
382
382
  // and idResolver paths). Slice output dicts are keyed by Spec.name; the
383
383
  // queryDb name appends a suffix per slice kind:
384
384
  // StateViewSlice — no suffix
385
- // AutomationSlice — "Todo"
385
+ // AutomationSlice — "Todo", and "Schedule" when it maps the Clock
386
386
  // OutboundTranslationSlice — "Todo"
387
387
  // InboundTranslationSlice — "Audit"
388
388
  dcbResult.stateViewSlicesOutputs
@@ -393,7 +393,10 @@ module Make = (
393
393
  ->Array.forEach(((k, v)) => allQueryDbs->Dict.set(k ++ "Audit", v.queryDb))
394
394
  dcbResult.automationSlicesOutputs
395
395
  ->Dict.toArray
396
- ->Array.forEach(((k, v)) => allQueryDbs->Dict.set(k ++ "Todo", v.queryDb))
396
+ ->Array.forEach(((k, v)) => {
397
+ allQueryDbs->Dict.set(k ++ "Todo", v.queryDb)
398
+ v.schedulesDb->Option.forEach(db => allQueryDbs->Dict.set(k ++ "Schedule", db))
399
+ })
397
400
  dcbResult.outboundTranslationSlicesOutputs
398
401
  ->Dict.toArray
399
402
  ->Array.forEach(((k, v)) => allQueryDbs->Dict.set(k ++ "Todo", v.queryDb))
@@ -466,9 +469,13 @@ module Make = (
466
469
  let extractEventTypes = schema => Reventless.DcbTag.extractVariantNames(schema)
467
470
  let routingSchemas: Dict.t<Plugin_Helpers.pluginDeployedSchema> = Dict.make()
468
471
  automationSlices->Array.forEach((module(As: ReventlessInfra.AutomationSlice.T)) => {
469
- // Union of source-event variants across every per-source mapping.
472
+ // Union of source-event variants across every per-source mapping. The
473
+ // Clock's tick is no event of the plugin's; its schedule says when it comes.
470
474
  let consumedEventTypes =
471
475
  As.Automation.mappings
476
+ ->Array.filter((module(M: As.Automation.Mapping)) =>
477
+ M.sourceName != Reventless.Clock.name
478
+ )
472
479
  ->Array.flatMap((module(M: As.Automation.Mapping)) =>
473
480
  extractEventTypes(M.sourceEventSchema->S.castToUnknown)
474
481
  )
@@ -6,6 +6,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
6
  import * as Pulumi from "@pulumi/pulumi";
7
7
  import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
8
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
+ import * as Clock$Reventless from "@reventlessdev/reventless-spec/src/components/Clock.res.mjs";
9
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
10
11
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
11
12
  import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
@@ -284,7 +285,12 @@ function Make(Spec) {
284
285
  allQueryDbs[param[0] + "Audit"] = param[1].queryDb;
285
286
  });
286
287
  Object.entries(dcbResult.automationSlicesOutputs).forEach(param => {
287
- allQueryDbs[param[0] + "Todo"] = param[1].queryDb;
288
+ let v = param[1];
289
+ let k = param[0];
290
+ allQueryDbs[k + "Todo"] = v.queryDb;
291
+ Stdlib_Option.forEach(v.schedulesDb, db => {
292
+ allQueryDbs[k + "Schedule"] = db;
293
+ });
288
294
  });
289
295
  Object.entries(dcbResult.outboundTranslationSlicesOutputs).forEach(param => {
290
296
  allQueryDbs[param[0] + "Todo"] = param[1].queryDb;
@@ -348,7 +354,7 @@ function Make(Spec) {
348
354
  });
349
355
  let routingSchemas = {};
350
356
  automationSlices.forEach(As => {
351
- let consumedEventTypes = Belt_SetString.toArray(Belt_SetString.fromArray(As.Automation.mappings.flatMap(M => DcbTag$Reventless.extractVariantNames(M.sourceEventSchema))));
357
+ let consumedEventTypes = Belt_SetString.toArray(Belt_SetString.fromArray(As.Automation.mappings.filter(M => M.sourceName !== Clock$Reventless.name).flatMap(M => DcbTag$Reventless.extractVariantNames(M.sourceEventSchema))));
352
358
  routingSchemas[As.Spec.name] = {
353
359
  consumedEventTypes: consumedEventTypes,
354
360
  producedCommandTypes: DcbTag$Reventless.extractAllVariantNames(As.Spec.commandSchema),
@@ -1775,6 +1775,8 @@ let make = (
1775
1775
  // contributes that one source's variants.
1776
1776
  let allConsumedVariants =
1777
1777
  AS.Automation.mappings
1778
+ // The Clock's tick is no event of the plugin's; `schedule` says when it comes.
1779
+ ->Array.filter((module(M: AS.Automation.Mapping)) => M.sourceName != Reventless.Clock.name)
1778
1780
  ->Array.flatMap((module(M: AS.Automation.Mapping)) =>
1779
1781
  eventVariantNames(M.sourceEventSchema->S.castToUnknown)
1780
1782
  )
@@ -1788,6 +1790,11 @@ let make = (
1788
1790
  producedCommandTypes: qualify(~prefix=name, commandVariantNames(AS.Spec.commandSchema)),
1789
1791
  targetName: AS.Spec.targetName,
1790
1792
  chapter: chapterOf(AS.Spec.name),
1793
+ schedule: ?(
1794
+ AS.Spec.schedule->Option.map(time =>
1795
+ time->Reventless.Util_Sury.toJson(Reventless.Schedule.timeSchema)->JSON.stringify
1796
+ )
1797
+ ),
1791
1798
  }: Reventless.Plugin.automationSliceDef
1792
1799
  )
1793
1800
  })
@@ -6,12 +6,14 @@ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
7
  import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
8
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
+ import * as Clock$Reventless from "@reventlessdev/reventless-spec/src/components/Clock.res.mjs";
9
10
  import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
10
11
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
11
12
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
12
13
  import * as Trait$Reventless from "@reventlessdev/reventless-spec/src/types/Trait.res.mjs";
13
14
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
14
15
  import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
16
+ import * as Schedule$Reventless from "@reventlessdev/reventless-spec/src/types/Schedule.res.mjs";
15
17
  import * as Semantic$Reventless from "@reventlessdev/reventless-spec/src/semantic/Semantic.res.mjs";
16
18
  import * as Lifecycle$Reventless from "@reventlessdev/reventless-spec/src/types/Lifecycle.res.mjs";
17
19
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -1252,13 +1254,14 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
1252
1254
  };
1253
1255
  });
1254
1256
  let automationSliceDefs = automationSlices.map(AS => {
1255
- let allConsumedVariants = Belt_SetString.toArray(Belt_SetString.fromArray(AS.Automation.mappings.flatMap(M => DcbTag$Reventless.extractVariantNames(M.sourceEventSchema))));
1257
+ let allConsumedVariants = Belt_SetString.toArray(Belt_SetString.fromArray(AS.Automation.mappings.filter(M => M.sourceName !== Clock$Reventless.name).flatMap(M => DcbTag$Reventless.extractVariantNames(M.sourceEventSchema))));
1256
1258
  return {
1257
1259
  name: AS.Spec.name,
1258
1260
  consumedEventTypes: qualify(name, allConsumedVariants),
1259
1261
  producedCommandTypes: qualify(name, DcbTag$Reventless.extractAllVariantNames(AS.Spec.commandSchema)),
1260
1262
  targetName: AS.Spec.targetName,
1261
- chapter: componentChapters[AS.Spec.name]
1263
+ chapter: componentChapters[AS.Spec.name],
1264
+ schedule: Stdlib_Option.map(AS.Spec.schedule, time => JSON.stringify(Util_Sury$Reventless.toJson(time, Schedule$Reventless.timeSchema)))
1262
1265
  };
1263
1266
  });
1264
1267
  let outboundTranslationSliceDefs = outboundTranslationSlices.map(OTS => ({
@@ -120,6 +120,7 @@ let automation: automationSliceDef = {
120
120
  producedCommandTypes: ["Restock"],
121
121
  targetName: "Product",
122
122
  chapter: Some("Replenishment"),
123
+ schedule: `{"TAG":"Every","_0":{"TAG":"Minutes","_0":15}}`,
123
124
  }
124
125
 
125
126
  let outbound: outboundTranslationSliceDef = {
@@ -184,12 +184,15 @@ let automation_producedCommandTypes = ["Restock"];
184
184
 
185
185
  let automation_chapter = "Replenishment";
186
186
 
187
+ let automation_schedule = `{"TAG":"Every","_0":{"TAG":"Minutes","_0":15}}`;
188
+
187
189
  let automation = {
188
190
  name: "Restocker",
189
191
  consumedEventTypes: automation_consumedEventTypes,
190
192
  producedCommandTypes: automation_producedCommandTypes,
191
193
  targetName: "Product",
192
- chapter: automation_chapter
194
+ chapter: automation_chapter,
195
+ schedule: automation_schedule
193
196
  };
194
197
 
195
198
  let outbound_consumedEventTypes = ["OrderPlaced"];
@@ -0,0 +1,143 @@
1
+ // A slice's schedule rows, and how the platform is brought in line with them: the
2
+ // declared time is written unless an event set or cleared the slice-wide key, the
3
+ // platform gets what it is missing and loses what the rows do not hold, and a
4
+ // once-only time already past is not put again.
5
+
6
+ open JestGlobals
7
+
8
+ module Schedules = AutomationSlice_Schedules
9
+
10
+ let at = iso => Date.fromString(iso)->Date.getTime
11
+ let daily6 = Reventless.Schedule.Calendar({calendar: Daily({hour: 6, minute: 0})})
12
+ let daily18 = Reventless.Schedule.Calendar({calendar: Daily({hour: 18, minute: 0})})
13
+ let now = at("2026-10-16T12:00:00Z")
14
+
15
+ // A platform that holds its schedules in a dict and records what it was told.
16
+ let fakePlatform = (~held: array<string>=[]) => {
17
+ let schedules = Dict.fromArray(held->Array.map(name => (name, daily6)))
18
+ let calls = []
19
+ let platform: ReventlessInfra.AutomationSlice.platformSchedules = {
20
+ put: async (~key, time) => {
21
+ calls->Array.push(`put ${key}`)
22
+ schedules->Dict.set(key, time)
23
+ },
24
+ removeName: async name => {
25
+ calls->Array.push(`remove ${name}`)
26
+ schedules->Dict.delete(name)
27
+ },
28
+ names: async () => schedules->Dict.keysToArray,
29
+ nameOf: key => key,
30
+ }
31
+ (platform, schedules, calls)
32
+ }
33
+
34
+ let liveKeys = rows =>
35
+ rows
36
+ ->Dict.valuesToArray
37
+ ->Array.filter(row => row.Schedules.time->Option.isSome)
38
+ ->Array.map(row => row.key)
39
+ ->Array.toSorted(String.compare)
40
+
41
+ let set = (rows, key, time, ~recordedAt=now) =>
42
+ rows->Schedules.apply(Set({key, time}), ~setBy="event", ~recordedAt)
43
+ let clear = (rows, key) => rows->Schedules.apply(Clear({key: key}), ~setBy="event", ~recordedAt=now)
44
+
45
+ describe("the declared time", () => {
46
+ testSync("is written when no event has touched the slice-wide key", () => {
47
+ let rows = Dict.make()
48
+ rows->Schedules.declare(Some(daily6), ~now)
49
+ expect(rows->Dict.get("default")->Option.flatMap(Schedules.timeOf))->toEqual(Some(daily6))
50
+ })
51
+
52
+ testSync("does not replace a time an event set", () => {
53
+ let rows = Dict.make()
54
+ let _ = rows->set("default", daily18)
55
+ rows->Schedules.declare(Some(daily6), ~now)
56
+ expect(rows->Dict.get("default")->Option.flatMap(Schedules.timeOf))->toEqual(Some(daily18))
57
+ })
58
+
59
+ testSync("does not come back after an event cleared it", () => {
60
+ let rows = Dict.make()
61
+ rows->Schedules.declare(Some(daily6), ~now)
62
+ let _ = rows->clear("default")
63
+ rows->Schedules.declare(Some(daily6), ~now)
64
+ expect(rows->liveKeys)->toEqual([])
65
+ })
66
+
67
+ testSync("follows a changed declaration, and goes with it", () => {
68
+ let rows = Dict.make()
69
+ rows->Schedules.declare(Some(daily6), ~now)
70
+ rows->Schedules.declare(Some(daily18), ~now)
71
+ let changed = rows->Dict.get("default")->Option.flatMap(Schedules.timeOf)
72
+ rows->Schedules.declare(None, ~now)
73
+ expect((changed, rows->liveKeys))->toEqual((Some(daily18), []))
74
+ })
75
+ })
76
+
77
+ describe("a change an event makes", () => {
78
+ testSync("counts an After from when the event was recorded", () => {
79
+ let rows = Dict.make()
80
+ let _ =
81
+ rows->set("o1", Once({moment: After(Minutes(10))}), ~recordedAt=at("2026-10-16T09:00:00Z"))
82
+ expect(rows->Dict.get("o1")->Option.flatMap(Schedules.timeOf))->toEqual(
83
+ Some(
84
+ Reventless.Schedule.Once({
85
+ moment: At({year: 2026, month: 10, day: 16, hour: 9, minute: 10}),
86
+ }),
87
+ ),
88
+ )
89
+ })
90
+
91
+ testSync("refuses a time that cannot fire, and changes nothing", () => {
92
+ let rows = Dict.make()
93
+ let refused = rows->set("r1", Calendar({calendar: Daily({hour: 25, minute: 0})}))
94
+ expect((refused->Result.isError, rows->liveKeys))->toEqual((true, []))
95
+ })
96
+
97
+ testSync("removes a once-only schedule when it fires, not a recurring one", () => {
98
+ let rows = Dict.make()
99
+ let _ = rows->set("o1", Once({moment: After(Minutes(10))}))
100
+ let _ = rows->set("r1", daily18)
101
+ rows->Schedules.fired(~key="o1")
102
+ rows->Schedules.fired(~key="r1")
103
+ expect(rows->liveKeys)->toEqual(["r1"])
104
+ })
105
+ })
106
+
107
+ describe("the reconcile", () => {
108
+ testPromise(
109
+ "puts what the platform is missing and removes what the rows do not hold",
110
+ async () => {
111
+ let rows = Dict.make()
112
+ let _ = rows->set("r1", daily18)
113
+ let _ = rows->set("r2", daily18)
114
+ let (platform, held, _) = fakePlatform(~held=["r2", "stray"])
115
+ await rows->Schedules.reconcile(platform, ~declaredTime=None, ~now)
116
+ expect(held->Dict.keysToArray->Array.toSorted(String.compare))->toEqual(["r1", "r2"])
117
+ },
118
+ )
119
+
120
+ testPromise("does not put again a schedule an event set and the platform holds", async () => {
121
+ let rows = Dict.make()
122
+ let _ = rows->set("r1", daily18)
123
+ let (platform, _, calls) = fakePlatform(~held=["r1"])
124
+ await rows->Schedules.reconcile(platform, ~declaredTime=None, ~now)
125
+ expect(calls)->toEqual([])
126
+ })
127
+
128
+ testPromise("puts the declared time again, in case it changed", async () => {
129
+ let rows = Dict.make()
130
+ let (platform, _, calls) = fakePlatform(~held=["default"])
131
+ await rows->Schedules.reconcile(platform, ~declaredTime=Some(daily6), ~now)
132
+ expect(calls)->toEqual(["put default"])
133
+ })
134
+
135
+ testPromise("drops a once-only time already past, from the rows and the platform", async () => {
136
+ let rows = Dict.make()
137
+ let _ =
138
+ rows->set("o1", Once({moment: After(Minutes(10))}), ~recordedAt=at("2026-10-16T09:00:00Z"))
139
+ let (platform, held, _) = fakePlatform(~held=["o1"])
140
+ await rows->Schedules.reconcile(platform, ~declaredTime=None, ~now)
141
+ expect((rows->liveKeys, held->Dict.keysToArray))->toEqual(([], []))
142
+ })
143
+ })
@@ -0,0 +1,247 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
6
+ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
7
+ import * as AutomationSlice_Schedules$ReventlessCore from "../../src/components/AutomationSlice/AutomationSlice_Schedules.res.mjs";
8
+
9
+ function at(iso) {
10
+ return new Date(iso).getTime();
11
+ }
12
+
13
+ let daily6 = {
14
+ TAG: "Calendar",
15
+ calendar: {
16
+ TAG: "Daily",
17
+ hour: 6,
18
+ minute: 0
19
+ }
20
+ };
21
+
22
+ let daily18 = {
23
+ TAG: "Calendar",
24
+ calendar: {
25
+ TAG: "Daily",
26
+ hour: 18,
27
+ minute: 0
28
+ }
29
+ };
30
+
31
+ let now = new Date("2026-10-16T12:00:00Z").getTime();
32
+
33
+ function fakePlatform(heldOpt) {
34
+ let held = heldOpt !== undefined ? heldOpt : [];
35
+ let schedules = Object.fromEntries(held.map(name => [
36
+ name,
37
+ daily6
38
+ ]));
39
+ let calls = [];
40
+ let platform_put = async (key, time) => {
41
+ calls.push(`put ` + key);
42
+ schedules[key] = time;
43
+ };
44
+ let platform_removeName = async name => {
45
+ calls.push(`remove ` + name);
46
+ return Stdlib_Dict.$$delete(schedules, name);
47
+ };
48
+ let platform_names = async () => Object.keys(schedules);
49
+ let platform_nameOf = key => key;
50
+ let platform = {
51
+ put: platform_put,
52
+ removeName: platform_removeName,
53
+ names: platform_names,
54
+ nameOf: platform_nameOf
55
+ };
56
+ return [
57
+ platform,
58
+ schedules,
59
+ calls
60
+ ];
61
+ }
62
+
63
+ function liveKeys(rows) {
64
+ return Object.values(rows).filter(row => Stdlib_Option.isSome(row.time)).map(row => row.key).toSorted(Primitive_string.compare);
65
+ }
66
+
67
+ function set(rows, key, time, recordedAtOpt) {
68
+ let recordedAt = recordedAtOpt !== undefined ? recordedAtOpt : now;
69
+ return AutomationSlice_Schedules$ReventlessCore.apply(rows, {
70
+ TAG: "Set",
71
+ key: key,
72
+ time: time
73
+ }, "event", recordedAt);
74
+ }
75
+
76
+ function clear(rows, key) {
77
+ return AutomationSlice_Schedules$ReventlessCore.apply(rows, {
78
+ TAG: "Clear",
79
+ key: key
80
+ }, "event", now);
81
+ }
82
+
83
+ globalThis.describe("the declared time", () => {
84
+ globalThis.test("is written when no event has touched the slice-wide key", () => {
85
+ let rows = {};
86
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, daily6, now);
87
+ globalThis.expect(Stdlib_Option.flatMap(rows["default"], AutomationSlice_Schedules$ReventlessCore.timeOf)).toEqual(daily6);
88
+ });
89
+ globalThis.test("does not replace a time an event set", () => {
90
+ let rows = {};
91
+ set(rows, "default", daily18, undefined);
92
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, daily6, now);
93
+ globalThis.expect(Stdlib_Option.flatMap(rows["default"], AutomationSlice_Schedules$ReventlessCore.timeOf)).toEqual(daily18);
94
+ });
95
+ globalThis.test("does not come back after an event cleared it", () => {
96
+ let rows = {};
97
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, daily6, now);
98
+ clear(rows, "default");
99
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, daily6, now);
100
+ globalThis.expect(liveKeys(rows)).toEqual([]);
101
+ });
102
+ globalThis.test("follows a changed declaration, and goes with it", () => {
103
+ let rows = {};
104
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, daily6, now);
105
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, daily18, now);
106
+ let changed = Stdlib_Option.flatMap(rows["default"], AutomationSlice_Schedules$ReventlessCore.timeOf);
107
+ AutomationSlice_Schedules$ReventlessCore.declare(rows, undefined, now);
108
+ globalThis.expect([
109
+ changed,
110
+ liveKeys(rows)
111
+ ]).toEqual([
112
+ daily18,
113
+ []
114
+ ]);
115
+ });
116
+ });
117
+
118
+ globalThis.describe("a change an event makes", () => {
119
+ globalThis.test("counts an After from when the event was recorded", () => {
120
+ let rows = {};
121
+ set(rows, "o1", {
122
+ TAG: "Once",
123
+ moment: {
124
+ TAG: "After",
125
+ _0: {
126
+ TAG: "Minutes",
127
+ _0: 10
128
+ }
129
+ }
130
+ }, new Date("2026-10-16T09:00:00Z").getTime());
131
+ globalThis.expect(Stdlib_Option.flatMap(rows["o1"], AutomationSlice_Schedules$ReventlessCore.timeOf)).toEqual({
132
+ TAG: "Once",
133
+ moment: {
134
+ TAG: "At",
135
+ _0: {
136
+ year: 2026,
137
+ month: 10,
138
+ day: 16,
139
+ hour: 9,
140
+ minute: 10
141
+ }
142
+ }
143
+ });
144
+ });
145
+ globalThis.test("refuses a time that cannot fire, and changes nothing", () => {
146
+ let rows = {};
147
+ let refused = set(rows, "r1", {
148
+ TAG: "Calendar",
149
+ calendar: {
150
+ TAG: "Daily",
151
+ hour: 25,
152
+ minute: 0
153
+ }
154
+ }, undefined);
155
+ globalThis.expect([
156
+ Stdlib_Result.isError(refused),
157
+ liveKeys(rows)
158
+ ]).toEqual([
159
+ true,
160
+ []
161
+ ]);
162
+ });
163
+ globalThis.test("removes a once-only schedule when it fires, not a recurring one", () => {
164
+ let rows = {};
165
+ set(rows, "o1", {
166
+ TAG: "Once",
167
+ moment: {
168
+ TAG: "After",
169
+ _0: {
170
+ TAG: "Minutes",
171
+ _0: 10
172
+ }
173
+ }
174
+ }, undefined);
175
+ set(rows, "r1", daily18, undefined);
176
+ AutomationSlice_Schedules$ReventlessCore.fired(rows, "o1");
177
+ AutomationSlice_Schedules$ReventlessCore.fired(rows, "r1");
178
+ globalThis.expect(liveKeys(rows)).toEqual(["r1"]);
179
+ });
180
+ });
181
+
182
+ globalThis.describe("the reconcile", () => {
183
+ globalThis.test("puts what the platform is missing and removes what the rows do not hold", async () => {
184
+ let rows = {};
185
+ set(rows, "r1", daily18, undefined);
186
+ set(rows, "r2", daily18, undefined);
187
+ let match = fakePlatform([
188
+ "r2",
189
+ "stray"
190
+ ]);
191
+ await AutomationSlice_Schedules$ReventlessCore.reconcile(rows, match[0], undefined, now);
192
+ globalThis.expect(Object.keys(match[1]).toSorted(Primitive_string.compare)).toEqual([
193
+ "r1",
194
+ "r2"
195
+ ]);
196
+ });
197
+ globalThis.test("does not put again a schedule an event set and the platform holds", async () => {
198
+ let rows = {};
199
+ set(rows, "r1", daily18, undefined);
200
+ let match = fakePlatform(["r1"]);
201
+ await AutomationSlice_Schedules$ReventlessCore.reconcile(rows, match[0], undefined, now);
202
+ globalThis.expect(match[2]).toEqual([]);
203
+ });
204
+ globalThis.test("puts the declared time again, in case it changed", async () => {
205
+ let rows = {};
206
+ let match = fakePlatform(["default"]);
207
+ await AutomationSlice_Schedules$ReventlessCore.reconcile(rows, match[0], daily6, now);
208
+ globalThis.expect(match[2]).toEqual(["put default"]);
209
+ });
210
+ globalThis.test("drops a once-only time already past, from the rows and the platform", async () => {
211
+ let rows = {};
212
+ set(rows, "o1", {
213
+ TAG: "Once",
214
+ moment: {
215
+ TAG: "After",
216
+ _0: {
217
+ TAG: "Minutes",
218
+ _0: 10
219
+ }
220
+ }
221
+ }, new Date("2026-10-16T09:00:00Z").getTime());
222
+ let match = fakePlatform(["o1"]);
223
+ await AutomationSlice_Schedules$ReventlessCore.reconcile(rows, match[0], undefined, now);
224
+ globalThis.expect([
225
+ liveKeys(rows),
226
+ Object.keys(match[1])
227
+ ]).toEqual([
228
+ [],
229
+ []
230
+ ]);
231
+ });
232
+ });
233
+
234
+ let Schedules;
235
+
236
+ export {
237
+ Schedules,
238
+ at,
239
+ daily6,
240
+ daily18,
241
+ now,
242
+ fakePlatform,
243
+ liveKeys,
244
+ set,
245
+ clear,
246
+ }
247
+ /* now Not a pure module */