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

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,21 @@
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.230 (2026-08-20)
7
+
8
+ ### Features
9
+
10
+ * **api:** emit a tagged-union state field as a GraphQL union ([3a380c0](https://github.com/ReventlessDev/reventless-core/commit/3a380c0ab055b87048d90a852ec1664f6aab6b00))
11
+
12
+
13
+ # 3.0.0-alpha.229 (2026-08-19)
14
+
15
+ **Note:** Version bump only for package @reventlessdev/reventless-local
16
+
17
+
18
+
19
+
20
+
6
21
  # 3.0.0-alpha.228 (2026-08-18)
7
22
 
8
23
  * feat(sury)!: migrate to sury 11.0.0-rc.1 ([2cf8969](https://github.com/ReventlessDev/reventless-core/commit/2cf8969a222ce1b775563668a4126cb20611966c))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.228",
3
+ "version": "3.0.0-alpha.230",
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": {
@@ -35,23 +35,23 @@
35
35
  "sury": "11.0.0-rc.1",
36
36
  "ws": "^8.18.0",
37
37
  "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
38
- "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
39
38
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
40
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
41
- "@reventlessdev/rescript-node": "2.0.0-alpha.8",
42
- "@reventlessdev/reventless-core": "3.0.0-alpha.240",
43
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.88",
44
39
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
45
- "@reventlessdev/reventless-infra": "3.0.0-alpha.146",
46
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.187",
40
+ "@reventlessdev/rescript-node": "2.0.0-alpha.8",
41
+ "@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",
47
47
  "@reventlessdev/reventless-seed": "1.0.0-alpha.16",
48
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.104",
49
- "@reventlessdev/reventless-spec": "3.0.0-alpha.118"
48
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.147",
49
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.119"
50
50
  },
51
51
  "devDependencies": {
52
52
  "rescript": "12.3.0",
53
53
  "sury-ppx": "11.0.0-rc.1",
54
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.71"
54
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.72"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "rescript": "12.3.0"
@@ -0,0 +1,107 @@
1
+ // A tagged-union state field, saved and read back under both local backends.
2
+ //
3
+ // The stamp that puts `__typename` beside `TAG` runs in QueryDb_Operations, so
4
+ // what a backend has to do is carry a nested object with one extra key and hand
5
+ // it back. Cheap to assume and cheap to check — and the failure it guards
6
+ // against is not a decode error but a union member that resolves to null, which
7
+ // takes its non-nullable parent with it and reads as data loss.
8
+ //
9
+ // The spec is declared here rather than shared with reventless-core's fixture:
10
+ // a package's `type: dev` sources are not visible to another package, and the
11
+ // union is four lines.
12
+
13
+ open JestGlobals
14
+
15
+ let _ = TestRunner.setup()
16
+ let opts: Pulumi.CustomResourceOptions.t = {}
17
+
18
+ @schema
19
+ type geolocation =
20
+ | Pending({requestedFor: string})
21
+ | Located({lat: float, lng: float})
22
+ | Unresolvable({reason: string})
23
+
24
+ let geolocationSchema = Reventless.TaggedUnion.named(~name="Geolocation", geolocationSchema)
25
+
26
+ module CustomersSpec = {
27
+ module Id = Reventless.Id.StringPure
28
+ let name = "UnionRoundTripCustomers"
29
+ let moduleUrl: string = %raw(`import.meta.url`)
30
+
31
+ @schema
32
+ type state = {customerId: string, geolocation: geolocation}
33
+
34
+ let config = Reventless.ReadModel.config()
35
+ let subIdConfig = None
36
+ }
37
+
38
+ let runUnderMemory = async fn => {
39
+ BackendState.setMemory()
40
+ await fn()
41
+ }
42
+
43
+ let runUnderSqlite = async fn => {
44
+ let db = SqliteDriver.openDb(~path=":memory:")
45
+ BackendState.setSqlite(~db, ~path=":memory:")
46
+ await fn()
47
+ BackendState.setMemory()
48
+ db->SqliteDriver.close
49
+ }
50
+
51
+ describe("QueryDb round-trips a union field", () => {
52
+ let scenario = async () => {
53
+ module TestBus = LocalBus.Make()
54
+ module Storage = LocalQueryDbStorage.Make(TestBus)
55
+ let s = Storage.make(
56
+ ~name="union-round-trip",
57
+ ~indexes=[],
58
+ ~api=(),
59
+ ~apiRole=(),
60
+ ~owner=None,
61
+ ~opts,
62
+ )
63
+ let jsonOps = await s.operations->TestRunner.resolve
64
+ module Ops = ReventlessCore.QueryDb_Operations.Make(
65
+ CustomersSpec,
66
+ {
67
+ let jsonOps = jsonOps
68
+ },
69
+ )
70
+
71
+ let _ = await Ops.save(
72
+ "c-1",
73
+ {customerId: "c-1", geolocation: Located({lat: 48.2082, lng: 16.3738})},
74
+ ReventlessCore.QueryDb.Any,
75
+ None,
76
+ )
77
+
78
+ // The stored row, as any read door would hand it back.
79
+ let stored =
80
+ await jsonOps.loadStream("c-1")
81
+ ->Stream.runCollect
82
+ ->Effect.catchAll(_ => Effect.succeed([]))
83
+ ->Effect.runPromise
84
+ let typename =
85
+ stored
86
+ ->Array.get(0)
87
+ ->Option.flatMap(JSON.Decode.object)
88
+ ->Option.flatMap(o => o->Dict.get("geolocation"))
89
+ ->Option.flatMap(JSON.Decode.object)
90
+ ->Option.flatMap(o => o->Dict.get("__typename"))
91
+ ->Option.flatMap(JSON.Decode.string)
92
+ expect(typename)->toEqual(Some("GeolocationLocated"))
93
+
94
+ let items =
95
+ await Ops.loadStream("c-1"->CustomersSpec.Id.makeFromString)
96
+ ->Stream.runCollect
97
+ ->Effect.catchAll(_ => Effect.succeed([]))
98
+ ->Effect.runPromise
99
+ switch items {
100
+ | [{geolocation: Located({lat, lng})}] => expect((lat, lng))->toEqual((48.2082, 16.3738))
101
+ | _ => expect("round trip")->toBe("Located with its point intact")
102
+ }
103
+ }
104
+
105
+ testPromise("in-memory", () => runUnderMemory(scenario))
106
+ testPromise("sqlite", () => runUnderSqlite(scenario))
107
+ })
@@ -0,0 +1,142 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Sury from "sury";
4
+ import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
5
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.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 Effect from "effect/Effect";
9
+ import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
10
+ import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
11
+ import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
12
+ import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
13
+ import * as BackendState$ReventlessLocal from "../../src/adapter/BackendState.res.mjs";
14
+ import * as SqliteDriver$ReventlessLocal from "../../src/adapter/SqliteDriver.res.mjs";
15
+ import * as QueryDb_Operations$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Operations.res.mjs";
16
+ import * as LocalQueryDbStorage$ReventlessLocal from "../../src/adapter/QueryDb/LocalQueryDbStorage.res.mjs";
17
+
18
+ TestRunner$ReventlessLocal.setup();
19
+
20
+ let opts = {};
21
+
22
+ let geolocationSchema = Sury.union([
23
+ Sury.$schema(s => ({
24
+ TAG: "Pending",
25
+ requestedFor: s.m(Sury.string)
26
+ })),
27
+ Sury.$schema(s => ({
28
+ TAG: "Located",
29
+ lat: s.m(Sury.float),
30
+ lng: s.m(Sury.float)
31
+ })),
32
+ Sury.$schema(s => ({
33
+ TAG: "Unresolvable",
34
+ reason: s.m(Sury.string)
35
+ }))
36
+ ]);
37
+
38
+ let geolocationSchema$1 = TaggedUnion$Reventless.named("Geolocation", geolocationSchema);
39
+
40
+ let name = "UnionRoundTripCustomers";
41
+
42
+ let moduleUrl = import.meta.url;
43
+
44
+ let stateSchema = Sury.$schema(s => ({
45
+ customerId: s.m(Sury.string),
46
+ geolocation: s.m(geolocationSchema$1)
47
+ }));
48
+
49
+ let config = ReadModel$Reventless.config(undefined, undefined, undefined);
50
+
51
+ let CustomersSpec = {
52
+ Id: undefined,
53
+ name: name,
54
+ moduleUrl: moduleUrl,
55
+ stateSchema: stateSchema,
56
+ config: config,
57
+ subIdConfig: undefined,
58
+ authorization: "AllowAuthenticated",
59
+ visibility: "Public"
60
+ };
61
+
62
+ async function runUnderMemory(fn) {
63
+ BackendState$ReventlessLocal.setMemory();
64
+ return await fn();
65
+ }
66
+
67
+ async function runUnderSqlite(fn) {
68
+ let db = SqliteDriver$ReventlessLocal.openDb(":memory:");
69
+ BackendState$ReventlessLocal.setSqlite(db, ":memory:");
70
+ await fn();
71
+ BackendState$ReventlessLocal.setMemory();
72
+ return SqliteDriver$ReventlessLocal.close(db);
73
+ }
74
+
75
+ globalThis.describe("QueryDb round-trips a union field", () => {
76
+ let scenario = async () => {
77
+ let TestBus = LocalBus$ReventlessLocal.Make({});
78
+ let Storage = LocalQueryDbStorage$ReventlessLocal.Make(TestBus);
79
+ let s = Storage.make("union-round-trip", [], undefined, undefined, undefined, undefined, undefined, opts);
80
+ let jsonOps = await TestRunner$ReventlessLocal.resolve(s.operations);
81
+ let Ops = QueryDb_Operations$ReventlessCore.Make({
82
+ Id: {
83
+ schema: Id$Reventless.StringPure.schema,
84
+ make: prim => prim,
85
+ makeFromString: prim => prim,
86
+ toString: prim => prim,
87
+ cmp: Id$Reventless.StringPure.cmp
88
+ },
89
+ name: name,
90
+ moduleUrl: moduleUrl,
91
+ stateSchema: stateSchema,
92
+ config: config,
93
+ subIdConfig: undefined,
94
+ authorization: "AllowAuthenticated",
95
+ visibility: "Public"
96
+ })({
97
+ jsonOps: jsonOps
98
+ });
99
+ await Ops.save("c-1", {
100
+ customerId: "c-1",
101
+ geolocation: {
102
+ TAG: "Located",
103
+ lat: 48.2082,
104
+ lng: 16.3738
105
+ }
106
+ }, "Any", undefined);
107
+ let stored = await Effect.runPromise(Effect.catchAll(Stream.runCollect(jsonOps.loadStream("c-1")), param => Effect.succeed([])));
108
+ let typename = Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(stored[0], Stdlib_JSON.Decode.object), o => o["geolocation"]), Stdlib_JSON.Decode.object), o => o["__typename"]), Stdlib_JSON.Decode.string);
109
+ globalThis.expect(typename).toEqual("GeolocationLocated");
110
+ let items = await Effect.runPromise(Effect.catchAll(Stream.runCollect(Ops.loadStream("c-1")), param => Effect.succeed([])));
111
+ if (items.length === 1) {
112
+ let match = items[0];
113
+ let match$1 = match.geolocation;
114
+ switch (match$1.TAG) {
115
+ case "Located" :
116
+ globalThis.expect([
117
+ match$1.lat,
118
+ match$1.lng
119
+ ]).toEqual([
120
+ 48.2082,
121
+ 16.3738
122
+ ]);
123
+ return;
124
+ case "Pending" :
125
+ case "Unresolvable" :
126
+ break;
127
+ }
128
+ }
129
+ globalThis.expect("round trip").toBe("Located with its point intact");
130
+ };
131
+ globalThis.test("in-memory", () => runUnderMemory(scenario));
132
+ globalThis.test("sqlite", () => runUnderSqlite(scenario));
133
+ });
134
+
135
+ export {
136
+ opts,
137
+ geolocationSchema$1 as geolocationSchema,
138
+ CustomersSpec,
139
+ runUnderMemory,
140
+ runUnderSqlite,
141
+ }
142
+ /* Not a pure module */