@reventlessdev/reventless-gwt 1.0.0-alpha.142 → 1.0.0-alpha.144

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
+ # 1.0.0-alpha.144 (2026-08-01)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **gwt:** compare produced events by their encoded wire form ([f773062](https://github.com/ReventlessDev/reventless-core/commit/f773062bc8ea18bf12e5611fcbad81aa4f1bd1b2))
11
+
12
+
13
+ # 1.0.0-alpha.143 (2026-07-31)
14
+
15
+ **Note:** Version bump only for package @reventlessdev/reventless-gwt
16
+
17
+
18
+
19
+
20
+
6
21
  # 1.0.0-alpha.142 (2026-07-30)
7
22
 
8
23
  **Note:** Version bump only for package @reventlessdev/reventless-gwt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-gwt",
3
- "version": "1.0.0-alpha.142",
3
+ "version": "1.0.0-alpha.144",
4
4
  "description": "Given-When-Then DSLs and test harness for Reventless slice testing",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -14,11 +14,12 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "sury": "11.0.0-alpha.4",
17
- "@reventlessdev/reventless-core": "3.0.0-alpha.195",
18
- "@reventlessdev/rescript-effect": "0.1.0-alpha.31",
19
- "@reventlessdev/rescript-jest": "1.0.0-alpha.9",
20
- "@reventlessdev/reventless-infra": "3.0.0-alpha.113",
21
- "@reventlessdev/reventless-spec": "3.0.0-alpha.88"
17
+ "@reventlessdev/rescript-node": "2.0.0-alpha.0",
18
+ "@reventlessdev/reventless-core": "3.0.0-alpha.197",
19
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.115",
20
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
21
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
22
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.90"
22
23
  },
23
24
  "devDependencies": {
24
25
  "rescript": "12.3.0",
package/rescript.json CHANGED
@@ -24,6 +24,7 @@
24
24
  ],
25
25
  "dependencies": [
26
26
  "sury",
27
+ "@reventlessdev/rescript-node",
27
28
  "@reventlessdev/rescript-jest",
28
29
  "@reventlessdev/rescript-effect",
29
30
  "@reventlessdev/reventless-infra",
@@ -153,10 +153,22 @@ module AssertionCore = (Spec: CoreSpec) => {
153
153
  )
154
154
  }
155
155
 
156
+ // Events are compared by their **encoded (wire) form**, not by ReScript
157
+ // structural equality. For an event-sourced system the serialized event *is*
158
+ // its identity: two events that reverse-convert to the same JSON are the same
159
+ // event. Raw `==` is stricter than that — it distinguishes an optional field
160
+ // that is present-but-`undefined` (what a decider's `deliveryWindow: ?x`
161
+ // passthrough emits when `x` is `None`) from one whose key is absent (what a
162
+ // test literal that omits the field produces), even though sury drops a `None`
163
+ // optional either way and both hit the log identically. Comparing on `encEvents`
164
+ // unifies that spurious difference, so an optional event field can be omitted in
165
+ // the expectation instead of spelled out as `?None`. It is a strictly weaker
166
+ // equality: equal values still encode equally, so nothing that passed can start
167
+ // failing — it only stops the wire-invisible key asymmetry from failing a test.
156
168
  let compareEvents = (events, expectedEvents) =>
157
169
  if errors.contents->Array.length > 0 {
158
170
  unexpectedError(events)
159
- } else if events == expectedEvents {
171
+ } else if events->encEvents == expectedEvents->encEvents {
160
172
  Outcome.pass
161
173
  } else {
162
174
  Outcome.fail(
@@ -211,7 +223,8 @@ module AssertionCore = (Spec: CoreSpec) => {
211
223
  }),
212
224
  )
213
225
  | Some(_) =>
