@reventlessdev/reventless-gwt 1.0.0-alpha.74

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 (172) hide show
  1. package/CHANGELOG.md +698 -0
  2. package/LICENSE +202 -0
  3. package/bin/reventless-dev.mjs +21 -0
  4. package/package.json +54 -0
  5. package/rescript.json +36 -0
  6. package/src/.gwtignore +10 -0
  7. package/src/Automation_GWT.res +207 -0
  8. package/src/Automation_GWT.res.mjs +160 -0
  9. package/src/Behavior_GWT.res +603 -0
  10. package/src/Behavior_GWT.res.mjs +426 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +137 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +31 -0
  16. package/src/Cancellation.res.mjs +45 -0
  17. package/src/ChildProcess.res +84 -0
  18. package/src/ChildProcess.res.mjs +92 -0
  19. package/src/Cli.res +671 -0
  20. package/src/Cli.res.mjs +754 -0
  21. package/src/Collector.res +201 -0
  22. package/src/Collector.res.mjs +215 -0
  23. package/src/ComponentMeta.res +104 -0
  24. package/src/ComponentMeta.res.mjs +120 -0
  25. package/src/ComponentScan.res +103 -0
  26. package/src/ComponentScan.res.mjs +105 -0
  27. package/src/Delegate_GWT.res +303 -0
  28. package/src/Delegate_GWT.res.mjs +372 -0
  29. package/src/Diff.res +91 -0
  30. package/src/Diff.res.mjs +98 -0
  31. package/src/Discovery.res +105 -0
  32. package/src/Discovery.res.mjs +103 -0
  33. package/src/DomainDeadCode.res +93 -0
  34. package/src/DomainDeadCode.res.mjs +69 -0
  35. package/src/DomainGraph.res +0 -0
  36. package/src/DomainGraph.res.mjs +0 -0
  37. package/src/EventMapping_GWT.res +29 -0
  38. package/src/EventMapping_GWT.res.mjs +44 -0
  39. package/src/Filter.res +36 -0
  40. package/src/Filter.res.mjs +40 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +138 -0
  44. package/src/FormatterHuman.res.mjs +122 -0
  45. package/src/FormatterJson.res +257 -0
  46. package/src/FormatterJson.res.mjs +320 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +139 -0
  50. package/src/FormatterTap.res.mjs +149 -0
  51. package/src/FormatterVsCode.res +362 -0
  52. package/src/FormatterVsCode.res.mjs +564 -0
  53. package/src/Hint.res +119 -0
  54. package/src/Hint.res.mjs +124 -0
  55. package/src/InboundTranslation_GWT.res +83 -0
  56. package/src/InboundTranslation_GWT.res.mjs +94 -0
  57. package/src/JestBind.res +70 -0
  58. package/src/JestBind.res.mjs +63 -0
  59. package/src/Loader.res +24 -0
  60. package/src/Loader.res.mjs +23 -0
  61. package/src/LocalHost.res +135 -0
  62. package/src/LocalHost.res.mjs +127 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MultiSourceProjection_GWT.res +316 -0
  66. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  67. package/src/OutboundTranslation_GWT.res +238 -0
  68. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  69. package/src/Outcome.res +161 -0
  70. package/src/Outcome.res.mjs +140 -0
  71. package/src/PackageScan.res +80 -0
  72. package/src/PackageScan.res.mjs +81 -0
  73. package/src/PlatformRunner.res +160 -0
  74. package/src/PlatformRunner.res.mjs +155 -0
  75. package/src/PlatformScan.res +138 -0
  76. package/src/PlatformScan.res.mjs +131 -0
  77. package/src/ProcessManager.res +91 -0
  78. package/src/ProcessManager.res.mjs +80 -0
  79. package/src/Projection_GWT.res +312 -0
  80. package/src/Projection_GWT.res.mjs +332 -0
  81. package/src/Query_GWT.res +379 -0
  82. package/src/Query_GWT.res.mjs +367 -0
  83. package/src/RenderRescript.res +125 -0
  84. package/src/RenderRescript.res.mjs +141 -0
  85. package/src/RunnerTypes.res +68 -0
  86. package/src/RunnerTypes.res.mjs +60 -0
  87. package/src/SideEffect_GWT.res +157 -0
  88. package/src/SideEffect_GWT.res.mjs +113 -0
  89. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  90. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  91. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  92. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  93. package/src/StateChange/WithFixtures.res +22 -0
  94. package/src/StateChange/WithFixtures.res.mjs +31 -0
  95. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  96. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  97. package/src/StateChange/WithManualOpen.res +23 -0
  98. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  99. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  100. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  101. package/src/StubRuntime.res +53 -0
  102. package/src/StubRuntime.res.mjs +54 -0
  103. package/src/TestFixtures.res +16 -0
  104. package/src/TestFixtures.res.mjs +34 -0
  105. package/src/Watch.res +92 -0
  106. package/src/Watch.res.mjs +83 -0
  107. package/src/WatcherProbe.res +48 -0
  108. package/src/WatcherProbe.res.mjs +46 -0
  109. package/tests/AutomationGwtTest.res +71 -0
  110. package/tests/AutomationGwtTest.res.mjs +189 -0
  111. package/tests/BuildClassifierTest.res +94 -0
  112. package/tests/BuildClassifierTest.res.mjs +132 -0
  113. package/tests/ComponentMetaTest.res +52 -0
  114. package/tests/ComponentMetaTest.res.mjs +57 -0
  115. package/tests/DelegateGwtTest.res +487 -0
  116. package/tests/DelegateGwtTest.res.mjs +891 -0
  117. package/tests/DiscoveryTest.res +42 -0
  118. package/tests/DiscoveryTest.res.mjs +43 -0
  119. package/tests/DomainDeadCodeTest.res +116 -0
  120. package/tests/DomainDeadCodeTest.res.mjs +155 -0
  121. package/tests/DomainGraphTest.res +365 -0
  122. package/tests/DomainGraphTest.res.mjs +413 -0
  123. package/tests/FlowAggregateGwtTest.res +169 -0
  124. package/tests/FlowAggregateGwtTest.res.mjs +269 -0
  125. package/tests/FlowCrossPluginGwtTest.res +340 -0
  126. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  127. package/tests/FlowGwtTest.res +222 -0
  128. package/tests/FlowGwtTest.res.mjs +432 -0
  129. package/tests/FormatterVsCodeMessageTest.res +41 -0
  130. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  131. package/tests/InboundTranslationGwtTest.res +34 -0
  132. package/tests/InboundTranslationGwtTest.res.mjs +98 -0
  133. package/tests/LocalHostIntegration.res +70 -0
  134. package/tests/LocalHostIntegration.res.mjs +64 -0
  135. package/tests/LocalHostTest.res +71 -0
  136. package/tests/LocalHostTest.res.mjs +63 -0
  137. package/tests/MappingGwtTest.res +348 -0
  138. package/tests/MappingGwtTest.res.mjs +592 -0
  139. package/tests/OutboundTranslationGwtTest.res +59 -0
  140. package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
  141. package/tests/PlatformRunnerTest.res +66 -0
  142. package/tests/PlatformRunnerTest.res.mjs +85 -0
  143. package/tests/PlatformScanTest.res +61 -0
  144. package/tests/PlatformScanTest.res.mjs +56 -0
  145. package/tests/QueryGwtTest.res +161 -0
  146. package/tests/QueryGwtTest.res.mjs +314 -0
  147. package/tests/QueryResolverGwtTest.res +71 -0
  148. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  149. package/tests/RunnerUnitTest.res +219 -0
  150. package/tests/RunnerUnitTest.res.mjs +244 -0
  151. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  152. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  153. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  154. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  155. package/tests/SideEffect/MockEmail.res +18 -0
  156. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  157. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  158. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  159. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  160. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  161. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  162. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  163. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  164. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  165. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  166. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  167. package/tests/StateChangeSliceGwtTest.res +160 -0
  168. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  169. package/tests/StateViewSliceGwtTest.res +64 -0
  170. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  171. package/tests/WatchDebounceTest.res +78 -0
  172. package/tests/WatchDebounceTest.res.mjs +97 -0
