@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.
- package/CHANGELOG.md +698 -0
- package/LICENSE +202 -0
- package/bin/reventless-dev.mjs +21 -0
- package/package.json +54 -0
- package/rescript.json +36 -0
- package/src/.gwtignore +10 -0
- package/src/Automation_GWT.res +207 -0
- package/src/Automation_GWT.res.mjs +160 -0
- package/src/Behavior_GWT.res +603 -0
- package/src/Behavior_GWT.res.mjs +426 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +137 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +31 -0
- package/src/Cancellation.res.mjs +45 -0
- package/src/ChildProcess.res +84 -0
- package/src/ChildProcess.res.mjs +92 -0
- package/src/Cli.res +671 -0
- package/src/Cli.res.mjs +754 -0
- package/src/Collector.res +201 -0
- package/src/Collector.res.mjs +215 -0
- package/src/ComponentMeta.res +104 -0
- package/src/ComponentMeta.res.mjs +120 -0
- package/src/ComponentScan.res +103 -0
- package/src/ComponentScan.res.mjs +105 -0
- package/src/Delegate_GWT.res +303 -0
- package/src/Delegate_GWT.res.mjs +372 -0
- package/src/Diff.res +91 -0
- package/src/Diff.res.mjs +98 -0
- package/src/Discovery.res +105 -0
- package/src/Discovery.res.mjs +103 -0
- package/src/DomainDeadCode.res +93 -0
- package/src/DomainDeadCode.res.mjs +69 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/EventMapping_GWT.res +29 -0
- package/src/EventMapping_GWT.res.mjs +44 -0
- package/src/Filter.res +36 -0
- package/src/Filter.res.mjs +40 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +138 -0
- package/src/FormatterHuman.res.mjs +122 -0
- package/src/FormatterJson.res +257 -0
- package/src/FormatterJson.res.mjs +320 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +139 -0
- package/src/FormatterTap.res.mjs +149 -0
- package/src/FormatterVsCode.res +362 -0
- package/src/FormatterVsCode.res.mjs +564 -0
- package/src/Hint.res +119 -0
- package/src/Hint.res.mjs +124 -0
- package/src/InboundTranslation_GWT.res +83 -0
- package/src/InboundTranslation_GWT.res.mjs +94 -0
- package/src/JestBind.res +70 -0
- package/src/JestBind.res.mjs +63 -0
- package/src/Loader.res +24 -0
- package/src/Loader.res.mjs +23 -0
- package/src/LocalHost.res +135 -0
- package/src/LocalHost.res.mjs +127 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -0
- package/src/MultiSourceProjection_GWT.res +316 -0
- package/src/MultiSourceProjection_GWT.res.mjs +354 -0
- package/src/OutboundTranslation_GWT.res +238 -0
- package/src/OutboundTranslation_GWT.res.mjs +186 -0
- package/src/Outcome.res +161 -0
- package/src/Outcome.res.mjs +140 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +160 -0
- package/src/PlatformRunner.res.mjs +155 -0
- package/src/PlatformScan.res +138 -0
- package/src/PlatformScan.res.mjs +131 -0
- package/src/ProcessManager.res +91 -0
- package/src/ProcessManager.res.mjs +80 -0
- package/src/Projection_GWT.res +312 -0
- package/src/Projection_GWT.res.mjs +332 -0
- package/src/Query_GWT.res +379 -0
- package/src/Query_GWT.res.mjs +367 -0
- package/src/RenderRescript.res +125 -0
- package/src/RenderRescript.res.mjs +141 -0
- package/src/RunnerTypes.res +68 -0
- package/src/RunnerTypes.res.mjs +60 -0
- package/src/SideEffect_GWT.res +157 -0
- package/src/SideEffect_GWT.res.mjs +113 -0
- package/src/StateChange/ExternalAddCategorySlice.res +25 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
- package/src/StateChange/WithFixtures.res +22 -0
- package/src/StateChange/WithFixtures.res.mjs +31 -0
- package/src/StateChange/WithFixtures_Behavior.res +17 -0
- package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
- package/src/StateChange/WithManualOpen.res +23 -0
- package/src/StateChange/WithManualOpen.res.mjs +31 -0
- package/src/StateChange/WithManualOpen_Behavior.res +17 -0
- package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
- package/src/StubRuntime.res +53 -0
- package/src/StubRuntime.res.mjs +54 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +92 -0
- package/src/Watch.res.mjs +83 -0
- package/src/WatcherProbe.res +48 -0
- package/src/WatcherProbe.res.mjs +46 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +94 -0
- package/tests/BuildClassifierTest.res.mjs +132 -0
- package/tests/ComponentMetaTest.res +52 -0
- package/tests/ComponentMetaTest.res.mjs +57 -0
- package/tests/DelegateGwtTest.res +487 -0
- package/tests/DelegateGwtTest.res.mjs +891 -0
- package/tests/DiscoveryTest.res +42 -0
- package/tests/DiscoveryTest.res.mjs +43 -0
- package/tests/DomainDeadCodeTest.res +116 -0
- package/tests/DomainDeadCodeTest.res.mjs +155 -0
- package/tests/DomainGraphTest.res +365 -0
- package/tests/DomainGraphTest.res.mjs +413 -0
- package/tests/FlowAggregateGwtTest.res +169 -0
- package/tests/FlowAggregateGwtTest.res.mjs +269 -0
- package/tests/FlowCrossPluginGwtTest.res +340 -0
- package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
- package/tests/FlowGwtTest.res +222 -0
- package/tests/FlowGwtTest.res.mjs +432 -0
- package/tests/FormatterVsCodeMessageTest.res +41 -0
- package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
- package/tests/InboundTranslationGwtTest.res +34 -0
- package/tests/InboundTranslationGwtTest.res.mjs +98 -0
- package/tests/LocalHostIntegration.res +70 -0
- package/tests/LocalHostIntegration.res.mjs +64 -0
- package/tests/LocalHostTest.res +71 -0
- package/tests/LocalHostTest.res.mjs +63 -0
- package/tests/MappingGwtTest.res +348 -0
- package/tests/MappingGwtTest.res.mjs +592 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
- package/tests/PlatformRunnerTest.res +66 -0
- package/tests/PlatformRunnerTest.res.mjs +85 -0
- package/tests/PlatformScanTest.res +61 -0
- package/tests/PlatformScanTest.res.mjs +56 -0
- package/tests/QueryGwtTest.res +161 -0
- package/tests/QueryGwtTest.res.mjs +314 -0
- package/tests/QueryResolverGwtTest.res +71 -0
- package/tests/QueryResolverGwtTest.res.mjs +136 -0
- package/tests/RunnerUnitTest.res +219 -0
- package/tests/RunnerUnitTest.res.mjs +244 -0
- package/tests/SideEffect/FakeOrderNotification.res +23 -0
- package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
- package/tests/SideEffect/MockEmail.res +18 -0
- package/tests/SideEffect/MockEmail.res.mjs +47 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
- package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
- package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithFixtures_GWT.res +23 -0
- package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithManualOpen_GWT.res +17 -0
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
- package/tests/StateChangeSliceGwtTest.res +160 -0
- package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
- package/tests/StateViewSliceGwtTest.res +64 -0
- package/tests/StateViewSliceGwtTest.res.mjs +161 -0
- package/tests/WatchDebounceTest.res +78 -0
- package/tests/WatchDebounceTest.res.mjs +97 -0
package/src/Hint.res.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
|
|
5
|
+
function variantTag(j) {
|
|
6
|
+
if (j === null || Array.isArray(j)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
switch (typeof j) {
|
|
10
|
+
case "string" :
|
|
11
|
+
return j;
|
|
12
|
+
case "object" :
|
|
13
|
+
let match = j["TAG"];
|
|
14
|
+
if (typeof match === "string") {
|
|
15
|
+
return match;
|
|
16
|
+
} else {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
default:
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function forMismatch(sliceOpt, m) {
|
|
25
|
+
let slice = sliceOpt !== undefined ? sliceOpt : "<slice>";
|
|
26
|
+
switch (m.TAG) {
|
|
27
|
+
case "EventsMismatch" :
|
|
28
|
+
return {
|
|
29
|
+
locus: slice + `.decide`,
|
|
30
|
+
branch: undefined,
|
|
31
|
+
message: "decide() returned different events than expected. Check the event payload and count."
|
|
32
|
+
};
|
|
33
|
+
case "ErrorMismatch" :
|
|
34
|
+
let actual = m.actual;
|
|
35
|
+
if (actual === undefined) {
|
|
36
|
+
return {
|
|
37
|
+
locus: slice + `.decide`,
|
|
38
|
+
branch: undefined,
|
|
39
|
+
message: "decide() returned Ok([...]) but the test expected Error."
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
let match = variantTag(m.expected);
|
|
43
|
+
let match$1 = variantTag(actual);
|
|
44
|
+
let message = match !== undefined && match$1 !== undefined && match === match$1 ? `decide() returned Error(` + match + `) as expected, but with a different payload.` : "decide() returned a different Error variant than expected.";
|
|
45
|
+
return {
|
|
46
|
+
locus: slice + `.decide`,
|
|
47
|
+
branch: undefined,
|
|
48
|
+
message: message
|
|
49
|
+
};
|
|
50
|
+
case "StateMismatch" :
|
|
51
|
+
return {
|
|
52
|
+
locus: slice + `.evolve`,
|
|
53
|
+
branch: undefined,
|
|
54
|
+
message: "evolve() produced a different state than expected. Check the fold and missing event branches."
|
|
55
|
+
};
|
|
56
|
+
case "NoEventExpected" :
|
|
57
|
+
return {
|
|
58
|
+
locus: slice + `.decide`,
|
|
59
|
+
branch: undefined,
|
|
60
|
+
message: "decide() emitted events but the test expected none — possible idempotency miss."
|
|
61
|
+
};
|
|
62
|
+
case "TodoMismatch" :
|
|
63
|
+
return {
|
|
64
|
+
locus: slice + `.collect / ` + slice + `.resolve`,
|
|
65
|
+
branch: undefined,
|
|
66
|
+
message: "The automation slice's TODO projection diverged from the expected set."
|
|
67
|
+
};
|
|
68
|
+
case "AppendConditionMismatch" :
|
|
69
|
+
return {
|
|
70
|
+
locus: slice + `.commandSchema`,
|
|
71
|
+
branch: undefined,
|
|
72
|
+
message: "DCB optimistic-concurrency condition drift — likely a missing `@s.matches(DcbTag.string)` annotation on a command field, or the expected condition documented in the test disagrees with what the runtime would build."
|
|
73
|
+
};
|
|
74
|
+
case "TranslateError" :
|
|
75
|
+
return {
|
|
76
|
+
locus: slice + `.translate`,
|
|
77
|
+
branch: undefined,
|
|
78
|
+
message: "The translation slice returned a different result than expected."
|
|
79
|
+
};
|
|
80
|
+
case "QueryRowsMismatch" :
|
|
81
|
+
return {
|
|
82
|
+
locus: slice + `.config`,
|
|
83
|
+
branch: undefined,
|
|
84
|
+
message: "The read-model query returned wrong rows — likely a missing index, sub-id, or resolver."
|
|
85
|
+
};
|
|
86
|
+
case "PublishedActionsMismatch" :
|
|
87
|
+
return {
|
|
88
|
+
locus: slice + `.mapOutgoingEvent / ` + slice + `.mapIncomingEvent`,
|
|
89
|
+
branch: undefined,
|
|
90
|
+
message: "The ExtensionPoint mapping / Extension delegate published a different set of events or commands than expected. Check the variant match arms and the one-to-many fan-out."
|
|
91
|
+
};
|
|
92
|
+
case "Throw" :
|
|
93
|
+
return {
|
|
94
|
+
locus: slice,
|
|
95
|
+
branch: undefined,
|
|
96
|
+
message: `The slice threw an exception: ` + m.error
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toJson(h) {
|
|
102
|
+
let obj = {};
|
|
103
|
+
obj["locus"] = h.locus;
|
|
104
|
+
obj["branch"] = Stdlib_Option.mapOr(h.branch, null, prim => prim);
|
|
105
|
+
obj["message"] = h.message;
|
|
106
|
+
return obj;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function format(h) {
|
|
110
|
+
let b = h.branch;
|
|
111
|
+
if (b !== undefined) {
|
|
112
|
+
return `hint: ` + h.message + `\n Look at ` + h.locus + `'s branch ` + b + `.`;
|
|
113
|
+
} else {
|
|
114
|
+
return `hint: ` + h.message + `\n Look at ` + h.locus + `.`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
variantTag,
|
|
120
|
+
forMismatch,
|
|
121
|
+
toJson,
|
|
122
|
+
format,
|
|
123
|
+
}
|
|
124
|
+
/* No side effect */
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
open ReventlessCore
|
|
2
|
+
|
|
3
|
+
// Minimal inline spec for InboundTranslationSlice. sury-ppx processes the
|
|
4
|
+
// @schema attributes in the same compilation unit as the functor — matches
|
|
5
|
+
// the pattern used by the other GWT DSLs.
|
|
6
|
+
module type SliceSpec = {
|
|
7
|
+
let name: string
|
|
8
|
+
|
|
9
|
+
@schema
|
|
10
|
+
type externalInput
|
|
11
|
+
|
|
12
|
+
@schema
|
|
13
|
+
type command
|
|
14
|
+
|
|
15
|
+
let translate: externalInput => result<array<(string, command)>, string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module type T = {
|
|
19
|
+
module Spec: SliceSpec
|
|
20
|
+
|
|
21
|
+
let describe: (string, unit => unit) => unit
|
|
22
|
+
let test: (string, unit => Outcome.outcome) => unit
|
|
23
|
+
|
|
24
|
+
type translateResult = result<array<(string, Spec.command)>, string>
|
|
25
|
+
|
|
26
|
+
let whenInput: Spec.externalInput => translateResult
|
|
27
|
+
let thenCommands: (translateResult, array<(string, Spec.command)>) => Outcome.outcome
|
|
28
|
+
let thenCommand: (translateResult, string, Spec.command) => Outcome.outcome
|
|
29
|
+
let thenNoCommand: translateResult => Outcome.outcome
|
|
30
|
+
let thenTranslateError: (translateResult, string) => Outcome.outcome
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
34
|
+
module Spec = Spec
|
|
35
|
+
|
|
36
|
+
S.enableJson()
|
|
37
|
+
|
|
38
|
+
let describe = JestBind.describe
|
|
39
|
+
let test = (name, body) => JestBind.test(~slice=Spec.name, name, body)
|
|
40
|
+
|
|
41
|
+
type translateResult = result<array<(string, Spec.command)>, string>
|
|
42
|
+
|
|
43
|
+
let encCommand = (c: Spec.command) => c->Message.encode(Spec.commandSchema)
|
|
44
|
+
let encPairs = (pairs: array<(string, Spec.command)>) =>
|
|
45
|
+
pairs->Array.map(((id, cmd)) => {
|
|
46
|
+
let d = Dict.make()
|
|
47
|
+
d->Dict.set("id", JSON.Encode.string(id))
|
|
48
|
+
d->Dict.set("command", encCommand(cmd))
|
|
49
|
+
JSON.Encode.object(d)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
let whenInput = input => Spec.translate(input)
|
|
53
|
+
|
|
54
|
+
let thenCommands = (result, expected) =>
|
|
55
|
+
switch result {
|
|
56
|
+
| Ok(actual) if actual == expected => Outcome.pass
|
|
57
|
+
| Ok(actual) =>
|
|
58
|
+
Outcome.fail(
|
|
59
|
+
EventsMismatch({expected: encPairs(expected), actual: encPairs(actual)}),
|
|
60
|
+
)
|
|
61
|
+
| Error(msg) =>
|
|
62
|
+
Outcome.fail(TranslateError({expected: "(commands)", actual: Some(msg)}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let thenCommand = (result, expectedId, expectedCmd) =>
|
|
66
|
+
thenCommands(result, [(expectedId, expectedCmd)])
|
|
67
|
+
|
|
68
|
+
let thenNoCommand = result =>
|
|
69
|
+
switch result {
|
|
70
|
+
| Ok([]) => Outcome.pass
|
|
71
|
+
| Ok(actual) => Outcome.fail(NoEventExpected({actual: encPairs(actual)}))
|
|
72
|
+
| Error(msg) =>
|
|
73
|
+
Outcome.fail(TranslateError({expected: "(no commands)", actual: Some(msg)}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let thenTranslateError = (result, expectedMsg) =>
|
|
77
|
+
switch result {
|
|
78
|
+
| Error(actual) if actual == expectedMsg => Outcome.pass
|
|
79
|
+
| Error(actual) =>
|
|
80
|
+
Outcome.fail(TranslateError({expected: expectedMsg, actual: Some(actual)}))
|
|
81
|
+
| Ok(_) => Outcome.fail(TranslateError({expected: expectedMsg, actual: None}))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
5
|
+
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
6
|
+
import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
7
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
8
|
+
|
|
9
|
+
function Make(Spec) {
|
|
10
|
+
S.enableJson();
|
|
11
|
+
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, body);
|
|
12
|
+
let encPairs = pairs => pairs.map(param => {
|
|
13
|
+
let d = {};
|
|
14
|
+
d["id"] = param[0];
|
|
15
|
+
d["command"] = Message$ReventlessCore.encode(param[1], Spec.commandSchema);
|
|
16
|
+
return d;
|
|
17
|
+
});
|
|
18
|
+
let whenInput = input => Spec.translate(input);
|
|
19
|
+
let thenCommands = (result, expected) => {
|
|
20
|
+
if (result.TAG !== "Ok") {
|
|
21
|
+
return Outcome$ReventlessGwt.fail({
|
|
22
|
+
TAG: "TranslateError",
|
|
23
|
+
expected: "(commands)",
|
|
24
|
+
actual: result._0
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
let actual = result._0;
|
|
28
|
+
if (Primitive_object.equal(actual, expected)) {
|
|
29
|
+
return Outcome$ReventlessGwt.pass;
|
|
30
|
+
} else {
|
|
31
|
+
return Outcome$ReventlessGwt.fail({
|
|
32
|
+
TAG: "EventsMismatch",
|
|
33
|
+
expected: encPairs(expected),
|
|
34
|
+
actual: encPairs(actual)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
let thenCommand = (result, expectedId, expectedCmd) => thenCommands(result, [[
|
|
39
|
+
expectedId,
|
|
40
|
+
expectedCmd
|
|
41
|
+
]]);
|
|
42
|
+
let thenNoCommand = result => {
|
|
43
|
+
if (result.TAG !== "Ok") {
|
|
44
|
+
return Outcome$ReventlessGwt.fail({
|
|
45
|
+
TAG: "TranslateError",
|
|
46
|
+
expected: "(no commands)",
|
|
47
|
+
actual: result._0
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
let actual = result._0;
|
|
51
|
+
if (actual.length !== 0) {
|
|
52
|
+
return Outcome$ReventlessGwt.fail({
|
|
53
|
+
TAG: "NoEventExpected",
|
|
54
|
+
actual: encPairs(actual)
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
return Outcome$ReventlessGwt.pass;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
let thenTranslateError = (result, expectedMsg) => {
|
|
61
|
+
if (result.TAG === "Ok") {
|
|
62
|
+
return Outcome$ReventlessGwt.fail({
|
|
63
|
+
TAG: "TranslateError",
|
|
64
|
+
expected: expectedMsg,
|
|
65
|
+
actual: undefined
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let actual = result._0;
|
|
69
|
+
if (actual === expectedMsg) {
|
|
70
|
+
return Outcome$ReventlessGwt.pass;
|
|
71
|
+
} else {
|
|
72
|
+
return Outcome$ReventlessGwt.fail({
|
|
73
|
+
TAG: "TranslateError",
|
|
74
|
+
expected: expectedMsg,
|
|
75
|
+
actual: actual
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
Spec: Spec,
|
|
81
|
+
describe: JestBind$ReventlessGwt.describe,
|
|
82
|
+
test: test,
|
|
83
|
+
whenInput: whenInput,
|
|
84
|
+
thenCommands: thenCommands,
|
|
85
|
+
thenCommand: thenCommand,
|
|
86
|
+
thenNoCommand: thenNoCommand,
|
|
87
|
+
thenTranslateError: thenTranslateError
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
Make,
|
|
93
|
+
}
|
|
94
|
+
/* S Not a pure module */
|
package/src/JestBind.res
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Bridge between the runner-agnostic `Outcome.outcome` algebra and Jest.
|
|
2
|
+
//
|
|
3
|
+
// GWT DSLs emit `Outcome.outcome` from every `then*` combinator. Test files
|
|
4
|
+
// registered via `JestBind.test` / `JestBind.testPromise` have their body's
|
|
5
|
+
// outcome translated into a Jest pass/fail, unless the standalone CLI runner
|
|
6
|
+
// has activated its in-process `Collector` — in which case the call pushes
|
|
7
|
+
// straight into the collector and skips Jest. This lets the same test file
|
|
8
|
+
// run under either `pnpm jest` or `reventless-gwt run` without modification.
|
|
9
|
+
//
|
|
10
|
+
// `~slice` is the slice/component name — each DSL threads `Spec.name` through
|
|
11
|
+
// so failure hints read `Look at AddCategory.decide` rather than the generic
|
|
12
|
+
// `<slice>.decide` fallback.
|
|
13
|
+
//
|
|
14
|
+
// The Jest arm binds directly to Jest's globals via @reventlessdev/rescript-jest
|
|
15
|
+
// (module `JestGlobals`) with throwing semantics: a passing outcome is a no-op,
|
|
16
|
+
// a failing one throws a JS Error whose message Jest reports. (Jest's built-in
|
|
17
|
+
// `fail(msg)` was removed in Jest 27+ ESM mode — the mode these tests run under
|
|
18
|
+
// — so throwing is the portable way to fail a test.)
|
|
19
|
+
|
|
20
|
+
// Translate an outcome into Jest's throwing model: Ok is a no-op, Error throws
|
|
21
|
+
// the formatted mismatch + hint so Jest reports it as the failure message.
|
|
22
|
+
let assertOutcome = (~slice=?, outcome: Outcome.outcome): unit =>
|
|
23
|
+
switch outcome {
|
|
24
|
+
| Ok() => ()
|
|
25
|
+
| Error(m) => {
|
|
26
|
+
let hint = Hint.forMismatch(~slice?, m)
|
|
27
|
+
JsError.throwWithMessage(`${Outcome.format(m)}\n\n${Hint.format(hint)}`)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let describe = (label: string, body: unit => unit) =>
|
|
32
|
+
if Collector.isActive() {
|
|
33
|
+
Collector.pushDescribe(label, body)
|
|
34
|
+
} else {
|
|
35
|
+
JestGlobals.describe(label, body)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Pending-spec placeholder emitted by the codegen for slices with no upstream
|
|
39
|
+
// specification. Registers a non-running, non-failing entry under both runners.
|
|
40
|
+
let todo = (label: string) =>
|
|
41
|
+
if Collector.isActive() {
|
|
42
|
+
Collector.pushTodo(label)
|
|
43
|
+
} else {
|
|
44
|
+
JestGlobals.todo(label)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let test = (~slice=?, name: string, body: unit => Outcome.outcome) =>
|
|
48
|
+
if Collector.isActive() {
|
|
49
|
+
let location = Collector.captureLocation(1)
|
|
50
|
+
Collector.push(~slice?, ~location?, name, () => Promise.resolve(body()))
|
|
51
|
+
} else {
|
|
52
|
+
JestGlobals.testSync(name, () => assertOutcome(~slice?, body()))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let testPromise = (
|
|
56
|
+
~slice=?,
|
|
57
|
+
name: string,
|
|
58
|
+
~timeout: option<int>=?,
|
|
59
|
+
body: unit => promise<Outcome.outcome>,
|
|
60
|
+
) =>
|
|
61
|
+
if Collector.isActive() {
|
|
62
|
+
let location = Collector.captureLocation(1)
|
|
63
|
+
Collector.push(~slice?, ~location?, name, body)
|
|
64
|
+
} else {
|
|
65
|
+
switch timeout {
|
|
66
|
+
| Some(t) =>
|
|
67
|
+
JestGlobals.testWithTimeout(name, async () => assertOutcome(~slice?, await body()), t)
|
|
68
|
+
| None => JestGlobals.testPromise(name, async () => assertOutcome(~slice?, await body()))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
4
|
+
import * as Hint$ReventlessGwt from "./Hint.res.mjs";
|
|
5
|
+
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
6
|
+
import * as Collector$ReventlessGwt from "./Collector.res.mjs";
|
|
7
|
+
|
|
8
|
+
function assertOutcome(slice, outcome) {
|
|
9
|
+
if (outcome.TAG === "Ok") {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let m = outcome._0;
|
|
13
|
+
let hint = Hint$ReventlessGwt.forMismatch(slice, m);
|
|
14
|
+
Stdlib_JsError.throwWithMessage(Outcome$ReventlessGwt.format(m) + `\n\n` + Hint$ReventlessGwt.format(hint));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function describe(label, body) {
|
|
18
|
+
if (Collector$ReventlessGwt.isActive()) {
|
|
19
|
+
return Collector$ReventlessGwt.pushDescribe(label, body);
|
|
20
|
+
} else {
|
|
21
|
+
globalThis.describe(label, body);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function todo(label) {
|
|
27
|
+
if (Collector$ReventlessGwt.isActive()) {
|
|
28
|
+
return Collector$ReventlessGwt.pushTodo(label);
|
|
29
|
+
} else {
|
|
30
|
+
globalThis.test.todo(label);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function test(slice, name, body) {
|
|
36
|
+
if (Collector$ReventlessGwt.isActive()) {
|
|
37
|
+
let location = Collector$ReventlessGwt.captureLocation(1);
|
|
38
|
+
return Collector$ReventlessGwt.push(slice, location, name, () => Promise.resolve(body()));
|
|
39
|
+
}
|
|
40
|
+
globalThis.test(name, () => assertOutcome(slice, body()));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function testPromise(slice, name, timeout, body) {
|
|
44
|
+
if (!Collector$ReventlessGwt.isActive()) {
|
|
45
|
+
if (timeout !== undefined) {
|
|
46
|
+
globalThis.test(name, async () => assertOutcome(slice, await body()), timeout);
|
|
47
|
+
} else {
|
|
48
|
+
globalThis.test(name, async () => assertOutcome(slice, await body()));
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let location = Collector$ReventlessGwt.captureLocation(1);
|
|
53
|
+
Collector$ReventlessGwt.push(slice, location, name, body);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
assertOutcome,
|
|
58
|
+
describe,
|
|
59
|
+
todo,
|
|
60
|
+
test,
|
|
61
|
+
testPromise,
|
|
62
|
+
}
|
|
63
|
+
/* No side effect */
|
package/src/Loader.res
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Dynamic-import wrapper. Each GWT test file registers describe/test at
|
|
2
|
+
// import time, so the caller must `Collector.activate()` before invoking
|
|
3
|
+
// `loadFile` and drain after. The loader converts the absolute path into
|
|
4
|
+
// a `file://` URL so Node's ESM loader accepts it.
|
|
5
|
+
//
|
|
6
|
+
// A monotonic cache-busting query (`?t=N`) is appended so every load
|
|
7
|
+
// re-executes the module's top-level registration. Node's ESM cache is keyed
|
|
8
|
+
// by URL (not mtime), so without this a second load in the same process — the
|
|
9
|
+
// `discover`-then-run within `watch`, and every re-run after a recompile —
|
|
10
|
+
// would hit the cache, skip registration, and drain zero tests. The `?t=`
|
|
11
|
+
// query is stripped from captured stack frames in `Collector.parseFrame`.
|
|
12
|
+
|
|
13
|
+
@module("node:url") external pathToFileURL: string => {"href": string} = "pathToFileURL"
|
|
14
|
+
|
|
15
|
+
let dynamicImport: string => promise<'a> = %raw(`(u) => import(u)`)
|
|
16
|
+
|
|
17
|
+
let counter = ref(0)
|
|
18
|
+
|
|
19
|
+
let loadFile = async (absolutePath: string): unit => {
|
|
20
|
+
counter := counter.contents + 1
|
|
21
|
+
let url = pathToFileURL(absolutePath)
|
|
22
|
+
let busted = url["href"] ++ "?t=" ++ Int.toString(counter.contents)
|
|
23
|
+
let _: 'a = await dynamicImport(busted)
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodeurl from "node:url";
|
|
4
|
+
|
|
5
|
+
let dynamicImport = ((u) => import(u));
|
|
6
|
+
|
|
7
|
+
let counter = {
|
|
8
|
+
contents: 0
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
async function loadFile(absolutePath) {
|
|
12
|
+
counter.contents = counter.contents + 1 | 0;
|
|
13
|
+
let url = Nodeurl.pathToFileURL(absolutePath);
|
|
14
|
+
let busted = url.href + "?t=" + counter.contents.toString();
|
|
15
|
+
await dynamicImport(busted);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
dynamicImport,
|
|
20
|
+
counter,
|
|
21
|
+
loadFile,
|
|
22
|
+
}
|
|
23
|
+
/* node:url Not a pure module */
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// LocalHost — Phase 4.5 cold local-host foundation (shared by Phases 5, 6, and
|
|
2
|
+
// the Phase 9 runner). Reflects the framework's own domain graph WITHOUT parsing
|
|
3
|
+
// .res: instantiate reventless-local's `Platform.Make()` once, build each compiled
|
|
4
|
+
// `Plugin.res.mjs` against it, read the resolved `pluginStructure` (a plain record
|
|
5
|
+
// — no Pulumi Output), and collect the per-plugin structures.
|
|
6
|
+
//
|
|
7
|
+
// Proven cold (docs/analysis/reventless-vscode-domain-graph-design.md "Spike
|
|
8
|
+
// result"): `Make()` and `Plugin.Make(plat)` open no ports and start no timers —
|
|
9
|
+
// reading `pluginStructure` never touches the bus/scheduler/server wiring that the
|
|
10
|
+
// live `deployPlugin` / `startServers` path adds (that path is the runner's, not
|
|
11
|
+
// this). reventless-local is deliberately NOT a static dependency of reventless-gwt
|
|
12
|
+
// (gwt is upstream of it); the platform module is reached at runtime by absolute
|
|
13
|
+
// path via dynamic import, so a missing local platform is a caller concern, not a
|
|
14
|
+
// compile-time coupling.
|
|
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
|
+
let dynamicImport: string => promise<'a> = %raw(`(u) => import(u)`)
|
|
27
|
+
|
|
28
|
+
// Monotonic cache-buster so repeated loads in one process (a watch session
|
|
29
|
+
// re-reading the graph after a recompile) re-execute the composition root rather
|
|
30
|
+
// than hit Node's URL-keyed ESM cache — same rationale as Loader.res. NOTE: only
|
|
31
|
+
// the directly-imported modules are busted; transitively-cached component modules
|
|
32
|
+
// stay warm, so a recompiled *component spec* is not picked up until the next load
|
|
33
|
+
// of the composition root re-imports it. Full transitive invalidation is deferred
|
|
34
|
+
// to the live-update work (Phase 6).
|
|
35
|
+
let counter = ref(0)
|
|
36
|
+
let bustedUrl = (absolutePath: string): string => {
|
|
37
|
+
counter := counter.contents + 1
|
|
38
|
+
pathToFileURL(absolutePath)["href"] ++ "?t=" ++ Int.toString(counter.contents)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type pluginRef = {name: string, modulePath: string, packageDir: string}
|
|
42
|
+
|
|
43
|
+
type graph = {
|
|
44
|
+
structures: array<(string, Reventless.Plugin.pluginStructure)>,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Dynamic-import shapes. Untyped at the boundary; reventless-local's Platform and
|
|
48
|
+
// every generated Plugin emit these exact exports.
|
|
49
|
+
type platform
|
|
50
|
+
type localPlatformExports = {"Make": unit => platform}
|
|
51
|
+
type builtPlugin = {"pluginStructure": Reventless.Plugin.pluginStructure}
|
|
52
|
+
type pluginExports = {"Make": platform => builtPlugin}
|
|
53
|
+
|
|
54
|
+
// ── Plugin name derivation — mirrors generator/Config.read exactly so the graph's
|
|
55
|
+
// plugin keys match the names the framework itself bakes into Plugin.res. ──
|
|
56
|
+
|
|
57
|
+
// "@scope/my-catalog" → "MyCatalog", "online-shop" → "OnlineShop".
|
|
58
|
+
let packageNameToPluginName = (packageName: string): string => {
|
|
59
|
+
let base = switch packageName->String.split("/") {
|
|
60
|
+
| [_scope, local] => local
|
|
61
|
+
| [local] => local
|
|
62
|
+
| parts => parts->Array.getUnsafe(parts->Array.length - 1)
|
|
63
|
+
}
|
|
64
|
+
base
|
|
65
|
+
->String.split("-")
|
|
66
|
+
->Array.flatMap(part => part->String.split("_"))
|
|
67
|
+
->Array.map(word =>
|
|
68
|
+
word === ""
|
|
69
|
+
? ""
|
|
70
|
+
: word->String.slice(~start=0, ~end=1)->String.toUpperCase ++
|
|
71
|
+
word->String.slice(~start=1, ~end=word->String.length)
|
|
72
|
+
)
|
|
73
|
+
->Array.join("")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let strField = (json, key) =>
|
|
77
|
+
json->JSON.Decode.object->Option.flatMap(d => d->Dict.get(key))->Option.flatMap(JSON.Decode.string)
|
|
78
|
+
|
|
79
|
+
let readJson = path =>
|
|
80
|
+
try Some(readFileSync(path, "utf8")->JSON.parseOrThrow) catch {
|
|
81
|
+
| _ => None
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// plugin.json `name` → else PascalCase(package.json `name`) → else "Plugin".
|
|
85
|
+
let derivePluginName = (~pluginSrcDir: string): string => {
|
|
86
|
+
let pluginJson = join(pluginSrcDir, "plugin.json")
|
|
87
|
+
let fromPluginJson =
|
|
88
|
+
existsSync(pluginJson) ? readJson(pluginJson)->Option.flatMap(j => strField(j, "name")) : None
|
|
89
|
+
switch fromPluginJson {
|
|
90
|
+
| Some(name) => name
|
|
91
|
+
| None =>
|
|
92
|
+
let pkgJson = join(dirname(pluginSrcDir), "package.json")
|
|
93
|
+
readJson(pkgJson)
|
|
94
|
+
->Option.flatMap(j => strField(j, "name"))
|
|
95
|
+
->Option.map(packageNameToPluginName)
|
|
96
|
+
->Option.getOr("Plugin")
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Given workspace package dirs (e.g. from PackageScan), keep those that carry a
|
|
101
|
+
// compiled composition root and pair each with its framework plugin name.
|
|
102
|
+
let discover = (~packageDirs: array<string>): array<pluginRef> =>
|
|
103
|
+
packageDirs->Array.filterMap(dir => {
|
|
104
|
+
let srcDir = join(dir, "src")
|
|
105
|
+
let modulePath = join(srcDir, "Plugin.res.mjs")
|
|
106
|
+
existsSync(modulePath)
|
|
107
|
+
? Some({name: derivePluginName(~pluginSrcDir=srcDir), modulePath, packageDir: dir})
|
|
108
|
+
: None
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Resolve reventless-local's compiled Platform module from a plugin package's
|
|
112
|
+
// node_modules. reventless-gwt is upstream of reventless-local (no static dep), but
|
|
113
|
+
// every plugin depends on it (`@reventlessdev/reventless-local`), so we resolve the
|
|
114
|
+
// specifier relative to the plugin's package.json. `None` when it isn't installed
|
|
115
|
+
// there — callers then skip platform-dependent features (dead-code / graph).
|
|
116
|
+
let localPlatformSpecifier = "@reventlessdev/reventless-local/src/Platform.res.mjs"
|
|
117
|
+
let resolveLocalPlatform = (~fromPackageDir: string): option<string> =>
|
|
118
|
+
try Some(createRequire(join(fromPackageDir, "package.json"))->requireResolve(localPlatformSpecifier)) catch {
|
|
119
|
+
| _ => None
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Cold-load: instantiate the local platform once, build each plugin against it,
|
|
123
|
+
// and read its (already-resolved) pluginStructure.
|
|
124
|
+
let loadGraph = async (~platformModulePath: string, ~plugins: array<pluginRef>): graph => {
|
|
125
|
+
let platMod: localPlatformExports = await dynamicImport(bustedUrl(platformModulePath))
|
|
126
|
+
let plat = platMod["Make"]()
|
|
127
|
+
let structures = []
|
|
128
|
+
for i in 0 to plugins->Array.length - 1 {
|
|
129
|
+
let plugin = plugins->Array.getUnsafe(i)
|
|
130
|
+
let pluginMod: pluginExports = await dynamicImport(bustedUrl(plugin.modulePath))
|
|
131
|
+
let built = pluginMod["Make"](plat)
|
|
132
|
+
structures->Array.push((plugin.name, built["pluginStructure"]))
|
|
133
|
+
}
|
|
134
|
+
{structures: structures}
|
|
135
|
+
}
|