@reventlessdev/reventless-local 3.0.0-alpha.286 → 3.0.0-alpha.287

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 (28) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +10 -10
  3. package/src/adapter/Scheduler/LocalScheduledPublisher.res +5 -52
  4. package/src/adapter/Scheduler/LocalScheduledPublisher.res.mjs +7 -47
  5. package/src/adapter/Scheduler/LocalTimers.res +62 -0
  6. package/src/adapter/Scheduler/LocalTimers.res.mjs +63 -0
  7. package/src/components/AutomationSlice_Builder.res +23 -2
  8. package/src/components/AutomationSlice_Builder.res.mjs +19 -2
  9. package/src/components/LocalAutomationSchedules.res +59 -0
  10. package/src/components/LocalAutomationSchedules.res.mjs +72 -0
  11. package/src/test/TestRunner.res +1 -0
  12. package/src/test/TestRunner.res.mjs +6 -0
  13. package/tests/components/automationslice/AutomationRescheduleFixtures.res +142 -0
  14. package/tests/components/automationslice/AutomationRescheduleFixtures.res.mjs +259 -0
  15. package/tests/components/automationslice/AutomationRescheduleTest.res +96 -0
  16. package/tests/components/automationslice/AutomationRescheduleTest.res.mjs +177 -0
  17. package/tests/components/automationslice/AutomationScheduleFixtures.res +118 -0
  18. package/tests/components/automationslice/AutomationScheduleFixtures.res.mjs +263 -0
  19. package/tests/components/automationslice/AutomationScheduleTest.res +88 -0
  20. package/tests/components/automationslice/AutomationScheduleTest.res.mjs +108 -0
  21. package/tests/components/automationslice/AutomationSliceFixtures.res +4 -0
  22. package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +14 -2
  23. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +2 -0
  24. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +7 -1
  25. package/tests/components/automationslice/AutomationSweepFixtures.res +2 -0
  26. package/tests/components/automationslice/AutomationSweepFixtures.res.mjs +7 -1
  27. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +3 -0
  28. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +13 -2
