@reventlessdev/reventless-local 3.0.0-alpha.233 → 3.0.0-alpha.235

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 (27) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +10 -10
  3. package/src/adapter/DomainGraphQL_Server.res +36 -0
  4. package/src/adapter/DomainGraphQL_Server.res.mjs +40 -1
  5. package/src/adapter/GraphQL_Server.res.mjs +6 -0
  6. package/src/adapter/PlatformGraphQL_Server.res +5 -0
  7. package/src/adapter/PlatformGraphQL_Server.res.mjs +4 -0
  8. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +171 -2
  9. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +118 -1
  10. package/tests/adapter/QueryDbListResolverTest.res +150 -0
  11. package/tests/adapter/QueryDbListResolverTest.res.mjs +133 -0
  12. package/tests/adapter/QueryDbResolvedFieldsTest.res +253 -0
  13. package/tests/adapter/QueryDbResolvedFieldsTest.res.mjs +291 -0
  14. package/tests/components/automationslice/AutomationSliceCallbackTest.res +64 -0
  15. package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +55 -0
  16. package/tests/components/automationslice/AutomationSliceFixtures.res +2 -0
  17. package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +10 -0
  18. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +1 -0
  19. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +5 -0
  20. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +1 -0
  21. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +5 -0
  22. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +135 -1
  23. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +202 -0
  24. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -0
  25. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res.mjs +15 -0
  26. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +2 -0
  27. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res.mjs +5 -0
