@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.152 → 1.0.0-alpha.154

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 (50) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.json +8 -8
  3. package/src/CatalogProduct/StateViewSliceStream/AvailableProducts.res.mjs +2 -0
  4. package/src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res +1 -1
  5. package/src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res.mjs +2 -1
  6. package/src/Customer/Aggregate/Customer.res +8 -0
  7. package/src/Customer/Aggregate/Customer.res.mjs +15 -1
  8. package/src/Customer/Aggregate/Customer_Behavior.res +4 -0
  9. package/src/Customer/Aggregate/Customer_Behavior.res.mjs +10 -0
  10. package/src/Customer/ReadModelStream/Customers.res +5 -0
  11. package/src/Customer/ReadModelStream/Customers.res.mjs +2 -0
  12. package/src/Customer/ReadModelStream/Customers_Projections.res +4 -2
  13. package/src/Customer/ReadModelStream/Customers_Projections.res.mjs +16 -0
  14. package/src/Order/AutomationSlice/AutoShipOrder.res +6 -2
  15. package/src/Order/AutomationSlice/AutoShipOrder_Automation.res +16 -2
  16. package/src/Order/AutomationSlice/AutoShipOrder_Automation.res.mjs +21 -7
  17. package/src/Order/StateChangeSlice/PlaceOrder.res +15 -1
  18. package/src/Order/StateChangeSlice/PlaceOrder.res.mjs +11 -2
  19. package/src/Order/StateChangeSlice/PlaceOrder_Behavior.res +2 -2
  20. package/src/Order/StateChangeSlice/PlaceOrder_Behavior.res.mjs +2 -1
  21. package/src/Order/StateChangeSlice/ShipOrder.res +9 -2
  22. package/src/Order/StateChangeSlice/ShipOrder.res.mjs +10 -2
  23. package/src/Order/StateChangeSlice/ShipOrder_Behavior.res +3 -1
  24. package/src/Order/StateChangeSlice/ShipOrder_Behavior.res.mjs +10 -9
  25. package/src/Order/StateViewSliceStream/Orders.res +19 -1
  26. package/src/Order/StateViewSliceStream/Orders.res.mjs +17 -3
  27. package/src/Order/StateViewSliceStream/Orders_Projection.res +9 -4
  28. package/src/Order/StateViewSliceStream/Orders_Projection.res.mjs +15 -4
  29. package/src/Plugin.res +3 -4
  30. package/src/Plugin.res.mjs +0 -25
  31. package/tests/Customer/Aggregate/Customer_GWT.res +12 -0
  32. package/tests/Customer/Aggregate/Customer_GWT.res.mjs +24 -0
  33. package/tests/Customer/ReadModelStream/Customers_GWT.res +23 -2
  34. package/tests/Customer/ReadModelStream/Customers_GWT.res.mjs +30 -0
  35. package/tests/Flow/OrderingFlow_GWT.res +33 -4
  36. package/tests/Flow/OrderingFlow_GWT.res.mjs +10 -4
  37. package/tests/Order/AutomationSlice/AutoShipOrder_GWT.res +15 -3
  38. package/tests/Order/AutomationSlice/AutoShipOrder_GWT.res.mjs +15 -3
  39. package/tests/Order/StateChangeSlice/PlaceOrder_GWT.res +36 -7
  40. package/tests/Order/StateChangeSlice/PlaceOrder_GWT.res.mjs +30 -7
  41. package/tests/Order/StateChangeSlice/ShipOrder_GWT.res +3 -3
  42. package/tests/Order/StateChangeSlice/ShipOrder_GWT.res.mjs +12 -3
  43. package/tests/Order/StateViewSliceStream/Orders_GWT.res +54 -6
  44. package/tests/Order/StateViewSliceStream/Orders_GWT.res.mjs +33 -6
  45. package/src/Order/StateChangeSlice/RefundOrder.res +0 -27
  46. package/src/Order/StateChangeSlice/RefundOrder.res.mjs +0 -59
  47. package/src/Order/StateChangeSlice/RefundOrder_Behavior.res +0 -26
  48. package/src/Order/StateChangeSlice/RefundOrder_Behavior.res.mjs +0 -77
  49. package/tests/Order/StateChangeSlice/RefundOrder_GWT.res +0 -31
  50. package/tests/Order/StateChangeSlice/RefundOrder_GWT.res.mjs +0 -100