214
- if events == expectedEvents {
226
+ // Encoded comparison, for the same reason as `compareEvents` above.
227
+ if events->encEvents == expectedEvents->encEvents {
215
228
  Outcome.pass
216
229
  } else {
217
230
  Outcome.fail(
@@ -66,7 +66,7 @@ function AssertionCore(Spec) {
66
66
  let compareEvents = (events, expectedEvents) => {
67
67
  if (errors.contents.length !== 0) {
68
68
  return unexpectedError(events);
69
- } else if (Primitive_object.equal(events, expectedEvents)) {
69
+ } else if (Primitive_object.equal(events.map(encEvent), expectedEvents.map(encEvent))) {
70
70
  return Outcome$ReventlessGwt.pass;
71
71
  } else {
72
72
  return Outcome$ReventlessGwt.fail({
@@ -120,7 +120,7 @@ function AssertionCore(Spec) {
120
120
  actual: Message$ReventlessCore.encode(actual$1, Spec.errorSchema),
121
121
  actualEvents: events.map(encEvent)
122
122
  });
123
- } else if (Primitive_object.equal(events, expectedEvents)) {
123
+ } else if (Primitive_object.equal(events.map(encEvent), expectedEvents.map(encEvent))) {
124
124
  return Outcome$ReventlessGwt.pass;
125
125
  } else {
126
126
  return Outcome$ReventlessGwt.fail({
@@ -180,7 +180,7 @@ function Make(Spec) {
180
180
  actual: Message$ReventlessCore.encode(actual$1, Spec.errorSchema),
181
181
  actualEvents: events.map(encEvent)
182
182
  });
183
- } else if (Primitive_object.equal(events, expectedEvents)) {
183
+ } else if (Primitive_object.equal(events.map(encEvent), expectedEvents.map(encEvent))) {
184
184
  return Outcome$ReventlessGwt.pass;
185
185
  } else {
186
186
  return Outcome$ReventlessGwt.fail({
@@ -281,7 +281,7 @@ function Make(Spec) {
281
281
  return o;
282
282
  } else if (errors.contents.length !== 0) {
283
283
  return unexpectedError(events);
284
- } else if (Primitive_object.equal(events, expectedEvents)) {
284
+ } else if (Primitive_object.equal(events.map(encEvent), expectedEvents.map(encEvent))) {
285
285
  return Outcome$ReventlessGwt.pass;
286
286
  } else {
287
287
  return Outcome$ReventlessGwt.fail({
@@ -413,7 +413,7 @@ function MakeFromAggregate(Spec) {
413
413
  let compareEvents = (events, expectedEvents) => {
414
414
  if (errors.contents.length !== 0) {
415
415
  return unexpectedError(events);
416
- } else if (Primitive_object.equal(events, expectedEvents)) {
416
+ } else if (Primitive_object.equal(events.map(encEvent), expectedEvents.map(encEvent))) {
417
417
  return Outcome$ReventlessGwt.pass;
418
418
  } else {
419
419
  return Outcome$ReventlessGwt.fail({
@@ -467,7 +467,7 @@ function MakeFromAggregate(Spec) {
467
467
  actual: Message$ReventlessCore.encode(actual$1, Spec.errorSchema),
468
468
  actualEvents: events.map(encEvent)
469
469
  });
470
- } else if (Primitive_object.equal(events, expectedEvents)) {
470
+ } else if (Primitive_object.equal(events.map(encEvent), expectedEvents.map(encEvent))) {
471
471
  return Outcome$ReventlessGwt.pass;
472
472
  } else {
473
473
  return Outcome$ReventlessGwt.fail({
package/src/LocalHost.res CHANGED
@@ -13,16 +13,6 @@
13
13
  // path via dynamic import, so a missing local platform is a caller concern, not a
14
14
  // compile-time coupling.
15
15
 
16
- @module("node:url") external pathToFileURL: string => {"href": string} = "pathToFileURL"
17
- @module("node:fs") external existsSync: string => bool = "existsSync"
18
- @module("node:fs") external readFileSync: (string, string) => string = "readFileSync"
19
- @module("node:path") external join: (string, string) => string = "join"
20
- @module("node:path") external dirname: string => string = "dirname"
21
-
22
- type nodeRequire
23
- @module("node:module") external createRequire: string => nodeRequire = "createRequire"
24
- @send external requireResolve: (nodeRequire, string) => string = "resolve"
25
-
26
16
  let dynamicImport: string => promise<'a> = %raw(`(u) => import(u)`)
27
17
 
28
18
  // Monotonic cache-buster so repeated loads in one process (a watch session
@@ -35,7 +25,7 @@ let dynamicImport: string => promise<'a> = %raw(`(u) => import(u)`)
35
25
  let counter = ref(0)
36
26
  let bustedUrl = (absolutePath: string): string => {
37
27
  counter := counter.contents + 1
38
- pathToFileURL(absolutePath)["href"] ++ "?t=" ++ Int.toString(counter.contents)
28
+ NodeUrl.pathToFileURL(absolutePath)["href"] ++ "?t=" ++ Int.toString(counter.contents)
39
29
  }
40
30
 
41
31
  type pluginRef = {name: string, modulePath: string, packageDir: string}
@@ -65,7 +55,7 @@ let strField = (json, key) =>
65
55
  json->JSON.Decode.object->Option.flatMap(d => d->Dict.get(key))->Option.flatMap(JSON.Decode.string)
66
56
 
67
57
  let readJson = path =>
68
- try Some(readFileSync(path, "utf8")->JSON.parseOrThrow) catch {
58
+ try Some(NodeFs.readFileSync(path)->JSON.parseOrThrow) catch {
69
59
  | _ => None
70
60
  }
71
61
 
@@ -73,11 +63,14 @@ let readJson = path =>
73
63
  // The precedence itself lives in `Reventless.PluginName.resolve`; this only
74
64
  // reads the two raw fields with the local node bindings.
75
65
  let derivePluginName = (~pluginSrcDir: string): string => {
76
- let pluginJson = join(pluginSrcDir, "plugin.json")
66
+ let pluginJson = NodePath.join([pluginSrcDir, "plugin.json"])
77
67
  let pluginJsonName =
78
- existsSync(pluginJson) ? readJson(pluginJson)->Option.flatMap(j => strField(j, "name")) : None
68
+ NodeFs.existsSync(pluginJson)
69
+ ? readJson(pluginJson)->Option.flatMap(j => strField(j, "name"))
70
+ : None
79
71
  let packageJsonName =
80
- readJson(join(dirname(pluginSrcDir), "package.json"))->Option.flatMap(j => strField(j, "name"))
72
+ readJson(NodePath.join([NodePath.dirname(pluginSrcDir), "package.json"]))
73
+ ->Option.flatMap(j => strField(j, "name"))
81
74
  Reventless.PluginName.resolve(~pluginJsonName, ~packageJsonName)
82
75
  }
83
76
 
@@ -85,9 +78,9 @@ let derivePluginName = (~pluginSrcDir: string): string => {
85
78
  // compiled composition root and pair each with its framework plugin name.
86
79
  let discover = (~packageDirs: array<string>): array<pluginRef> =>
87
80
  packageDirs->Array.filterMap(dir => {
88
- let srcDir = join(dir, "src")
89
- let modulePath = join(srcDir, "Plugin.res.mjs")
90
- existsSync(modulePath)
81
+ let srcDir = NodePath.join([dir, "src"])
82
+ let modulePath = NodePath.join([srcDir, "Plugin.res.mjs"])
83
+ NodeFs.existsSync(modulePath)
91
84
  ? Some({name: derivePluginName(~pluginSrcDir=srcDir), modulePath, packageDir: dir})
92
85
  : None
93
86
  })
@@ -99,7 +92,10 @@ let discover = (~packageDirs: array<string>): array<pluginRef> =>
99
92
  // there — callers then skip platform-dependent features (dead-code / graph).
100
93
  let localPlatformSpecifier = "@reventlessdev/reventless-local/src/Platform.res.mjs"
101
94
  let resolveLocalPlatform = (~fromPackageDir: string): option<string> =>
102
- try Some(createRequire(join(fromPackageDir, "package.json"))->requireResolve(localPlatformSpecifier)) catch {
95
+ try Some(
96
+ NodeModule.createRequire(NodePath.join([fromPackageDir, "package.json"]))
97
+ ->NodeModule.requireResolve(localPlatformSpecifier),
98
+ ) catch {
103
99
  | _ => None
104
100
  }
105
101
 
@@ -18,15 +18,11 @@
18
18
  @module("node:assert/strict") external deepEqual: ('a, 'a) => unit = "deepEqual"
19
19
  @module("node:assert/strict") external ok: (bool, ~message: string=?) => unit = "ok"
20
20
 
21
- @module("node:url") external fileURLToPath: string => string = "fileURLToPath"
22
- @module("node:path") external dirname: string => string = "dirname"
23
- @module("node:path") @variadic external join: array<string> => string = "join"
24
-
25
- let here = dirname(fileURLToPath(%raw(`import.meta.url`)))
26
- let repoRoot = join([here, "..", "..", ".."]) // reventless-gwt/test → repo root
27
- let catalogDir = join([repoRoot, "examples", "online-shop-aggregates", "catalog"])
28
- let orderingDir = join([repoRoot, "examples", "online-shop-aggregates", "ordering"])
29
- let platformPath = join([repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs"])
21
+ let here = NodePath.dirname(NodeUrl.fileURLToPath(%raw(`import.meta.url`)))
22
+ let repoRoot = NodePath.join([here, "..", "..", ".."]) // reventless-gwt/test repo root
23
+ let catalogDir = NodePath.join([repoRoot, "examples", "online-shop-aggregates", "catalog"])
24
+ let orderingDir = NodePath.join([repoRoot, "examples", "online-shop-aggregates", "ordering"])
25
+ let platformPath = NodePath.join([repoRoot, "reventless", "local", "src", "Platform.res.mjs"])
30
26
 
31
27
  let structureFor = (g: LocalHost.graph, name) =>
32
28
  g.structures->Array.find(((n, _)) => n == name)->Option.map(((_, s)) => s)
@@ -15,7 +15,7 @@ let catalogDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "
15
15
 
16
16
  let orderingDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "ordering");
17
17
 
18
- let platformPath = Nodepath.join(repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs");
18
+ let platformPath = Nodepath.join(repoRoot, "reventless", "local", "src", "Platform.res.mjs");
19
19
 
20
20
  function structureFor(g, name) {
21
21
  return Stdlib_Option.map(g.structures.find(param => param[0] === name), param => param[1]);
@@ -10,16 +10,6 @@
10
10
 
11
11
  open JestGlobals
12
12
 
13
- @module("node:os") external tmpdir: unit => string = "tmpdir"
14
- @module("node:path") external join: (string, string) => string = "join"
15
-
16
- type mkdirOpts = {recursive: bool}
17
- @module("node:fs/promises")
18
- external mkdir: (string, mkdirOpts) => promise<Nullable.t<string>> = "mkdir"
19
- @module("node:fs/promises") external writeFile: (string, string) => promise<unit> = "writeFile"
20
- type rmOpts = {recursive: bool, force: bool}
21
- @module("node:fs/promises") external rm: (string, rmOpts) => promise<unit> = "rm"
22
-
23
13
  describe("LocalHost.packageNameToPluginName", () => {
24
14
  testPromise("PascalCases scoped / dashed / underscored package names", async () => {
25
15
  expect(LocalHost.packageNameToPluginName("@scope/my-catalog"))->toBe("MyCatalog")
@@ -34,27 +24,32 @@ describe("LocalHost name derivation + discovery", () => {
34
24
  testPromise(
35
25
  "derivePluginName prefers plugin.json then PascalCase(package.json); discover skips non-plugins",
36
26
  async () => {
37
- let root = join(tmpdir(), "reventless-localhost-test")
38
- let _ = await rm(root, {recursive: true, force: true})
27
+ let root = NodePath.join([NodeOs.tmpdir(), "reventless-localhost-test"])
28
+ let _ = await NodeFs.Promises.rm(root, {recursive: true, force: true})
39
29
 
40
30
  // a: explicit plugin.json name + a compiled composition root.
41
- let a = join(root, "a")
42
- let aSrc = join(a, "src")
43
- let _ = await mkdir(aSrc, {recursive: true})
44
- let _ = await writeFile(join(a, "package.json"), `{"name":"@x/a-pkg"}`)
45
- let _ = await writeFile(join(aSrc, "plugin.json"), `{"name":"Catalog"}`)
46
- let _ = await writeFile(join(aSrc, "Plugin.res.mjs"), "")
31
+ let a = NodePath.join([root, "a"])
32
+ let aSrc = NodePath.join([a, "src"])
33
+ let _ = await NodeFs.Promises.mkdir(aSrc, {recursive: true})
34
+ let _ =
35
+ await NodeFs.Promises.writeFile(NodePath.join([a, "package.json"]), `{"name":"@x/a-pkg"}`)
36
+ let _ =
37
+ await NodeFs.Promises.writeFile(NodePath.join([aSrc, "plugin.json"]), `{"name":"Catalog"}`)
38
+ let _ = await NodeFs.Promises.writeFile(NodePath.join([aSrc, "Plugin.res.mjs"]), "")
47
39
 
48
40
  // b: no plugin.json → name falls back to PascalCase(package.json name).
49
- let b = join(root, "b")
50
- let bSrc = join(b, "src")
51
- let _ = await mkdir(bSrc, {recursive: true})
52
- let _ = await writeFile(join(b, "package.json"), `{"name":"@scope/my-ordering"}`)
53
- let _ = await writeFile(join(bSrc, "Plugin.res.mjs"), "")
41
+ let b = NodePath.join([root, "b"])
42
+ let bSrc = NodePath.join([b, "src"])
43
+ let _ = await NodeFs.Promises.mkdir(bSrc, {recursive: true})
44
+ let _ = await NodeFs.Promises.writeFile(
45
+ NodePath.join([b, "package.json"]),
46
+ `{"name":"@scope/my-ordering"}`,
47
+ )
48
+ let _ = await NodeFs.Promises.writeFile(NodePath.join([bSrc, "Plugin.res.mjs"]), "")
54
49
 
55
50
  // c: has src/ but no Plugin.res.mjs → discover must skip it.
56
- let c = join(root, "c")
57
- let _ = await mkdir(join(c, "src"), {recursive: true})
51
+ let c = NodePath.join([root, "c"])
52
+ let _ = await NodeFs.Promises.mkdir(NodePath.join([c, "src"]), {recursive: true})
58
53
 
59
54
  expect(LocalHost.derivePluginName(~pluginSrcDir=aSrc))->toBe("Catalog")
60
55
  expect(LocalHost.derivePluginName(~pluginSrcDir=bSrc))->toBe("MyOrdering")
@@ -65,7 +60,7 @@ describe("LocalHost name derivation + discovery", () => {
65
60
  expect(first.modulePath->String.endsWith("Plugin.res.mjs"))->toBe(true)
66
61
  expect(first.packageDir)->toBe(a)
67
62
 
68
- let _ = await rm(root, {recursive: true, force: true})
63
+ let _ = await NodeFs.Promises.rm(root, {recursive: true, force: true})
69
64
  },
70
65
  )
71
66
  })