@reventlessdev/reventless-local 3.0.0-alpha.230 → 3.0.0-alpha.232

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,22 @@
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.232 (2026-08-20)
7
+
8
+ **Note:** Version bump only for package @reventlessdev/reventless-local
9
+
10
+
11
+
12
+
13
+
14
+ # 3.0.0-alpha.231 (2026-08-20)
15
+
16
+ **Note:** Version bump only for package @reventlessdev/reventless-local
17
+
18
+
19
+
20
+
21
+
6
22
  # 3.0.0-alpha.230 (2026-08-20)
7
23
 
8
24
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.230",
3
+ "version": "3.0.0-alpha.232",
4
4
  "description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -34,19 +34,19 @@
34
34
  "graphql-yoga": "^5.21.0",
35
35
  "sury": "11.0.0-rc.1",
36
36
  "ws": "^8.18.0",
37
- "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
38
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
39
37
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
38
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
39
+ "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
40
+ "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
40
41
  "@reventlessdev/rescript-node": "2.0.0-alpha.8",
41
42
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
42
- "@reventlessdev/reventless-core": "3.0.0-alpha.242",
43
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.90",
44
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.189",
45
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.106",
46
- "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
43
+ "@reventlessdev/reventless-core": "3.0.0-alpha.244",
44
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.92",
45
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.108",
46
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.191",
47
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.148",
47
48
  "@reventlessdev/reventless-seed": "1.0.0-alpha.16",
48
- "@reventlessdev/reventless-infra": "3.0.0-alpha.147",
49
- "@reventlessdev/reventless-spec": "3.0.0-alpha.119"
49
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.120"
50
50
  },