@@ -0,0 +1,118 @@
1
+ // A declared schedule: the demand report every morning at 06:00 UTC. Its one
2
+ // mapping reads the framework's Clock source; each tick makes the day's item and
3
+ // `process` requests the report. The slice is made by `makeSlice`, not at import, so
4
+ // a test can fix the time its schedule is set at.
5
+
6
+ open Reventless
7
+
8
+ module DemandReportSpec = {
9
+ let name = "LocalDemandReport"
10
+ let moduleUrl: string = %raw(`import.meta.url`)
11
+
12
+ @schema
13
+ type todoItem = {day: float}
14
+
15
+ @schema
16
+ type command = RequestDemandReport({day: float})
17
+
18
+ let maxRetries = 2
19
+ let heartbeatInterval = 60
20
+ let targetName = "ReportSettings"
21
+ let schedule = Some(Schedule.Calendar({calendar: Daily({hour: 6, minute: 0})}))
22
+ }
23
+
24
+ module FromClock = AutomationSlice.Mapping.Make(
25
+ Clock,
26
+ DemandReportSpec,
27
+ {
28
+ open Clock
29
+ let collect = (Tick({key, at}), ~sourceId as _, _ctx) => [
30
+ (itemId(key, at), ({day: at}: DemandReportSpec.todoItem)),
31
+ ]
32
+ let resolve = _ => None
33
+ let reschedule = _ => []
34
+ },
35
+ )
36
+
37
+ module DemandReportAutomation: AutomationSlice.Automation with module Spec := DemandReportSpec = {
38
+ let process = (id, item: DemandReportSpec.todoItem, ~now as _) => Some((
39
+ id,
40
+ DemandReportSpec.RequestDemandReport({day: item.day}),
41
+ ))
42
+ let onExhausted = (_id, _item: DemandReportSpec.todoItem) => None
43
+ let moduleUrl: string = %raw(`import.meta.url`)
44
+ module M = AutomationSlice.Mappings.Make(DemandReportSpec)
45
+ module type Mapping = M.Mapping
46
+ let mappings: array<module(Mapping)> = [module(FromClock)]
47
+ }
48
+
49
+ // The same slice with its schedule left out: nothing would ever tick it.
50
+ module UnscheduledSpec = {
51
+ include DemandReportSpec
52
+ let name = "LocalUnscheduledReport"
53
+ let schedule: option<Schedule.time> = None
54
+ }
55
+
56
+ module FromClockUnscheduled = AutomationSlice.Mapping.Make(
57
+ Clock,
58
+ UnscheduledSpec,
59
+ {
60
+ let collect = (_, ~sourceId as _, _ctx) => []
61
+ let resolve = _ => None
62
+ let reschedule = _ => []
63
+ },
64
+ )
65
+
66
+ module UnscheduledAutomation: AutomationSlice.Automation with module Spec := UnscheduledSpec = {
67
+ let process = (_id, _item: UnscheduledSpec.todoItem, ~now as _) => None
68
+ let onExhausted = (_id, _item: UnscheduledSpec.todoItem) => None
69
+ let moduleUrl: string = %raw(`import.meta.url`)
70
+ module M = AutomationSlice.Mappings.Make(UnscheduledSpec)
71
+ module type Mapping = M.Mapping
72
+ let mappings: array<module(Mapping)> = [module(FromClockUnscheduled)]
73
+ }
74
+
75
+ module Bus = LocalBus.Make()
76
+ let _ = TestRunner.setup()
77
+
78
+ module AutomationSliceMaker = AutomationSlice_Builder.Make(Bus)
79
+ module DemandReport = AutomationSliceMaker.Make(DemandReportSpec, DemandReportAutomation)
80
+ module Unscheduled = AutomationSliceMaker.Make(UnscheduledSpec, UnscheduledAutomation)
81
+
82
+ let publishedCommands: ref<array<Reventless.Message.commandJson>> = ref([])
83
+ let publishJsons: ReventlessInfra.CommandTopic.publishJsons = async cmds =>
84
+ publishedCommands := publishedCommands.contents->Array.concat(cmds)
85
+
86
+ let context = (sliceName): AutomationSlice.context => {
87
+ environment: "local",
88
+ platformName: "local",
89
+ pluginName: "TestPlugin",
90
+ sliceName,
91
+ }
92
+
93
+ /** Makes the demand report, which starts its declared schedule at the current time. */
94
+ let makeSlice = () =>
95
+ DemandReport.make(
96
+ ~allEventTopics=Dict.make(),
97
+ ~publishJsons=publishJsons->Pulumi.Output.make,
98
+ ~context=context(DemandReportSpec.name),
99
+ )
100
+
101
+ let makeUnscheduled = () =>
102
+ Unscheduled.make(
103
+ ~allEventTopics=Dict.make(),
104
+ ~publishJsons=publishJsons->Pulumi.Output.make,
105
+ ~context=context(UnscheduledSpec.name),
106
+ )
107
+
108
+ /** The stored to-do row of `id`, as the query store holds it. */
109
+ let storedRow = id =>
110
+ Bus.getQueryDbScan(DemandReport.queryDbName)
111
+ ->Option.map(scan => scan())
112
+ ->Option.getOr([])
113
+ ->Array.find(row =>
114
+ row->JSON.Decode.object->Option.flatMap(o => o->Dict.get("id")) == Some(id->JSON.Encode.string)
115
+ )
116
+ ->Option.map(row =>
117
+ row->Reventless.Util_Sury.fromJson(ReventlessCore.AutomationSlice_Callback.todoRowSchema)
118
+ )
@@ -0,0 +1,263 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Sury from "sury";
4
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
+ import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Pulumi from "@pulumi/pulumi";
8
+ import * as Clock$Reventless from "@reventlessdev/reventless-spec/src/components/Clock.res.mjs";
9
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
10
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
11
+ import * as LocalBus$ReventlessLocal from "../../../src/adapter/LocalBus.res.mjs";
12
+ import * as AutomationSlice$Reventless from "@reventlessdev/reventless-spec/src/components/AutomationSlice.res.mjs";
13
+ import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
14
+ import * as AutomationSlice_Builder$ReventlessLocal from "../../../src/components/AutomationSlice_Builder.res.mjs";
15
+ import * as AutomationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs";
16
+
17
+ let name = "LocalDemandReport";
18
+
19
+ let moduleUrl = import.meta.url;
20
+
21
+ let todoItemSchema = Sury.$schema(s => ({
22
+ day: s.m(Sury.number)
23
+ }));
24
+
25
+ let commandSchema = Sury.$schema(s => ({
26
+ TAG: "RequestDemandReport",
27
+ day: s.m(Sury.number)
28
+ }));
29
+
30
+ let targetName = "ReportSettings";
31
+
32
+ function authorizationOf(param) {
33
+ return "AllowAuthenticated";
34
+ }
35
+
36
+ function commandTransition(param) {
37
+ return "Undeclared";
38
+ }
39
+
40
+ let traits = [];
41
+
42
+ let DemandReportSpec_schedule = {
43
+ TAG: "Calendar",
44
+ calendar: {
45
+ TAG: "Daily",
46
+ hour: 6,
47
+ minute: 0
48
+ }
49
+ };
50
+
51
+ let DemandReportSpec = {
52
+ name: name,
53
+ moduleUrl: moduleUrl,
54
+ todoItemSchema: todoItemSchema,
55
+ commandSchema: commandSchema,
56
+ maxRetries: 2,
57
+ heartbeatInterval: 60,
58
+ targetName: targetName,
59
+ schedule: DemandReportSpec_schedule,
60
+ authorizationOf: authorizationOf,
61
+ commandTransition: commandTransition,
62
+ traits: traits
63
+ };
64
+
65
+ function collect(param, param$1, _ctx) {
66
+ let at = param.at;
67
+ return [[
68
+ Clock$Reventless.itemId(param.key, at),
69
+ {
70
+ day: at
71
+ }
72
+ ]];
73
+ }
74
+
75
+ function resolve(param) {
76
+
77
+ }
78
+
79
+ function reschedule(param) {
80
+ return [];
81
+ }
82
+
83
+ let FromClock = AutomationSlice$Reventless.Mapping.Make({
84
+ Id: Id$Reventless.$$String,
85
+ name: Clock$Reventless.name,
86
+ eventSchema: Clock$Reventless.eventSchema
87
+ })(DemandReportSpec)({
88
+ collect: collect,
89
+ resolve: resolve,
90
+ reschedule: reschedule
91
+ });
92
+
93
+ function process(id, item, param) {
94
+ return [
95
+ id,
96
+ {
97
+ TAG: "RequestDemandReport",
98
+ day: item.day
99
+ }
100
+ ];
101
+ }
102
+
103
+ function onExhausted(_id, _item) {
104
+
105
+ }
106
+
107
+ let moduleUrl$1 = import.meta.url;
108
+
109
+ AutomationSlice$Reventless.Mappings.Make(DemandReportSpec);
110
+
111
+ let mappings = [FromClock];
112
+
113
+ let DemandReportAutomation = {
114
+ process: process,
115
+ onExhausted: onExhausted,
116
+ moduleUrl: moduleUrl$1,
117
+ mappings: mappings
118
+ };
119
+
120
+ let name$1 = "LocalUnscheduledReport";
121
+
122
+ let UnscheduledSpec = {
123
+ moduleUrl: moduleUrl,
124
+ todoItemSchema: todoItemSchema,
125
+ commandSchema: commandSchema,
126
+ maxRetries: 2,
127
+ heartbeatInterval: 60,
128
+ targetName: targetName,
129
+ authorizationOf: authorizationOf,
130
+ commandTransition: commandTransition,
131
+ traits: traits,
132
+ name: name$1,
133
+ schedule: undefined
134
+ };
135
+
136
+ function collect$1(param, param$1, _ctx) {
137
+ return [];
138
+ }
139
+
140
+ function resolve$1(param) {
141
+
142
+ }
143
+
144
+ function reschedule$1(param) {
145
+ return [];
146
+ }
147
+
148
+ let FromClockUnscheduled = AutomationSlice$Reventless.Mapping.Make({
149
+ Id: Id$Reventless.$$String,
150
+ name: Clock$Reventless.name,
151
+ eventSchema: Clock$Reventless.eventSchema
152
+ })({
153
+ name: name$1,
154
+ moduleUrl: moduleUrl,
155
+ todoItemSchema: todoItemSchema,
156
+ commandSchema: commandSchema,
157
+ maxRetries: 2,
158
+ heartbeatInterval: 60,
159
+ targetName: targetName,
160
+ schedule: undefined
161
+ })({
162
+ collect: collect$1,
163
+ resolve: resolve$1,
164
+ reschedule: reschedule$1
165
+ });
166
+
167
+ function process$1(_id, _item, param) {
168
+
169
+ }
170
+
171
+ function onExhausted$1(_id, _item) {
172
+
173
+ }
174
+
175
+ let moduleUrl$2 = import.meta.url;
176
+
177
+ AutomationSlice$Reventless.Mappings.Make({
178
+ name: name$1,
179
+ moduleUrl: moduleUrl,
180
+ todoItemSchema: todoItemSchema,
181
+ commandSchema: commandSchema,
182
+ maxRetries: 2,
183
+ heartbeatInterval: 60,
184
+ targetName: targetName,
185
+ schedule: undefined
186
+ });
187
+
188
+ let mappings$1 = [FromClockUnscheduled];
189
+
190
+ let UnscheduledAutomation = {
191
+ process: process$1,
192
+ onExhausted: onExhausted$1,
193
+ moduleUrl: moduleUrl$2,
194
+ mappings: mappings$1
195
+ };
196
+
197
+ let Bus = LocalBus$ReventlessLocal.Make({});
198
+
199
+ TestRunner$ReventlessLocal.setup();
200
+
201
+ let AutomationSliceMaker = AutomationSlice_Builder$ReventlessLocal.Make(Bus);
202
+
203
+ let DemandReport = AutomationSliceMaker.Make(DemandReportSpec)(DemandReportAutomation);
204
+
205
+ let Unscheduled = AutomationSliceMaker.Make({
206
+ name: name$1,
207
+ moduleUrl: moduleUrl,
208
+ todoItemSchema: todoItemSchema,
209
+ commandSchema: commandSchema,
210
+ maxRetries: 2,
211
+ heartbeatInterval: 60,
212
+ targetName: targetName,
213
+ schedule: undefined
214
+ })(UnscheduledAutomation);
215
+
216
+ let publishedCommands = {
217
+ contents: []
218
+ };
219
+
220
+ async function publishJsons(cmds) {
221
+ publishedCommands.contents = publishedCommands.contents.concat(cmds);
222
+ }
223
+
224
+ function context(sliceName) {
225
+ return {
226
+ environment: "local",
227
+ platformName: "local",
228
+ pluginName: "TestPlugin",
229
+ sliceName: sliceName
230
+ };
231
+ }
232
+
233
+ function makeSlice() {
234
+ return DemandReport.make({}, Pulumi.output(publishJsons), context(name), undefined, undefined);
235
+ }
236
+
237
+ function makeUnscheduled() {
238
+ return Unscheduled.make({}, Pulumi.output(publishJsons), context(name$1), undefined, undefined);
239
+ }
240
+
241
+ function storedRow(id) {
242
+ return Stdlib_Option.map(Stdlib_Option.getOr(Stdlib_Option.map(Bus.getQueryDbScan(DemandReport.queryDbName), scan => scan()), []).find(row => Primitive_object.equal(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(row), o => o["id"]), id)), row => Util_Sury$Reventless.fromJson(row, AutomationSlice_Callback$ReventlessCore.todoRowSchema));
243
+ }
244
+
245
+ export {
246
+ DemandReportSpec,
247
+ FromClock,
248
+ DemandReportAutomation,
249
+ UnscheduledSpec,
250
+ FromClockUnscheduled,
251
+ UnscheduledAutomation,
252
+ Bus,
253
+ AutomationSliceMaker,
254
+ DemandReport,
255
+ Unscheduled,
256
+ publishedCommands,
257
+ publishJsons,
258
+ context,
259
+ makeSlice,
260
+ makeUnscheduled,
261
+ storedRow,
262
+ }
263
+ /* moduleUrl Not a pure module */
@@ -0,0 +1,88 @@
1
+ // A declared schedule on the local platform: it ticks the slice at its time, a tick
2
+ // delivered twice sends one command, a command left unanswered is sent again after
3
+ // the timeout, and a slice that maps the Clock without a schedule is refused.
4
+
5
+ open JestGlobals
6
+ open AutomationScheduleFixtures
7
+
8
+ module Callback = ReventlessCore.AutomationSlice_Callback
9
+
10
+ type jestObj
11
+ @module("@jest/globals") external jest: jestObj = "jest"
12
+ @send external useFakeTimers: jestObj => unit = "useFakeTimers"
13
+ @send external useRealTimers: jestObj => unit = "useRealTimers"
14
+ @send external setSystemTime: (jestObj, float) => unit = "setSystemTime"
15
+ @send
16
+ external advanceTimersByTimeAsync: (jestObj, float) => promise<unit> = "advanceTimersByTimeAsync"
17
+
18
+ let time = iso => Date.fromString(iso)->Date.getTime
19
+ let morning = time("2026-10-16T06:00:00Z")
20
+ let dayItem = "default@2026-10-16T06:00Z"
21
+
22
+ let commandIds = () => publishedCommands.contents->Array.map(c => c.id)
23
+
24
+ // Lets the tick's detached work and Effect's own scheduling run to the end.
25
+ let settle = async () =>
26
+ for _ in 1 to 20 {
27
+ await jest->advanceTimersByTimeAsync(0.)
28
+ }
29
+
30
+ describe("a declared schedule, locally", () => {
31
+ let slice = ref(None)
32
+ let ops = async () =>
33
+ await slice.contents->Option.getOrThrow->ReventlessCore.Component.operations->TestRunner.resolve
34
+
35
+ let _ = beforeAllAsync(async () => {
36
+ jest->useFakeTimers
37
+ jest->setSystemTime(time("2026-10-16T05:59:00Z"))
38
+ slice := Some(makeSlice())
39
+ let _ = await ops()
40
+ await settle()
41
+ })
42
+ let _ = afterAll(() => {
43
+ LocalAutomationSchedules.reset()
44
+ jest->useRealTimers
45
+ })
46
+
47
+ testPromise("ticks the slice at its time, and process requests the report", async () => {
48
+ await jest->advanceTimersByTimeAsync(59_000.)
49
+ await settle()
50
+ expect(commandIds())->toEqual([])
51
+
52
+ await jest->advanceTimersByTimeAsync(1_000.)
53
+ await settle()
54
+ expect(commandIds())->toEqual([dayItem])
55
+ expect(storedRow(dayItem)->Option.map(row => row.status))->toEqual(Some(Callback.Processing))
56
+ })
57
+
58
+ testPromise("the same tick delivered twice sends one command", async () => {
59
+ let ops = await ops()
60
+ await ops.tick(~key=Reventless.Clock.defaultKey, ~at=morning)
61
+ await settle()
62
+ expect(commandIds())->toEqual([dayItem])
63
+ })
64
+
65
+ testPromise("a command left unanswered is sent again after the timeout", async () => {
66
+ await LocalAutomationSweep.sweepOnce()
67
+ expect(commandIds())->toEqual([dayItem])
68
+
69
+ jest->setSystemTime(Date.now() +. 15. *. 60_000.)
70
+ await LocalAutomationSweep.sweepOnce()
71
+ expect(commandIds())->toEqual([dayItem, dayItem])
72
+ expect(storedRow(dayItem)->Option.map(row => (row.status, row.retryCount)))->toEqual(
73
+ Some((Callback.Processing, 1)),
74
+ )
75
+ })
76
+
77
+ testSync("a slice that maps the Clock without a schedule is refused", () => {
78
+ let refusal = switch makeUnscheduled() {
79
+ | _ => None
80
+ | exception JsExn(err) => err->JsExn.message
81
+ }
82
+ expect(
83
+ refusal->Option.map(
84
+ m => m->String.includes("maps the Clock source but declares no schedule"),
85
+ ),
86
+ )->toEqual(Some(true))
87
+ })
88
+ })
@@ -0,0 +1,108 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
4
+ import * as Globals from "@jest/globals";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Clock$Reventless from "@reventlessdev/reventless-spec/src/components/Clock.res.mjs";
7
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
9
+ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
10
+ import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
11
+ import * as LocalAutomationSweep$ReventlessLocal from "../../../src/components/LocalAutomationSweep.res.mjs";
12
+ import * as LocalAutomationSchedules$ReventlessLocal from "../../../src/components/LocalAutomationSchedules.res.mjs";
13
+ import * as AutomationScheduleFixtures$ReventlessLocal from "./AutomationScheduleFixtures.res.mjs";
14
+
15
+ function time(iso) {
16
+ return new Date(iso).getTime();
17
+ }
18
+
19
+ let morning = new Date("2026-10-16T06:00:00Z").getTime();
20
+
21
+ let dayItem = "default@2026-10-16T06:00Z";
22
+
23
+ function commandIds() {
24
+ return AutomationScheduleFixtures$ReventlessLocal.publishedCommands.contents.map(c => c.id);
25
+ }
26
+
27
+ async function settle() {
28
+ for (let _for = 1; _for <= 20; ++_for) {
29
+ await Globals.jest.advanceTimersByTimeAsync(0);
30
+ }
31
+ }
32
+
33
+ globalThis.describe("a declared schedule, locally", () => {
34
+ let slice = {
35
+ contents: undefined
36
+ };
37
+ let ops = async () => await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(Stdlib_Option.getOrThrow(slice.contents, undefined)));
38
+ globalThis.beforeAll(async () => {
39
+ Globals.jest.useFakeTimers();
40
+ Globals.jest.setSystemTime(new Date("2026-10-16T05:59:00Z").getTime());
41
+ slice.contents = Primitive_option.some(AutomationScheduleFixtures$ReventlessLocal.makeSlice());
42
+ await ops();
43
+ return await settle();
44
+ });
45
+ globalThis.afterAll(() => {
46
+ LocalAutomationSchedules$ReventlessLocal.reset();
47
+ Globals.jest.useRealTimers();
48
+ });
49
+ globalThis.test("ticks the slice at its time, and process requests the report", async () => {
50
+ await Globals.jest.advanceTimersByTimeAsync(59000);
51
+ await settle();
52
+ globalThis.expect(commandIds()).toEqual([]);
53
+ await Globals.jest.advanceTimersByTimeAsync(1000);
54
+ await settle();
55
+ globalThis.expect(commandIds()).toEqual([dayItem]);
56
+ globalThis.expect(Stdlib_Option.map(AutomationScheduleFixtures$ReventlessLocal.storedRow(dayItem), row => row.status)).toEqual("Processing");
57
+ });
58
+ globalThis.test("the same tick delivered twice sends one command", async () => {
59
+ let ops$1 = await ops();
60
+ await ops$1.tick(Clock$Reventless.defaultKey, morning);
61
+ await settle();
62
+ globalThis.expect(commandIds()).toEqual([dayItem]);
63
+ });
64
+ globalThis.test("a command left unanswered is sent again after the timeout", async () => {
65
+ await LocalAutomationSweep$ReventlessLocal.sweepOnce();
66
+ globalThis.expect(commandIds()).toEqual([dayItem]);
67
+ Globals.jest.setSystemTime(Date.now() + 15 * 60000);
68
+ await LocalAutomationSweep$ReventlessLocal.sweepOnce();
69
+ globalThis.expect(commandIds()).toEqual([
70
+ dayItem,
71
+ dayItem
72
+ ]);
73
+ globalThis.expect(Stdlib_Option.map(AutomationScheduleFixtures$ReventlessLocal.storedRow(dayItem), row => [
74
+ row.status,
75
+ row.retryCount
76
+ ])).toEqual([
77
+ "Processing",
78
+ 1
79
+ ]);
80
+ });
81
+ globalThis.test("a slice that maps the Clock without a schedule is refused", () => {
82
+ let refusal;
83
+ try {
84
+ AutomationScheduleFixtures$ReventlessLocal.makeUnscheduled();
85
+ refusal = undefined;
86
+ } catch (raw_err) {
87
+ let err = Primitive_exceptions.internalToException(raw_err);
88
+ if (err.RE_EXN_ID === "JsExn") {
89
+ refusal = Stdlib_JsExn.message(err._1);
90
+ } else {
91
+ throw err;
92
+ }
93
+ }
94
+ globalThis.expect(Stdlib_Option.map(refusal, m => m.includes("maps the Clock source but declares no schedule"))).toEqual(true);
95
+ });
96
+ });
97
+
98
+ let Callback;
99
+
100
+ export {
101
+ Callback,
102
+ time,
103
+ morning,
104
+ dayItem,
105
+ commandIds,
106
+ settle,
107
+ }
108
+ /* morning Not a pure module */
@@ -41,6 +41,7 @@ module ShipOrderSpec = {
41
41
  let maxRetries = 3
42
42
  let heartbeatInterval = 60
43
43
  let targetName = "CreateShipment"
44
+ let schedule = None
44
45
  }