package/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
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
+ # 1.0.0-alpha.154 (2026-07-26)
7
+
8
+ * feat(examples)!: gate hybrid auto-shipping on a shipping method ([d620e12](https://github.com/ReventlessDev/reventless-core/commit/d620e1292db1b470670654588d04cc51b34d2ab9))
9
+ ### Features
10
+
11
+ * **example:** geo-point + file command inputs and a map-bearing view ([1cd89e8](https://github.com/ReventlessDev/reventless-core/commit/1cd89e8dbf256a84a2d689df58887203ea345358))
12
+ * **example:** product image upload replaces the customer document demo ([467b8d3](https://github.com/ReventlessDev/reventless-core/commit/467b8d3ab729be1d4fb334939453e86fdb402015))
13
+ * **example:** ship an order from the Orders board (manual branch) + [@target](https://github.com/target)State by constructor ([e36e14d](https://github.com/ReventlessDev/reventless-core/commit/e36e14dd2545bf2db5413e177f8296b684468a41)), closes [#6](https://github.com/ReventlessDev/reventless-core/issues/6) [#5](https://github.com/ReventlessDev/reventless-core/issues/5) [#5](https://github.com/ReventlessDev/reventless-core/issues/5) [#4](https://github.com/ReventlessDev/reventless-core/issues/4)
14
+
15
+ ### BREAKING CHANGES
16
+
17
+ * PlaceOrder and OrderPlaced gain a required shippingMethod
18
+ field; the RefundOrder slice and its IssueRefund command are removed.
19
+
20
+
21
+
22
+ # 1.0.0-alpha.153 (2026-07-24)
23
+
24
+ **Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
25
+
26
+
27
+
28
+
29
+
6
30
  # 1.0.0-alpha.152 (2026-07-23)
7
31
 
8
32
  **Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/online-shop-hybrid-ordering",
3
- "version": "1.0.0-alpha.152",
3
+ "version": "1.0.0-alpha.154",
4
4
  "description": "Online Shop Hybrid example — Ordering plugin (Customer aggregate + Order/CatalogProduct DCB)",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -21,18 +21,18 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "sury": "^11.0.0-alpha.4",
24
- "@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.73",
24
+ "@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.74",
25
25
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
26
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.126",
27
- "@reventlessdev/reventless-infra": "3.0.0-alpha.104",
28
- "@reventlessdev/reventless-local": "3.0.0-alpha.166",
29
- "@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.73",
30
- "@reventlessdev/reventless-spec": "3.0.0-alpha.80"
26
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.128",
27
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.105",
28
+ "@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.74",
29
+ "@reventlessdev/reventless-local": "3.0.0-alpha.168",
30
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.81"
31
31
  },
32
32
  "devDependencies": {
33
33
  "rescript": "12.3.0",
34
34
  "sury-ppx": "11.0.0-alpha.2",
35
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.54"
35
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.56"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "rescript": "12.3.0"
@@ -39,6 +39,8 @@ let stateSchema$1 = S.Metadata.set(stateSchema, StateAnnotations$Reventless.stat
39
39
  collapsed: [],
40
40
  scan: [],
41
41
  scanSort: [],
42
+ semantic: [],
43
+ metric: [],
42
44
  status: undefined,
43
45
  groupBy: undefined,
44
46
  visibility: "Internal"
@@ -1,6 +1,6 @@
1
1
  @@reventless.projection
2
2
 
3
- let project = event =>
3
+ let project = ({event}) =>
4
4
  switch event {
5
5
  | CatalogProductSynced({productId, name, price}) => [Set(productId, {productId, name, price})]
6
6
  | CatalogProductPriceChanged({productId, price}) =>
@@ -1,7 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
 
4
- function project(event) {
4
+ function project(param) {
5
+ let event = param.event;
5
6
  if (event.TAG === "CatalogProductSynced") {
6
7
  let productId = event.productId;
7
8
  return [{
@@ -3,11 +3,18 @@
3
3
 
4
4
  @@reventless.spec
5
5
 
6
+ // A map coordinate carried by location commands/events. Emitted as a JSON-Schema
7
+ // object with numeric `lat`/`lng` sub-properties, which the generated command
8
+ // form recognises as a geo-point picker.
9
+ @schema
10
+ type location = {lat: float, lng: float}
11
+
6
12
  @schema
7
13
  type command =
8
14
  | Register({email: string, address: string})
9
15
  | UpdateEmail({email: string})
10
16
  | UpdateAddress({address: string})
17
+ | SetLocation({location: location})
11
18
  | Deactivate
12
19
 
13
20
  @schema
@@ -15,6 +22,7 @@ type event =
15
22
  | Registered({email: string, address: string})
16
23
  | EmailUpdated({email: string})
17
24
  | AddressUpdated({address: string})
25
+ | LocationSet({location: location})
18
26
  | Deactivated
19
27
 
20
28
  @schema
@@ -2,6 +2,11 @@
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
4
 
5
+ let locationSchema = S.schema(s => ({
6
+ lat: s.m(S.float),
7
+ lng: s.m(S.float)
8
+ }));
9
+
5
10
  let commandSchema = S.union([
6
11
  S.schema(s => ({
7
12
  TAG: "Register",
@@ -16,6 +21,10 @@ let commandSchema = S.union([
16
21
  TAG: "UpdateAddress",
17
22
  address: s.m(S.string)
18
23
  })),
24
+ S.schema(s => ({
25
+ TAG: "SetLocation",
26
+ location: s.m(locationSchema)
27
+ })),
19
28
  S.literal("Deactivate")
20
29
  ]);
21
30
 
@@ -33,6 +42,10 @@ let eventSchema = S.union([
33
42
  TAG: "AddressUpdated",
34
43
  address: s.m(S.string)
35
44
  })),
45
+ S.schema(s => ({
46
+ TAG: "LocationSet",
47
+ location: s.m(locationSchema)
48
+ })),
36
49
  S.literal("Deactivated")
37
50
  ]);
38
51
 
@@ -55,10 +68,11 @@ let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Customer/Aggrega
55
68
  export {
56
69
  name,
57
70
  Id,
71
+ locationSchema,
58
72
  commandSchema,
59
73
  eventSchema,
60
74
  errorSchema,
61
75
  moduleUrl,
62
76
  commandAuthorization,
63
77
  }
64
- /* commandSchema Not a pure module */
78
+ /* locationSchema Not a pure module */
@@ -17,6 +17,7 @@ let evolve = (state, event) =>
17
17
  | (Active(_), Registered({email, address})) => Active({email, address})
18
18
  | (Active(s), EmailUpdated({email})) => Active({...s, email})
19
19
  | (Active(s), AddressUpdated({address})) => Active({...s, address})
20
+ | (Active(_), LocationSet(_)) => state
20
21
  | (Active(_), Customer.Deactivated) => Deactivated
21
22
  | (Deactivated, _) => state
22
23
  | (NotCreated, _) => state
@@ -33,9 +34,12 @@ let decide = (state, command) =>
33
34
  | (Active(_), UpdateEmail({email})) => Ok([EmailUpdated({email: email})])
34
35
  | (Active(s), UpdateAddress({address})) if address == s.address => Ok([])
35
36
  | (Active(_), UpdateAddress({address})) => Ok([AddressUpdated({address: address})])
37
+ | (Active(_), SetLocation({location})) => Ok([LocationSet({location: location})])
36
38
  | (Active(_), Deactivate) => Ok([Customer.Deactivated])
39
+ | (NotCreated, SetLocation(_)) => Error(CustomerNotFound)
37
40
  | (Deactivated, Register(_)) => Error(CustomerAlreadyDeactivated)
38
41
  | (Deactivated, UpdateEmail(_)) => Error(CustomerAlreadyDeactivated)
39
42
  | (Deactivated, UpdateAddress(_)) => Error(CustomerAlreadyDeactivated)
43
+ | (Deactivated, SetLocation(_)) => Error(CustomerAlreadyDeactivated)
40
44
  | (Deactivated, Deactivate) => Ok([]) // idempotent
41
45
  }
@@ -46,6 +46,8 @@ function evolve(state, event) {
46
46
  email: state.email,
47
47
  address: event.address
48
48
  };
49
+ case "LocationSet" :
50
+ return state;
49
51
  }
50
52
  }
51
53
 
@@ -123,6 +125,14 @@ function decide(state, command) {
123
125
  }]
124
126
  };
125
127
  }
128
+ case "SetLocation" :
129
+ return {
130
+ TAG: "Ok",
131
+ _0: [{
132
+ TAG: "LocationSet",
133
+ location: command.location
134
+ }]
135
+ };
126
136
  }
127
137
  }
128
138
 
@@ -6,10 +6,15 @@
6
6
 
7
7
  @@reventless.spec
8
8
 
9
+ // `lat`/`lng` are a numeric coordinate pair, so the generated read-model view
10
+ // offers a map display that drops a pin per customer. They are fed by the
11
+ // Customer aggregate's `LocationSet` event (see `Customers_Projections.res`).
9
12
  @schema
10
13
  type state = {
11
14
  @displayName email: string,
12
15
  address: string,
16
+ lat: float,
17
+ lng: float,
13
18
  deactivated: bool,
14
19
  orderCount: int,
15
20
  }
@@ -7,6 +7,8 @@ import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/comp
7
7
  let stateSchema = S.schema(s => ({
8
8
  email: s.m(S.string),
9
9
  address: s.m(S.string),
10
+ lat: s.m(S.float),
11
+ lng: s.m(S.float),
10
12
  deactivated: s.m(S.bool),
11
13
  orderCount: s.m(S.int),
12
14
  displayName: s.m(S.option(S.string))
@@ -44,11 +44,13 @@ module CustomerMapping = Mapping.Make(
44
44
  | Registered({email, address}) =>
45
45
  UpdateWithDefault(
46
46
  id,
47
- {Customers.email: email, address, deactivated: false, orderCount: 0},
47
+ {Customers.email: email, address, lat: 0.0, lng: 0.0, deactivated: false, orderCount: 0},
48
48
  state => {...state, email, address, deactivated: false},
49
49
  )
50
50
  | EmailUpdated({email}) => Update(id, state => {...state, email})
51
51
  | AddressUpdated({address}) => Update(id, state => {...state, address})
52
+ | LocationSet({location}) =>
53
+ Update(id, state => {...state, lat: location.lat, lng: location.lng})
52
54
  | Deactivated => Update(id, state => {...state, deactivated: true})
53
55
  }
54
56
  },
@@ -68,7 +70,7 @@ module CustomerOrdersMapping = Mapping.Make(
68
70
  | OrderPlaced({customerId}) =>
69
71
  UpdateWithDefault(
70
72
  customerId,
71
- {Customers.email: "", address: "", deactivated: false, orderCount: 1},
73
+ {Customers.email: "", address: "", lat: 0.0, lng: 0.0, deactivated: false, orderCount: 1},
72
74
  state => {...state, orderCount: state.orderCount + 1},
73
75
  )
74
76
  }
@@ -52,6 +52,8 @@ function project(param) {
52
52
  _1: {
53
53
  email: email,
54
54
  address: address,
55
+ lat: 0.0,
56
+ lng: 0.0,
55
57
  deactivated: false,
56
58
  orderCount: 0
57
59
  },
@@ -85,6 +87,18 @@ function project(param) {
85
87
  return newrecord;
86
88
  }
87
89
  };
90
+ case "LocationSet" :
91
+ let location = event.location;
92
+ return {
93
+ TAG: "Update",
94
+ _0: id,
95
+ _1: state => {
96
+ let newrecord = {...state};
97
+ newrecord.lng = location.lng;
98
+ newrecord.lat = location.lat;
99
+ return newrecord;
100
+ }
101
+ };
88
102
  }
89
103
  }
90
104
 
@@ -108,6 +122,8 @@ function project$1(param) {
108
122
  _1: {
109
123
  email: "",
110
124
  address: "",
125
+ lat: 0.0,
126
+ lng: 0.0,
111
127
  deactivated: false,
112
128
  orderCount: 1
113
129
  },
@@ -1,6 +1,10 @@
1
1
  // AutoShipOrder AutomationSlice.
2
- // When OrderPlaced is emitted, automatically issue a ShipOrder command.
3
- // Resolved when OrderShipped arrives.
2
+ // Expedited dispatch: when an Express order is placed, issue ShipOrder without
3
+ // waiting for the batch run. Resolved when OrderShipped arrives.
4
+ //
5
+ // Standard orders ship with the batch (an explicit ShipOrder) and Pickup orders
6
+ // are collected in store, so neither is this slice's business — `collect` in the
7
+ // sibling `_Automation.res` admits only Express.
4
8
  //
5
9
  // Consumed events come from per-source `Mapping.Make` modules in the sibling
6
10
  // `_Automation.res`. The framework derives the consumed-event set from each
@@ -6,9 +6,16 @@
6
6
  // registers under.
7
7
  module OrderingDcbSource = {
8
8
  let name = "OrderingDcbEventLog"
9
+
10
+ @schema
11
+ type shippingMethod =
12
+ | Standard
13
+ | Express
14
+ | Pickup
15
+
9
16
  @schema
10
17
  type event =
11
- | OrderPlaced({orderId: string})
18
+ | OrderPlaced({orderId: string, shippingMethod: shippingMethod})
12
19
  | OrderShipped({orderId: string})
13
20
  }
14
21
 
@@ -18,9 +25,16 @@ module FromOrderingDcb = Mapping.Make(
18
25
  {
19
26
  open OrderingDcbSource
20
27
 
28
+ // Filtering here rather than in `process` is deliberate: a todo row is a
29
+ // claim that this slice owes an action. Admitting Standard and Pickup and
30
+ // then declining them in `process` would leave rows Pending forever, so the
31
+ // todo view would show a backlog that is never worked off.
21
32
  let collect = (event, _ctx) =>
22
33
  switch event {
23
- | OrderPlaced({orderId}) => [(orderId, ({orderId: orderId}: AutoShipOrder.todoItem))]
34
+ | OrderPlaced({orderId, shippingMethod: Express}) => [
35
+ (orderId, ({orderId: orderId}: AutoShipOrder.todoItem)),
36
+ ]
37
+ | OrderPlaced(_) => []
24
38
  | OrderShipped(_) => []
25
39
  }
26
40
 
@@ -18,10 +18,17 @@ let M = AutomationSlice$Reventless.Mappings.Make({
18
18
 
19
19
  let name = "OrderingDcbEventLog";
20
20
 
21
+ let shippingMethodSchema = S.union([
22
+ S.literal("Standard"),
23
+ S.literal("Express"),
24
+ S.literal("Pickup")
25
+ ]);
26
+
21
27
  let eventSchema = S.union([
22
28
  S.schema(s => ({
23
29
  TAG: "OrderPlaced",
24
- orderId: s.m(DcbTag$Reventless.string)
30
+ orderId: s.m(DcbTag$Reventless.string),
31
+ shippingMethod: s.m(shippingMethodSchema)
25
32
  })),
26
33
  S.schema(s => ({
27
34
  TAG: "OrderShipped",
@@ -32,6 +39,7 @@ let eventSchema = S.union([
32
39
  let OrderingDcbSource = {
33
40
  Id: undefined,
34
41
  name: name,
42
+ shippingMethodSchema: shippingMethodSchema,
35
43
  eventSchema: eventSchema
36
44
  };
37
45
 
@@ -40,12 +48,18 @@ function collect(event, _ctx) {
40
48
  return [];
41
49
  }
42
50
  let orderId = event.orderId;
43
- return [[
44
- orderId,
45
- {
46
- orderId: orderId
47
- }
48
- ]];
51
+ switch (event.shippingMethod) {
52
+ case "Express" :
53
+ return [[
54
+ orderId,
55
+ {
56
+ orderId: orderId
57
+ }
58
+ ]];
59
+ case "Standard" :
60
+ case "Pickup" :
61
+ return [];
62
+ }
49
63
  }
50
64
 
51
65
  function resolve(event) {
@@ -9,6 +9,14 @@ type consumedEvent =
9
9
  | OrderPlaced({orderId: string})
10
10
  | CatalogProductSynced({productId: string})
11
11
 
12
+ // Declared in the order the UI should present them: the batched default first,
13
+ // then the expedited option, then in-store collection.
14
+ @schema
15
+ type shippingMethod =
16
+ | Standard
17
+ | Express
18
+ | Pickup
19
+
12
20
  @schema
13
21
  type command =
14
22
  PlaceOrder({
@@ -16,6 +24,7 @@ type command =
16
24
  // customerId is payload, not a query key — @noDcbTag stops it auto-tagging.
17
25
  @noDcbTag customerId: string,
18
26
  @ref("AvailableProducts") productIds: array<string>,
27
+ shippingMethod: shippingMethod,
19
28
  })
20
29
 
21
30
  @schema
@@ -25,4 +34,9 @@ type error =
25
34
 
26
35
  @schema
27
36
  type event =
28
- OrderPlaced({@partitionTag orderId: string, customerId: string, productIds: array<string>})
37
+ OrderPlaced({
38
+ @partitionTag orderId: string,
39
+ customerId: string,
40
+ productIds: array<string>,
41
+ shippingMethod: shippingMethod,
42
+ })
@@ -15,11 +15,18 @@ let consumedEventSchema = S.union([
15
15
  }))
16
16
  ]);
17
17
 
18
+ let shippingMethodSchema = S.union([
19
+ S.literal("Standard"),
20
+ S.literal("Express"),
21
+ S.literal("Pickup")
22
+ ]);
23
+
18
24
  let commandSchema = S.schema(s => ({
19
25
  TAG: "PlaceOrder",
20
26
  orderId: s.m(DcbTag$Reventless.partition),
21
27
  customerId: s.m(S.string),
22
- productIds: s.m(S.array(Reference$Reventless.to_(undefined, "productId", "AvailableProducts")))
28
+ productIds: s.m(S.array(Reference$Reventless.to_(undefined, "productId", "AvailableProducts"))),
29
+ shippingMethod: s.m(shippingMethodSchema)
23
30
  }));
24
31
 
25
32
  let errorSchema = S.union([
@@ -34,7 +41,8 @@ let eventSchema = S.schema(s => ({
34
41
  TAG: "OrderPlaced",
35
42
  orderId: s.m(DcbTag$Reventless.partition),
36
43
  customerId: s.m(DcbTag$Reventless.string),
37
- productIds: s.m(S.array(DcbTag$Reventless.stringForKey("productId")))
44
+ productIds: s.m(S.array(DcbTag$Reventless.stringForKey("productId"))),
45
+ shippingMethod: s.m(shippingMethodSchema)
38
46
  }));
39
47
 
40
48
  function commandAuthorization(param) {
@@ -53,6 +61,7 @@ export {
53
61
  name,
54
62
  Id,
55
63
  consumedEventSchema,
64
+ shippingMethodSchema,
56
65
  commandSchema,
57
66
  errorSchema,
58
67
  eventSchema,
@@ -25,7 +25,7 @@ let evolve = (state, event: consumedEvent) =>
25
25
 
26
26
  let decide = (state, command) =>
27
27
  switch command {
28
- | PlaceOrder({orderId, customerId, productIds}) =>
28
+ | PlaceOrder({orderId, customerId, productIds, shippingMethod}) =>
29
29
  if state.placedOrderIds->Array.includes(orderId) {
30
30
  Error(OrderAlreadyPlaced)
31
31
  } else {
@@ -33,7 +33,7 @@ let decide = (state, command) =>
33
33
  if missing->Array.length > 0 {
34
34
  Error(ProductsNotAvailable({missing: missing}))
35
35
  } else {
36
- Ok([OrderPlaced({orderId, customerId, productIds})])
36
+ Ok([OrderPlaced({orderId, customerId, productIds, shippingMethod})])
37
37
  }
38
38
  }
39
39
  }
@@ -50,7 +50,8 @@ function decide(state, command) {
50
50
  TAG: "OrderPlaced",
51
51
  orderId: orderId,
52
52
  customerId: command.customerId,
53
- productIds: productIds
53
+ productIds: productIds,
54
+ shippingMethod: command.shippingMethod
54
55
  }]
55
56
  };
56
57
  }
@@ -1,17 +1,24 @@
1
1
  // ShipOrder StateChangeSlice.
2
2
  // Requires order to exist and not be cancelled; idempotent if already shipped.
3
+ //
4
+ // `OrderPlaced` carries a payload so it survives the DCB payload-less filter and
5
+ // lands in `consumedEventTypes`, overlapping the Orders view's consumed events.
6
+ // That is what links this slice to Orders (`consistencyRead=Orders`) so AutoUI's
7
+ // board offers it — shipping is a manual operator action on the Orders board, not
8
+ // automation-only. `@targetState("Shipped")` declares the transition outright, so
9
+ // the board resolves the drop via its DeclaredTarget tier rather than guessing.
3
10
 
4
11
  @@reventless.spec
5
12
 
6
13
  @schema
7
14
  type consumedEvent =
8
- | OrderPlaced
15
+ | OrderPlaced({productIds: array<string>})
9
16
  | OrderShipped
10
17
  | OrderCancelled
11
18
 
12
19
  @schema
13
20
  type command =
14
- | @allowedStates([Orders.Placed]) ShipOrder({orderId: string})
21
+ | @allowedStates([Orders.Placed]) @targetState(Orders.Shipped) ShipOrder({orderId: string})
15
22
 
16
23
  @schema
17
24
  type error =
@@ -5,7 +5,10 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
5
5
  import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
6
6
 
7
7
  let consumedEventSchema = S.union([
8
- S.literal("OrderPlaced"),
8
+ S.schema(s => ({
9
+ TAG: "OrderPlaced",
10
+ productIds: s.m(S.array(DcbTag$Reventless.stringForKey("productId")))
11
+ })),
9
12
  S.literal("OrderShipped"),
10
13
  S.literal("OrderCancelled")
11
14
  ]);
@@ -30,6 +33,11 @@ let commandSchema$1 = Api$ReventlessInfra.markAllowedStates(commandSchema, [[
30
33
  ["Placed"]
31
34
  ]]);
32
35
 
36
+ let commandSchema$2 = Api$ReventlessInfra.markTargetState(commandSchema$1, [[
37
+ "ShipOrder",
38
+ "Shipped"
39
+ ]]);
40
+
33
41
  function commandAuthorization(param) {
34
42
  return "AllowAuthenticated";
35
43
  }
@@ -48,7 +56,7 @@ export {
48
56
  consumedEventSchema,
49
57
  errorSchema,
50
58
  eventSchema,
51
- commandSchema$1 as commandSchema,
59
+ commandSchema$2 as commandSchema,
52
60
  moduleUrl,
53
61
  commandAuthorization,
54
62
  readConsistency,
@@ -6,7 +6,9 @@ let initialState = {exists: false, shipped: false, cancelled: false}
6
6
 
7
7
  let evolve = (state, event) =>
8
8
  switch event {
9
- | OrderPlaced => {exists: true, shipped: false, cancelled: false}
9
+ // productIds is carried only to keep OrderPlaced out of the payload-less
10
+ // filter (so the slice links to Orders); ShipOrder's decision doesn't use it.
11
+ | OrderPlaced(_) => {exists: true, shipped: false, cancelled: false}
10
12
  | OrderShipped => {...state, shipped: true}
11
13
  | OrderCancelled => {...state, cancelled: true}
12
14
  }
@@ -2,25 +2,26 @@
2
2
 
3
3
 
4
4
  function evolve(state, event) {
5
- switch (event) {
6
- case "OrderPlaced" :
7
- return {
8
- exists: true,
9
- shipped: false,
10
- cancelled: false
11
- };
12
- case "OrderShipped" :
5
+ if (typeof event !== "object") {
6
+ if (event === "OrderShipped") {
13
7
  return {
14
8
  exists: state.exists,
15
9
  shipped: true,
16
10
  cancelled: state.cancelled
17
11
  };
18
- case "OrderCancelled" :
12
+ } else {
19
13
  return {
20
14
  exists: state.exists,
21
15
  shipped: state.shipped,
22
16
  cancelled: true
23
17
  };
18
+ }
19
+ } else {
20
+ return {
21
+ exists: true,
22
+ shipped: false,
23
+ cancelled: false
24
+ };
24
25
  }
25
26
  }
26
27
 
@@ -3,9 +3,20 @@
3
3
 
4
4
  @@reventless.spec
5
5
 
6
+ @schema
7
+ type shippingMethod =
8
+ | Standard
9
+ | Express
10
+ | Pickup
11
+
6
12
  @schema
7
13
  type consumedEvent =
8
- | OrderPlaced({orderId: string, customerId: string, productIds: array<string>})
14
+ | OrderPlaced({
15
+ orderId: string,
16
+ customerId: string,
17
+ productIds: array<string>,
18
+ shippingMethod: shippingMethod,
19
+ })
9
20
  | OrderShipped({orderId: string})
10
21
  | OrderCancelled({orderId: string})
11
22
 
@@ -21,4 +32,11 @@ type state = {
21
32
  customerId: string,
22
33
  productIds: array<string>,
23
34
  @status status: status,
35
+ shippingMethod: shippingMethod,
36
+ // Producer timestamps taken from the event envelope's `meta.time` — no need to
37
+ // carry the time in the event payload. The `DateTime` marker surfaces
38
+ // `format: "date-time"` on the state's JSON Schema, which the AutoUI date
39
+ // views (Calendar/Timeline) key off. `shippedAt` is "" until the order ships.
40
+ placedAt: @s.matches(Reventless.DateTime.string) string,
41
+ shippedAt: @s.matches(Reventless.DateTime.string) string,
24
42
  }