51
51
  "devDependencies": {
52
52
  "rescript": "12.3.0",
@@ -12,11 +12,22 @@ let _ = TestRunner.setup()
12
12
  // Test state spec — five-row fixture exercising filter / sort / paginate
13
13
  // ─────────────────────────────────────────────────────────────
14
14
 
15
+ // A union field on an *indexed* view: the by-index door is one of two in the
16
+ // mechanism plan's table that no fixture carried a union through. Optional so the
17
+ // existing five-row literals below are untouched.
18
+ @schema
19
+ type geolocation =
20
+ | Pending({requestedFor: string})
21
+ | Located({lat: float, lng: float})
22
+
23
+ let geolocationSchema = Reventless.TaggedUnion.named(~name="Geolocation", geolocationSchema)
24
+
15
25
  @schema
16
26
  type rowState = {
17
27
  productId: @s.matches(Reventless.DcbTag.string) string,
18
28
  status: string,
19
29
  name: string,
30
+ geolocation?: geolocation,
20
31
  }
21
32
 
22
33
  let rowStateSchemaWithAnnotations =
@@ -85,6 +96,13 @@ let edgeNodeField = (edge: JSON.t, field: string): string =>
85
96
  ->Option.flatMap(n => n->getString(field))
86
97
  ->Option.getOr("")
87
98
 
99
+ let edges2Find = (edges: array<JSON.t>, pid: string): JSON.t =>
100
+ edges->Array.find(e => e->getField("node")->Option.flatMap(n => n->getString("productId")) === Some(pid))
101
+ ->Option.getOr(JSON.Encode.null)
102
+
103
+ let edgeNodeJson = (edge: JSON.t, field: string): option<JSON.t> =>
104
+ edge->getField("node")->Option.flatMap(n => n->getField(field))
105
+
88
106
  let pageInfo = (response: JSON.t): JSON.t =>
89
107
  response->getField("pageInfo")->Option.getOr(JSON.Encode.null)
90
108
 
@@ -175,7 +193,13 @@ let buildFixture = async (~name: string, ~indexes: array<Reventless.ReadModel.in
175
193
  ]
176
194
  for i in 0 to rows->Array.length - 1 {
177
195
  let (pid, status, n) = rows->Array.getUnsafe(i)
178
- let _ = await ops.save(pid, ({productId: pid, status, name: n}: rowState), Init, None)
196
+ // p-1 carries the union; the rest leave it absent, which also proves an
197
+ // absent optional union is not stamped into something.
198
+ let state: rowState =
199
+ pid === "p-1"
200
+ ? {productId: pid, status, name: n, geolocation: Located({lat: 48.2082, lng: 16.3738})}
201
+ : {productId: pid, status, name: n}
202
+ let _ = await ops.save(pid, state, Init, None)
179
203
  }
180
204
 
181
205
  (resolver, listFieldName)
@@ -497,6 +521,36 @@ describe("QueryDb by-index resolver", () => {
497
521
  expect(pageInfoBool(page2, "hasNextPage"))->toBe(false)
498
522
  })
499
523
 
524
+ // The gap this fixture exists to close: a union field reaching a caller through
525
+ // the by-index door, carrying the `__typename` the write-time stamp put on it.
526
+ // A member without one resolves to null and takes its non-nullable parent with
527
+ // it, so the whole row would vanish rather than error.
528
+ testPromise("carries a union field's member type through", async () => {
529
+ let resolver = await indexResolver(~name="IdxU")
530
+ let response = await resolver(JSON.Encode.null, argsOf([("status", strJson("active"))]), emptyCtx)
531
+ let edges = getEdges(response)
532
+ let seeded =
533
+ edges->Array.find(e => edgeNodeField(e, "productId") === "p-1")->Option.getOr(JSON.Encode.null)
534
+ let geo = edgeNodeJson(seeded, "geolocation")->Option.flatMap(JSON.Decode.object)
535
+ expect(geo->Option.flatMap(o => o->Dict.get("__typename"))->Option.flatMap(JSON.Decode.string))
536
+ ->toEqual(Some("GeolocationLocated"))
537
+ expect(
538
+ geo
539
+ ->Option.flatMap(o => o->Dict.get("lat"))
540
+ ->Option.flatMap(JSON.Decode.float),
541
+ )->toEqual(Some(48.2082))
542
+ })
543
+
544
+ // An absent optional union is left absent rather than stamped into an arm.
545
+ testPromise("leaves an absent union absent", async () => {
546
+ let resolver = await indexResolver(~name="IdxV")
547
+ let response = await resolver(JSON.Encode.null, argsOf([("status", strJson("active"))]), emptyCtx)
548
+ let other = edges2Find(getEdges(response), "p-2")
549
+ // Assert the row is real first, so an absent field cannot pass as an absent row.
550
+ expect(edgeNodeField(other, "name"))->toBe("Alpha")
551
+ expect(edgeNodeJson(other, "geolocation")->Option.isNone)->toBe(true)
552
+ })
553
+
500
554
  // Refused rather than ignored, and refused here because AppSync cannot do it:
501
555
  // a door that paged backward on one backend and quietly returned the forward
502
556
  // page on the other is the divergence this whole field was rebuilt to remove.
@@ -11,6 +11,7 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
11
11
  import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
12
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
13
  import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
14
+ import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
14
15
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
15
16
  import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
16
17
  import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
@@ -26,10 +27,25 @@ import * as QueryDbResolvers_GraphQL$ReventlessLocal from "../../src/adapter/Que
26
27
 
27
28
  TestRunner$ReventlessLocal.setup();
28
29
 
30
+ let geolocationSchema = Sury.union([
31
+ Sury.$schema(s => ({
32
+ TAG: "Pending",
33
+ requestedFor: s.m(Sury.string)
34
+ })),
35
+ Sury.$schema(s => ({
36
+ TAG: "Located",
37
+ lat: s.m(Sury.float),
38
+ lng: s.m(Sury.float)
39
+ }))
40
+ ]);
41
+
42
+ let geolocationSchema$1 = TaggedUnion$Reventless.named("Geolocation", geolocationSchema);
43
+
29
44
  let rowStateSchema = Sury.$schema(s => ({
30
45
  productId: s.m(DcbTag$Reventless.string),
31
46
  status: s.m(Sury.string),
32
- name: s.m(Sury.string)
47
+ name: s.m(Sury.string),
48
+ geolocation: s.m(Sury.$option(geolocationSchema$1))
33
49
  }));
34
50
 
35
51
  let rowStateSchemaWithAnnotations = Sury.$Metadata_set(rowStateSchema, StateAnnotations$Reventless.stateAnnotationsId, {
@@ -102,6 +118,14 @@ function edgeNodeField(edge, field) {
102
118
  return Stdlib_Option.getOr(Stdlib_Option.flatMap(getField(edge, "node"), n => getString(n, field)), "");
103
119
  }
104
120
 
121
+ function edges2Find(edges, pid) {
122
+ return Stdlib_Option.getOr(edges.find(e => Stdlib_Option.flatMap(getField(e, "node"), n => getString(n, "productId")) === pid), null);
123
+ }
124
+
125
+ function edgeNodeJson(edge, field) {
126
+ return Stdlib_Option.flatMap(getField(edge, "node"), n => getField(n, field));
127
+ }
128
+
105
129
  function pageInfo(response) {
106
130
  return Stdlib_Option.getOr(getField(response, "pageInfo"), null);
107
131
  }
@@ -186,12 +210,24 @@ async function buildFixture(name, indexesOpt) {
186
210
  ];
187
211
  for (let i = 0, i_finish = rows.length; i < i_finish; ++i) {
188
212
  let match = rows[i];
213
+ let n = match[2];
214
+ let status = match[1];
189
215
  let pid = match[0];
190
- await ops.save(pid, {
191
- productId: pid,
192
- status: match[1],
193
- name: match[2]
194
- }, "Init", undefined);
216
+ let state = pid === "p-1" ? ({
217
+ productId: pid,
218
+ status: status,
219
+ name: n,
220
+ geolocation: {
221
+ TAG: "Located",
222
+ lat: 48.2082,
223
+ lng: 16.3738
224
+ }
225
+ }) : ({
226
+ productId: pid,
227
+ status: status,
228
+ name: n
229
+ });
230
+ await ops.save(pid, state, "Init", undefined);
195
231
  }
196
232
  return [
197
233
  resolver,
@@ -557,6 +593,28 @@ globalThis.describe("QueryDb by-index resolver", () => {
557
593
  globalThis.expect(edgeNodeField(edges2[0], "productId")).toBe("p-4");
558
594
  globalThis.expect(pageInfoBool(page2, "hasNextPage")).toBe(false);
559
595
  });
596
+ globalThis.test("carries a union field's member type through", async () => {
597
+ let resolver = await indexResolver("IdxU");
598
+ let response = await resolver(null, Object.fromEntries([[
599
+ "status",
600
+ "active"
601
+ ]]), emptyCtx);
602
+ let edges = getEdges(response);
603
+ let seeded = Stdlib_Option.getOr(edges.find(e => edgeNodeField(e, "productId") === "p-1"), null);
604
+ let geo = Stdlib_Option.flatMap(edgeNodeJson(seeded, "geolocation"), Stdlib_JSON.Decode.object);
605
+ globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(geo, o => o["__typename"]), Stdlib_JSON.Decode.string)).toEqual("GeolocationLocated");
606
+ globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(geo, o => o["lat"]), Stdlib_JSON.Decode.float)).toEqual(48.2082);
607
+ });
608
+ globalThis.test("leaves an absent union absent", async () => {
609
+ let resolver = await indexResolver("IdxV");
610
+ let response = await resolver(null, Object.fromEntries([[
611
+ "status",
612
+ "active"
613
+ ]]), emptyCtx);
614
+ let other = edges2Find(getEdges(response), "p-2");
615
+ globalThis.expect(edgeNodeField(other, "name")).toBe("Alpha");
616
+ globalThis.expect(Stdlib_Option.isNone(edgeNodeJson(other, "geolocation"))).toBe(true);
617
+ });
560
618
  globalThis.test("refuses backward paging instead of answering forward", async () => {
561
619
  let resolver = await indexResolver("IdxC");
562
620
  let outcome;
@@ -591,6 +649,7 @@ globalThis.describe("QueryDb by-index resolver", () => {
591
649
  });
592
650
 
593
651
  export {
652
+ geolocationSchema$1 as geolocationSchema,
594
653
  rowStateSchema,
595
654
  rowStateSchemaWithAnnotations,
596
655
  emptyCtx,
@@ -602,6 +661,8 @@ export {
602
661
  getEdges,
603
662
  edgeCursor,
604
663
  edgeNodeField,
664
+ edges2Find,
665
+ edgeNodeJson,
605
666
  pageInfo,
606
667
  pageInfoBool,
607
668
  pageInfoString,