@@ -0,0 +1,291 @@
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 Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
7
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
+ import * as Pulumi from "@pulumi/pulumi";
9
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
+ import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
11
+ import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
12
+ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
13
+ import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
14
+ import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
15
+ import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
16
+ import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
17
+ import * as QueryDb_Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Adapter.res.mjs";
18
+ import * as QueryDb_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Builder.res.mjs";
19
+ import * as LocalQueryDbStorage$ReventlessLocal from "../../src/adapter/QueryDb/LocalQueryDbStorage.res.mjs";
20
+ import * as DomainGraphQL_Server$ReventlessLocal from "../../src/adapter/DomainGraphQL_Server.res.mjs";
21
+ import * as QueryDbResolvers_GraphQL$ReventlessLocal from "../../src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs";
22
+
23
+ TestRunner$ReventlessLocal.setup();
24
+
25
+ let productRowSchema = Sury.$schema(s => ({
26
+ productId: s.m(Sury.string),
27
+ name: s.m(Sury.string),
28
+ archived: s.m(Sury.bool)
29
+ }));
30
+
31
+ let orderRowSchema = Sury.$schema(s => ({
32
+ orderId: s.m(Sury.string),
33
+ productId: s.m(Sury.string),
34
+ productIds: s.m(Sury.array(Sury.string))
35
+ }));
36
+
37
+ function annotations(retired) {
38
+ return {
39
+ ids: [],
40
+ compositeIds: [],
41
+ subIds: [],
42
+ compositeSubIds: [],
43
+ indexes: [],
44
+ hidden: [],
45
+ summary: [],
46
+ drillTargets: [],
47
+ drillTargetKeys: [],
48
+ collapsed: [],
49
+ scan: [],
50
+ scanSort: [],
51
+ semantic: [],
52
+ metric: [],
53
+ lifecycle: undefined,
54
+ groupBy: undefined,
55
+ visibility: undefined,
56
+ live: undefined,
57
+ retired: retired
58
+ };
59
+ }
60
+
61
+ function ctxFor(groups) {
62
+ let newrecord = {...Identity$Reventless.anonymous};
63
+ return Object.fromEntries([
64
+ [
65
+ "request",
66
+ Object.fromEntries([[
67
+ "headers",
68
+ {}
69
+ ]])
70
+ ],
71
+ [
72
+ "identity",
73
+ (newrecord.groups = groups, newrecord.username = "test-user", newrecord.userId = "test-user", newrecord)
74
+ ]
75
+ ]);
76
+ }
77
+
78
+ let plainCtx = ctxFor(["User"]);
79
+
80
+ let noArgs = {};
81
+
82
+ function getString(json, key) {
83
+ return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), d => d[key]), Stdlib_JSON.Decode.string);
84
+ }
85
+
86
+ function names(json) {
87
+ return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(json), []), item => getString(item, "name"));
88
+ }
89
+
90
+ async function buildFixture(suffix, retired) {
91
+ let Bus = LocalBus$ReventlessLocal.Make({});
92
+ let Storage = LocalQueryDbStorage$ReventlessLocal.Make(Bus);
93
+ let Resolvers = QueryDbResolvers_GraphQL$ReventlessLocal.Make(Bus);
94
+ let productsName = "Products" + suffix;
95
+ let ordersName = "Orders" + suffix;
96
+ let orderTypeName = "Shop_Order" + suffix;
97
+ let moduleUrl = import.meta.url;
98
+ let config = ReadModel$Reventless.config(undefined, undefined, undefined);
99
+ let moduleUrl$1 = import.meta.url;
100
+ let config$1 = ReadModel$Reventless.config([{
101
+ source: {
102
+ idField: "productId",
103
+ subId: "NoSubId",
104
+ resolvedField: {
105
+ TAG: "Single",
106
+ _0: "product"
107
+ }
108
+ },
109
+ target: {
110
+ tableName: productsName,
111
+ idField: "Id"
112
+ }
113
+ }], [{
114
+ source: {
115
+ idsField: "productIds",
116
+ resolvedField: "products"
117
+ },
118
+ target: {
119
+ tableName: productsName
120
+ }
121
+ }], undefined);
122
+ let NoResolvers = QueryDb_Adapter$ReventlessCore.NoResolvers({
123
+ make: Storage.make
124
+ });
125
+ let ProductsDb = QueryDb_Builder$ReventlessCore.Make({
126
+ Id: {
127
+ schema: Id$Reventless.StringPure.schema,
128
+ make: prim => prim,
129
+ makeFromString: prim => prim,
130
+ toString: prim => prim,
131
+ cmp: Id$Reventless.StringPure.cmp
132
+ },
133
+ name: productsName,
134
+ moduleUrl: moduleUrl,
135
+ stateSchema: productRowSchema,
136
+ config: config,
137
+ subIdConfig: undefined,
138
+ authorization: "AllowAuthenticated",
139
+ visibility: "Public"
140
+ })({
141
+ make: Storage.make
142
+ })(NoResolvers);
143
+ let OrdersDb = QueryDb_Builder$ReventlessCore.Make({
144
+ Id: {
145
+ schema: Id$Reventless.StringPure.schema,
146
+ make: prim => prim,
147
+ makeFromString: prim => prim,
148
+ toString: prim => prim,
149
+ cmp: Id$Reventless.StringPure.cmp
150
+ },
151
+ name: ordersName,
152
+ moduleUrl: moduleUrl$1,
153
+ stateSchema: orderRowSchema,
154
+ config: config$1,
155
+ subIdConfig: undefined,
156
+ authorization: "AllowAuthenticated",
157
+ visibility: "Public"
158
+ })({
159
+ make: Storage.make
160
+ })(NoResolvers);
161
+ let register = (viewName, returnTypeName) => {
162
+ Plugin_Helpers$ReventlessCore.queryFieldNamesRegistry[viewName] = {
163
+ singleFieldName: returnTypeName,
164
+ listFieldName: returnTypeName + "s",
165
+ returnTypeName: returnTypeName,
166
+ pluralTypeName: returnTypeName + "s",
167
+ includeIdParam: true,
168
+ connectionSpec: true
169
+ };
170
+ };
171
+ register(productsName, "Shop_Product" + suffix);
172
+ register(ordersName, orderTypeName);
173
+ Plugin_Helpers$ReventlessCore.stateSchemaRegistry[productsName] = Sury.$Metadata_set(productRowSchema, StateAnnotations$Reventless.stateAnnotationsId, annotations(retired));
174
+ let productsDb = ProductsDb.make(undefined, undefined, undefined, undefined, undefined);
175
+ let productOps = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(productsDb));
176
+ let ordersDb = OrdersDb.make(undefined, undefined, undefined, undefined, undefined);
177
+ let orderOps = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(ordersDb));
178
+ Resolvers.make(ordersName, undefined, undefined, Pulumi.output(""), [], undefined, config$1.idResolvers, config$1.idsResolvers, "AllowAuthenticated", {});
179
+ await productOps.save("p-1", {
180
+ productId: "p-1",
181
+ name: "Book",
182
+ archived: false
183
+ }, "Init", undefined);
184
+ await productOps.save("p-2", {
185
+ productId: "p-2",
186
+ name: "Pen",
187
+ archived: true
188
+ }, "Init", undefined);
189
+ await orderOps.save("o-1", {
190
+ orderId: "o-1",
191
+ productId: "p-1",
192
+ productIds: [
193
+ "p-1",
194
+ "gone",
195
+ "p-2"
196
+ ]
197
+ }, "Init", undefined);
198
+ let resolverFor = field => {
199
+ let r = DomainGraphQL_Server$ReventlessLocal.getFieldResolver(orderTypeName, field);
200
+ if (r !== undefined) {
201
+ return r;
202
+ } else {
203
+ return Stdlib_JsError.throwWithMessage("field resolver not registered: " + field);
204
+ }
205
+ };
206
+ let order = Object.fromEntries([
207
+ [
208
+ "orderId",
209
+ "o-1"
210
+ ],
211
+ [
212
+ "productId",
213
+ "p-1"
214
+ ],
215
+ [
216
+ "productIds",
217
+ [
218
+ "p-1",
219
+ "gone",
220
+ "p-2"
221
+ ].map(prim => prim)
222
+ ]
223
+ ]);
224
+ return [
225
+ resolverFor,
226
+ order
227
+ ];
228
+ }
229
+
230
+ globalThis.describe("QueryDb cross-table field resolvers", () => {
231
+ globalThis.beforeEach(() => DomainGraphQL_Server$ReventlessLocal.reset());
232
+ globalThis.test("@resolves follows the foreign key into the target's row", async () => {
233
+ let match = await buildFixture("A", undefined);
234
+ let product = await match[0]("product")(match[1], noArgs, plainCtx);
235
+ globalThis.expect([
236
+ getString(product, "name"),
237
+ getString(product, "id")
238
+ ]).toEqual([
239
+ "Book",
240
+ "p-1"
241
+ ]);
242
+ });
243
+ globalThis.test("@resolvesMany batch-follows the id array", async () => {
244
+ let match = await buildFixture("B", undefined);
245
+ let products = await match[0]("products")(match[1], noArgs, plainCtx);
246
+ globalThis.expect(names(products)).toEqual([
247
+ "Book",
248
+ "Pen"
249
+ ]);
250
+ });
251
+ globalThis.test("a foreign key naming no row resolves to nothing", async () => {
252
+ let match = await buildFixture("C", undefined);
253
+ let orphan = Object.fromEntries([[
254
+ "productId",
255
+ "nope"
256
+ ]]);
257
+ let product = await match[0]("product")(orphan, noArgs, plainCtx);
258
+ globalThis.expect(product).toBe(null);
259
+ });
260
+ globalThis.test("a retired target row is withheld from both forms", async () => {
261
+ let match = await buildFixture("D", {
262
+ field: "archived",
263
+ label: "",
264
+ showWhenFalse: false,
265
+ values: undefined,
266
+ namedWhenRetired: false
267
+ });
268
+ let resolverFor = match[0];
269
+ let products = await resolverFor("products")(match[1], noArgs, plainCtx);
270
+ globalThis.expect(names(products)).toEqual(["Book"]);
271
+ let archivedOnly = Object.fromEntries([[
272
+ "productId",
273
+ "p-2"
274
+ ]]);
275
+ let product = await resolverFor("product")(archivedOnly, noArgs, plainCtx);
276
+ globalThis.expect(product).toBe(null);
277
+ });
278
+ });
279
+
280
+ export {
281
+ productRowSchema,
282
+ orderRowSchema,
283
+ annotations,
284
+ ctxFor,
285
+ plainCtx,
286
+ noArgs,
287
+ getString,
288
+ names,
289
+ buildFixture,
290
+ }
291
+ /* Not a pure module */
@@ -179,6 +179,70 @@ describe("AutomationSlice Callback (mixed-source)", () => {
179
179
  })
