@reventlessdev/reventless-aws 3.0.0-alpha.316 → 3.0.0-alpha.317

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.317 (2026-08-20)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** stamp union member types on the deployed projection path ([140251a](https://github.com/ReventlessDev/reventless-core/commit/140251ac7c18f484be32e8683a725f6fac2b9519))
11
+
12
+
6
13
  # 3.0.0-alpha.316 (2026-08-20)
7
14
 
8
15
  **Note:** Version bump only for package @reventlessdev/reventless-aws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.316",
3
+ "version": "3.0.0-alpha.317",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -14,16 +14,16 @@
14
14
  "uuid": "^13.0.0",
15
15
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.12",
16
16
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
17
+ "@reventlessdev/rescript-node": "2.0.0-alpha.8",
17
18
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
18
19
  "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.1",
19
- "@reventlessdev/rescript-node": "2.0.0-alpha.8",
20
20
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
21
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
21
22
  "@reventlessdev/reventless-core": "3.0.0-alpha.245",
22
23
  "@reventlessdev/reventless-infra": "3.0.0-alpha.148",
23
- "@reventlessdev/reventless-spec": "3.0.0-alpha.120",
24
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.109",
25
24
  "@reventlessdev/reventless-interop": "3.0.0-alpha.32",
26
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.0"
25
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.109",
26
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.120"
27
27
  },
28
28
  "devDependencies": {
29
29
  "rescript": "12.3.0",
@@ -55,6 +55,37 @@ external withLiveUpdates: (
55
55
  liveConfig,
56
56
  ) => ReventlessCore.QueryDb_Adapter.operations = "withLiveUpdates"
57
57
 
58
+ /**
59
+ Stamps every union value in a saved row with the GraphQL member type it is.
60
+
61
+ `QueryDb_Operations.Make` does this for the typed path — the local platform and
62
+ the storage tests — but the deployed projection Lambdas do not go through that
63
+ functor: they assemble JSON-level operations here. Without this the stamp never
64
+ ran on AWS, every union member resolved to null, and the null took its
65
+ non-nullable parent, so rows vanished from a view rather than erroring.
66
+
67
+ `stateSchema` is `None` for a spec module that predates it, which leaves rows
68
+ exactly as they were.
69
+ */
70
+ let withUnionMemberTypes = (
71
+ base: ReventlessCore.QueryDb_Adapter.operations,
72
+ ~stateSchema: option<S.t<unknown>>,
73
+ ): ReventlessCore.QueryDb_Adapter.operations =>
74
+ switch stateSchema {
75
+ | None => base
76
+ | Some(schema) =>
77
+ let stamp = (state: JSON.t) => {
78
+ Reventless.TaggedUnion.stampInto(~schema, state)
79
+ state
80
+ }
81
+ {
82
+ ...base,
83
+ save: (id, state, saveMode, ttl) => base.save(id, stamp(state), saveMode, ttl),
84
+ saveBatch: items =>
85
+ base.saveBatch(items->Array.map(((id, state, ttl)) => (id, stamp(state), ttl))),
86
+ }
87
+ }
88
+
58
89
  let makeQueryDbOps = (
59
90
  ~queryDbTableName: string,
60
91
  ~pgConnection: option<PgConnection.connectionConfig>,
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
4
5
  import * as StateTopicPublishMjs from "./StateTopicPublish.mjs";
5
6
  import * as QueryDbEntryPoint_Ops$ReventlessAws from "./QueryDbEntryPoint_Ops.res.mjs";
6
7
  import * as QueryDbStorage_Postgres_Runtime$ReventlessAws from "../QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs";
@@ -17,6 +18,29 @@ function withLiveUpdates(prim0, prim1) {
17
18
  return StateTopicPublishMjs.withLiveUpdates(prim0, prim1);
18
19
  }
19
20
 
21
+ function withUnionMemberTypes(base, stateSchema) {
22
+ if (stateSchema !== undefined) {
23
+ return {
24
+ load: base.load,
25
+ loadStream: base.loadStream,
26
+ save: (id, state, saveMode, ttl) => base.save(id, (TaggedUnion$Reventless.stampInto(stateSchema, state), state), saveMode, ttl),
27
+ saveBatch: items => base.saveBatch(items.map(param => {
28
+ let state = param[1];
29
+ return [
30
+ param[0],
31
+ (TaggedUnion$Reventless.stampInto(stateSchema, state), state),
32
+ param[2]
33
+ ];
34
+ })),
35
+ count: base.count,
36
+ delete: base.delete,
37
+ deleteBatch: base.deleteBatch
38
+ };
39
+ } else {
40
+ return base;
41
+ }
42
+ }
43
+
20
44
  function makeQueryDbOps(queryDbTableName, pgConnection, stateTopicName, indexes, subIdField) {
21
45
  if (pgConnection !== undefined) {
22
46
  return StateTopicPublishMjs.withLiveUpdates(QueryDbStorage_Postgres_Runtime$ReventlessAws.opsFor(pgConnection, queryDbTableName, indexes, subIdField), {
@@ -34,6 +58,7 @@ export {
34
58
  indexesOf,
35
59
  subIdFieldOf,
36
60
  withLiveUpdates,
61
+ withUnionMemberTypes,
37
62
  makeQueryDbOps,
38
63
  }
39
- /* ./StateTopicPublish.mjs Not a pure module */
64
+ /* TaggedUnion-Reventless Not a pure module */
@@ -47,6 +47,7 @@ async function buildAllHandlers() {
47
47
  const operations = Ops.buildOperations(entry, {
48
48
  config: specModule.config,
49
49
  subIdConfig: specModule.subIdConfig,
50
+ stateSchema: specModule.stateSchema,
50
51
  });
51
52
  const callback = readModelCallbackMake(specModule)(mappingsModule)({
52
53
  ReadModelSpec: specModule,
@@ -90,6 +90,8 @@ let withInjectedId = (
90
90
  type specInfo = {
91
91
  config: option<ProjectionEntryPoint_Ops.specConfig>,
92
92
  subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
93
+ // Read off the runtime-loaded spec module; `None` on a module that predates it.
94
+ stateSchema: option<S.t<unknown>>,
93
95
  }
94
96
 
95
97
  let buildOperations = (
@@ -102,7 +104,9 @@ let buildOperations = (
102
104
  ~stateTopicName=entry.stateTopicName,
103
105
  ~indexes=ProjectionEntryPoint_Ops.indexesOf(spec.config),
104
106
  ~subIdField=ProjectionEntryPoint_Ops.subIdFieldOf(spec.subIdConfig),
105
- )->withInjectedId
107
+ )
108
+ ->ProjectionEntryPoint_Ops.withUnionMemberTypes(~stateSchema=spec.stateSchema)
109
+ ->withInjectedId
106
110
 
107
111
  let makeRegisteredHandler = (
108
112
  entry: handlerEntry,
@@ -60,7 +60,7 @@ function withInjectedId(base) {
60
60
  }
61
61
 
62
62
  function buildOperations(entry, spec) {
63
- return withInjectedId(ProjectionEntryPoint_Ops$ReventlessAws.makeQueryDbOps(entry.queryDbTableName, entry.pgConnection, entry.stateTopicName, ProjectionEntryPoint_Ops$ReventlessAws.indexesOf(spec.config), ProjectionEntryPoint_Ops$ReventlessAws.subIdFieldOf(spec.subIdConfig)));
63
+ return withInjectedId(ProjectionEntryPoint_Ops$ReventlessAws.withUnionMemberTypes(ProjectionEntryPoint_Ops$ReventlessAws.makeQueryDbOps(entry.queryDbTableName, entry.pgConnection, entry.stateTopicName, ProjectionEntryPoint_Ops$ReventlessAws.indexesOf(spec.config), ProjectionEntryPoint_Ops$ReventlessAws.subIdFieldOf(spec.subIdConfig)), spec.stateSchema));
64
64
  }
65
65
 
66
66
  function makeRegisteredHandler(entry, handleJsonEvents) {
@@ -42,6 +42,7 @@ async function buildAllHandlers() {
42
42
  project: projectionModule.project,
43
43
  config: specModule.config,
44
44
  subIdConfig: specModule.subIdConfig,
45
+ stateSchema: specModule.stateSchema,
45
46
  })
46
47
  );
47
48
  }));
@@ -176,6 +176,8 @@ type sliceModules<'event> = {
176
176
  >,
177
177
  config: option<ProjectionEntryPoint_Ops.specConfig>,
178
178
  subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
179
+ // Same union stamp the read-model path needs; a slice's state can hold one too.
180
+ stateSchema: option<S.t<unknown>>,
179
181
  }
180
182
 
181
183
  let makeRegisteredHandler = (
@@ -188,7 +190,7 @@ let makeRegisteredHandler = (
188
190
  ~stateTopicName=entry.stateTopicName,
189
191
  ~indexes=ProjectionEntryPoint_Ops.indexesOf(modules.config),
190
192
  ~subIdField=ProjectionEntryPoint_Ops.subIdFieldOf(modules.subIdConfig),
191
- )
193
+ )->ProjectionEntryPoint_Ops.withUnionMemberTypes(~stateSchema=modules.stateSchema)
192
194
  {
193
195
  handler: StreamRoutedEntryPoint_Ops.toStreamHandler(
194
196
  makeJsonEventsHandler(
@@ -84,7 +84,7 @@ function makeJsonEventsHandler(sliceName, eventSchema, project, queryDbOps, subI
84
84
  }
85
85
 
86
86
  function makeRegisteredHandler(entry, modules) {
87
- let queryDbOps = ProjectionEntryPoint_Ops$ReventlessAws.makeQueryDbOps(entry.queryDbTableName, entry.pgConnection, entry.stateTopicName, ProjectionEntryPoint_Ops$ReventlessAws.indexesOf(modules.config), ProjectionEntryPoint_Ops$ReventlessAws.subIdFieldOf(modules.subIdConfig));
87
+ let queryDbOps = ProjectionEntryPoint_Ops$ReventlessAws.withUnionMemberTypes(ProjectionEntryPoint_Ops$ReventlessAws.makeQueryDbOps(entry.queryDbTableName, entry.pgConnection, entry.stateTopicName, ProjectionEntryPoint_Ops$ReventlessAws.indexesOf(modules.config), ProjectionEntryPoint_Ops$ReventlessAws.subIdFieldOf(modules.subIdConfig)), modules.stateSchema);
88
88
  return {
89
89
  handler: StreamRoutedEntryPoint_Ops$ReventlessAws.toStreamHandler(makeJsonEventsHandler(modules.name, modules.consumedEventSchema, modules.project, queryDbOps, modules.subIdConfig)),
90
90
  comp: `StateViewSlice(` + modules.name + `)`
@@ -0,0 +1,88 @@
1
+ // The deployed projection Lambdas assemble JSON-level QueryDb operations rather
2
+ // than going through `QueryDb_Operations.Make`, so the union `__typename` stamp
3
+ // that functor applies never ran on AWS. Every union member resolved to null and
4
+ // took its non-nullable parent with it — rows vanished from a view instead of
5
+ // erroring, on every read door, with nothing reporting it.
6
+ //
7
+ // The typed path was covered from the start; this side was not, which is the
8
+ // whole reason it shipped.
9
+
10
+ open JestGlobals
11
+
12
+ @schema
13
+ type geolocation =
14
+ | Pending({requestedFor: string})
15
+ | Located({lat: float, lng: float})
16
+
17
+ let geolocationSchema = Reventless.TaggedUnion.named(~name="Geolocation", geolocationSchema)
18
+
19
+ @schema
20
+ type rowState = {customerId: string, geolocation: geolocation}
21
+
22
+ // A recording stand-in for the storage the runtime would wrap.
23
+ let capture = (): (ReventlessCore.QueryDb_Adapter.operations, array<JSON.t>) => {
24
+ let saved: array<JSON.t> = []
25
+ let base: ReventlessCore.QueryDb_Adapter.operations = {
26
+ load: _ => Promise.resolve(Ok([])),
27
+ loadStream: _ => Stream.empty,
28
+ save: (_id, state, _saveMode, _ttl) => {
29
+ saved->Array.push(state)
30
+ Promise.resolve(Ok())
31
+ },
32
+ saveBatch: items => {
33
+ items->Array.forEach(((_, state, _)) => saved->Array.push(state))
34
+ Promise.resolve(Ok())
35
+ },
36
+ count: (_, _, _) => Promise.resolve(Ok(0)),
37
+ delete: (_, _) => Promise.resolve(Ok()),
38
+ deleteBatch: _ => Promise.resolve(Ok()),
39
+ }
40
+ (base, saved)
41
+ }
42
+
43
+ let memberOf = (json: JSON.t) =>
44
+ json
45
+ ->JSON.Decode.object
46
+ ->Option.flatMap(o => o->Dict.get("geolocation"))
47
+ ->Option.flatMap(JSON.Decode.object)
48
+ ->Option.flatMap(o => o->Dict.get("__typename"))
49
+ ->Option.flatMap(JSON.Decode.string)
50
+
51
+ let row = (): JSON.t =>
52
+ ({customerId: "c1", geolocation: Located({lat: 1.0, lng: 2.0})}: rowState)
53
+ ->ReventlessCore.Message.encode(rowStateSchema)
54
+
55
+ describe("ProjectionEntryPoint_Ops.withUnionMemberTypes", () => {
56
+ testPromise("stamps the member type on save", async () => {
57
+ let (base, saved) = capture()
58
+ let ops =
59
+ base->ProjectionEntryPoint_Ops.withUnionMemberTypes(
60
+ ~stateSchema=Some(rowStateSchema->S.castToUnknown),
61
+ )
62
+ let _ = await ops.save("c1", row(), ReventlessCore.QueryDb.Any, None)
63
+ expect(saved->Array.get(0)->Option.flatMap(memberOf))->toEqual(Some("GeolocationLocated"))
64
+ })
65
+
66
+ // saveBatch is the path a multi-event projection takes, and it was missing the
67
+ // stamp for the same reason save was.
68
+ testPromise("stamps every row in a batch", async () => {
69
+ let (base, saved) = capture()
70
+ let ops =
71
+ base->ProjectionEntryPoint_Ops.withUnionMemberTypes(
72
+ ~stateSchema=Some(rowStateSchema->S.castToUnknown),
73
+ )
74
+ let _ = await ops.saveBatch([("c1", row(), None), ("c2", row(), None)])
75
+ expect(saved->Array.map(memberOf))->toEqual([
76
+ Some("GeolocationLocated"),
77
+ Some("GeolocationLocated"),
78
+ ])
79
+ })
80
+
81
+ // A spec module without a stateSchema leaves rows untouched rather than throwing.
82
+ testPromise("passes rows through when the spec has no schema", async () => {
83
+ let (base, saved) = capture()
84
+ let ops = base->ProjectionEntryPoint_Ops.withUnionMemberTypes(~stateSchema=None)
85
+ let _ = await ops.save("c1", row(), ReventlessCore.QueryDb.Any, None)
86
+ expect(saved->Array.get(0)->Option.flatMap(memberOf))->toEqual(None)
87
+ })
88
+ })
@@ -0,0 +1,137 @@
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_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Stream from "effect/Stream";
7
+ import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
8
+ import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
9
+ import * as ProjectionEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs";
10
+
11
+ let geolocationSchema = Sury.union([
12
+ Sury.$schema(s => ({
13
+ TAG: "Pending",
14
+ requestedFor: s.m(Sury.string)
15
+ })),
16
+ Sury.$schema(s => ({
17
+ TAG: "Located",
18
+ lat: s.m(Sury.float),
19
+ lng: s.m(Sury.float)
20
+ }))
21
+ ]);
22
+
23
+ let geolocationSchema$1 = TaggedUnion$Reventless.named("Geolocation", geolocationSchema);
24
+
25
+ let rowStateSchema = Sury.$schema(s => ({
26
+ customerId: s.m(Sury.string),
27
+ geolocation: s.m(geolocationSchema$1)
28
+ }));
29
+
30
+ function capture() {
31
+ let saved = [];
32
+ let base_load = param => Promise.resolve({
33
+ TAG: "Ok",
34
+ _0: []
35
+ });
36
+ let base_loadStream = param => Stream.empty;
37
+ let base_save = (_id, state, _saveMode, _ttl) => {
38
+ saved.push(state);
39
+ return Promise.resolve({
40
+ TAG: "Ok",
41
+ _0: undefined
42
+ });
43
+ };
44
+ let base_saveBatch = items => {
45
+ items.forEach(param => {
46
+ saved.push(param[1]);
47
+ });
48
+ return Promise.resolve({
49
+ TAG: "Ok",
50
+ _0: undefined
51
+ });
52
+ };
53
+ let base_count = (param, param$1, param$2) => Promise.resolve({
54
+ TAG: "Ok",
55
+ _0: 0
56
+ });
57
+ let base_delete = (param, param$1) => Promise.resolve({
58
+ TAG: "Ok",
59
+ _0: undefined
60
+ });
61
+ let base_deleteBatch = param => Promise.resolve({
62
+ TAG: "Ok",
63
+ _0: undefined
64
+ });
65
+ let base = {
66
+ load: base_load,
67
+ loadStream: base_loadStream,
68
+ save: base_save,
69
+ saveBatch: base_saveBatch,
70
+ count: base_count,
71
+ delete: base_delete,
72
+ deleteBatch: base_deleteBatch
73
+ };
74
+ return [
75
+ base,
76
+ saved
77
+ ];
78
+ }
79
+
80
+ function memberOf(json) {
81
+ return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => o["geolocation"]), Stdlib_JSON.Decode.object), o => o["__typename"]), Stdlib_JSON.Decode.string);
82
+ }
83
+
84
+ function row() {
85
+ return Message$ReventlessCore.encode({
86
+ customerId: "c1",
87
+ geolocation: {
88
+ TAG: "Located",
89
+ lat: 1.0,
90
+ lng: 2.0
91
+ }
92
+ }, rowStateSchema);
93
+ }
94
+
95
+ globalThis.describe("ProjectionEntryPoint_Ops.withUnionMemberTypes", () => {
96
+ globalThis.test("stamps the member type on save", async () => {
97
+ let match = capture();
98
+ let ops = ProjectionEntryPoint_Ops$ReventlessAws.withUnionMemberTypes(match[0], rowStateSchema);
99
+ await ops.save("c1", row(), "Any", undefined);
100
+ globalThis.expect(Stdlib_Option.flatMap(match[1][0], memberOf)).toEqual("GeolocationLocated");
101
+ });
102
+ globalThis.test("stamps every row in a batch", async () => {
103
+ let match = capture();
104
+ let ops = ProjectionEntryPoint_Ops$ReventlessAws.withUnionMemberTypes(match[0], rowStateSchema);
105
+ await ops.saveBatch([
106
+ [
107
+ "c1",
108
+ row(),
109
+ undefined
110
+ ],
111
+ [
112
+ "c2",
113
+ row(),
114
+ undefined
115
+ ]
116
+ ]);
117
+ globalThis.expect(match[1].map(memberOf)).toEqual([
118
+ "GeolocationLocated",
119
+ "GeolocationLocated"
120
+ ]);
121
+ });
122
+ globalThis.test("passes rows through when the spec has no schema", async () => {
123
+ let match = capture();
124
+ let ops = ProjectionEntryPoint_Ops$ReventlessAws.withUnionMemberTypes(match[0], undefined);
125
+ await ops.save("c1", row(), "Any", undefined);
126
+ globalThis.expect(Stdlib_Option.flatMap(match[1][0], memberOf)).toEqual(undefined);
127
+ });
128
+ });
129
+
130
+ export {
131
+ geolocationSchema$1 as geolocationSchema,
132
+ rowStateSchema,
133
+ capture,
134
+ memberOf,
135
+ row,
136
+ }
137
+ /* geolocationSchema Not a pure module */
@@ -85,6 +85,7 @@ let runOneEvent: (string, JSON.t) => promise<unit> = %raw(`
85
85
  project: projectionModule.project,
86
86
  config: specModule.config,
87
87
  subIdConfig: specModule.subIdConfig,
88
+ stateSchema: specModule.stateSchema,
88
89
  },
89
90
  );
90
91
  const event = {
@@ -146,6 +147,21 @@ describe("StateViewSliceEntryPoint integration", () => {
146
147
  ->Array.toSorted(String.compare)
147
148
  expect(productIds)->toEqual(["prod-a", "prod-b"])
148
149
 
150
+ // The runtime path's union stamp, against real DynamoDB. The deployed
151
+ // Lambdas assemble JSON-level ops and skipped it entirely, so a member
152
+ // resolved to null and took its non-nullable parent with it.
153
+ let members =
154
+ rows
155
+ ->Array.filterMap(row =>
156
+ row
157
+ ->JSON.Decode.object
158
+ ->Option.flatMap(o => o->Dict.get("fulfilment"))
159
+ ->Option.flatMap(JSON.Decode.object)
160
+ ->Option.flatMap(o => o->Dict.get("__typename"))
161
+ ->Option.flatMap(JSON.Decode.string)
162
+ )
163
+ expect(members)->toEqual(["FulfilmentShipped", "FulfilmentShipped"])
164
+
149
165
  await H.deleteTable(table)
150
166
  },
151
167
  )
@@ -94,6 +94,7 @@ let runOneEvent = (async (tableName, eventJson) => {
94
94
  project: projectionModule.project,
95
95
  config: specModule.config,
96
96
  subIdConfig: specModule.subIdConfig,
97
+ stateSchema: specModule.stateSchema,
97
98
  },
98
99
  );
99
100
  const event = {
@@ -138,6 +139,11 @@ globalThis.describe("StateViewSliceEntryPoint integration", () => {
138
139
  "prod-a",
139
140
  "prod-b"
140
141
  ]);
142
+ let members = Stdlib_Array.filterMap(rows$1, row => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(row), o => o["fulfilment"]), Stdlib_JSON.Decode.object), o => o["__typename"]), Stdlib_JSON.Decode.string));
143
+ globalThis.expect(members).toEqual([
144
+ "FulfilmentShipped",
145
+ "FulfilmentShipped"
146
+ ]);
141
147
  return await DcbIntegrationHarness$ReventlessAws.deleteTable(table);
142
148
  });
143
149
  });
@@ -16,11 +16,21 @@
16
16
  @schema
17
17
  type consumedEvent = ItemAddedToCart({cartId: string, productId: string, qty: int})
18
18
 
19
+ // A union on the state, so the integration test covers the runtime path's
20
+ // `__typename` stamp — the one the deployed Lambdas skipped entirely.
21
+ @schema
22
+ type fulfilment =
23
+ | Pending({requestedFor: string})
24
+ | Shipped({carrier: string})
25
+
26
+ let fulfilmentSchema = Reventless.TaggedUnion.named(~name="Fulfilment", fulfilmentSchema)
27
+
19
28
  @schema
20
29
  type state = {
21
30
  cartId: string,
22
31
  productId: string,
23
32
  qty: int,
33
+ fulfilment: fulfilment,
24
34
  }
25
35
 
26
36
  // `Some({subIdField, getSubId})` — the compiled per-slice spec that a real
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Sury from "sury";
4
+ import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
4
5
 
5
6
  let consumedEventSchema = Sury.$schema(s => ({
6
7
  TAG: "ItemAddedToCart",
@@ -9,10 +10,24 @@ let consumedEventSchema = Sury.$schema(s => ({
9
10
  qty: s.m(Sury.int)
10
11
  }));
11
12
 
13
+ let fulfilmentSchema = Sury.union([
14
+ Sury.$schema(s => ({
15
+ TAG: "Pending",
16
+ requestedFor: s.m(Sury.string)
17
+ })),
18
+ Sury.$schema(s => ({
19
+ TAG: "Shipped",
20
+ carrier: s.m(Sury.string)
21
+ }))
22
+ ]);
23
+
24
+ let fulfilmentSchema$1 = TaggedUnion$Reventless.named("Fulfilment", fulfilmentSchema);
25
+
12
26
  let stateSchema = Sury.$schema(s => ({
13
27
  cartId: s.m(Sury.string),
14
28
  productId: s.m(Sury.string),
15
- qty: s.m(Sury.int)
29
+ qty: s.m(Sury.int),
30
+ fulfilment: s.m(fulfilmentSchema$1)
16
31
  }));
17
32
 
18
33
  let subIdConfig = {
@@ -22,6 +37,7 @@ let subIdConfig = {
22
37
 
23
38
  export {
24
39
  consumedEventSchema,
40
+ fulfilmentSchema$1 as fulfilmentSchema,
25
41
  stateSchema,
26
42
  subIdConfig,
27
43
  }
@@ -17,7 +17,9 @@ let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
17
17
  cartId,
18
18
  states => {
19
19
  let others = states->Array.filter(s => s.productId != productId)
20
- others->Array.concat([{cartId, productId, qty}])
20
+ others->Array.concat([
21
+ {cartId, productId, qty, fulfilment: Shipped({carrier: "dhl"})},
22
+ ])
21
23
  },
22
24
  ),
23
25
  ]
@@ -14,7 +14,11 @@ function project(param) {
14
14
  return others.concat([{
15
15
  cartId: cartId,
16
16
  productId: productId,
17
- qty: qty
17
+ qty: qty,
18
+ fulfilment: {
19
+ TAG: "Shipped",
20
+ carrier: "dhl"
21
+ }
18
22
  }]);
19
23
  }
20
24
  }];