@reventlessdev/reventless-gwt 1.0.0-alpha.75 → 1.0.0-alpha.77

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 (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +8 -8
  3. package/src/Behavior_GWT.res +125 -159
  4. package/src/Behavior_GWT.res.mjs +156 -59
  5. package/src/Bind.res.mjs +2 -2
  6. package/src/BuildClassifier.res +15 -5
  7. package/src/BuildClassifier.res.mjs +3 -3
  8. package/src/Cancellation.res +6 -0
  9. package/src/Cancellation.res.mjs +3 -2
  10. package/src/ChildProcess.res +19 -1
  11. package/src/ChildProcess.res.mjs +16 -1
  12. package/src/Cli.res +260 -26
  13. package/src/Cli.res.mjs +227 -29
  14. package/src/Collector.res +13 -2
  15. package/src/Collector.res.mjs +8 -5
  16. package/src/ComponentMeta.res +16 -43
  17. package/src/ComponentMeta.res.mjs +12 -41
  18. package/src/Discovery.res +19 -19
  19. package/src/Discovery.res.mjs +5 -9
  20. package/src/DomainGraph.res +0 -0
  21. package/src/DomainGraph.res.mjs +0 -0
  22. package/src/Filter.res +11 -2
  23. package/src/Filter.res.mjs +10 -3
  24. package/src/FormatterHuman.res +14 -43
  25. package/src/FormatterHuman.res.mjs +21 -28
  26. package/src/FormatterJson.res +12 -5
  27. package/src/FormatterJson.res.mjs +8 -6
  28. package/src/FormatterTap.res +13 -52
  29. package/src/FormatterTap.res.mjs +25 -43
  30. package/src/FormatterVsCode.res +4 -19
  31. package/src/FormatterVsCode.res.mjs +4 -23
  32. package/src/JestBind.res +1 -1
  33. package/src/JestBind.res.mjs +2 -2
  34. package/src/LocalHost.res +11 -27
  35. package/src/LocalHost.res.mjs +6 -34
  36. package/src/MismatchRender.res +72 -0
  37. package/src/MismatchRender.res.mjs +127 -0
  38. package/src/Outcome.res +0 -64
  39. package/src/Outcome.res.mjs +0 -47
  40. package/src/PlatformRunner.res +27 -6
  41. package/src/PlatformRunner.res.mjs +27 -6
  42. package/src/PlatformScan.res +14 -10
  43. package/src/PlatformScan.res.mjs +14 -13
  44. package/src/ProcessManager.res +51 -7
  45. package/src/ProcessManager.res.mjs +43 -4
  46. package/src/RunWorker.res +24 -0
  47. package/src/RunWorker.res.mjs +16 -0
  48. package/src/Watch.res +46 -30
  49. package/src/Watch.res.mjs +34 -32
  50. package/src/WatcherProbe.res +11 -3
  51. package/src/WatcherProbe.res.mjs +15 -2
  52. package/src/Worker.res +24 -0
  53. package/src/Worker.res.mjs +9 -0
  54. package/tests/BuildClassifierTest.res +39 -0
  55. package/tests/BuildClassifierTest.res.mjs +44 -0
  56. package/tests/CliRunEntryTest.res +57 -0
  57. package/tests/CliRunEntryTest.res.mjs +65 -0
  58. package/tests/CliWatchScopeTest.res +62 -0
  59. package/tests/CliWatchScopeTest.res.mjs +95 -0
  60. package/tests/ComponentMetaTest.res +15 -0
  61. package/tests/ComponentMetaTest.res.mjs +14 -0
  62. package/tests/DomainGraphTest.res +2 -2
  63. package/tests/FlowAggregateGwtTest.res +2 -0
  64. package/tests/FlowAggregateGwtTest.res.mjs +4 -0
  65. package/tests/FormatterGoldenTest.res +125 -0
  66. package/tests/FormatterGoldenTest.res.mjs +162 -0
  67. package/tests/MappingGwtTest.res +2 -0
  68. package/tests/MappingGwtTest.res.mjs +4 -0
  69. package/tests/WatchDebounceTest.res +35 -7
  70. package/tests/WatchDebounceTest.res.mjs +37 -6
@@ -0,0 +1,162 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Outcome$ReventlessGwt from "../src/Outcome.res.mjs";
4
+ import * as FormatterTap$ReventlessGwt from "../src/FormatterTap.res.mjs";
5
+ import * as FormatterHuman$ReventlessGwt from "../src/FormatterHuman.res.mjs";
6
+
7
+ let evA = JSON.parse(`{"TAG":"ProductAdded","_0":{"productId":"p1","name":"Widget"}}`);
8
+
9
+ let evB = JSON.parse(`{"TAG":"ProductRenamed","_0":{"productId":"p1","name":"Gadget"}}`);
10
+
11
+ let stateActive = JSON.parse(`{"status":"active"}`);
12
+
13
+ let stateArchived = JSON.parse(`{"status":"archived"}`);
14
+
15
+ let appendExpected = JSON.parse(`{"query":[]}`);
16
+
17
+ let appendActual = JSON.parse(`{"query":[{"eventTypes":["ProductAdded"]}]}`);
18
+
19
+ let goldens = [
20
+ {
21
+ name: "EventsMismatch",
22
+ mismatch: {
23
+ TAG: "EventsMismatch",
24
+ expected: [evA],
25
+ actual: [evB]
26
+ },
27
+ format: "EventsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
28
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
29
+ tap: " kind: \"EventsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
30
+ },
31
+ {
32
+ name: "ErrorMismatch",
33
+ mismatch: {
34
+ TAG: "ErrorMismatch",
35
+ expected: "CategoryAlreadyExists",
36
+ actual: undefined,
37
+ actualEvents: [evA]
38
+ },
39
+ format: "ErrorMismatch:\n expected error: \"CategoryAlreadyExists\"\n actual error: (none)\n actual events: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
40
+ human: " expected: Error(\"CategoryAlreadyExists\")\n actual: Ok([ProductAdded({productId: \"p1\", name: \"Widget\"})])",
41
+ tap: " kind: \"ErrorMismatch\"\n expected: \"Error(\\\"CategoryAlreadyExists\\\")\"\n actual: \"Ok([ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})])\""
42
+ },
43
+ {
44
+ name: "StateMismatch",
45
+ mismatch: {
46
+ TAG: "StateMismatch",
47
+ key: "p1",
48
+ expected: stateActive,
49
+ actual: stateArchived
50
+ },
51
+ format: "StateMismatch (key: p1):\n expected: {\n \"status\": \"active\"\n}\n actual: {\n \"status\": \"archived\"\n}",
52
+ human: " key: \"p1\"\n expected: {status: \"active\"}\n actual: {status: \"archived\"}",
53
+ tap: " kind: \"StateMismatch\"\n key: \"p1\"\n expected: \"{status: \\\"active\\\"}\"\n actual: \"{status: \\\"archived\\\"}\""
54
+ },
55
+ {
56
+ name: "NoEventExpected",
57
+ mismatch: {
58
+ TAG: "NoEventExpected",
59
+ actual: [evA]
60
+ },
61
+ format: "NoEventExpected: expected no events, got:\n [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
62
+ human: " expected no events\n actual: [ProductAdded({productId: \"p1\", name: \"Widget\"})]",
63
+ tap: " kind: \"NoEventExpected\"\n actual: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\""
64
+ },
65
+ {
66
+ name: "TranslateError",
67
+ mismatch: {
68
+ TAG: "TranslateError",
69
+ expected: "boom",
70
+ actual: "kaboom"
71
+ },
72
+ format: "TranslateError:\n expected: boom\n actual: kaboom",
73
+ human: " expected: boom\n actual: kaboom",
74
+ tap: " kind: \"TranslateError\"\n expected: \"boom\"\n actual: \"kaboom\""
75
+ },
76
+ {
77
+ name: "Throw",
78
+ mismatch: {
79
+ TAG: "Throw",
80
+ error: "unexpected",
81
+ stack: "at foo"
82
+ },
83
+ format: "Throw: unexpected\nat foo",
84
+ human: " error: unexpected\nat foo",
85
+ tap: " kind: \"Throw\"\n error: \"unexpected\"\n stack: \"at foo\""
86
+ },
87
+ {
88
+ name: "TodoMismatch",
89
+ mismatch: {
90
+ TAG: "TodoMismatch",
91
+ expected: [[
92
+ "case-1",
93
+ evA
94
+ ]],
95
+ actual: [[
96
+ "case-2",
97
+ evB
98
+ ]]
99
+ },
100
+ format: "TodoMismatch:\n expected: [(case-1, {\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n})]\n actual: [(case-2, {\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n})]",
101
+ human: " expected: [(case-1, ProductAdded({productId: \"p1\", name: \"Widget\"}))]\n actual: [(case-2, ProductRenamed({productId: \"p1\", name: \"Gadget\"}))]",
102
+ tap: " kind: \"TodoMismatch\"\n expected: \"[(case-1, ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"}))]\"\n actual: \"[(case-2, ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"}))]\""
103
+ },
104
+ {
105
+ name: "AppendConditionMismatch",
106
+ mismatch: {
107
+ TAG: "AppendConditionMismatch",
108
+ expected: appendExpected,
109
+ actual: appendActual
110
+ },
111
+ format: "AppendConditionMismatch:\n expected: {\n \"query\": []\n}\n actual: {\n \"query\": [\n {\n \"eventTypes\": [\n \"ProductAdded\"\n ]\n }\n ]\n}",
112
+ human: " expected: {query: []}\n actual: {query: [{eventTypes: [\"ProductAdded\"]}]}",
113
+ tap: " kind: \"AppendConditionMismatch\"\n expected: \"{query: []}\"\n actual: \"{query: [{eventTypes: [\\\"ProductAdded\\\"]}]}\""
114
+ },
115
+ {
116
+ name: "QueryRowsMismatch",
117
+ mismatch: {
118
+ TAG: "QueryRowsMismatch",
119
+ expected: [evA],
120
+ actual: [evB]
121
+ },
122
+ format: "QueryRowsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
123
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
124
+ tap: " kind: \"QueryRowsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
125
+ },
126
+ {
127
+ name: "PublishedActionsMismatch",
128
+ mismatch: {
129
+ TAG: "PublishedActionsMismatch",
130
+ expected: [evA],
131
+ actual: [evB]
132
+ },
133
+ format: "PublishedActionsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
134
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
135
+ tap: " kind: \"PublishedActionsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
136
+ }
137
+ ];
138
+
139
+ globalThis.describe("Formatter golden outputs (per-mismatch rendering)", () => {
140
+ goldens.forEach(g => {
141
+ globalThis.test(g.name + ` — Outcome.format`, () => {
142
+ globalThis.expect(Outcome$ReventlessGwt.format(g.mismatch)).toEqual(g.format);
143
+ });
144
+ globalThis.test(g.name + ` — FormatterHuman.renderMismatch`, () => {
145
+ globalThis.expect(FormatterHuman$ReventlessGwt.renderMismatch(g.mismatch)).toEqual(g.human);
146
+ });
147
+ globalThis.test(g.name + ` — FormatterTap.renderMismatchYaml`, () => {
148
+ globalThis.expect(FormatterTap$ReventlessGwt.renderMismatchYaml(g.mismatch)).toEqual(g.tap);
149
+ });
150
+ });
151
+ });
152
+
153
+ export {
154
+ evA,
155
+ evB,
156
+ stateActive,
157
+ stateArchived,
158
+ appendExpected,
159
+ appendActual,
160
+ goldens,
161
+ }
162
+ /* evA Not a pure module */
@@ -33,6 +33,7 @@ module CategoryBehavior = {
33
33
  module Spec = CategorySpec
34
34
  type state = NotCreated | Created
35
35
  let initialState = NotCreated
36
+ let snapshot = None
36
37
 
37
38
  let evolve = (_state, event: CategorySpec.event) =>
38
39
  switch event {
@@ -70,6 +71,7 @@ module ProductBehavior = {
70
71
  module Spec = ProductSpec
71
72
  type state = Pristine | Mirrored
72
73
  let initialState = Pristine
74
+ let snapshot = None
73
75
 
74
76
  let evolve = (_state, event: ProductSpec.event) =>
75
77
  switch event {
@@ -66,6 +66,7 @@ let moduleUrl$1 = "";
66
66
  let CategoryBehavior = {
67
67
  Spec: undefined,
68
68
  initialState: "NotCreated",
69
+ snapshot: undefined,
69
70
  evolve: evolve,
70
71
  decide: decide,
71
72
  moduleUrl: moduleUrl$1
@@ -133,6 +134,7 @@ let moduleUrl$3 = "";
133
134
  let ProductBehavior = {
134
135
  Spec: undefined,
135
136
  initialState: "Pristine",
137
+ snapshot: undefined,
136
138
  evolve: evolve$1,
137
139
  decide: decide$1,
138
140
  moduleUrl: moduleUrl$3
@@ -299,6 +301,7 @@ let CategorySource = Mapping_GWT$ReventlessGwt.FromBehavior({
299
301
  initialState: "NotCreated",
300
302
  evolve: evolve,
301
303
  decide: decide,
304
+ snapshot: undefined,
302
305
  moduleUrl: moduleUrl$1
303
306
  });
304
307
 
@@ -335,6 +338,7 @@ let ProductTarget = Mapping_GWT$ReventlessGwt.FromBehavior({
335
338
  initialState: "Pristine",
336
339
  evolve: evolve$1,
337
340
  decide: decide$1,
341
+ snapshot: undefined,
338
342
  moduleUrl: moduleUrl$3
339
343
  });
340
344
 
@@ -49,10 +49,11 @@ describe("Watch.debounce coalescing", () => {
49
49
  expect(calls.contents)->toEqual([Watch.Unlink])
50
50
  })
51
51
 
52
- testPromise("interleaved .res.mjs unlinks don't steal the representative path", async () => {
52
+ testPromise("interleaved .res.mjs unlinks never surface; each distinct .res source does", async () => {
53
53
  // The real directory-mv burst chokidar emits: source AND generated files
54
- // unlink interleaved. The coalesced path must be a `.res` source, not the
55
- // last-seen `.res.mjs` (which would misclassify as a non-structural change).
54
+ // unlink interleaved. Only `.res` sources are emitted (never a `.res.mjs`),
55
+ // and every *distinct* source is emitted two moved modules must both drive
56
+ // a rebuild, not collapse to one representative.
56
57
  let calls = ref([])
57
58
  let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
58
59
  fire(Watch.Unlink, "/p/src/old/A.res")
@@ -62,10 +63,37 @@ describe("Watch.debounce coalescing", () => {
62
63
  fire(Watch.Add, "/p/src/new/A.res")
63
64
  fire(Watch.Add, "/p/src/new/A.res.mjs")
64
65
  await delay(120)
65
- expect(calls.contents->Array.length)->toEqual(1)
66
- let (e, p) = calls.contents->Array.getUnsafe(0)
67
- expect(e)->toEqual(Watch.Unlink)
68
- expect(Watch.isStructuralSource(e, p))->toEqual(true)
66
+ let paths = calls.contents->Array.map(((_, p)) => p)
67
+ expect(calls.contents->Array.every(((e, p)) => Watch.isStructuralSource(e, p)))->toEqual(true)
68
+ expect(paths->Array.includes("/p/src/old/A.res"))->toEqual(true)
69
+ expect(paths->Array.includes("/p/src/old/A_Behavior.res"))->toEqual(true)
70
+ expect(paths->Array.some(p => p->String.endsWith(".mjs")))->toEqual(false)
71
+ expect(calls.contents->Array.length)->toEqual(2)
72
+ })
73
+
74
+ testPromise("a burst spanning two packages emits an unlink per package (A4.1)", async () => {
75
+ // Regression: the old single-`bestPath` debounce clean-rebuilt only one
76
+ // package on a multi-package burst, stranding the other with stale .res.mjs.
77
+ let calls = ref([])
78
+ let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
79
+ fire(Watch.Unlink, "/pkgA/src/Foo.res")
80
+ fire(Watch.Unlink, "/pkgB/src/Bar.res")
81
+ await delay(120)
82
+ let paths = calls.contents->Array.map(((_, p)) => p)
83
+ expect(paths->Array.includes("/pkgA/src/Foo.res"))->toEqual(true)
84
+ expect(paths->Array.includes("/pkgB/src/Bar.res"))->toEqual(true)
85
+ expect(calls.contents->Array.length)->toEqual(2)
86
+ })
87
+
88
+ testPromise("a structural signal suppresses a co-occurring plain change", async () => {
89
+ // A structural rebuild does its own re-run, so a plain edit in the same
90
+ // window must not also emit a separate Change (which would double-run).
91
+ let calls = ref([])
92
+ let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
93
+ fire(Watch.Change, "/p/src/Edited.res")
94
+ fire(Watch.Unlink, "/p/src/old/Moved.res")
95
+ await delay(120)
96
+ expect(calls.contents)->toEqual([Watch.Unlink])
69
97
  })
70
98
 
71
99
  testPromise("a lone change stays a change (incremental re-run)", async () => {
@@ -55,7 +55,7 @@ globalThis.describe("Watch.debounce coalescing", () => {
55
55
  await delay(120);
56
56
  globalThis.expect(calls.contents).toEqual(["Unlink"]);
57
57
  });
58
- globalThis.test("interleaved .res.mjs unlinks don't steal the representative path", async () => {
58
+ globalThis.test("interleaved .res.mjs unlinks never surface; each distinct .res source does", async () => {
59
59
  let calls = {
60
60
  contents: []
61
61
  };
@@ -72,11 +72,42 @@ globalThis.describe("Watch.debounce coalescing", () => {
72
72
  fire("Add", "/p/src/new/A.res");
73
73
  fire("Add", "/p/src/new/A.res.mjs");
74
74
  await delay(120);
75
- globalThis.expect(calls.contents.length).toEqual(1);
76
- let match = calls.contents[0];
77
- let e = match[0];
78
- globalThis.expect(e).toEqual("Unlink");
79
- globalThis.expect(Watch$ReventlessGwt.isStructuralSource(e, match[1])).toEqual(true);
75
+ let paths = calls.contents.map(param => param[1]);
76
+ globalThis.expect(calls.contents.every(param => Watch$ReventlessGwt.isStructuralSource(param[0], param[1]))).toEqual(true);
77
+ globalThis.expect(paths.includes("/p/src/old/A.res")).toEqual(true);
78
+ globalThis.expect(paths.includes("/p/src/old/A_Behavior.res")).toEqual(true);
79
+ globalThis.expect(paths.some(p => p.endsWith(".mjs"))).toEqual(false);
80
+ globalThis.expect(calls.contents.length).toEqual(2);
81
+ });
82
+ globalThis.test("a burst spanning two packages emits an unlink per package (A4.1)", async () => {
83
+ let calls = {
84
+ contents: []
85
+ };
86
+ let fire = Watch$ReventlessGwt.debounce(40, (e, p) => {
87
+ calls.contents = calls.contents.concat([[
88
+ e,
89
+ p
90
+ ]]);
91
+ });
92
+ fire("Unlink", "/pkgA/src/Foo.res");
93
+ fire("Unlink", "/pkgB/src/Bar.res");
94
+ await delay(120);
95
+ let paths = calls.contents.map(param => param[1]);
96
+ globalThis.expect(paths.includes("/pkgA/src/Foo.res")).toEqual(true);
97
+ globalThis.expect(paths.includes("/pkgB/src/Bar.res")).toEqual(true);
98
+ globalThis.expect(calls.contents.length).toEqual(2);
99
+ });
100
+ globalThis.test("a structural signal suppresses a co-occurring plain change", async () => {
101
+ let calls = {
102
+ contents: []
103
+ };
104
+ let fire = Watch$ReventlessGwt.debounce(40, (e, _p) => {
105
+ calls.contents = calls.contents.concat([e]);
106
+ });
107
+ fire("Change", "/p/src/Edited.res");
108
+ fire("Unlink", "/p/src/old/Moved.res");
109
+ await delay(120);
110
+ globalThis.expect(calls.contents).toEqual(["Unlink"]);
80
111
  });
81
112
  globalThis.test("a lone change stays a change (incremental re-run)", async () => {
82
113
  let calls = {