45
46
 
46
47
  module SkipProcessSpec = {
@@ -56,6 +57,7 @@ module SkipProcessSpec = {
56
57
  let maxRetries = 0
57
58
  let heartbeatInterval = 60
58
59
  let targetName = "Noop"
60
+ let schedule = None
59
61
  }
60
62
 
61
63
  // ─────────────────────────────────────────────────────────────
@@ -84,6 +86,7 @@ module ShipOrderMapping = Reventless.AutomationSlice.Mapping.Make(
84
86
  | ShipmentCreated({orderId}) => Some(orderId)
85
87
  | OrderPlaced(_) => None
86
88
  }
89
+ let reschedule = _ => []
87
90
  },
88
91
  )
89
92
 
@@ -101,6 +104,7 @@ module SkipProcessMapping = Reventless.AutomationSlice.Mapping.Make(
101
104
  | ShipmentCreated({orderId}) => Some(orderId)
102
105
  | OrderPlaced(_) => None
103
106
  }
107
+ let reschedule = _ => []
104
108
  },
105
109
  )
106
110
 
@@ -77,6 +77,7 @@ let ShipOrderSpec = {
77
77
  maxRetries: 3,
78
78
  heartbeatInterval: 60,
79
79
  targetName: "CreateShipment",
80
+ schedule: undefined,
80
81
  authorizationOf: authorizationOf,
81
82
  commandTransition: commandTransition,
82
83
  traits: traits
@@ -108,6 +109,7 @@ let SkipProcessSpec = {
108
109
  maxRetries: 0,
109
110
  heartbeatInterval: 60,
110
111
  targetName: "Noop",
112
+ schedule: undefined,
111
113
  authorizationOf: authorizationOf$1,
112
114
  commandTransition: commandTransition$1,
113
115
  traits: traits$1
@@ -135,13 +137,18 @@ function resolve(event) {
135
137
  }
136
138
  }
137
139
 
140
+ function reschedule(param) {
141
+ return [];
142
+ }
143
+
138
144
  let ShipOrderMapping = AutomationSlice$Reventless.Mapping.Make({
139
145
  Id: Id$Reventless.$$String,
140
146
  name: name,
141
147
  eventSchema: eventSchema
142
148
  })(ShipOrderSpec)({
143
149
  collect: collect,
144
- resolve: resolve
150
+ resolve: resolve,
151
+ reschedule: reschedule
145
152
  });
146
153
 
147
154
  function collect$1(event, param, _ctx) {
@@ -165,13 +172,18 @@ function resolve$1(event) {
165
172
  }
166
173
  }
167
174
 
175
+ function reschedule$1(param) {
176
+ return [];
177
+ }
178
+
168
179
  let SkipProcessMapping = AutomationSlice$Reventless.Mapping.Make({
169
180
  Id: Id$Reventless.$$String,
170
181
  name: name$1,
171
182
  eventSchema: eventSchema$1
172
183
  })(SkipProcessSpec)({
173
184
  collect: collect$1,
174
- resolve: resolve$1
185
+ resolve: resolve$1,
186
+ reschedule: reschedule$1
175
187
  });
176
188
 
177
189
  function process(id, _item, param) {
@@ -136,6 +136,7 @@ module AutoShipSpec = {
136
136
  let maxRetries = 3
137
137
  let heartbeatInterval = 60
138
138
  let targetName = "Ship"
139
+ let schedule = None
139
140
  }
140
141
 
141
142
  module FromDcb = AutomationSlice.Mapping.Make(
@@ -152,6 +153,7 @@ module FromDcb = AutomationSlice.Mapping.Make(
152
153
  | Shipped({orderId}) => Some(orderId)
153
154
  | Placed(_) => None
154
155
  }
156
+ let reschedule = _ => []
155
157
  },
156
158
  )
157
159
 
@@ -243,6 +243,7 @@ let AutoShipSpec = {
243
243
  maxRetries: 3,
244
244
  heartbeatInterval: 60,
245
245
  targetName: "Ship",
246
+ schedule: undefined,
246
247
  authorizationOf: authorizationOf$2,
247
248
  commandTransition: commandTransition$2,
248
249
  traits: traits$2
@@ -269,13 +270,18 @@ function resolve(event) {
269
270
  }
270
271
  }
271
272
 
273
+ function reschedule(param) {
274
+ return [];
275
+ }
276
+
272
277
  let FromDcb = AutomationSlice$Reventless.Mapping.Make({
273
278
  Id: Id$Reventless.$$String,
274
279
  name: name$2,
275
280
  eventSchema: eventSchema$2
276
281
  })(AutoShipSpec)({
277
282
  collect: collect,
278
- resolve: resolve
283
+ resolve: resolve,
284
+ reschedule: reschedule
279
285
  });
280
286
 
281
287
  function process(id, _item, param) {