@@ -0,0 +1,53 @@
1
+ // Inert runtime arguments shared by the Delegate / Flow GWT DSLs.
2
+ //
3
+ // ExtensionPoint mappings and Extension delegates are pure translations, but
4
+ // their user-level functions take infrastructure handles (a query engine, a
5
+ // scheduler, message metadata, the plugin definition). The GWT runs them with
6
+ // these no-op stubs so the translation logic is exercised without any real
7
+ // infrastructure. See `docs/plans/done/gwt-flow-and-extension-test-kinds.md` Phase 0.
8
+
9
+ // No-op QueryEngine — any DB lookup resolves to empty results. Kept identical
10
+ // to the inline stub `Mapping_GWT` defines.
11
+ let queryEngine: Reventless.QueryEngine.operations = {
12
+ scan: (~readModelName as _, ~filterConfigs as _, ~limit as _) => []->Promise.resolve,
13
+ query: (
14
+ ~readModelName as _: string,
15
+ ~key as _: option<string>=?,
16
+ ~id as _: Reventless.QueryEngine.value,
17
+ ~subIdConfig as _: option<Reventless.QueryEngine.SubId.config>=?,
18
+ ~filterConfigs as _: option<array<Reventless.QueryEngine.Filter.config>>=?,
19
+ ~ascending as _: option<bool>=?,
20
+ ~limit as _: option<int>=?,
21
+ ) => []->Promise.resolve,
22
+ }
23
+
24
+ // Scheduler stubs — `create` / `delete` are inert. EP/Extension `Call`
25
+ // directives receive these as the side-effect handles they never act on.
26
+ let createSchedule: Reventless.Schedule.create = _schedule => Promise.resolve()
27
+ let deleteSchedule: Reventless.Schedule.delete = _name => Promise.resolve()
28
+
29
+ // Default message metadata threaded into the user-level mapping functions.
30
+ let meta: Reventless.Message.meta = {
31
+ service: "gwt",
32
+ time: "1970-01-01T00:00:00Z",
33
+ msgId: "gwt-msg",
34
+ correlationId: "gwt-corr",
35
+ }
36
+
37
+ // Minimal plugin definition — Extension delegates receive it as an inert arg.
38
+ // Mirrors the `fakePluginDefinition` the platform admin uses for its internal
39
+ // callback wiring.
40
+ let pluginDefinition: Reventless.Plugin.pluginDefinition = {
41
+ id: "Gwt@TEST",
42
+ name: "Gwt",
43
+ version: "TEST",
44
+ extensionPoints: [],
45
+ extensions: [],
46
+ eventCollector: "NOT-SET",
47
+ extensionProtocols: [],
48
+ apiSchemaFragment: None,
49
+ apiTarget: None,
50
+ uiFragments: None,
51
+ structure: None,
52
+ dcbEventLog: None,
53
+ }
@@ -0,0 +1,54 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ function queryEngine_scan(param, param$1, param$2) {
5
+ return Promise.resolve([]);
6
+ }
7
+
8
+ function queryEngine_query(param, param$1, param$2, param$3, param$4, param$5, param$6) {
9
+ return Promise.resolve([]);
10
+ }
11
+
12
+ let queryEngine = {
13
+ scan: queryEngine_scan,
14
+ query: queryEngine_query
15
+ };
16
+
17
+ function createSchedule(_schedule) {
18
+ return Promise.resolve();
19
+ }
20
+
21
+ function deleteSchedule(_name) {
22
+ return Promise.resolve();
23
+ }
24
+
25
+ let pluginDefinition = {
26
+ id: "Gwt@TEST",
27
+ name: "Gwt",
28
+ version: "TEST",
29
+ extensionPoints: [],
30
+ extensions: [],
31
+ eventCollector: "NOT-SET",
32
+ extensionProtocols: [],
33
+ apiSchemaFragment: undefined,
34
+ apiTarget: undefined,
35
+ uiFragments: undefined,
36
+ structure: undefined,
37
+ dcbEventLog: undefined
38
+ };
39
+
40
+ let meta = {
41
+ service: "gwt",
42
+ time: "1970-01-01T00:00:00Z",
43
+ msgId: "gwt-msg",
44
+ correlationId: "gwt-corr"
45
+ };
46
+
47
+ export {
48
+ queryEngine,
49
+ createSchedule,
50
+ deleteSchedule,
51
+ meta,
52
+ pluginDefinition,
53
+ }
54
+ /* No side effect */
@@ -0,0 +1,16 @@
1
+ let id = "id"
2
+ let meta = {
3
+ Reventless.Message.service: "service",
4
+ user: "ProjectionTest",
5
+ ip: "ip",
6
+ time: "time",
7
+ msgId: "msgId",
8
+ correlationId: "correlationId",
9
+ }
10
+
11
+ let context = {Reventless.Message.meta, id}
12
+
13
+ let statusChange = {
14
+ Reventless.Message.at: context.meta.time,
15
+ by: context.meta.user->Option.getOr(""),
16
+ }
@@ -0,0 +1,34 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+
5
+ let id = "id";
6
+
7
+ let meta = {
8
+ service: "service",
9
+ time: "time",
10
+ ip: "ip",
11
+ user: "ProjectionTest",
12
+ msgId: "msgId",
13
+ correlationId: "correlationId"
14
+ };
15
+
16
+ let context = {
17
+ id: id,
18
+ meta: meta
19
+ };
20
+
21
+ let statusChange_by = Stdlib_Option.getOr("ProjectionTest", "");
22
+
23
+ let statusChange = {
24
+ at: "time",
25
+ by: statusChange_by
26
+ };
27
+
28
+ export {
29
+ id,
30
+ meta,
31
+ context,
32
+ statusChange,
33
+ }
34
+ /* statusChange Not a pure module */
package/src/Watch.res ADDED
@@ -0,0 +1,92 @@
1
+ // Wraps `chokidar` for the `reventless-gwt watch` and `reventless-gwt run
2
+ // --watch` modes. Emits one callback per file change event (creates, edits,
3
+ // deletes) after a small debounce window so rapid editor saves don't trigger
4
+ // overlapping runs.
5
+
6
+ type watcher
7
+ type opts = {
8
+ ignoreInitial: bool,
9
+ ignored: array<string>,
10
+ }
11
+
12
+ // The kind of filesystem change, threaded through so the consumer can tell a
13
+ // *structural* change (a source file appearing/disappearing — the move/delete
14
+ // half of a relocation) from a plain edit. Phase 12 keys its clean-rebuild on
15
+ // an `Unlink` of a `.res` source.
16
+ type event = Add | Change | Unlink
17
+
18
+ @module("chokidar") external watch: (array<string>, opts) => watcher = "watch"
19
+ @send external on: (watcher, string, string => unit) => watcher = "on"
20
+ @send external close: watcher => promise<unit> = "close"
21
+
22
+ @val external setTimeout: (unit => unit, int) => float = "setTimeout"
23
+ @val external clearTimeout: float => unit = "clearTimeout"
24
+
25
+ // Rank events so a coalesced burst reports the *strongest structural* signal
26
+ // with a representative path. A single directory `mv` emits an interleaved
27
+ // burst — `unlink` of the `.res` AND the generated `.res.mjs`, then `add`s at
28
+ // the new path — inside one debounce window. The representative path must be a
29
+ // `.res` *source* unlink (the relocation signal), never a `.res.mjs` or a
30
+ // trailing add, or the consumer misclassifies it as an ordinary change. So an
31
+ // unlink of a `.res` source outranks everything; any other unlink/add outranks
32
+ // a plain edit.
33
+ let rank = (event: event, path: string): int =>
34
+ switch event {
35
+ | Unlink => String.endsWith(path, ".res") ? 2 : 1
36
+ | Add => 1
37
+ | Change => 0
38
+ }
39
+
40
+ // True when an event is the *structural* signal Phase 12 acts on: a source
41
+ // `.res` file leaving a directory (the move/delete half of a relocation).
42
+ // Generated `.res.mjs` outputs end in `.mjs`, so they never match — which is
43
+ // also what keeps a `rescript clean` (it deletes those outputs) from
44
+ // re-triggering the clean-rebuild it just ran.
45
+ let isStructuralSource = (event: event, path: string): bool =>
46
+ event == Unlink && String.endsWith(path, ".res")
47
+
48
+ let debounce = (wait: int, fn: (event, string) => unit) => {
49
+ let pending: ref<option<float>> = ref(None)
50
+ let bestRank = ref(-1)
51
+ let bestEvent = ref(Change)
52
+ let bestPath = ref("")
53
+ (event, path) => {
54
+ let r = rank(event, path)
55
+ if r >= bestRank.contents {
56
+ bestRank := r
57
+ bestEvent := event
58
+ bestPath := path
59
+ }
60
+ switch pending.contents {
61
+ | Some(t) => clearTimeout(t)
62
+ | None => ()
63
+ }
64
+ pending :=
65
+ Some(
66
+ setTimeout(() => {
67
+ pending := None
68
+ let e = bestEvent.contents
69
+ let p = bestPath.contents
70
+ bestRank := -1
71
+ bestEvent := Change
72
+ bestPath := ""
73
+ fn(e, p)
74
+ }, wait),
75
+ )
76
+ }
77
+ }
78
+
79
+ let start = (roots: array<string>, onChange: (event, string) => unit): watcher => {
80
+ let w = watch(
81
+ roots,
82
+ {
83
+ ignoreInitial: true,
84
+ ignored: ["**/node_modules/**", "**/lib/**", "**/.git/**"],
85
+ },
86
+ )
87
+ let debounced = debounce(120, onChange)
88
+ w
89
+ ->on("add", p => debounced(Add, p))
90
+ ->on("change", p => debounced(Change, p))
91
+ ->on("unlink", p => debounced(Unlink, p))
92
+ }
@@ -0,0 +1,83 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Chokidar from "chokidar";
4
+
5
+ function rank(event, path) {
6
+ switch (event) {
7
+ case "Add" :
8
+ return 1;
9
+ case "Change" :
10
+ return 0;
11
+ case "Unlink" :
12
+ if (path.endsWith(".res")) {
13
+ return 2;
14
+ } else {
15
+ return 1;
16
+ }
17
+ }
18
+ }
19
+
20
+ function isStructuralSource(event, path) {
21
+ if (event === "Unlink") {
22
+ return path.endsWith(".res");
23
+ } else {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function debounce(wait, fn) {
29
+ let pending = {
30
+ contents: undefined
31
+ };
32
+ let bestRank = {
33
+ contents: -1
34
+ };
35
+ let bestEvent = {
36
+ contents: "Change"
37
+ };
38
+ let bestPath = {
39
+ contents: ""
40
+ };
41
+ return (event, path) => {
42
+ let r = rank(event, path);
43
+ if (r >= bestRank.contents) {
44
+ bestRank.contents = r;
45
+ bestEvent.contents = event;
46
+ bestPath.contents = path;
47
+ }
48
+ let t = pending.contents;
49
+ if (t !== undefined) {
50
+ clearTimeout(t);
51
+ }
52
+ pending.contents = setTimeout(() => {
53
+ pending.contents = undefined;
54
+ let e = bestEvent.contents;
55
+ let p = bestPath.contents;
56
+ bestRank.contents = -1;
57
+ bestEvent.contents = "Change";
58
+ bestPath.contents = "";
59
+ fn(e, p);
60
+ }, wait);
61
+ };
62
+ }
63
+
64
+ function start(roots, onChange) {
65
+ let w = Chokidar.watch(roots, {
66
+ ignoreInitial: true,
67
+ ignored: [
68
+ "**/node_modules/**",
69
+ "**/lib/**",
70
+ "**/.git/**"
71
+ ]
72
+ });
73
+ let debounced = debounce(120, onChange);
74
+ return w.on("add", p => debounced("Add", p)).on("change", p => debounced("Change", p)).on("unlink", p => debounced("Unlink", p));
75
+ }
76
+
77
+ export {
78
+ rank,
79
+ isStructuralSource,
80
+ debounce,
81
+ start,
82
+ }
83
+ /* chokidar Not a pure module */
@@ -0,0 +1,48 @@
1
+ // Detects whether a ReScript build watcher is already running for a package, so
2
+ // the watch manager can defer to a developer's (or rescript-vscode's) own
3
+ // watcher rather than spawn a duplicate. ReScript records the building process's
4
+ // PID in a lock file and liveness-checks it (a dead-pid lock is taken over), so
5
+ // the reliable signal is: lock present AND its pid is alive. A stale lock is
6
+ // harmless — rescript self-heals it on the next build.
7
+ //
8
+ // Two lock files exist depending on which command form started the build:
9
+ // - `lib/watch.lock` — canonical `rescript watch` (used by rescript-vscode
10
+ // ≥ 1.73's "Start Build", and the non-deprecated form)
11
+ // - `lib/rescript.lock` — legacy `rescript build -w` (what `pnpm run start`
12
+ // still maps to in the example packages)
13
+ // Both must be checked: missing `watch.lock` is exactly how the rescript-vscode
14
+ // watcher slipped past an earlier rescript.lock-only probe, causing a doomed
15
+ // duplicate spawn.
16
+
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
+ @val external _processKillSig: (int, int) => unit = "process.kill"
21
+
22
+ // `process.kill(pid, 0)` performs no signal delivery — it only probes existence
23
+ // (throws ESRCH when the pid is gone), which is exactly the liveness check.
24
+ let isAlive = (pid: int): bool =>
25
+ try {
26
+ _processKillSig(pid, 0)
27
+ true
28
+ } catch {
29
+ | _ => false
30
+ }
31
+
32
+ let libLock = (dir: string, name: string): string => join(join(dir, "lib"), name)
33
+
34
+ // True when the named lock file exists and holds a live pid.
35
+ let lockHasLivePid = (lock: string): bool =>
36
+ if _existsSync(lock) {
37
+ switch Int.fromString(String.trim(_readFileSync(lock, "utf8"))) {
38
+ | Some(pid) => isAlive(pid)
39
+ | None => false
40
+ }
41
+ } else {
42
+ false
43
+ }
44
+
45
+ // True when an external watcher (either command form) already owns this
46
+ // package's build.
47
+ let hasLiveWatcher = (dir: string): bool =>
48
+ lockHasLivePid(libLock(dir, "watch.lock")) || lockHasLivePid(libLock(dir, "rescript.lock"))
@@ -0,0 +1,46 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
6
+
7
+ function isAlive(pid) {
8
+ try {
9
+ process.kill(pid, 0);
10
+ return true;
11
+ } catch (exn) {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function libLock(dir, name) {
17
+ return Nodepath.join(Nodepath.join(dir, "lib"), name);
18
+ }
19
+
20
+ function lockHasLivePid(lock) {
21
+ if (!Nodefs.existsSync(lock)) {
22
+ return false;
23
+ }
24
+ let pid = Stdlib_Int.fromString(Nodefs.readFileSync(lock, "utf8").trim(), undefined);
25
+ if (pid !== undefined) {
26
+ return isAlive(pid);
27
+ } else {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function hasLiveWatcher(dir) {
33
+ if (lockHasLivePid(libLock(dir, "watch.lock"))) {
34
+ return true;
35
+ } else {
36
+ return lockHasLivePid(libLock(dir, "rescript.lock"));
37
+ }
38
+ }
39
+
40
+ export {
41
+ isAlive,
42
+ libLock,
43
+ lockHasLivePid,
44
+ hasLiveWatcher,
45
+ }
46
+ /* node:fs Not a pure module */
@@ -0,0 +1,71 @@
1
+ // Worked example for AutomationSlice_GWT — a "ship order" automation:
2
+ // OrderPlaced ⇒ TODO, ShipmentCreated resolves the TODO, process emits
3
+ // CreateShipment. Covers the unit combinators (collect / resolve / process)
4
+ // and the scenario combinators (sweep / andThenEvents).
5
+
6
+ @@reventless.gwt
7
+
8
+ module ShipOrderSlice = {
9
+ let name = "ShipOrder"
10
+
11
+ @schema
12
+ type consumedEvent =
13
+ | OrderPlaced({orderId: string, shippingAddress: string})
14
+ | ShipmentCreated({orderId: string})
15
+
16
+ @schema
17
+ type todoItem = {orderId: string, shippingAddress: string}
18
+
19
+ @schema
20
+ type command = CreateShipment({orderId: string, address: string})
21
+
22
+ let collect = event =>
23
+ switch event {
24
+ | OrderPlaced({orderId, shippingAddress}) => [(orderId, {orderId, shippingAddress})]
25
+ | _ => []
26
+ }
27
+
28
+ let resolve = event =>
29
+ switch event {
30
+ | ShipmentCreated({orderId}) => Some(orderId)
31
+ | _ => None
32
+ }
33
+
34
+ let process = (_id, item) =>
35
+ Some((item.orderId, CreateShipment({orderId: item.orderId, address: item.shippingAddress})))
36
+ }
37
+
38
+ describe("ShipOrder AutomationSlice", () => {
39
+ test("collect: OrderPlaced creates a pending TODO", () =>
40
+ givenEvent(OrderPlaced({orderId: "o1", shippingAddress: "1 Main St"}))
41
+ ->whenCollect
42
+ ->thenTodos([("o1", {orderId: "o1", shippingAddress: "1 Main St"})])
43
+ )
44
+
45
+ test("resolve: ShipmentCreated marks the TODO done", () =>
46
+ givenEvent(ShipmentCreated({orderId: "o1"}))
47
+ ->whenResolve
48
+ ->thenResolved(Some("o1"))
49
+ )
50
+
51
+ test("process: pending TODO emits CreateShipment", () =>
52
+ givenTodo("o1", {orderId: "o1", shippingAddress: "1 Main St"})
53
+ ->whenProcess
54
+ ->thenCommand("o1", CreateShipment({orderId: "o1", address: "1 Main St"}))
55
+ )
56
+
57
+ test("sweep: events → commands, andThenEvents drains todos", () => {
58
+ let s =
59
+ givenEvents([OrderPlaced({orderId: "o1", shippingAddress: "1 Main St"})])
60
+ ->whenSweep
61
+ let commandsOk =
62
+ thenCommands(s, [("o1", CreateShipment({orderId: "o1", address: "1 Main St"}))])
63
+ let drained = andThenEvents(s, [ShipmentCreated({orderId: "o1"})])
64
+ let todosOk = thenScenarioTodos(drained, [])
65
+ switch (commandsOk, todosOk) {
66
+ | (Ok(), Ok()) => Outcome.pass
67
+ | (Error(_) as e, _) => e
68
+ | (_, Error(_) as e) => e
69
+ }
70
+ })
71
+ })
@@ -0,0 +1,189 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Outcome$ReventlessGwt from "../src/Outcome.res.mjs";
5
+ import * as Automation_GWT$ReventlessGwt from "../src/Automation_GWT.res.mjs";
6
+
7
+ let consumedEventSchema = S.union([
8
+ S.schema(s => ({
9
+ TAG: "OrderPlaced",
10
+ orderId: s.m(S.string),
11
+ shippingAddress: s.m(S.string)
12
+ })),
13
+ S.schema(s => ({
14
+ TAG: "ShipmentCreated",
15
+ orderId: s.m(S.string)
16
+ }))
17
+ ]);
18
+
19
+ let todoItemSchema = S.schema(s => ({
20
+ orderId: s.m(S.string),
21
+ shippingAddress: s.m(S.string)
22
+ }));
23
+
24
+ let commandSchema = S.schema(s => ({
25
+ TAG: "CreateShipment",
26
+ orderId: s.m(S.string),
27
+ address: s.m(S.string)
28
+ }));
29
+
30
+ function collect(event) {
31
+ if (event.TAG !== "OrderPlaced") {
32
+ return [];
33
+ }
34
+ let orderId = event.orderId;
35
+ return [[
36
+ orderId,
37
+ {
38
+ orderId: orderId,
39
+ shippingAddress: event.shippingAddress
40
+ }
41
+ ]];
42
+ }
43
+
44
+ function resolve(event) {
45
+ if (event.TAG === "OrderPlaced") {
46
+ return;
47
+ } else {
48
+ return event.orderId;
49
+ }
50
+ }
51
+
52
+ function process(_id, item) {
53
+ return [
54
+ item.orderId,
55
+ {
56
+ TAG: "CreateShipment",
57
+ orderId: item.orderId,
58
+ address: item.shippingAddress
59
+ }
60
+ ];
61
+ }
62
+
63
+ function commandAuthorization(param) {
64
+ return "AllowAuthenticated";
65
+ }
66
+
67
+ let ShipOrderSlice = {
68
+ name: "ShipOrder",
69
+ consumedEventSchema: consumedEventSchema,
70
+ todoItemSchema: todoItemSchema,
71
+ commandSchema: commandSchema,
72
+ collect: collect,
73
+ resolve: resolve,
74
+ process: process,
75
+ commandAuthorization: commandAuthorization
76
+ };
77
+
78
+ let include = Automation_GWT$ReventlessGwt.Make(ShipOrderSlice);
79
+
80
+ let describe = include.describe;
81
+
82
+ let test = include.test;
83
+
84
+ let givenEvent = include.givenEvent;
85
+
86
+ let whenCollect = include.whenCollect;
87
+
88
+ let thenTodos = include.thenTodos;
89
+
90
+ let whenResolve = include.whenResolve;
91
+
92
+ let thenResolved = include.thenResolved;
93
+
94
+ let givenTodo = include.givenTodo;
95
+
96
+ let whenProcess = include.whenProcess;
97
+
98
+ let thenCommand = include.thenCommand;
99
+
100
+ let givenEvents = include.givenEvents;
101
+
102
+ let whenSweep = include.whenSweep;
103
+
104
+ let thenCommands = include.thenCommands;
105
+
106
+ let andThenEvents = include.andThenEvents;
107
+
108
+ let thenScenarioTodos = include.thenScenarioTodos;
109
+
110
+ describe("ShipOrder AutomationSlice", () => {
111
+ test("collect: OrderPlaced creates a pending TODO", () => thenTodos(whenCollect(givenEvent({
112
+ TAG: "OrderPlaced",
113
+ orderId: "o1",
114
+ shippingAddress: "1 Main St"
115
+ })), [[
116
+ "o1",
117
+ {
118
+ orderId: "o1",
119
+ shippingAddress: "1 Main St"
120
+ }
121
+ ]]));
122
+ test("resolve: ShipmentCreated marks the TODO done", () => thenResolved(whenResolve(givenEvent({
123
+ TAG: "ShipmentCreated",
124
+ orderId: "o1"
125
+ })), "o1"));
126
+ test("process: pending TODO emits CreateShipment", () => thenCommand(whenProcess(givenTodo("o1", {
127
+ orderId: "o1",
128
+ shippingAddress: "1 Main St"
129
+ })), "o1", {
130
+ TAG: "CreateShipment",
131
+ orderId: "o1",
132
+ address: "1 Main St"
133
+ }));
134
+ test("sweep: events → commands, andThenEvents drains todos", () => {
135
+ let s = whenSweep(givenEvents([{
136
+ TAG: "OrderPlaced",
137
+ orderId: "o1",
138
+ shippingAddress: "1 Main St"
139
+ }]));
140
+ let commandsOk = thenCommands(s, [[
141
+ "o1",
142
+ {
143
+ TAG: "CreateShipment",
144
+ orderId: "o1",
145
+ address: "1 Main St"
146
+ }
147
+ ]]);
148
+ let drained = andThenEvents(s, [{
149
+ TAG: "ShipmentCreated",
150
+ orderId: "o1"
151
+ }]);
152
+ let todosOk = thenScenarioTodos(drained, []);
153
+ if (commandsOk.TAG === "Ok") {
154
+ if (todosOk.TAG === "Ok") {
155
+ return Outcome$ReventlessGwt.pass;
156
+ } else {
157
+ return todosOk;
158
+ }
159
+ } else {
160
+ return commandsOk;
161
+ }
162
+ });
163
+ });
164
+
165
+ let Spec = include.Spec;
166
+
167
+ let thenNoCommand = include.thenNoCommand;
168
+
169
+ export {
170
+ ShipOrderSlice,
171
+ Spec,
172
+ describe,
173
+ test,
174
+ givenEvent,
175
+ whenCollect,
176
+ thenTodos,
177
+ whenResolve,
178
+ thenResolved,
179
+ givenTodo,
180
+ whenProcess,
181
+ thenCommand,
182
+ thenNoCommand,
183
+ givenEvents,
184
+ whenSweep,
185
+ thenCommands,
186
+ andThenEvents,
187
+ thenScenarioTodos,
188
+ }
189
+ /* consumedEventSchema Not a pure module */