180
180
  })
181
181
 
182
+ // A row used to leave the retry budget by falling out of phase2's filter, so
183
+ // `Failed` meant both "will be retried" and "never again" and only the count
184
+ // told them apart — against a ceiling that lives in the Spec, not in the row.
185
+ describe("retry exhaustion", () => {
186
+ let failingPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds =>
187
+ JsError.throwWithMessage("publish failed")
188
+
189
+ let collectOne = () =>
190
+ Callback.phase1(
191
+ [encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
192
+ testContext,
193
+ )
194
+
195
+ let statusOf = id => (Callback.todoItems->Dict.get(id)->Option.getOrThrow).status
196
+
197
+ testPromise("the attempt that spends the budget writes Abandoned, not Failed", async () => {
198
+ collectOne()
199
+ // ShipOrderSpec.maxRetries is 3: attempts at retryCount 0, 1 and 2 leave the
200
+ // row retriable, and the third failure is the one that ends it.
201
+ await Callback.phase2(failingPublish)
202
+ expect(statusOf("ord-1"))->toBe(Failed)
203
+ await Callback.phase2(failingPublish)
204
+ expect(statusOf("ord-1"))->toBe(Failed)
205
+ await Callback.phase2(failingPublish)
206
+ expect(statusOf("ord-1"))->toBe(Abandoned)
207
+ expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).retryCount)->toBe(3)
208
+ })
209
+
210
+ testPromise("an abandoned row is not picked up again", async () => {
211
+ collectOne()
212
+ await Callback.phase2(failingPublish)
213
+ await Callback.phase2(failingPublish)
214
+ await Callback.phase2(failingPublish)
215
+ expect(statusOf("ord-1"))->toBe(Abandoned)
216
+
217
+ // A pass that would have succeeded: the row must still not be attempted,
218
+ // which is what makes the status a decision rather than a label.
219
+ let published = ref([])
220
+ await Callback.phase2(async cmds => published := cmds)
221
+ expect(published.contents->Array.length)->toBe(0)
222
+ expect(statusOf("ord-1"))->toBe(Abandoned)
223
+ expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).retryCount)->toBe(3)
224
+ })
225
+
226
+ testPromise("a row left stranded at the ceiling is normalised on the next pass", async () => {
227
+ collectOne()
228
+ // What an older build wrote, and what a Spec that lowered maxRetries leaves
229
+ // behind: Failed at the ceiling, which no pass would ever pick up or mark.
230
+ let row = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
231
+ Callback.todoItems->Dict.set("ord-1", {...row, status: Failed, retryCount: 3})
232
+
233
+ let published = ref([])
234
+ await Callback.phase2(async cmds => published := cmds)
235
+ expect(published.contents->Array.length)->toBe(0)
236
+ expect(statusOf("ord-1"))->toBe(Abandoned)
237
+ })
238
+
239
+ testPromise("the row carries the ceiling its count is racing", async () => {
240
+ collectOne()
241
+ expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).maxRetries)
242
+ ->toEqual(Some(ShipOrderSpec.maxRetries))
243
+ })
244
+ })
245
+
182
246
  describe("full lifecycle", () => {
183
247
  testPromise("collect → process → resolve completes the TODO item", async () => {
184
248
  Callback.phase1(
@@ -192,6 +192,61 @@ globalThis.describe("AutomationSlice Callback (mixed-source)", () => {
192
192
  globalThis.expect(row2.status).toBe("Processing");
193
193
  });
194
194
  });
195
+ globalThis.describe("retry exhaustion", () => {
196
+ let failingPublish = async _cmds => Stdlib_JsError.throwWithMessage("publish failed");
197
+ let collectOne = () => Callback.phase1([AutomationSliceFixtures$ReventlessLocal.encodeShipOrderEvent({
198
+ TAG: "OrderPlaced",
199
+ orderId: "ord-1",
200
+ address: "123 Main St"
201
+ })], AutomationSliceFixtures$ReventlessLocal.testContext);
202
+ let statusOf = id => Stdlib_Option.getOrThrow(Callback.todoItems[id], undefined).status;
203
+ globalThis.test("the attempt that spends the budget writes Abandoned, not Failed", async () => {
204
+ collectOne();
205
+ await Callback.phase2(failingPublish);
206
+ globalThis.expect(statusOf("ord-1")).toBe("Failed");
207
+ await Callback.phase2(failingPublish);
208
+ globalThis.expect(statusOf("ord-1")).toBe("Failed");
209
+ await Callback.phase2(failingPublish);
210
+ globalThis.expect(statusOf("ord-1")).toBe("Abandoned");
211
+ globalThis.expect(Stdlib_Option.getOrThrow(Callback.todoItems["ord-1"], undefined).retryCount).toBe(3);
212
+ });
213
+ globalThis.test("an abandoned row is not picked up again", async () => {
214
+ collectOne();
215
+ await Callback.phase2(failingPublish);
216
+ await Callback.phase2(failingPublish);
217
+ await Callback.phase2(failingPublish);
218
+ globalThis.expect(statusOf("ord-1")).toBe("Abandoned");
219
+ let published = {
220
+ contents: []
221
+ };
222
+ await Callback.phase2(async cmds => {
223
+ published.contents = cmds;
224
+ });
225
+ globalThis.expect(published.contents.length).toBe(0);
226
+ globalThis.expect(statusOf("ord-1")).toBe("Abandoned");
227
+ globalThis.expect(Stdlib_Option.getOrThrow(Callback.todoItems["ord-1"], undefined).retryCount).toBe(3);
228
+ });
229
+ globalThis.test("a row left stranded at the ceiling is normalised on the next pass", async () => {
230
+ collectOne();
231
+ let row = Stdlib_Option.getOrThrow(Callback.todoItems["ord-1"], undefined);
232
+ let newrecord = {...row};
233
+ newrecord.retryCount = 3;
234
+ newrecord.status = "Failed";
235
+ Callback.todoItems["ord-1"] = newrecord;
236
+ let published = {
237
+ contents: []
238
+ };
239
+ await Callback.phase2(async cmds => {
240
+ published.contents = cmds;
241
+ });
242
+ globalThis.expect(published.contents.length).toBe(0);
243
+ globalThis.expect(statusOf("ord-1")).toBe("Abandoned");
244
+ });
245
+ globalThis.test("the row carries the ceiling its count is racing", async () => {
246
+ collectOne();
247
+ globalThis.expect(Stdlib_Option.getOrThrow(Callback.todoItems["ord-1"], undefined).maxRetries).toEqual(AutomationSliceFixtures$ReventlessLocal.ShipOrderSpec.maxRetries);
248
+ });
249
+ });
195
250
  globalThis.describe("full lifecycle", () => {
196
251
  globalThis.test("collect → process → resolve completes the TODO item", async () => {
197
252
  Callback.phase1([AutomationSliceFixtures$ReventlessLocal.encodeShipOrderEvent({
@@ -108,6 +108,7 @@ module ShipOrderAutomation: Reventless.AutomationSlice.Automation
108
108
  with module Spec := ShipOrderSpec = {
109
109
  let process = (id, _item: ShipOrderSpec.todoItem) =>
110
110
  Some((id, ShipOrderSpec.CreateShipment({orderId: id})))
111
+ let onExhausted = (_id, _item: ShipOrderSpec.todoItem) => None
111
112
  let moduleUrl: string = %raw(`import.meta.url`)
112
113
  module M = Reventless.AutomationSlice.Mappings.Make(ShipOrderSpec)
113
114
  module type Mapping = M.Mapping
@@ -117,6 +118,7 @@ module ShipOrderAutomation: Reventless.AutomationSlice.Automation
117
118
  module SkipProcessAutomation: Reventless.AutomationSlice.Automation
118
119
  with module Spec := SkipProcessSpec = {
119
120
  let process = (_id, _item: SkipProcessSpec.todoItem) => None
121
+ let onExhausted = (_id, _item: SkipProcessSpec.todoItem) => None
120
122
  let moduleUrl: string = %raw(`import.meta.url`)
121
123
  module M = Reventless.AutomationSlice.Mappings.Make(SkipProcessSpec)
122
124
  module type Mapping = M.Mapping
@@ -168,6 +168,10 @@ function process(id, _item) {
168
168
  ];
169
169
  }
170
170
 
171
+ function onExhausted(_id, _item) {
172
+
173
+ }
174
+
171
175
  let moduleUrl$2 = import.meta.url;
172
176
 
173
177
  AutomationSlice$Reventless.Mappings.Make(ShipOrderSpec);
@@ -176,6 +180,7 @@ let mappings = [ShipOrderMapping];
176
180
 
177
181
  let ShipOrderAutomation = {
178
182
  process: process,
183
+ onExhausted: onExhausted,
179
184
  moduleUrl: moduleUrl$2,
180
185
  mappings: mappings
181
186
  };
@@ -184,6 +189,10 @@ function process$1(_id, _item) {
184
189
 
185
190
  }
186
191
 
192
+ function onExhausted$1(_id, _item) {
193
+
194
+ }
195
+
187
196
  let moduleUrl$3 = import.meta.url;
188
197
 
189
198
  AutomationSlice$Reventless.Mappings.Make(SkipProcessSpec);
@@ -192,6 +201,7 @@ let mappings$1 = [SkipProcessMapping];
192
201
 
193
202
  let SkipProcessAutomation = {
194
203
  process: process$1,
204
+ onExhausted: onExhausted$1,
195
205
  moduleUrl: moduleUrl$3,
196
206
  mappings: mappings$1
197
207
  };
@@ -158,6 +158,7 @@ module FromDcb = AutomationSlice.Mapping.Make(
158
158
  module AutoShipAutomation: AutomationSlice.Automation with module Spec := AutoShipSpec = {
159
159
  let process = (id, _item: AutoShipSpec.todoItem) =>
160
160
  Some((id, AutoShipSpec.Ship({orderId: id})))
161
+ let onExhausted = (_id, _item: AutoShipSpec.todoItem) => None
161
162
  let moduleUrl: string = %raw(`import.meta.url`)
162
163
  module M = AutomationSlice.Mappings.Make(AutoShipSpec)
163
164
  module type Mapping = M.Mapping
@@ -264,6 +264,10 @@ function process(id, _item) {
264
264
  ];
265
265
  }
266
266
 
267
+ function onExhausted(_id, _item) {
268
+
269
+ }
270
+
267
271
  let moduleUrl$5 = import.meta.url;
268
272
 
269
273
  AutomationSlice$Reventless.Mappings.Make(AutoShipSpec);
@@ -272,6 +276,7 @@ let mappings = [FromDcb];
272
276
 
273
277
  let AutoShipAutomation = {
274
278
  process: process,
279
+ onExhausted: onExhausted,
275
280
  moduleUrl: moduleUrl$5,
276
281
  mappings: mappings
277
282
  };
@@ -117,6 +117,7 @@ module AutoFulfillAutomation: AutomationSlice.Automation
117
117
  with module Spec := AutoFulfillSpec = {
118
118
  let process = (id, item: AutoFulfillSpec.todoItem) =>
119
119
  Some((id, AutoFulfillSpec.MarkFulfilled({orderId: item.orderId, productId: item.productId})))
120
+ let onExhausted = (_id, _item: AutoFulfillSpec.todoItem) => None
120
121
  let moduleUrl: string = %raw(`import.meta.url`)
121
122
  module M = AutomationSlice.Mappings.Make(AutoFulfillSpec)
122
123
  module type Mapping = M.Mapping
@@ -149,6 +149,10 @@ function process(id, item) {
149
149
  ];
150
150
  }
151
151
 
152
+ function onExhausted(_id, _item) {
153
+
154
+ }
155
+
152
156
  let moduleUrl$1 = import.meta.url;
153
157
 
154
158
  AutomationSlice$Reventless.Mappings.Make(AutoFulfillSpec);
@@ -160,6 +164,7 @@ let mappings = [
160
164
 
161
165
  let AutoFulfillAutomation = {
162
166
  process: process,
167
+ onExhausted: onExhausted,
163
168
  moduleUrl: moduleUrl$1,
164
169
  mappings: mappings
165
170
  };